nim-eth/eth/db/kvstore.nim

186 lines
5.9 KiB
Nim

# beacon_chain
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
## Simple Key-Value store database interface that allows creating multiple
## tables within each store
{.push raises: [].}
{.pragma: callback, gcsafe, raises: [].}
import
std/[tables, hashes, sets],
results
export results
type
MemStoreRef* = ref object of RootObj
records: Table[seq[byte], seq[byte]]
# TODO interaction with this table would benefit from heterogeneous lookup
# (see `@key` below)
# https://github.com/nim-lang/Nim/issues/7457
KvResult*[T] = Result[T, string]
DataProc* = proc(val: openArray[byte]) {.callback.}
KeyValueProc* = proc(key, val: openArray[byte]) {.callback.}
PutProc = proc (db: RootRef, key, val: openArray[byte]): KvResult[void] {.nimcall, callback.}
GetProc = proc (db: RootRef, key: openArray[byte], onData: DataProc): KvResult[bool] {.nimcall, callback.}
FindProc = proc (db: RootRef, prefix: openArray[byte], onFind: KeyValueProc): KvResult[int] {.nimcall, callback.}
DelProc = proc (db: RootRef, key: openArray[byte]): KvResult[bool] {.nimcall, callback.}
ClearProc = proc (db: RootRef): KvResult[bool] {.nimcall, callback.}
ContainsProc = proc (db: RootRef, key: openArray[byte]): KvResult[bool] {.nimcall, callback.}
CloseProc = proc (db: RootRef): KvResult[void] {.nimcall, callback.}
KvStoreRef* = ref object
## Key-Value store virtual interface
obj: RootRef
putProc: PutProc
getProc: GetProc
findProc: FindProc
delProc: DelProc
clearProc: ClearProc
containsProc: ContainsProc
closeProc: CloseProc
template put*(dbParam: KvStoreRef, key, val: openArray[byte]): KvResult[void] =
## Store ``value`` at ``key`` - overwrites existing value if already present
let db = dbParam
db.putProc(db.obj, key, val)
template get*(dbParam: KvStoreRef, key: openArray[byte], onData: untyped): KvResult[bool] =
## Retrieve value at ``key`` and call ``onData`` with the value. The data is
## valid for the duration of the callback.
## ``onData``: ``proc(data: openArray[byte])``
## returns true if found and false otherwise.
let db = dbParam
db.getProc(db.obj, key, onData)
template find*(
dbParam: KvStoreRef, prefix: openArray[byte], onFind: untyped): KvResult[int] =
## Perform a prefix find, returning all data starting with the given prefix.
## An empty prefix returns all rows in the store.
## The data is valid for the duration of the callback.
## ``onFind``: ``proc(key, value: openArray[byte])``
## returns the number of rows found
let db = dbParam
db.findProc(db.obj, prefix, onFind)
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
db.containsProc(db.obj, key)
template close*(dbParam: KvStoreRef): KvResult[void] =
## Close database
let db = dbParam
db.closeProc(db.obj)
proc putImpl[T](db: RootRef, key, val: openArray[byte]): KvResult[void] {.gcsafe.} =
mixin put
put(T(db), key, val)
proc getImpl[T](db: RootRef, key: openArray[byte], onData: DataProc): KvResult[bool] {.gcsafe.} =
mixin get
get(T(db), key, onData)
proc findImpl[T](db: RootRef, key: openArray[byte], onFind: KeyValueProc): KvResult[int] {.gcsafe.} =
mixin get
find(T(db), key, onFind)
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)
proc closeImpl[T](db: RootRef): KvResult[void] {.gcsafe.} =
mixin close
close(T(db))
func kvStore*[T: RootRef](x: T): KvStoreRef =
mixin del, clear, get, put, contains, close
KvStoreRef(
obj: x,
putProc: putImpl[T],
getProc: getImpl[T],
findProc: findImpl[T],
delProc: delImpl[T],
clearProc: clearImpl[T],
containsProc: containsImpl[T],
closeProc: closeImpl[T]
)
proc get*(db: MemStoreRef, key: openArray[byte], onData: DataProc): KvResult[bool] =
db.records.withValue(@key, v):
onData(v[])
return ok(true)
ok(false)
proc find*(
db: MemStoreRef, prefix: openArray[byte],
onFind: KeyValueProc): KvResult[int] =
var total = 0
# Should use lower/upper bounds instead
for k, v in db.records:
if k.len() >= prefix.len and k.toOpenArray(0, prefix.len() - 1) == prefix:
onFind(k, v)
total += 1
ok(total)
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))
proc put*(db: MemStoreRef, key, val: openArray[byte]): KvResult[void] =
db.records[@key] = @val
ok()
proc close*(db: MemStoreRef): KvResult[void] =
db.records.clear()
ok()
proc init*(T: type MemStoreRef): T =
T(
records: initTable[seq[byte], seq[byte]]()
)