import os, sqlite3, stew/ranges, stew/ranges/ptr_arith, eth/trie/[db_tracing, trie_defs], backend_defs type SqliteChainDB* = ref object of RootObj store: PSqlite3 selectStmt, insertStmt, deleteStmt: PStmt ChainDB* = SqliteChainDB proc put*(db: ChainDB, key, value: openarray[byte]) proc newChainDB*(basePath: string, inMemory = false): ChainDB = result.new() let dbPath = if inMemory: ":memory:" else: basePath / "nimbus.db" var s = sqlite3.open(dbPath, result.store) if s != SQLITE_OK: raiseStorageInitError() template execQuery(q: string) = var s: Pstmt if prepare_v2(result.store, q, q.len.int32, s, nil) == SQLITE_OK: if step(s) != SQLITE_DONE or finalize(s) != SQLITE_OK: raiseStorageInitError() else: raiseStorageInitError() # TODO: check current version and implement schema versioning execQuery "PRAGMA user_version = 1;" execQuery """ CREATE TABLE IF NOT EXISTS trie_nodes( key BLOB PRIMARY KEY, value BLOB ); """ template prepare(q: string): PStmt = var s: Pstmt if prepare_v2(result.store, q, q.len.int32, s, nil) != SQLITE_OK: raiseStorageInitError() s result.selectStmt = prepare "SELECT value FROM trie_nodes WHERE key = ?;" if sqlite3.libversion_number() < 3024000: result.insertStmt = prepare """ INSERT OR REPLACE INTO trie_nodes(key, value) VALUES (?, ?); """ else: result.insertStmt = prepare """ INSERT INTO trie_nodes(key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value; """ result.deleteStmt = prepare "DELETE FROM trie_nodes WHERE key = ?;" put(result, emptyRlpHash.data, emptyRlp) proc bindBlob(s: Pstmt, n: int, blob: openarray[byte]): int32 = sqlite3.bind_blob(s, n.int32, blob.baseAddr, blob.len.int32, nil) proc get*(db: ChainDB, key: openarray[byte]): seq[byte] = template check(op) = let status = op if status != SQLITE_OK: raiseKeyReadError(key) check reset(db.selectStmt) check clearBindings(db.selectStmt) check bindBlob(db.selectStmt, 1, key) case step(db.selectStmt) of SQLITE_ROW: var resStart = columnBlob(db.selectStmt, 0) resLen = columnBytes(db.selectStmt, 0) result = newSeq[byte](resLen) copyMem(result.baseAddr, resStart, resLen) traceGet key, result of SQLITE_DONE: discard else: raiseKeyReadError(key) proc put*(db: ChainDB, key, value: openarray[byte]) = tracePut key, value template check(op) = let status = op if status != SQLITE_OK: raiseKeyWriteError(key) check reset(db.insertStmt) check clearBindings(db.insertStmt) check bindBlob(db.insertStmt, 1, key) check bindBlob(db.insertStmt, 2, value) if step(db.insertStmt) != SQLITE_DONE: raiseKeyWriteError(key) proc contains*(db: ChainDB, key: openarray[byte]): bool = template check(op) = let status = op if status != SQLITE_OK: raiseKeySearchError(key) check reset(db.selectStmt) check clearBindings(db.selectStmt) check bindBlob(db.selectStmt, 1, key) case step(db.selectStmt) of SQLITE_ROW: result = true of SQLITE_DONE: result = false else: raiseKeySearchError(key) proc del*(db: ChainDB, key: openarray[byte]) = traceDel key template check(op) = let status = op if status != SQLITE_OK: raiseKeyDeletionError(key) check reset(db.deleteStmt) check clearBindings(db.deleteStmt) check bindBlob(db.deleteStmt, 1, key) if step(db.deleteStmt) != SQLITE_DONE: raiseKeyDeletionError(key) proc close*(db: ChainDB) = discard sqlite3.close(db.store) reset(db[])