github.com/Tri-stone/burrow@v0.25.0/vent/sqldb/adapters/sqlite_adapter.go (about) 1 // sqlite3 is a CGO dependency - we cannot have it on board if we want to use pure Go (e.g. for cross-compiling and other things) 2 // +build sqlite 3 4 package adapters 5 6 import ( 7 "database/sql" 8 "fmt" 9 "strings" 10 11 "github.com/hyperledger/burrow/vent/logger" 12 "github.com/hyperledger/burrow/vent/types" 13 sqlite3 "github.com/mattn/go-sqlite3" 14 ) 15 16 var sqliteDataTypes = map[types.SQLColumnType]string{ 17 types.SQLColumnTypeBool: "BOOLEAN", 18 types.SQLColumnTypeByteA: "BLOB", 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: "TEXT", 26 types.SQLColumnTypeBigInt: "BIGINT", 27 } 28 29 // SQLiteAdapter implements DBAdapter for SQLiteDB 30 type SQLiteAdapter struct { 31 Log *logger.Logger 32 } 33 34 // NewSQLiteAdapter constructs a new db adapter 35 func NewSQLiteAdapter(log *logger.Logger) *SQLiteAdapter { 36 return &SQLiteAdapter{ 37 Log: log, 38 } 39 } 40 41 // Open connects to a SQLiteQL database, opens it & create default schema if provided 42 func (adapter *SQLiteAdapter) Open(dbURL string) (*sql.DB, error) { 43 db, err := sql.Open("sqlite3", dbURL) 44 if err != nil { 45 adapter.Log.Info("msg", "Error creating database connection", "err", err) 46 return nil, err 47 } 48 49 return db, nil 50 } 51 52 // TypeMapping convert generic dataTypes to database dependent dataTypes 53 func (adapter *SQLiteAdapter) TypeMapping(sqlColumnType types.SQLColumnType) (string, error) { 54 if sqlDataType, ok := sqliteDataTypes[sqlColumnType]; ok { 55 return sqlDataType, nil 56 } 57 58 return "", fmt.Errorf("datatype %v not recognized", sqlColumnType) 59 } 60 61 // SecureColumnName return columns between appropriate security containers 62 func (adapter *SQLiteAdapter) SecureName(name string) string { 63 return Cleanf("[%s]", name) 64 } 65 66 // CreateTableQuery builds query for creating a new table 67 func (adapter *SQLiteAdapter) CreateTableQuery(tableName string, columns []*types.SQLTableColumn) (string, string) { 68 // build query 69 columnsDef := "" 70 primaryKey := "" 71 dictionaryValues := "" 72 hasSerial := false 73 74 for i, column := range columns { 75 secureColumn := adapter.SecureName(column.Name) 76 sqlType, _ := adapter.TypeMapping(column.Type) 77 pKey := 0 78 79 if columnsDef != "" { 80 columnsDef += ", " 81 dictionaryValues += ", " 82 } 83 84 if column.Type == types.SQLColumnTypeSerial { 85 // SQLITE AUTOINCREMENT LIMITATION 86 columnsDef += Cleanf("%s %s", secureColumn, "INTEGER PRIMARY KEY AUTOINCREMENT") 87 hasSerial = true 88 } else { 89 columnsDef += Cleanf("%s %s", secureColumn, sqlType) 90 } 91 92 if column.Length > 0 { 93 columnsDef += Cleanf("(%v)", column.Length) 94 } 95 96 if column.Primary { 97 pKey = 1 98 columnsDef += " NOT NULL" 99 if primaryKey != "" { 100 primaryKey += ", " 101 } 102 primaryKey += secureColumn 103 } 104 105 dictionaryValues += Cleanf("('%s','%s',%d,%d,%d,%d)", 106 tableName, 107 column.Name, 108 column.Type, 109 column.Length, 110 pKey, 111 i) 112 } 113 114 query := Cleanf("CREATE TABLE %s (%s", adapter.SecureName(tableName), columnsDef) 115 if primaryKey != "" { 116 if hasSerial { 117 // SQLITE AUTOINCREMENT LIMITATION 118 query += "," + Cleanf("UNIQUE (%s)", primaryKey) 119 } else { 120 query += "," + Cleanf("CONSTRAINT %s_pkey PRIMARY KEY (%s)", tableName, primaryKey) 121 } 122 } 123 query += ");" 124 125 dictionaryQuery := Cleanf("INSERT INTO %s (%s,%s,%s,%s,%s,%s) VALUES %s;", 126 types.SQLDictionaryTableName, 127 types.SQLColumnLabelTableName, types.SQLColumnLabelColumnName, 128 types.SQLColumnLabelColumnType, types.SQLColumnLabelColumnLength, 129 types.SQLColumnLabelPrimaryKey, types.SQLColumnLabelColumnOrder, 130 dictionaryValues) 131 132 return query, dictionaryQuery 133 } 134 135 // LastBlockIDQuery returns a query for last inserted blockId in log table 136 func (adapter *SQLiteAdapter) LastBlockIDQuery() string { 137 query := ` 138 WITH ll AS ( 139 SELECT MAX(%s) AS %s FROM %s 140 ) 141 SELECT COALESCE(%s, '0') AS %s 142 FROM ll LEFT OUTER JOIN %s log ON (ll.%s = log.%s);` 143 144 return Cleanf(query, 145 types.SQLColumnLabelId, // max 146 types.SQLColumnLabelId, // as 147 types.SQLLogTableName, // from 148 types.SQLColumnLabelHeight, // coalesce 149 types.SQLColumnLabelHeight, // as 150 types.SQLLogTableName, // from 151 types.SQLColumnLabelId, types.SQLColumnLabelId) // on 152 } 153 154 // FindTableQuery returns a query that checks if a table exists 155 func (adapter *SQLiteAdapter) FindTableQuery() string { 156 query := "SELECT COUNT(*) found FROM %s WHERE %s = $1;" 157 158 return Cleanf(query, 159 types.SQLDictionaryTableName, // from 160 types.SQLColumnLabelTableName) // where 161 } 162 163 // TableDefinitionQuery returns a query with table structure 164 func (adapter *SQLiteAdapter) TableDefinitionQuery() string { 165 query := ` 166 SELECT 167 %s,%s,%s,%s 168 FROM 169 %s 170 WHERE 171 %s = $1 172 ORDER BY 173 %s;` 174 175 return Cleanf(query, 176 types.SQLColumnLabelColumnName, types.SQLColumnLabelColumnType, // select 177 types.SQLColumnLabelColumnLength, types.SQLColumnLabelPrimaryKey, // select 178 types.SQLDictionaryTableName, // from 179 types.SQLColumnLabelTableName, // where 180 types.SQLColumnLabelColumnOrder) // order by 181 } 182 183 // AlterColumnQuery returns a query for adding a new column to a table 184 func (adapter *SQLiteAdapter) AlterColumnQuery(tableName, columnName string, sqlColumnType types.SQLColumnType, length, order int) (string, string) { 185 sqlType, _ := adapter.TypeMapping(sqlColumnType) 186 if length > 0 { 187 sqlType = Cleanf("%s(%d)", sqlType, length) 188 } 189 190 query := Cleanf("ALTER TABLE %s ADD COLUMN %s %s;", 191 adapter.SecureName(tableName), 192 adapter.SecureName(columnName), 193 sqlType) 194 195 dictionaryQuery := Cleanf(` 196 INSERT INTO %s (%s,%s,%s,%s,%s,%s) 197 VALUES ('%s','%s',%d,%d,%d,%d);`, 198 199 types.SQLDictionaryTableName, 200 201 types.SQLColumnLabelTableName, types.SQLColumnLabelColumnName, 202 types.SQLColumnLabelColumnType, types.SQLColumnLabelColumnLength, 203 types.SQLColumnLabelPrimaryKey, types.SQLColumnLabelColumnOrder, 204 205 tableName, columnName, sqlColumnType, length, 0, order) 206 207 return query, dictionaryQuery 208 } 209 210 // SelectRowQuery returns a query for selecting row values 211 func (adapter *SQLiteAdapter) SelectRowQuery(tableName, fields, indexValue string) string { 212 return Cleanf("SELECT %s FROM %s WHERE %s = '%s';", fields, adapter.SecureName(tableName), types.SQLColumnLabelHeight, indexValue) 213 } 214 215 // SelectLogQuery returns a query for selecting all tables involved in a block trn 216 func (adapter *SQLiteAdapter) SelectLogQuery() string { 217 query := ` 218 SELECT DISTINCT %s,%s FROM %s l WHERE %s = $1;` 219 220 return Cleanf(query, 221 types.SQLColumnLabelTableName, types.SQLColumnLabelEventName, // select 222 types.SQLLogTableName, // from 223 types.SQLColumnLabelHeight) // where 224 } 225 226 // InsertLogQuery returns a query to insert a row in log table 227 func (adapter *SQLiteAdapter) InsertLogQuery() string { 228 query := ` 229 INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) 230 VALUES (CURRENT_TIMESTAMP, $1, $2, $3, $4, $5, $6 ,$7, $8, $9);` 231 232 return Cleanf(query, 233 types.SQLLogTableName, // insert 234 //fields 235 types.SQLColumnLabelTimeStamp, types.SQLColumnLabelTableName, types.SQLColumnLabelEventName, types.SQLColumnLabelEventFilter, 236 types.SQLColumnLabelHeight, types.SQLColumnLabelTxHash, types.SQLColumnLabelAction, types.SQLColumnLabelDataRow, 237 types.SQLColumnLabelSqlStmt, types.SQLColumnLabelSqlValues) 238 } 239 240 // ErrorEquals verify if an error is of a given SQL type 241 func (adapter *SQLiteAdapter) ErrorEquals(err error, sqlErrorType types.SQLErrorType) bool { 242 if err, ok := err.(sqlite3.Error); ok { 243 errDescription := err.Error() 244 245 switch sqlErrorType { 246 case types.SQLErrorTypeGeneric: 247 return true 248 case types.SQLErrorTypeDuplicatedColumn: 249 return err.Code == 1 && strings.Contains(errDescription, "duplicate column") 250 case types.SQLErrorTypeDuplicatedTable: 251 return err.Code == 1 && strings.Contains(errDescription, "table") && strings.Contains(errDescription, "already exists") 252 case types.SQLErrorTypeUndefinedTable: 253 return err.Code == 1 && strings.Contains(errDescription, "no such table") 254 case types.SQLErrorTypeUndefinedColumn: 255 return err.Code == 1 && strings.Contains(errDescription, "table") && strings.Contains(errDescription, "has no column named") 256 case types.SQLErrorTypeInvalidType: 257 // NOT SUPPORTED 258 return false 259 } 260 } 261 262 return false 263 } 264 265 func (adapter *SQLiteAdapter) UpsertQuery(table *types.SQLTable, row types.EventDataRow) (types.UpsertDeleteQuery, interface{}, error) { 266 pointers := make([]interface{}, 0) 267 columns := "" 268 insValues := "" 269 updValues := "" 270 pkColumns := "" 271 values := "" 272 var txHash interface{} = nil 273 274 i := 0 275 276 // for each column in table 277 for _, column := range table.Columns { 278 secureColumn := adapter.SecureName(column.Name) 279 280 i++ 281 282 // INSERT INTO TABLE (*columns)......... 283 if columns != "" { 284 columns += ", " 285 insValues += ", " 286 values += ", " 287 } 288 columns += secureColumn 289 insValues += "$" + Cleanf("%d", i) 290 291 //find data for column 292 if value, ok := row.RowData[column.Name]; ok { 293 //load hash value 294 if column.Name == types.SQLColumnLabelTxHash { 295 txHash = value 296 } 297 298 // column found (not null) 299 // load values 300 pointers = append(pointers, &value) 301 values += fmt.Sprint(value) 302 303 if !column.Primary { 304 // column is no PK 305 // add to update list 306 // INSERT........... ON CONFLICT......DO UPDATE (*updValues) 307 if updValues != "" { 308 updValues += ", " 309 } 310 updValues += secureColumn + " = $" + Cleanf("%d", i) 311 } 312 } else if column.Primary { 313 // column NOT found (is null) and is PK 314 return types.UpsertDeleteQuery{}, nil, fmt.Errorf("error null primary key for column %s", secureColumn) 315 } else { 316 // column NOT found (is null) and is NOT PK 317 pointers = append(pointers, nil) 318 values += "null" 319 } 320 321 if column.Primary { 322 // ON CONFLICT (....values....) 323 if pkColumns != "" { 324 pkColumns += ", " 325 } 326 pkColumns += secureColumn 327 } 328 329 } 330 331 query := Cleanf("INSERT INTO %s (%s) VALUES (%s) ", adapter.SecureName(table.Name), columns, insValues) 332 333 if pkColumns != "" { 334 if updValues != "" { 335 query += Cleanf("ON CONFLICT (%s) DO UPDATE SET %s", pkColumns, updValues) 336 } else { 337 query += Cleanf("ON CONFLICT (%s) DO NOTHING", pkColumns) 338 } 339 } 340 query += ";" 341 342 return types.UpsertDeleteQuery{Query: query, Values: values, Pointers: pointers}, txHash, nil 343 } 344 345 func (adapter *SQLiteAdapter) DeleteQuery(table *types.SQLTable, row types.EventDataRow) (types.UpsertDeleteQuery, error) { 346 347 pointers := make([]interface{}, 0) 348 columns := "" 349 values := "" 350 i := 0 351 352 // for each column in table 353 for _, column := range table.Columns { 354 355 //only PK for delete 356 if column.Primary { 357 i++ 358 359 secureColumn := adapter.SecureName(column.Name) 360 361 // WHERE .......... 362 if columns != "" { 363 columns += " AND " 364 values += ", " 365 } 366 367 columns += Cleanf("%s = $%d", secureColumn, i) 368 369 //find data for column 370 if value, ok := row.RowData[column.Name]; ok { 371 // column found (not null) 372 // load values 373 pointers = append(pointers, &value) 374 values += fmt.Sprint(value) 375 376 } else { 377 // column NOT found (is null) and is PK 378 return types.UpsertDeleteQuery{}, fmt.Errorf("error null primary key for column %s", secureColumn) 379 } 380 } 381 } 382 383 if columns == "" { 384 return types.UpsertDeleteQuery{}, fmt.Errorf("error primary key not found for deletion") 385 } 386 387 query := Cleanf("DELETE FROM %s WHERE %s;", adapter.SecureName(table.Name), columns) 388 389 return types.UpsertDeleteQuery{Query: query, Values: values, Pointers: pointers}, nil 390 } 391 392 func (adapter *SQLiteAdapter) RestoreDBQuery() string { 393 394 query := Cleanf("SELECT %s, %s, %s, %s FROM %s", 395 types.SQLColumnLabelTableName, types.SQLColumnLabelAction, types.SQLColumnLabelSqlStmt, types.SQLColumnLabelSqlValues, 396 types.SQLLogTableName) 397 398 query += " WHERE strftime('%Y-%m-%d %H:%M:%S'," 399 400 query += Cleanf("%s)<=$1 ORDER BY %s;", 401 types.SQLColumnLabelTimeStamp, types.SQLColumnLabelId) 402 403 return query 404 405 } 406 407 func (adapter *SQLiteAdapter) CleanDBQueries() types.SQLCleanDBQuery { 408 // Chain info 409 selectChainIDQry := Cleanf(` 410 SELECT 411 COUNT(*) REGISTERS, 412 COALESCE(MAX(%s),'') CHAINID, 413 COALESCE(MAX(%s),'') BVERSION 414 FROM %s;`, 415 types.SQLColumnLabelChainID, types.SQLColumnLabelBurrowVer, 416 types.SQLChainInfoTableName) 417 418 deleteChainIDQry := Cleanf(` 419 DELETE FROM %s;`, 420 types.SQLChainInfoTableName) 421 422 insertChainIDQry := Cleanf(` 423 INSERT INTO %s (%s,%s) VALUES($1,$2)`, 424 types.SQLChainInfoTableName, 425 types.SQLColumnLabelChainID, types.SQLColumnLabelBurrowVer) 426 427 // Dictionary 428 selectDictionaryQry := Cleanf(` 429 SELECT DISTINCT %s 430 FROM %s 431 WHERE %s 432 NOT IN ('%s','%s','%s');`, 433 types.SQLColumnLabelTableName, 434 types.SQLDictionaryTableName, 435 types.SQLColumnLabelTableName, 436 types.SQLLogTableName, types.SQLDictionaryTableName, types.SQLChainInfoTableName) 437 438 deleteDictionaryQry := Cleanf(` 439 DELETE FROM %s 440 WHERE %s 441 NOT IN ('%s','%s','%s');`, 442 types.SQLDictionaryTableName, 443 types.SQLColumnLabelTableName, 444 types.SQLLogTableName, types.SQLDictionaryTableName, types.SQLChainInfoTableName) 445 446 // log 447 deleteLogQry := Cleanf(` 448 DELETE FROM %s;`, 449 types.SQLLogTableName) 450 451 return types.SQLCleanDBQuery{ 452 SelectChainIDQry: selectChainIDQry, 453 DeleteChainIDQry: deleteChainIDQry, 454 InsertChainIDQry: insertChainIDQry, 455 SelectDictionaryQry: selectDictionaryQry, 456 DeleteDictionaryQry: deleteDictionaryQry, 457 DeleteLogQry: deleteLogQry, 458 } 459 } 460 461 func (adapter *SQLiteAdapter) DropTableQuery(tableName string) string { 462 // SQLite does not support DROP TABLE CASCADE so this will fail if there are dependent objects 463 return Cleanf(`DROP TABLE %s;`, adapter.SecureName(tableName)) 464 }