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 }