github.com/Tri-stone/burrow@v0.25.0/vent/sqldb/adapters/postgres_adapter.go (about)

     1  package adapters
     2  
     3  import (
     4  	"database/sql"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/hyperledger/burrow/vent/logger"
     9  	"github.com/hyperledger/burrow/vent/types"
    10  	"github.com/lib/pq"
    11  )
    12  
    13  var pgDataTypes = map[types.SQLColumnType]string{
    14  	types.SQLColumnTypeBool:      "BOOLEAN",
    15  	types.SQLColumnTypeByteA:     "BYTEA",
    16  	types.SQLColumnTypeInt:       "INTEGER",
    17  	types.SQLColumnTypeSerial:    "SERIAL",
    18  	types.SQLColumnTypeText:      "TEXT",
    19  	types.SQLColumnTypeVarchar:   "VARCHAR",
    20  	types.SQLColumnTypeTimeStamp: "TIMESTAMP",
    21  	types.SQLColumnTypeNumeric:   "NUMERIC",
    22  	types.SQLColumnTypeJSON:      "JSON",
    23  	types.SQLColumnTypeBigInt:    "BIGINT",
    24  }
    25  
    26  // PostgresAdapter implements DBAdapter for Postgres
    27  type PostgresAdapter struct {
    28  	Log    *logger.Logger
    29  	Schema string
    30  }
    31  
    32  // NewPostgresAdapter constructs a new db adapter
    33  func NewPostgresAdapter(schema string, log *logger.Logger) *PostgresAdapter {
    34  	return &PostgresAdapter{
    35  		Log:    log,
    36  		Schema: schema,
    37  	}
    38  }
    39  
    40  // Open connects to a PostgreSQL database, opens it & create default schema if provided
    41  func (adapter *PostgresAdapter) Open(dbURL string) (*sql.DB, error) {
    42  	db, err := sql.Open("postgres", dbURL)
    43  	if err != nil {
    44  		adapter.Log.Info("msg", "Error creating database connection", "err", err)
    45  		return nil, err
    46  	}
    47  
    48  	// if there is a supplied Schema
    49  	if adapter.Schema != "" {
    50  		if err = db.Ping(); err != nil {
    51  			adapter.Log.Info("msg", "Error opening database connection", "err", err)
    52  			return nil, err
    53  		}
    54  
    55  		var found bool
    56  
    57  		query := Cleanf(`SELECT EXISTS (SELECT 1 FROM pg_catalog.pg_namespace n WHERE n.nspname = '%s');`, adapter.Schema)
    58  		adapter.Log.Info("msg", "FIND SCHEMA", "query", query)
    59  
    60  		if err := db.QueryRow(query).Scan(&found); err == nil {
    61  			if !found {
    62  				adapter.Log.Warn("msg", "Schema not found")
    63  			}
    64  			adapter.Log.Info("msg", "Creating schema")
    65  
    66  			query = Cleanf("CREATE SCHEMA %s;", adapter.Schema)
    67  			adapter.Log.Info("msg", "CREATE SCHEMA", "query", query)
    68  
    69  			if _, err = db.Exec(query); err != nil {
    70  				if adapter.ErrorEquals(err, types.SQLErrorTypeDuplicatedSchema) {
    71  					adapter.Log.Warn("msg", "Duplicated schema")
    72  					return db, nil
    73  				}
    74  			}
    75  		} else {
    76  			adapter.Log.Info("msg", "Error searching schema", "err", err)
    77  			return nil, err
    78  		}
    79  	} else {
    80  		return nil, fmt.Errorf("no schema supplied")
    81  	}
    82  
    83  	return db, err
    84  }
    85  
    86  // TypeMapping convert generic dataTypes to database dependent dataTypes
    87  func (adapter *PostgresAdapter) TypeMapping(sqlColumnType types.SQLColumnType) (string, error) {
    88  	if sqlDataType, ok := pgDataTypes[sqlColumnType]; ok {
    89  		return sqlDataType, nil
    90  	}
    91  
    92  	return "", fmt.Errorf("datatype %v not recognized", sqlColumnType)
    93  }
    94  
    95  // SecureColumnName return columns between appropriate security containers
    96  func (adapter *PostgresAdapter) SecureName(name string) string {
    97  	return secureName(name)
    98  }
    99  
   100  // CreateTableQuery builds query for creating a new table
   101  func (adapter *PostgresAdapter) CreateTableQuery(tableName string, columns []*types.SQLTableColumn) (string, string) {
   102  	// build query
   103  	columnsDef := ""
   104  	primaryKey := ""
   105  	dictionaryValues := ""
   106  
   107  	for i, column := range columns {
   108  		secureColumn := adapter.SecureName(column.Name)
   109  		sqlType, _ := adapter.TypeMapping(column.Type)
   110  		pKey := 0
   111  
   112  		if columnsDef != "" {
   113  			columnsDef += ", "
   114  			dictionaryValues += ", "
   115  		}
   116  
   117  		columnsDef += Cleanf("%s %s", secureColumn, sqlType)
   118  
   119  		if column.Length > 0 {
   120  			columnsDef += Cleanf("(%v)", column.Length)
   121  		}
   122  
   123  		if column.Primary {
   124  			pKey = 1
   125  			columnsDef += " NOT NULL"
   126  			if primaryKey != "" {
   127  				primaryKey += ", "
   128  			}
   129  			primaryKey += secureColumn
   130  		}
   131  
   132  		dictionaryValues += Cleanf("('%s','%s',%d,%d,%d,%d)",
   133  			tableName,
   134  			column.Name,
   135  			column.Type,
   136  			column.Length,
   137  			pKey,
   138  			i)
   139  	}
   140  
   141  	query := Cleanf("CREATE TABLE %s.%s (%s", adapter.Schema, adapter.SecureName(tableName), columnsDef)
   142  	if primaryKey != "" {
   143  		query += "," + Cleanf("CONSTRAINT %s_pkey PRIMARY KEY (%s)", tableName, primaryKey)
   144  	}
   145  	query += ");"
   146  
   147  	dictionaryQuery := Cleanf("INSERT INTO %s.%s (%s,%s,%s,%s,%s,%s) VALUES %s;",
   148  		adapter.Schema, types.SQLDictionaryTableName,
   149  		types.SQLColumnLabelTableName, types.SQLColumnLabelColumnName,
   150  		types.SQLColumnLabelColumnType, types.SQLColumnLabelColumnLength,
   151  		types.SQLColumnLabelPrimaryKey, types.SQLColumnLabelColumnOrder,
   152  		dictionaryValues)
   153  
   154  	return query, dictionaryQuery
   155  }
   156  
   157  // LastBlockIDQuery returns a query for last inserted blockId in log table
   158  func (adapter *PostgresAdapter) LastBlockIDQuery() string {
   159  	query := `
   160  		WITH ll AS (
   161  			SELECT MAX(%s) AS %s FROM %s.%s
   162  		)
   163  		SELECT COALESCE(%s, '0') AS %s
   164  			FROM ll LEFT OUTER JOIN %s.%s log ON (ll.%s = log.%s);`
   165  
   166  	return Cleanf(query,
   167  		types.SQLColumnLabelId,                // max
   168  		types.SQLColumnLabelId,                // as
   169  		adapter.Schema, types.SQLLogTableName, // from
   170  		types.SQLColumnLabelHeight,            // coalesce
   171  		types.SQLColumnLabelHeight,            // as
   172  		adapter.Schema, types.SQLLogTableName, // from
   173  		types.SQLColumnLabelId, types.SQLColumnLabelId) // on
   174  
   175  }
   176  
   177  // FindTableQuery returns a query that checks if a table exists
   178  func (adapter *PostgresAdapter) FindTableQuery() string {
   179  	query := "SELECT COUNT(*) found FROM %s.%s WHERE %s = $1;"
   180  
   181  	return Cleanf(query,
   182  		adapter.Schema, types.SQLDictionaryTableName, // from
   183  		types.SQLColumnLabelTableName) // where
   184  }
   185  
   186  // TableDefinitionQuery returns a query with table structure
   187  func (adapter *PostgresAdapter) TableDefinitionQuery() string {
   188  	query := `
   189  		SELECT
   190  			%s,%s,%s,%s
   191  		FROM
   192  			%s.%s
   193  		WHERE
   194  			%s = $1
   195  		ORDER BY
   196  			%s;`
   197  
   198  	return Cleanf(query,
   199  		types.SQLColumnLabelColumnName, types.SQLColumnLabelColumnType, // select
   200  		types.SQLColumnLabelColumnLength, types.SQLColumnLabelPrimaryKey, // select
   201  		adapter.Schema, types.SQLDictionaryTableName, // from
   202  		types.SQLColumnLabelTableName,   // where
   203  		types.SQLColumnLabelColumnOrder) // order by
   204  
   205  }
   206  
   207  // AlterColumnQuery returns a query for adding a new column to a table
   208  func (adapter *PostgresAdapter) AlterColumnQuery(tableName, columnName string, sqlColumnType types.SQLColumnType, length, order int) (string, string) {
   209  	sqlType, _ := adapter.TypeMapping(sqlColumnType)
   210  	if length > 0 {
   211  		sqlType = Cleanf("%s(%d)", sqlType, length)
   212  	}
   213  
   214  	query := Cleanf("ALTER TABLE %s.%s ADD COLUMN %s %s;",
   215  		adapter.Schema,
   216  		adapter.SecureName(tableName),
   217  		adapter.SecureName(columnName),
   218  		sqlType)
   219  
   220  	dictionaryQuery := Cleanf(`
   221  		INSERT INTO %s.%s (%s,%s,%s,%s,%s,%s)
   222  		VALUES ('%s','%s',%d,%d,%d,%d);`,
   223  
   224  		adapter.Schema, types.SQLDictionaryTableName,
   225  
   226  		types.SQLColumnLabelTableName, types.SQLColumnLabelColumnName,
   227  		types.SQLColumnLabelColumnType, types.SQLColumnLabelColumnLength,
   228  		types.SQLColumnLabelPrimaryKey, types.SQLColumnLabelColumnOrder,
   229  
   230  		tableName, columnName, sqlColumnType, length, 0, order)
   231  
   232  	return query, dictionaryQuery
   233  }
   234  
   235  // SelectRowQuery returns a query for selecting row values
   236  func (adapter *PostgresAdapter) SelectRowQuery(tableName, fields, indexValue string) string {
   237  	return Cleanf("SELECT %s FROM %s.%s WHERE %s = '%s';",
   238  		fields,                                        // select
   239  		adapter.Schema, adapter.SecureName(tableName), // from
   240  		types.SQLColumnLabelHeight, indexValue, // where
   241  	)
   242  }
   243  
   244  // SelectLogQuery returns a query for selecting all tables involved in a block trn
   245  func (adapter *PostgresAdapter) SelectLogQuery() string {
   246  	query := `
   247  		SELECT DISTINCT %s,%s FROM %s.%s l WHERE %s = $1;`
   248  
   249  	return Cleanf(query,
   250  		types.SQLColumnLabelTableName, types.SQLColumnLabelEventName, // select
   251  		adapter.Schema, types.SQLLogTableName, // from
   252  		types.SQLColumnLabelHeight) // where
   253  }
   254  
   255  // InsertLogQuery returns a query to insert a row in log table
   256  func (adapter *PostgresAdapter) InsertLogQuery() string {
   257  	query := `
   258  		INSERT INTO %s.%s (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
   259  		VALUES (CURRENT_TIMESTAMP, $1, $2, $3, $4, $5, $6 ,$7, $8, $9);`
   260  
   261  	return Cleanf(query,
   262  		adapter.Schema, types.SQLLogTableName, // insert
   263  		//fields
   264  		types.SQLColumnLabelTimeStamp, types.SQLColumnLabelTableName, types.SQLColumnLabelEventName, types.SQLColumnLabelEventFilter,
   265  		types.SQLColumnLabelHeight, types.SQLColumnLabelTxHash, types.SQLColumnLabelAction, types.SQLColumnLabelDataRow,
   266  		types.SQLColumnLabelSqlStmt, types.SQLColumnLabelSqlValues)
   267  }
   268  
   269  // ErrorEquals verify if an error is of a given SQL type
   270  func (adapter *PostgresAdapter) ErrorEquals(err error, sqlErrorType types.SQLErrorType) bool {
   271  	if err, ok := err.(*pq.Error); ok {
   272  		switch sqlErrorType {
   273  		case types.SQLErrorTypeGeneric:
   274  			return true
   275  		case types.SQLErrorTypeDuplicatedColumn:
   276  			return err.Code == "42701"
   277  		case types.SQLErrorTypeDuplicatedTable:
   278  			return err.Code == "42P07"
   279  		case types.SQLErrorTypeDuplicatedSchema:
   280  			return err.Code == "42P06"
   281  		case types.SQLErrorTypeUndefinedTable:
   282  			return err.Code == "42P01"
   283  		case types.SQLErrorTypeUndefinedColumn:
   284  			return err.Code == "42703"
   285  		case types.SQLErrorTypeInvalidType:
   286  			return err.Code == "42704"
   287  		}
   288  	}
   289  
   290  	return false
   291  }
   292  
   293  func (adapter *PostgresAdapter) UpsertQuery(table *types.SQLTable, row types.EventDataRow) (types.UpsertDeleteQuery, interface{}, error) {
   294  
   295  	pointers := make([]interface{}, 0)
   296  
   297  	columns := ""
   298  	insValues := ""
   299  	updValues := ""
   300  	values := ""
   301  	var txHash interface{} = nil
   302  
   303  	i := 0
   304  
   305  	// for each column in table
   306  	for _, column := range table.Columns {
   307  		secureColumn := adapter.SecureName(column.Name)
   308  
   309  		i++
   310  
   311  		// INSERT INTO TABLE (*columns).........
   312  		if columns != "" {
   313  			columns += ", "
   314  			insValues += ", "
   315  			values += ", "
   316  		}
   317  		columns += secureColumn
   318  		insValues += "$" + Cleanf("%d", i)
   319  
   320  		//find data for column
   321  		if value, ok := row.RowData[column.Name]; ok {
   322  			//load hash value
   323  			if column.Name == types.SQLColumnLabelTxHash {
   324  				txHash = value
   325  			}
   326  
   327  			// column found (not null)
   328  			// load values
   329  			pointers = append(pointers, &value)
   330  			values += fmt.Sprint(value)
   331  
   332  			if !column.Primary {
   333  				// column is no PK
   334  				// add to update list
   335  				// INSERT........... ON CONFLICT......DO UPDATE (*updValues)
   336  				if updValues != "" {
   337  					updValues += ", "
   338  				}
   339  				updValues += secureColumn + " = $" + Cleanf("%d", i)
   340  			}
   341  		} else if column.Primary {
   342  			// column NOT found (is null) and is PK
   343  			return types.UpsertDeleteQuery{}, nil, fmt.Errorf("error null primary key for column %s", secureColumn)
   344  		} else {
   345  			// column NOT found (is null) and is NOT PK
   346  			//pointers = append(pointers, &null)
   347  			pointers = append(pointers, nil)
   348  			values += "null"
   349  		}
   350  	}
   351  
   352  	query := Cleanf("INSERT INTO %s.%s (%s) VALUES (%s) ", adapter.Schema, adapter.SecureName(table.Name),
   353  		columns, insValues)
   354  
   355  	if updValues != "" {
   356  		query += Cleanf("ON CONFLICT ON CONSTRAINT %s_pkey DO UPDATE SET %s", table.Name, updValues)
   357  	} else {
   358  		query += Cleanf("ON CONFLICT ON CONSTRAINT %s_pkey DO NOTHING", table.Name)
   359  	}
   360  	query += ";"
   361  
   362  	return types.UpsertDeleteQuery{Query: query, Values: values, Pointers: pointers}, txHash, nil
   363  }
   364  
   365  func (adapter *PostgresAdapter) DeleteQuery(table *types.SQLTable, row types.EventDataRow) (types.UpsertDeleteQuery, error) {
   366  
   367  	pointers := make([]interface{}, 0)
   368  	columns := ""
   369  	values := ""
   370  	i := 0
   371  
   372  	// for each column in table
   373  	for _, column := range table.Columns {
   374  
   375  		//only PK for delete
   376  		if column.Primary {
   377  			i++
   378  
   379  			secureColumn := adapter.SecureName(column.Name)
   380  
   381  			// WHERE ..........
   382  			if columns != "" {
   383  				columns += " AND "
   384  				values += ", "
   385  			}
   386  
   387  			columns += Cleanf("%s = $%d", secureColumn, i)
   388  
   389  			//find data for column
   390  			if value, ok := row.RowData[column.Name]; ok {
   391  				// column found (not null)
   392  				// load values
   393  				pointers = append(pointers, &value)
   394  				values += fmt.Sprint(value)
   395  
   396  			} else {
   397  				// column NOT found (is null) and is PK
   398  				return types.UpsertDeleteQuery{}, fmt.Errorf("error null primary key for column %s", secureColumn)
   399  			}
   400  		}
   401  	}
   402  
   403  	if columns == "" {
   404  		return types.UpsertDeleteQuery{}, fmt.Errorf("error primary key not found for deletion")
   405  	}
   406  
   407  	query := Cleanf("DELETE FROM %s.%s WHERE %s;", adapter.Schema, adapter.SecureName(table.Name), columns)
   408  
   409  	return types.UpsertDeleteQuery{Query: query, Values: values, Pointers: pointers}, nil
   410  }
   411  
   412  func (adapter *PostgresAdapter) RestoreDBQuery() string {
   413  	return Cleanf(`SELECT %s, %s, %s, %s FROM %s.%s 
   414  								WHERE to_char(%s,'YYYY-MM-DD HH24:MI:SS')<=$1 
   415  								ORDER BY %s;`,
   416  		types.SQLColumnLabelTableName, types.SQLColumnLabelAction, types.SQLColumnLabelSqlStmt, types.SQLColumnLabelSqlValues,
   417  		adapter.Schema, types.SQLLogTableName,
   418  		types.SQLColumnLabelTimeStamp,
   419  		types.SQLColumnLabelId)
   420  }
   421  
   422  func (adapter *PostgresAdapter) CleanDBQueries() types.SQLCleanDBQuery {
   423  
   424  	// Chain info
   425  	selectChainIDQry := Cleanf(`
   426  		SELECT 
   427  		COUNT(*) REGISTERS,
   428  		COALESCE(MAX(%s),'') CHAINID,
   429  		COALESCE(MAX(%s),'') BVERSION 
   430  		FROM %s.%s;`,
   431  		types.SQLColumnLabelChainID, types.SQLColumnLabelBurrowVer,
   432  		adapter.Schema, types.SQLChainInfoTableName)
   433  
   434  	deleteChainIDQry := Cleanf(`
   435  		DELETE FROM %s.%s;`,
   436  		adapter.Schema, types.SQLChainInfoTableName)
   437  
   438  	insertChainIDQry := Cleanf(`
   439  		INSERT INTO %s.%s (%s,%s) VALUES($1,$2)`,
   440  		adapter.Schema, types.SQLChainInfoTableName,
   441  		types.SQLColumnLabelChainID, types.SQLColumnLabelBurrowVer)
   442  
   443  	// Dictionary
   444  	selectDictionaryQry := Cleanf(`
   445  		SELECT DISTINCT %s 
   446  		FROM %s.%s 
   447   		WHERE %s
   448  		NOT IN ('%s','%s','%s');`,
   449  		types.SQLColumnLabelTableName,
   450  		adapter.Schema, types.SQLDictionaryTableName,
   451  		types.SQLColumnLabelTableName,
   452  		types.SQLLogTableName, types.SQLDictionaryTableName, types.SQLChainInfoTableName)
   453  
   454  	deleteDictionaryQry := Cleanf(`
   455  		DELETE FROM %s.%s 
   456  		WHERE %s 
   457  		NOT IN ('%s','%s','%s');`,
   458  		adapter.Schema, types.SQLDictionaryTableName,
   459  		types.SQLColumnLabelTableName,
   460  		types.SQLLogTableName, types.SQLDictionaryTableName, types.SQLChainInfoTableName)
   461  
   462  	// log
   463  	deleteLogQry := Cleanf(`
   464  		DELETE FROM %s.%s;`,
   465  		adapter.Schema, types.SQLLogTableName)
   466  
   467  	return types.SQLCleanDBQuery{
   468  		SelectChainIDQry:    selectChainIDQry,
   469  		DeleteChainIDQry:    deleteChainIDQry,
   470  		InsertChainIDQry:    insertChainIDQry,
   471  		SelectDictionaryQry: selectDictionaryQry,
   472  		DeleteDictionaryQry: deleteDictionaryQry,
   473  		DeleteLogQry:        deleteLogQry,
   474  	}
   475  }
   476  
   477  func (adapter *PostgresAdapter) DropTableQuery(tableName string) string {
   478  	// We cascade here to drop any associated views and triggers. We work under the assumption that vent
   479  	// owns its database and any users need to be able to recreate objects that depend on vent tables in the event of
   480  	// table drops
   481  	return Cleanf(`DROP TABLE %s CASCADE;`, adapter.schemaName(tableName))
   482  }
   483  
   484  func (adapter *PostgresAdapter) CreateNotifyFunctionQuery(function, channel string, columns ...string) string {
   485  	return Cleanf(`CREATE OR REPLACE FUNCTION %s() RETURNS trigger AS
   486  		$trigger$
   487  		BEGIN
   488  			CASE TG_OP
   489  			WHEN 'DELETE' THEN
   490  				PERFORM pg_notify('%s', CAST(json_build_object('%s', TG_OP, %s) as text));
   491  				RETURN OLD;
   492  			ELSE
   493  				PERFORM pg_notify('%s', CAST(json_build_object('%s', TG_OP, %s) as text));
   494  				RETURN NEW;
   495  			END CASE;
   496  		END;
   497  		$trigger$
   498  		LANGUAGE 'plpgsql';
   499  		`,
   500  		adapter.schemaName(function),                                             // create function
   501  		channel, types.SQLColumnLabelAction, jsonBuildObjectArgs("OLD", columns), // case delete
   502  		channel, types.SQLColumnLabelAction, jsonBuildObjectArgs("NEW", columns), // case else
   503  	)
   504  }
   505  
   506  func (adapter *PostgresAdapter) CreateTriggerQuery(triggerName, tableName, functionName string) string {
   507  	trigger := adapter.SecureName(triggerName)
   508  	table := adapter.schemaName(tableName)
   509  	return Cleanf(`DROP TRIGGER IF EXISTS %s ON %s CASCADE; 
   510  		CREATE TRIGGER %s AFTER INSERT OR UPDATE OR DELETE ON %s
   511  		FOR EACH ROW 
   512  		EXECUTE PROCEDURE %s();
   513  		`,
   514  		trigger,                          // drop
   515  		table,                            // on
   516  		trigger,                          // create
   517  		table,                            // on
   518  		adapter.schemaName(functionName), // function
   519  	)
   520  }
   521  
   522  func (adapter *PostgresAdapter) schemaName(tableName string) string {
   523  	return fmt.Sprintf("%s.%s", adapter.Schema, adapter.SecureName(tableName))
   524  }
   525  
   526  func secureName(columnName string) string {
   527  	return `"` + columnName + `"`
   528  }
   529  
   530  func jsonBuildObjectArgs(record string, columns []string) string {
   531  	elements := make([]string, len(columns))
   532  	for i, column := range columns {
   533  		elements[i] = "'" + column + "', " + record + "." + secureName(column)
   534  	}
   535  
   536  	return strings.Join(elements, ", ")
   537  }