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 }