github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/vent/sqldb/adapters/postgres_adapter.go (about)

     1  package adapters
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/lib/pq"
     8  	"github.com/pkg/errors"
     9  
    10  	"github.com/hyperledger/burrow/logging"
    11  	"github.com/hyperledger/burrow/vent/types"
    12  	"github.com/jmoiron/sqlx"
    13  	"github.com/prometheus/common/log"
    14  )
    15  
    16  var pgDataTypes = map[types.SQLColumnType]string{
    17  	types.SQLColumnTypeBool:      "BOOLEAN",
    18  	types.SQLColumnTypeByteA:     "BYTEA",
    19  	types.SQLColumnTypeInt:       "INTEGER",
    20  	types.SQLColumnTypeSerial:    "SERIAL",
    21  	types.SQLColumnTypeText:      "TEXT",
    22  	types.SQLColumnTypeVarchar:   "VARCHAR",
    23  	types.SQLColumnTypeTimeStamp: "TIMESTAMP",
    24  	types.SQLColumnTypeNumeric:   "NUMERIC",
    25  	types.SQLColumnTypeJSON:      "JSON",
    26  	types.SQLColumnTypeBigInt:    "BIGINT",
    27  }
    28  
    29  // PostgresAdapter implements DBAdapter for Postgres
    30  type PostgresAdapter struct {
    31  	Schema string
    32  	types.SQLNames
    33  	Log *logging.Logger
    34  }
    35  
    36  var _ DBAdapter = &PostgresAdapter{}
    37  
    38  // NewPostgresAdapter constructs a new db adapter
    39  func NewPostgresAdapter(schema string, sqlNames types.SQLNames, log *logging.Logger) *PostgresAdapter {
    40  	return &PostgresAdapter{
    41  		Schema:   schema,
    42  		SQLNames: sqlNames,
    43  		Log:      log,
    44  	}
    45  }
    46  
    47  func (pa *PostgresAdapter) Open(dbURL string) (*sqlx.DB, error) {
    48  	db, err := sqlx.Open("postgres", dbURL)
    49  	if err != nil {
    50  		log.Info("msg", "Error creating database connection", "err", err)
    51  		return nil, err
    52  	}
    53  
    54  	if err := db.Ping(); err != nil {
    55  		log.Info("msg", "Error opening database connection", "err", err)
    56  		return nil, err
    57  	}
    58  
    59  	if pa.Schema != "" {
    60  		err = ensureSchema(db, pa.Schema, pa.Log)
    61  		if err != nil {
    62  			return nil, err
    63  		}
    64  	} else {
    65  		return nil, fmt.Errorf("no schema supplied")
    66  	}
    67  
    68  	return db, nil
    69  }
    70  
    71  func ensureSchema(db sqlx.Ext, schema string, log *logging.Logger) error {
    72  	query := Cleanf(`SELECT EXISTS (SELECT 1 FROM pg_catalog.pg_namespace n WHERE n.nspname = '%s');`, schema)
    73  	log.InfoMsg("FIND SCHEMA", "query", query)
    74  
    75  	var found bool
    76  	if err := db.QueryRowx(query).Scan(&found); err == nil {
    77  		if !found {
    78  			log.InfoMsg("Schema not found")
    79  		}
    80  		log.InfoMsg("Creating schema")
    81  
    82  		query = Cleanf("CREATE SCHEMA %s;", schema)
    83  		log.InfoMsg("CREATE SCHEMA", "query", query)
    84  
    85  		if _, err = db.Exec(query); err != nil {
    86  			if errorEquals(err, types.SQLErrorTypeDuplicatedSchema) {
    87  				log.InfoMsg("Duplicated schema")
    88  				return nil
    89  			}
    90  		}
    91  	} else {
    92  		log.InfoMsg("Error searching schema", "err", err)
    93  		return err
    94  	}
    95  	return nil
    96  }
    97  
    98  // TypeMapping convert generic dataTypes to database dependent dataTypes
    99  func (pa *PostgresAdapter) TypeMapping(sqlColumnType types.SQLColumnType) (string, error) {
   100  	if sqlDataType, ok := pgDataTypes[sqlColumnType]; ok {
   101  		return sqlDataType, nil
   102  	}
   103  
   104  	return "", fmt.Errorf("datatype %v not recognized", sqlColumnType)
   105  }
   106  
   107  // SecureColumnName return columns between appropriate security containers
   108  func (pa *PostgresAdapter) SecureName(name string) string {
   109  	return secureName(name)
   110  }
   111  
   112  // CreateTableQuery builds query for creating a new table
   113  func (pa *PostgresAdapter) CreateTableQuery(tableName string, columns []*types.SQLTableColumn) (string, string) {
   114  	// build query
   115  	columnsDef := ""
   116  	primaryKey := ""
   117  	dictionaryValues := ""
   118  
   119  	for i, column := range columns {
   120  		secureColumn := pa.SecureName(column.Name)
   121  		sqlType, _ := pa.TypeMapping(column.Type)
   122  		pKey := 0
   123  
   124  		if columnsDef != "" {
   125  			columnsDef += ", "
   126  			dictionaryValues += ", "
   127  		}
   128  
   129  		columnsDef += Cleanf("%s %s", secureColumn, sqlType)
   130  
   131  		if column.Length > 0 {
   132  			columnsDef += Cleanf("(%v)", column.Length)
   133  		}
   134  
   135  		if column.Primary {
   136  			pKey = 1
   137  			columnsDef += " NOT NULL"
   138  			if primaryKey != "" {
   139  				primaryKey += ", "
   140  			}
   141  			primaryKey += secureColumn
   142  		}
   143  
   144  		dictionaryValues += Cleanf("('%s','%s',%d,%d,%d,%d)",
   145  			tableName,
   146  			column.Name,
   147  			column.Type,
   148  			column.Length,
   149  			pKey,
   150  			i)
   151  	}
   152  
   153  	query := Cleanf("CREATE TABLE %s.%s (%s", pa.Schema, pa.SecureName(tableName), columnsDef)
   154  	if primaryKey != "" {
   155  		query += "," + Cleanf("CONSTRAINT %s_pkey PRIMARY KEY (%s)", tableName, primaryKey)
   156  	}
   157  	query += ");"
   158  
   159  	dictionaryQuery := Cleanf("INSERT INTO %s.%s (%s,%s,%s,%s,%s,%s) VALUES %s;",
   160  		pa.Schema, pa.Tables.Dictionary,
   161  		pa.Columns.TableName, pa.Columns.ColumnName,
   162  		pa.Columns.ColumnType, pa.Columns.ColumnLength,
   163  		pa.Columns.PrimaryKey, pa.Columns.ColumnOrder,
   164  		dictionaryValues)
   165  
   166  	return query, dictionaryQuery
   167  }
   168  
   169  // FindTableQuery returns a query that checks if a table exists
   170  func (pa *PostgresAdapter) FindTableQuery() string {
   171  	query := "SELECT COUNT(*) found FROM %s.%s WHERE %s = $1;"
   172  
   173  	return Cleanf(query,
   174  		pa.Schema, pa.Tables.Dictionary, // from
   175  		pa.Columns.TableName) // where
   176  }
   177  
   178  // TableDefinitionQuery returns a query with table structure
   179  func (pa *PostgresAdapter) TableDefinitionQuery() string {
   180  	query := `
   181  		SELECT
   182  			%s,%s,%s,%s
   183  		FROM
   184  			%s.%s
   185  		WHERE
   186  			%s = $1
   187  		ORDER BY
   188  			%s;`
   189  
   190  	return Cleanf(query,
   191  		pa.Columns.ColumnName, pa.Columns.ColumnType, // select
   192  		pa.Columns.ColumnLength, pa.Columns.PrimaryKey, // select
   193  		pa.Schema, pa.Tables.Dictionary, // from
   194  		pa.Columns.TableName,   // where
   195  		pa.Columns.ColumnOrder) // order by
   196  
   197  }
   198  
   199  // AlterColumnQuery returns a query for adding a new column to a table
   200  func (pa *PostgresAdapter) AlterColumnQuery(tableName, columnName string, sqlColumnType types.SQLColumnType, length, order int) (string, string) {
   201  	sqlType, _ := pa.TypeMapping(sqlColumnType)
   202  	if length > 0 {
   203  		sqlType = Cleanf("%s(%d)", sqlType, length)
   204  	}
   205  
   206  	query := Cleanf("ALTER TABLE %s.%s ADD COLUMN %s %s;",
   207  		pa.Schema,
   208  		pa.SecureName(tableName),
   209  		pa.SecureName(columnName),
   210  		sqlType)
   211  
   212  	dictionaryQuery := Cleanf(`
   213  		INSERT INTO %s.%s (%s,%s,%s,%s,%s,%s)
   214  		VALUES ('%s','%s',%d,%d,%d,%d);`,
   215  
   216  		pa.Schema, pa.Tables.Dictionary,
   217  
   218  		pa.Columns.TableName, pa.Columns.ColumnName,
   219  		pa.Columns.ColumnType, pa.Columns.ColumnLength,
   220  		pa.Columns.PrimaryKey, pa.Columns.ColumnOrder,
   221  
   222  		tableName, columnName, sqlColumnType, length, 0, order)
   223  
   224  	return query, dictionaryQuery
   225  }
   226  
   227  // SelectRowQuery returns a query for selecting row values
   228  func (pa *PostgresAdapter) SelectRowQuery(tableName, fields, indexValue string) string {
   229  	return Cleanf("SELECT %s FROM %s.%s WHERE %s = '%s';",
   230  		fields,                              // select
   231  		pa.Schema, pa.SecureName(tableName), // from
   232  		pa.Columns.Height, indexValue, // where
   233  	)
   234  }
   235  
   236  // SelectLogQuery returns a query for selecting all tables involved in a block trn
   237  func (pa *PostgresAdapter) SelectLogQuery() string {
   238  	query := `
   239  		SELECT DISTINCT %s,%s FROM %s.%s l WHERE %s = $1 AND %s = $2;`
   240  
   241  	return Cleanf(query,
   242  		pa.Columns.TableName, pa.Columns.EventName, // select
   243  		pa.Schema, pa.Tables.Log, // from
   244  		pa.Columns.Height,
   245  		pa.Columns.ChainID) // where
   246  }
   247  
   248  // InsertLogQuery returns a query to insert a row in log table
   249  func (pa *PostgresAdapter) InsertLogQuery() string {
   250  	query := `
   251  		INSERT INTO %s.%s (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
   252  		VALUES (CURRENT_TIMESTAMP, $1, $2, $3, $4, $5, $6 ,$7, $8, $9, $10);`
   253  
   254  	return Cleanf(query,
   255  		pa.Schema, pa.Tables.Log, // insert
   256  		//fields
   257  		pa.Columns.TimeStamp,
   258  		pa.Columns.ChainID, pa.Columns.TableName, pa.Columns.EventName, pa.Columns.EventFilter,
   259  		pa.Columns.Height, pa.Columns.TxHash, pa.Columns.Action, pa.Columns.DataRow,
   260  		pa.Columns.SqlStmt, pa.Columns.SqlValues)
   261  }
   262  
   263  // ErrorEquals verify if an error is of a given SQL type
   264  func (pa *PostgresAdapter) ErrorEquals(err error, sqlErrorType types.SQLErrorType) bool {
   265  	return errorEquals(err, sqlErrorType)
   266  }
   267  
   268  func errorEquals(err error, sqlErrorType types.SQLErrorType) bool {
   269  	pqErr := new(pq.Error)
   270  
   271  	if errors.As(err, &pqErr) {
   272  		switch sqlErrorType {
   273  		case types.SQLErrorTypeGeneric:
   274  			return true
   275  		case types.SQLErrorTypeDuplicatedColumn:
   276  			return pqErr.Code == "42701"
   277  		case types.SQLErrorTypeDuplicatedTable:
   278  			return pqErr.Code == "42P07"
   279  		case types.SQLErrorTypeDuplicatedSchema:
   280  			return pqErr.Code == "42P06"
   281  		case types.SQLErrorTypeUndefinedTable:
   282  			return pqErr.Code == "42P01"
   283  		case types.SQLErrorTypeUndefinedColumn:
   284  			return pqErr.Code == "42703"
   285  		case types.SQLErrorTypeInvalidType:
   286  			return pqErr.Code == "42704"
   287  		}
   288  	}
   289  
   290  	return false
   291  }
   292  
   293  func (pa *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 := pa.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 == pa.Columns.TxHash {
   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 not 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) ", pa.Schema, pa.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 (pa *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 := pa.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;", pa.Schema, pa.SecureName(table.Name), columns)
   408  
   409  	return types.UpsertDeleteQuery{Query: query, Values: values, Pointers: pointers}, nil
   410  }
   411  
   412  func (pa *PostgresAdapter) RestoreDBQuery() string {
   413  	return Cleanf(`SELECT %s, %s, %s, %s, %s FROM %s 
   414  								WHERE %s != '%s' AND  %s != '%s' AND to_char(%s,'YYYY-MM-DD HH24:MI:SS')<=$1 
   415  								ORDER BY %s;`,
   416  		pa.Columns.Id, pa.Columns.TableName, pa.Columns.Action, // select id, table, action
   417  		pa.Columns.SqlStmt, pa.Columns.SqlValues, // select stmt, values
   418  		pa.SchemaName(pa.Tables.Log),          // from
   419  		pa.Columns.TableName, pa.Tables.Block, // where not _vent_block
   420  		pa.Columns.TableName, pa.Tables.Tx, // where not _vent_tx
   421  		pa.Columns.TimeStamp, // where time
   422  		pa.Columns.Id)
   423  }
   424  
   425  func (pa *PostgresAdapter) CleanDBQueries() types.SQLCleanDBQuery {
   426  
   427  	// Chain info
   428  	selectChainIDQry := Cleanf(`
   429  		SELECT 
   430  		COUNT(*) REGISTERS,
   431  		COALESCE(MAX(%s),'') CHAINID,
   432  		COALESCE(MAX(%s),'') BVERSION 
   433  		FROM %s.%s;`,
   434  		pa.Columns.ChainID, pa.Columns.BurrowVersion,
   435  		pa.Schema, pa.Tables.ChainInfo)
   436  
   437  	deleteChainIDQry := Cleanf(`
   438  		DELETE FROM %s;`,
   439  		pa.SchemaName(pa.Tables.ChainInfo))
   440  
   441  	insertChainIDQry := Cleanf(`
   442  		INSERT INTO %s (%s,%s,%s) VALUES($1,$2,$3)`,
   443  		pa.SchemaName(pa.Tables.ChainInfo),
   444  		pa.Columns.ChainID, pa.Columns.BurrowVersion, pa.Columns.Height)
   445  
   446  	// Dictionary
   447  	selectDictionaryQry := Cleanf(`
   448  		SELECT DISTINCT %s 
   449  		FROM %s.%s 
   450   		WHERE %s
   451  		NOT IN ('%s','%s','%s');`,
   452  		pa.Columns.TableName,
   453  		pa.Schema, pa.Tables.Dictionary,
   454  		pa.Columns.TableName,
   455  		pa.Tables.Log, pa.Tables.Dictionary, pa.Tables.ChainInfo)
   456  
   457  	deleteDictionaryQry := Cleanf(`
   458  		DELETE FROM %s.%s 
   459  		WHERE %s 
   460  		NOT IN ('%s','%s','%s');`,
   461  		pa.Schema, pa.Tables.Dictionary,
   462  		pa.Columns.TableName,
   463  		pa.Tables.Log, pa.Tables.Dictionary, pa.Tables.ChainInfo)
   464  
   465  	// log
   466  	deleteLogQry := Cleanf(`
   467  		DELETE FROM %s.%s;`,
   468  		pa.Schema, pa.Tables.Log)
   469  
   470  	return types.SQLCleanDBQuery{
   471  		SelectChainIDQry:    selectChainIDQry,
   472  		DeleteChainIDQry:    deleteChainIDQry,
   473  		InsertChainIDQry:    insertChainIDQry,
   474  		SelectDictionaryQry: selectDictionaryQry,
   475  		DeleteDictionaryQry: deleteDictionaryQry,
   476  		DeleteLogQry:        deleteLogQry,
   477  	}
   478  }
   479  
   480  func (pa *PostgresAdapter) DropTableQuery(tableName string) string {
   481  	// We cascade here to drop any associated views and triggers. We work under the assumption that vent
   482  	// owns its database and any users need to be able to recreate objects that depend on vent tables in the event of
   483  	// table drops
   484  	return Cleanf(`DROP TABLE IF EXISTS %s CASCADE;`, pa.SchemaName(tableName))
   485  }
   486  
   487  func (pa *PostgresAdapter) CreateNotifyFunctionQuery(function, channel string, columns ...string) string {
   488  	return Cleanf(`CREATE OR REPLACE FUNCTION %s() RETURNS trigger AS
   489  		$trigger$
   490  		BEGIN
   491  			CASE TG_OP
   492  			WHEN 'DELETE' THEN
   493  				PERFORM pg_notify('%s', CAST(json_build_object('%s', TG_OP, %s) as text));
   494  				RETURN OLD;
   495  			ELSE
   496  				PERFORM pg_notify('%s', CAST(json_build_object('%s', TG_OP, %s) as text));
   497  				RETURN NEW;
   498  			END CASE;
   499  		END;
   500  		$trigger$
   501  		LANGUAGE 'plpgsql';
   502  		`,
   503  		pa.SchemaName(function),                                         // create function
   504  		channel, pa.Columns.Action, jsonBuildObjectArgs("OLD", columns), // case delete
   505  		channel, pa.Columns.Action, jsonBuildObjectArgs("NEW", columns), // case else
   506  	)
   507  }
   508  
   509  func (pa *PostgresAdapter) CreateTriggerQuery(triggerName, tableName, functionName string) string {
   510  	trigger := pa.SecureName(triggerName)
   511  	table := pa.SchemaName(tableName)
   512  	return Cleanf(`DROP TRIGGER IF EXISTS %s ON %s CASCADE; 
   513  		CREATE TRIGGER %s AFTER INSERT OR UPDATE OR DELETE ON %s
   514  		FOR EACH ROW 
   515  		EXECUTE PROCEDURE %s();
   516  		`,
   517  		trigger,                     // drop
   518  		table,                       // on
   519  		trigger,                     // create
   520  		table,                       // on
   521  		pa.SchemaName(functionName), // function
   522  	)
   523  }
   524  
   525  func (pa *PostgresAdapter) SchemaName(tableName string) string {
   526  	return fmt.Sprintf("%s.%s", pa.Schema, pa.SecureName(tableName))
   527  }
   528  
   529  func secureName(columnName string) string {
   530  	return `"` + columnName + `"`
   531  }
   532  
   533  func jsonBuildObjectArgs(record string, columns []string) string {
   534  	elements := make([]string, len(columns))
   535  	for i, column := range columns {
   536  		elements[i] = "'" + column + "', " + record + "." + secureName(column)
   537  	}
   538  
   539  	return strings.Join(elements, ", ")
   540  }