121 lines
3.3 KiB
Nim
121 lines
3.3 KiB
Nim
import
|
|
sqlite3, ranges, ranges/ptr_arith, ../storage_types
|
|
|
|
type
|
|
SqliteChainDB* = object
|
|
store: PSqlite3
|
|
selectStmt, insertStmt, deleteStmt: PStmt
|
|
|
|
ChainDB* = SqliteChainDB
|
|
|
|
proc initChainDB*(dbPath: string): ChainDB =
|
|
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 = ?;"
|
|
|
|
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: DbKey): ByteRange =
|
|
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.toOpenArray)
|
|
|
|
case step(db.selectStmt)
|
|
of SQLITE_ROW:
|
|
var
|
|
resStart = columnBlob(db.selectStmt, 0)
|
|
resLen = columnBytes(db.selectStmt, 0)
|
|
resSeq = newSeq[byte](resLen)
|
|
copyMem(resSeq.baseAddr, resStart, resLen)
|
|
return resSeq.toRange
|
|
of SQLITE_DONE:
|
|
return ByteRange()
|
|
else: raiseKeySearchError(key)
|
|
|
|
proc put*(db: var ChainDB, key: DbKey, value: ByteRange) =
|
|
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.toOpenArray)
|
|
check bindBlob(db.insertStmt, 2, value.toOpenArray)
|
|
|
|
if step(db.insertStmt) != SQLITE_DONE:
|
|
raiseKeyWriteError(key)
|
|
|
|
proc contains*(db: ChainDB, key: DbKey): 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.toOpenArray)
|
|
|
|
case step(db.selectStmt)
|
|
of SQLITE_ROW: result = true
|
|
of SQLITE_DONE: result = false
|
|
else: raiseKeySearchError(key)
|
|
|
|
proc del*(db: var ChainDB, key: DbKey) =
|
|
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.toOpenArray)
|
|
|
|
if step(db.deleteStmt) != SQLITE_DONE:
|
|
raiseKeyDeletionError(key)
|
|
|
|
proc close*(db: var ChainDB) =
|
|
discard sqlite3.close(db.store)
|
|
reset(db)
|
|
|