github.com/Tri-stone/burrow@v0.25.0/vent/sqldb/sqldb.go (about) 1 package sqldb 2 3 import ( 4 "database/sql" 5 "errors" 6 "fmt" 7 "strconv" 8 "strings" 9 "time" 10 11 "github.com/hyperledger/burrow/vent/logger" 12 "github.com/hyperledger/burrow/vent/sqldb/adapters" 13 "github.com/hyperledger/burrow/vent/types" 14 ) 15 16 // SQLDB implements the access to a sql database 17 type SQLDB struct { 18 DB *sql.DB 19 DBAdapter adapters.DBAdapter 20 Schema string 21 Log *logger.Logger 22 } 23 24 // NewSQLDB delegates work to a specific database adapter implementation, 25 // opens database connection and create log tables 26 func NewSQLDB(connection types.SQLConnection) (*SQLDB, error) { 27 db := &SQLDB{ 28 Schema: connection.DBSchema, 29 Log: connection.Log, 30 } 31 32 var url string 33 34 switch connection.DBAdapter { 35 case types.PostgresDB: 36 db.DBAdapter = adapters.NewPostgresAdapter(safe(connection.DBSchema), connection.Log) 37 url = connection.DBURL 38 39 case types.SQLiteDB: 40 db.DBAdapter = adapters.NewSQLiteAdapter(connection.Log) 41 // "?_journal_mode=WAL" parameter is necessary to prevent database locking 42 url = connection.DBURL + "?_journal_mode=WAL" 43 44 default: 45 return nil, errors.New("invalid database adapter") 46 } 47 48 var err error 49 db.DB, err = db.DBAdapter.Open(url) 50 if err != nil { 51 db.Log.Info("msg", "Error opening database connection", "err", err) 52 return nil, err 53 } 54 55 if err = db.Ping(); err != nil { 56 db.Log.Info("msg", "Error database not available", "err", err) 57 return nil, err 58 } 59 60 db.Log.Info("msg", "Initializing DB") 61 62 // Create dictionary and log tables 63 sysTables := db.getSysTablesDefinition() 64 65 // IMPORTANT: DO NOT CHANGE TABLE CREATION ORDER (1) 66 if err = db.createTable(sysTables[types.SQLDictionaryTableName], string(types.ActionInitialize)); err != nil { 67 if !db.DBAdapter.ErrorEquals(err, types.SQLErrorTypeDuplicatedTable) { 68 db.Log.Info("msg", "Error creating Dictionary table", "err", err) 69 return nil, err 70 } 71 } 72 73 // IMPORTANT: DO NOT CHANGE TABLE CREATION ORDER (2) 74 if err = db.createTable(sysTables[types.SQLLogTableName], string(types.ActionInitialize)); err != nil { 75 if !db.DBAdapter.ErrorEquals(err, types.SQLErrorTypeDuplicatedTable) { 76 db.Log.Info("msg", "Error creating Log table", "err", err) 77 return nil, err 78 } 79 } 80 81 // IMPORTANT: DO NOT CHANGE TABLE CREATION ORDER (3) 82 if err = db.createTable(sysTables[types.SQLChainInfoTableName], string(types.ActionInitialize)); err != nil { 83 if !db.DBAdapter.ErrorEquals(err, types.SQLErrorTypeDuplicatedTable) { 84 db.Log.Info("msg", "Error creating Chain Info table", "err", err) 85 return nil, err 86 } 87 } 88 89 if err = db.CleanTables(connection.ChainID, connection.BurrowVersion); err != nil { 90 db.Log.Info("msg", "Error cleaning tables", "err", err) 91 return nil, err 92 } 93 return db, nil 94 } 95 96 // CleanTables, drop tables if stored chainID is different from the given one & store new chainID 97 // if the chainID is the same, do nothing 98 func (db *SQLDB) CleanTables(chainID, burrowVersion string) error { 99 100 if chainID == "" { 101 return fmt.Errorf("error CHAIN ID cannot by empty") 102 } 103 104 cleanQueries := db.DBAdapter.CleanDBQueries() 105 106 var savedChainID, savedBurrowVersion, query string 107 savedRows := 0 108 109 // Read chainID 110 query = cleanQueries.SelectChainIDQry 111 if err := db.DB.QueryRow(query).Scan(&savedRows, &savedChainID, &savedBurrowVersion); err != nil { 112 db.Log.Info("msg", "Error selecting CHAIN ID", "err", err, "query", query) 113 return err 114 } 115 116 switch { 117 // Must be empty or one row 118 case savedRows != 0 && savedRows != 1: 119 return fmt.Errorf("error multiple CHAIN ID returned") 120 121 // First database access 122 case savedRows == 0: 123 // Save new values and exit 124 query = cleanQueries.InsertChainIDQry 125 if _, err := db.DB.Exec(query, chainID, burrowVersion); err != nil { 126 db.Log.Info("msg", "Error inserting CHAIN ID", "err", err, "query", query) 127 return err 128 } 129 return nil 130 131 // if data equals previous version exit 132 case savedChainID == chainID: 133 return nil 134 135 // clean database 136 default: 137 var tx *sql.Tx 138 var err error 139 var tableName string 140 tables := make([]string, 0) 141 142 // Begin tx 143 if tx, err = db.DB.Begin(); err != nil { 144 db.Log.Info("msg", "Error beginning transaction", "err", err) 145 return err 146 } 147 defer tx.Rollback() 148 149 // Delete chainID 150 query := cleanQueries.DeleteChainIDQry 151 if _, err = tx.Exec(query); err != nil { 152 db.Log.Info("msg", "Error deleting CHAIN ID", "err", err, "query", query) 153 return err 154 } 155 156 // Insert chainID 157 query = cleanQueries.InsertChainIDQry 158 if _, err := tx.Exec(query, chainID, burrowVersion); err != nil { 159 db.Log.Info("msg", "Error inserting CHAIN ID", "err", err, "query", query) 160 return err 161 } 162 163 // Load Tables 164 query = cleanQueries.SelectDictionaryQry 165 rows, err := tx.Query(query) 166 if err != nil { 167 db.Log.Info("msg", "error querying dictionary", "err", err, "query", query) 168 return err 169 } 170 defer rows.Close() 171 172 for rows.Next() { 173 174 if err = rows.Scan(&tableName); err != nil { 175 db.Log.Info("msg", "error scanning table structure", "err", err) 176 return err 177 } 178 179 if err = rows.Err(); err != nil { 180 db.Log.Info("msg", "error scanning table structure", "err", err) 181 return err 182 } 183 tables = append(tables, tableName) 184 } 185 186 // Delete Dictionary 187 query = cleanQueries.DeleteDictionaryQry 188 if _, err = tx.Exec(query); err != nil { 189 db.Log.Info("msg", "Error deleting dictionary", "err", err, "query", query) 190 return err 191 } 192 193 // Delete Log 194 query = cleanQueries.DeleteLogQry 195 if _, err = tx.Exec(query); err != nil { 196 db.Log.Info("msg", "Error deleting log", "err", err, "query", query) 197 return err 198 } 199 // Drop database tables 200 for _, tableName = range tables { 201 query = db.DBAdapter.DropTableQuery(tableName) 202 if _, err = tx.Exec(query); err != nil { 203 // if error == table does not exists, continue 204 if !db.DBAdapter.ErrorEquals(err, types.SQLErrorTypeUndefinedTable) { 205 db.Log.Info("msg", "error dropping tables", "err", err, "value", tableName, "query", query) 206 return err 207 } 208 } 209 } 210 211 // Commit 212 if err = tx.Commit(); err != nil { 213 db.Log.Info("msg", "Error commiting transaction", "err", err) 214 return err 215 } 216 217 return nil 218 } 219 } 220 221 // Close database connection 222 func (db *SQLDB) Close() { 223 if err := db.DB.Close(); err != nil { 224 db.Log.Error("msg", "Error closing database", "err", err) 225 } 226 } 227 228 // Ping database 229 func (db *SQLDB) Ping() error { 230 if err := db.DB.Ping(); err != nil { 231 db.Log.Info("msg", "Error database not available", "err", err) 232 return err 233 } 234 235 return nil 236 } 237 238 // GetLastBlockID returns last inserted blockId from log table 239 func (db *SQLDB) GetLastBlockHeight() (uint64, error) { 240 query := db.DBAdapter.LastBlockIDQuery() 241 id := "" 242 243 db.Log.Info("msg", "MAX ID", "query", query) 244 245 if err := db.DB.QueryRow(query).Scan(&id); err != nil { 246 db.Log.Info("msg", "Error selecting last block id", "err", err) 247 return 0, err 248 } 249 height, err := strconv.ParseUint(id, 10, 64) 250 if err != nil { 251 return 0, fmt.Errorf("could not parse height from block ID: %v", err) 252 } 253 return height, nil 254 } 255 256 // SynchronizeDB synchronize db tables structures from given tables specifications 257 func (db *SQLDB) SynchronizeDB(eventTables types.EventTables) error { 258 db.Log.Info("msg", "Synchronizing DB") 259 260 for eventName, table := range eventTables { 261 found, err := db.findTable(table.Name) 262 if err != nil { 263 return err 264 } 265 266 if found { 267 err = db.alterTable(table, eventName) 268 } else { 269 err = db.createTable(table, eventName) 270 } 271 if err != nil { 272 return err 273 } 274 } 275 276 return nil 277 } 278 279 // SetBlock inserts or updates multiple rows and stores log info in SQL tables 280 func (db *SQLDB) SetBlock(eventTables types.EventTables, eventData types.EventData) error { 281 db.Log.Info("msg", "Synchronize Block..........") 282 283 //Declarations 284 var logStmt *sql.Stmt 285 var tx *sql.Tx 286 var safeTable string 287 var query string 288 var queryVal types.UpsertDeleteQuery 289 var err error 290 var errQuery error 291 var txHash interface{} 292 var jsonData []byte 293 var sqlValues []byte 294 295 // Begin tx 296 if tx, err = db.DB.Begin(); err != nil { 297 db.Log.Info("msg", "Error beginning transaction", "err", err) 298 return err 299 } 300 defer tx.Rollback() 301 302 // Prepare log statement 303 logQuery := db.DBAdapter.InsertLogQuery() 304 if logStmt, err = tx.Prepare(logQuery); err != nil { 305 db.Log.Info("msg", "Error preparing log stmt", "err", err) 306 return err 307 } 308 309 loop: 310 // for each table in the block 311 for eventName, table := range eventTables { 312 safeTable = safe(table.Name) 313 dataRows := eventData.Tables[table.Name] 314 // for Each Row 315 for _, row := range dataRows { 316 317 switch row.Action { 318 case types.ActionUpsert: 319 //Prepare Upsert 320 if queryVal, txHash, errQuery = db.DBAdapter.UpsertQuery(table, row); errQuery != nil { 321 db.Log.Info("msg", "Error building upsert query", "err", errQuery, "value", fmt.Sprintf("%v %v", table, row)) 322 break loop // exits from all loops -> continue in close log stmt 323 } 324 325 case types.ActionDelete: 326 //Prepare Delete 327 txHash = nil 328 if queryVal, errQuery = db.DBAdapter.DeleteQuery(table, row); errQuery != nil { 329 db.Log.Info("msg", "Error building delete query", "err", errQuery, "value", fmt.Sprintf("%v %v", table, row)) 330 break loop // exits from all loops -> continue in close log stmt 331 } 332 default: 333 //Invalid Action 334 db.Log.Info("msg", "invalid action", "value", row.Action) 335 err = fmt.Errorf("invalid row action %s", row.Action) 336 break loop // exits from all loops -> continue in close log stmt 337 } 338 339 query = queryVal.Query 340 341 // Perform row action 342 db.Log.Info("msg", row.Action, "query", query, "value", queryVal.Values) 343 if _, err = tx.Exec(query, queryVal.Pointers...); err != nil { 344 db.Log.Info("msg", fmt.Sprintf("error performing %s on row", row.Action), "err", err, "value", queryVal.Values) 345 break loop // exits from all loops -> continue in close log stmt 346 } 347 348 // Marshal the rowData map 349 if jsonData, err = db.getJSON(row.RowData); err != nil { 350 db.Log.Info("msg", "error marshaling rowData", "err", err, "value", fmt.Sprintf("%v", row.RowData)) 351 break loop // exits from all loops -> continue in close log stmt 352 } 353 354 // Marshal sql values 355 if sqlValues, err = db.getJSONFromValues(queryVal.Pointers); err != nil { 356 db.Log.Info("msg", "error marshaling rowdata", "err", err, "value", fmt.Sprintf("%v", row.RowData)) 357 break loop // exits from all loops -> continue in close log stmt 358 } 359 360 // Insert in log 361 db.Log.Info("msg", "INSERT LOG", "query", logQuery, "value", 362 fmt.Sprintf("tableName = %s eventName = %s block = %d", safeTable, eventName, eventData.BlockHeight)) 363 364 if _, err = logStmt.Exec(safeTable, eventName, row.EventClass.GetFilter(), eventData.BlockHeight, txHash, 365 row.Action, jsonData, query, sqlValues); err != nil { 366 db.Log.Info("msg", "Error inserting into log", "err", err) 367 break loop // exits from all loops -> continue in close log stmt 368 } 369 } 370 } 371 372 // Close log statement 373 if err == nil { 374 if err = logStmt.Close(); err != nil { 375 db.Log.Info("msg", "Error closing log stmt", "err", err) 376 } 377 } 378 379 // Error handling 380 if err != nil { 381 // Rollback error 382 if errRb := tx.Rollback(); errRb != nil { 383 db.Log.Info("msg", "Error on rollback", "err", errRb) 384 return errRb 385 } 386 387 //Is a SQL error 388 if db.DBAdapter.ErrorEquals(err, types.SQLErrorTypeGeneric) { 389 390 // Table does not exists 391 if db.DBAdapter.ErrorEquals(err, types.SQLErrorTypeUndefinedTable) { 392 db.Log.Warn("msg", "Table not found", "value", safeTable) 393 //Synchronize DB 394 if err = db.SynchronizeDB(eventTables); err != nil { 395 return err 396 } 397 //Retry 398 return db.SetBlock(eventTables, eventData) 399 } 400 401 // Columns do not match 402 if db.DBAdapter.ErrorEquals(err, types.SQLErrorTypeUndefinedColumn) { 403 db.Log.Warn("msg", "Column not found", "value", safeTable) 404 //Synchronize DB 405 if err = db.SynchronizeDB(eventTables); err != nil { 406 return err 407 } 408 //Retry 409 return db.SetBlock(eventTables, eventData) 410 } 411 return err 412 } 413 return err 414 } 415 416 db.Log.Info("msg", "COMMIT") 417 418 if err := tx.Commit(); err != nil { 419 db.Log.Info("msg", "Error on commit", "err", err) 420 return err 421 } 422 423 return nil 424 } 425 426 // GetBlock returns all tables structures and row data for given block 427 func (db *SQLDB) GetBlock(height uint64) (types.EventData, error) { 428 var data types.EventData 429 data.BlockHeight = height 430 data.Tables = make(map[string]types.EventDataTable) 431 432 // get all table structures involved in the block 433 tables, err := db.getBlockTables(height) 434 if err != nil { 435 return data, err 436 } 437 438 query := "" 439 440 // for each table 441 for _, table := range tables { 442 // get query for table 443 query, err = db.getSelectQuery(table, height) 444 if err != nil { 445 db.Log.Info("msg", "Error building table query", "err", err) 446 return data, err 447 } 448 db.Log.Info("msg", "Query table data", "query", query) 449 rows, err := db.DB.Query(query) 450 if err != nil { 451 db.Log.Info("msg", "Error querying table data", "err", err) 452 return data, err 453 } 454 defer rows.Close() 455 456 cols, err := rows.Columns() 457 if err != nil { 458 db.Log.Info("msg", "Error getting row columns", "err", err) 459 return data, err 460 } 461 462 // builds pointers 463 length := len(cols) 464 pointers := make([]interface{}, length) 465 containers := make([]sql.NullString, length) 466 467 for i := range pointers { 468 pointers[i] = &containers[i] 469 } 470 471 // for each row in table 472 var dataRows []types.EventDataRow 473 474 for rows.Next() { 475 476 row := make(map[string]interface{}) 477 478 if err = rows.Scan(pointers...); err != nil { 479 db.Log.Info("msg", "Error scanning data", "err", err) 480 return data, err 481 } 482 db.Log.Info("msg", "Query resultset", "value", fmt.Sprintf("%+v", containers)) 483 484 // for each column in row 485 for i, col := range cols { 486 // add value if not null 487 if containers[i].Valid { 488 row[col] = containers[i].String 489 } 490 } 491 dataRows = append(dataRows, types.EventDataRow{Action: types.ActionRead, RowData: row}) 492 } 493 494 if err = rows.Err(); err != nil { 495 db.Log.Info("msg", "Error during rows iteration", "err", err) 496 return data, err 497 } 498 data.Tables[table.Name] = dataRows 499 } 500 return data, nil 501 } 502 503 // RestoreDB restores the DB to a given moment in time 504 func (db *SQLDB) RestoreDB(time time.Time, prefix string) error { 505 506 const yymmddhhmmss = "2006-01-02 15:04:05" 507 508 var pointers []interface{} 509 510 if prefix == "" { 511 return fmt.Errorf("error prefix mus not be empty") 512 } 513 514 // Get Restore DB query 515 query := db.DBAdapter.RestoreDBQuery() 516 strTime := time.Format(yymmddhhmmss) 517 518 db.Log.Info("msg", "RESTORING DB..................................") 519 520 // Open rows 521 db.Log.Info("msg", "open log", "query", query) 522 rows, err := db.DB.Query(query, strTime) 523 if err != nil { 524 db.Log.Info("msg", "error querying log", "err", err) 525 return err 526 } 527 defer rows.Close() 528 529 // For each row returned 530 for rows.Next() { 531 var tableName, sqlSmt, sqlValues string 532 var action types.DBAction 533 534 if err = rows.Scan(&tableName, &action, &sqlSmt, &sqlValues); err != nil { 535 db.Log.Info("msg", "error scanning table structure", "err", err) 536 return err 537 } 538 539 if err = rows.Err(); err != nil { 540 db.Log.Info("msg", "error scanning table structure", "err", err) 541 return err 542 } 543 544 restoreTable := fmt.Sprintf("%s_%s", prefix, tableName) 545 546 switch action { 547 case types.ActionUpsert, types.ActionDelete: 548 // get row values 549 if pointers, err = db.getValuesFromJSON(sqlValues); err != nil { 550 db.Log.Info("msg", "error unmarshaling json", "err", err, "value", sqlValues) 551 return err 552 } 553 554 // Prepare Upsert/delete 555 query = strings.Replace(sqlSmt, tableName, restoreTable, -1) 556 557 db.Log.Info("msg", "SQL COMMAND", "sql", query) 558 if _, err = db.DB.Exec(query, pointers...); err != nil { 559 db.Log.Info("msg", "Error executing upsert/delete ", "err", err, "value", sqlSmt, "data", sqlValues) 560 return err 561 } 562 563 case types.ActionAlterTable, types.ActionCreateTable: 564 // Prepare Alter/Create Table 565 query = strings.Replace(sqlSmt, tableName, restoreTable, -1) 566 567 db.Log.Info("msg", "SQL COMMAND", "sql", query) 568 if _, err = db.DB.Exec(query); err != nil { 569 db.Log.Info("msg", "Error executing alter/create table command ", "err", err, "value", sqlSmt) 570 return err 571 } 572 default: 573 // Invalid Action 574 db.Log.Info("msg", "invalid action", "value", action) 575 return fmt.Errorf("invalid row action %s", action) 576 } 577 } 578 return nil 579 }