From 8f0ae55353b95f888dce8a32bf810e58f7091b96 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Tue, 3 Jan 2023 16:38:27 +0100 Subject: [PATCH] kvstore: return bool from del, add clear (#574) This allows making decisions based on whether an element was present during the `del` --- eth/db/kvstore.nim | 38 +++++++++++++++++++++++++++++++------- eth/db/kvstore_sqlite3.nim | 29 ++++++++++++++++++++++++----- tests/db/test_kvstore.nim | 14 ++++++++++---- 3 files changed, 65 insertions(+), 16 deletions(-) diff --git a/eth/db/kvstore.nim b/eth/db/kvstore.nim index 865ba96..e8fb294 100644 --- a/eth/db/kvstore.nim +++ b/eth/db/kvstore.nim @@ -31,7 +31,8 @@ type PutProc = proc (db: RootRef, key, val: openArray[byte]): KvResult[void] {.nimcall, gcsafe, raises: [Defect].} GetProc = proc (db: RootRef, key: openArray[byte], onData: DataProc): KvResult[bool] {.nimcall, gcsafe, raises: [Defect].} FindProc = proc (db: RootRef, prefix: openArray[byte], onFind: KeyValueProc): KvResult[int] {.nimcall, gcsafe, raises: [Defect].} - DelProc = proc (db: RootRef, key: openArray[byte]): KvResult[void] {.nimcall, gcsafe, raises: [Defect].} + DelProc = proc (db: RootRef, key: openArray[byte]): KvResult[bool] {.nimcall, gcsafe, raises: [Defect].} + ClearProc = proc (db: RootRef): KvResult[bool] {.nimcall, gcsafe, raises: [Defect].} ContainsProc = proc (db: RootRef, key: openArray[byte]): KvResult[bool] {.nimcall, gcsafe, raises: [Defect].} CloseProc = proc (db: RootRef): KvResult[void] {.nimcall, gcsafe, raises: [Defect].} @@ -42,6 +43,7 @@ type getProc: GetProc findProc: FindProc delProc: DelProc + clearProc: ClearProc containsProc: ContainsProc closeProc: CloseProc @@ -68,11 +70,18 @@ template find*( let db = dbParam db.findProc(db.obj, prefix, onFind) -template del*(dbParam: KvStoreRef, key: openArray[byte]): KvResult[void] = +template del*(dbParam: KvStoreRef, key: openArray[byte]): KvResult[bool] = ## Remove value at ``key`` from store - do nothing if the value is not present + ## Returns true iff value was found and deleted let db = dbParam db.delProc(db.obj, key) +template clear*(dbParam: KvStoreRef): KvResult[bool] = + ## Remove all values from store + ## Returns true iff a value was found and deleted + let db = dbParam + db.clearProc(db.obj) + template contains*(dbParam: KvStoreRef, key: openArray[byte]): KvResult[bool] = ## Return true iff ``key`` has a value in store let db = dbParam @@ -95,10 +104,14 @@ proc findImpl[T](db: RootRef, key: openArray[byte], onFind: KeyValueProc): KvRes mixin get find(T(db), key, onFind) -proc delImpl[T](db: RootRef, key: openArray[byte]): KvResult[void] {.gcsafe.} = +proc delImpl[T](db: RootRef, key: openArray[byte]): KvResult[bool] {.gcsafe.} = mixin del del(T(db), key) +proc clearImpl[T](db: RootRef): KvResult[bool] {.gcsafe.} = + mixin clear + clear(T(db)) + proc containsImpl[T](db: RootRef, key: openArray[byte]): KvResult[bool] {.gcsafe.} = mixin contains contains(T(db), key) @@ -108,7 +121,7 @@ proc closeImpl[T](db: RootRef): KvResult[void] {.gcsafe.} = close(T(db)) func kvStore*[T: RootRef](x: T): KvStoreRef = - mixin del, get, put, contains, close + mixin del, clear, get, put, contains, close KvStoreRef( obj: x, @@ -116,6 +129,7 @@ func kvStore*[T: RootRef](x: T): KvStoreRef = getProc: getImpl[T], findProc: findImpl[T], delProc: delImpl[T], + clearProc: clearImpl[T], containsProc: containsImpl[T], closeProc: closeImpl[T] ) @@ -139,9 +153,19 @@ proc find*( ok(total) -proc del*(db: MemStoreRef, key: openArray[byte]): KvResult[void] = - db.records.del(@key) - ok() +proc del*(db: MemStoreRef, key: openArray[byte]): KvResult[bool] = + if @key in db.records: + db.records.del(@key) + ok(true) + else: + ok(false) + +proc clear*(db: MemStoreRef): KvResult[bool] = + if db.records.len > 0: + db.records.clear() + ok(true) + else: + ok(false) proc contains*(db: MemStoreRef, key: openArray[byte]): KvResult[bool] = ok(db.records.contains(@key)) diff --git a/eth/db/kvstore_sqlite3.nim b/eth/db/kvstore_sqlite3.nim index a1b11b4..0465d1e 100644 --- a/eth/db/kvstore_sqlite3.nim +++ b/eth/db/kvstore_sqlite3.nim @@ -38,7 +38,8 @@ type # A Keyspace is a single key-value table - it is generally efficient to # create separate keyspaces for each type of data stored open: bool - getStmt, putStmt, delStmt, containsStmt, + env: Sqlite + getStmt, putStmt, delStmt, clearStmt, containsStmt, findStmt0, findStmt1, findStmt2: RawStmtPtr SqKeyspaceRef* = ref SqKeyspace @@ -433,17 +434,17 @@ proc contains*(db: SqKeyspaceRef, key: openArray[byte]): KvResult[bool] = res -proc del*(db: SqKeyspaceRef, key: openArray[byte]): KvResult[void] = +proc del*(db: SqKeyspaceRef, key: openArray[byte]): KvResult[bool] = if not db.open: return err("sqlite: database closed") let delStmt = db.delStmt - if delStmt == nil: return ok() # no such table + if delStmt == nil: return ok(false) # no such table checkErr bindParam(delStmt, 1, key) let res = if (let v = sqlite3_step(delStmt); v != SQLITE_DONE): err($sqlite3_errstr(v)) else: - ok() + ok(sqlite3_changes(db.env) > 0) # release implict transaction discard sqlite3_reset(delStmt) # same return information as step @@ -451,11 +452,28 @@ proc del*(db: SqKeyspaceRef, key: openArray[byte]): KvResult[void] = res +proc clear*(db: SqKeyspaceRef): KvResult[bool] = + if not db.open: return err("sqlite: database closed") + let clearStmt = db.clearStmt + if clearStmt == nil: return ok(false) # no such table + + let res = + if (let v = sqlite3_step(clearStmt); v != SQLITE_DONE): + err($sqlite3_errstr(v)) + else: + ok(sqlite3_changes(db.env) > 0) + + # release implicit transaction + discard sqlite3_reset(clearStmt) # same return information as step + + res + proc close*(db: var SqKeyspace) = # Calling with null stmt is harmless discard sqlite3_finalize(db.putStmt) discard sqlite3_finalize(db.getStmt) discard sqlite3_finalize(db.delStmt) + discard sqlite3_finalize(db.clearStmt) discard sqlite3_finalize(db.containsStmt) discard sqlite3_finalize(db.findStmt0) discard sqlite3_finalize(db.findStmt1) @@ -604,7 +622,7 @@ proc openKvStore*( if withoutRowid: createSql & " WITHOUT ROWID;" else: createSql & ";" true var - tmp: SqKeyspace + tmp = SqKeyspace(env: db.env) defer: # We'll "move" ownership to the return value, effectively disabling "close" close(tmp) @@ -615,6 +633,7 @@ proc openKvStore*( tmp.putStmt = prepare(db.env, "INSERT OR REPLACE INTO '" & name & "'(key, value) VALUES (?, ?);") tmp.delStmt = prepare(db.env, "DELETE FROM '" & name & "' WHERE key = ?;") + tmp.clearStmt = prepare(db.env, "DELETE FROM '" & name & "';") tmp.containsStmt = prepare(db.env, "SELECT 1 FROM '" & name & "' WHERE key = ?;") tmp.findStmt0 = prepare(db.env, "SELECT key, value FROM '" & name & "';") tmp.findStmt1 = prepare(db.env, "SELECT key, value FROM '" & name & "' WHERE key >= ?;") diff --git a/tests/db/test_kvstore.nim b/tests/db/test_kvstore.nim index 49020ed..027a58b 100644 --- a/tests/db/test_kvstore.nim +++ b/tests/db/test_kvstore.nim @@ -16,8 +16,7 @@ proc testKvStore*(db: KvStoreRef, supportsFind: bool) = not db.get(key, proc(data: openArray[byte]) = discard)[] not db.contains(key)[] - - db.del(key)[] # does nothing + not db.del(key)[] # does nothing db.put(key, value)[] @@ -39,12 +38,19 @@ proc testKvStore*(db: KvStoreRef, supportsFind: bool) = db.get(key, grab)[] v == value2 - db.del(key)[] check: + db.del(key)[] not db.get(key, proc(data: openArray[byte]) = discard)[] not db.contains(key)[] - db.del(key)[] # does nothing + not db.del(key)[] # does nothing + + db.put(key, value2)[] # overwrite old value + check: + db.contains(key)[] + db.clear()[] + not db.contains(key)[] + not db.clear()[] if supportsFind: check: