From e528ee949a753c7c6644da26a4a83197e55a9e35 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Thu, 23 Apr 2020 20:55:21 +0200 Subject: [PATCH] get with callback (#22) * get with callback avoids seq copy sometimes * comment on defect exception safety --- rocksdb.nim | 66 ++++++++++++++++++++++++++++-------------- tests/test_rocksdb.nim | 43 +++++++++++++++------------ 2 files changed, 70 insertions(+), 39 deletions(-) diff --git a/rocksdb.nim b/rocksdb.nim index 7470004..3c0f65e 100644 --- a/rocksdb.nim +++ b/rocksdb.nim @@ -9,7 +9,7 @@ {.push raises: [Defect].} -import cpuinfo, options, stew/results +import cpuinfo, options, stew/[byteutils, results] export results @@ -31,10 +31,6 @@ else: res type - # TODO: Replace this with a converter concept that will - # handle openArray[char] and openArray[byte] in the same way. - KeyValueType = openArray[byte] - RocksDBInstance* = object db: rocksdb_t backupEngine: rocksdb_backup_engine_t @@ -42,6 +38,8 @@ type readOptions: rocksdb_readoptions_t writeOptions: rocksdb_writeoptions_t + DataProc* = proc(val: openArray[byte]) {.gcsafe, raises: [Defect].} + RocksDBResult*[T] = Result[T, string] template bailOnErrors {.dirty.} = @@ -88,15 +86,6 @@ template initRocksDB*(args: varargs[untyped]): Option[RocksDBInstance] = else: some(db) -# TODO: These should be in the standard lib somewhere. -proc to(chars: openArray[char], S: typedesc[string]): string = - result = newString(chars.len) - copyMem(addr result[0], unsafeAddr chars[0], chars.len * sizeof(char)) - -proc to(chars: openArray[char], S: typedesc[seq[byte]]): seq[byte] = - result = newSeq[byte](chars.len) - copyMem(addr result[0], unsafeAddr chars[0], chars.len * sizeof(char)) - template getImpl(T: type) {.dirty.} = if key.len <= 0: return err("rocksdb: key cannot be empty on get") @@ -114,17 +103,52 @@ template getImpl(T: type) {.dirty.} = else: result = err("") -proc get*(db: RocksDBInstance, key: KeyValueType): RocksDBResult[string] = +proc get*(db: RocksDBInstance, key: openArray[byte], onData: DataProc): RocksDBResult[bool] = + if key.len <= 0: + return err("rocksdb: key cannot be empty on get") + + var + errors: cstring + len: csize_t + data = rocksdb_get(db.db, db.readOptions, + cast[cstring](unsafeAddr key[0]), csize_t(key.len), + addr len, addr errors) + bailOnErrors() + if not data.isNil: + # TODO onData may raise a Defect - in theory we could catch it and free the + # memory but this has a small overhead - setjmp (C) or RTTI (C++) - + # reconsider this once the exception dust settles + onData(toOpenArrayByte(data, 0, int(len) - 1)) + rocksdb_free(data) + ok(true) + else: + ok(false) + +proc get*(db: RocksDBInstance, key: openArray[byte]): RocksDBResult[string] {.deprecated: "DataProc".} = ## Get value for `key`. If no value exists, set `result.ok` to `false`, ## and result.error to `""`. - getImpl(string) + var res: RocksDBResult[string] + proc onData(data: openArray[byte]) = + res.ok(string.fromBytes(data)) -proc getBytes*(db: RocksDBInstance, key: KeyValueType): RocksDBResult[seq[byte]] = + if ? db.get(key, onData): + res + else: + ok("") + +proc getBytes*(db: RocksDBInstance, key: openArray[byte]): RocksDBResult[seq[byte]] {.deprecated: "DataProc".} = ## Get value for `key`. If no value exists, set `result.ok` to `false`, ## and result.error to `""`. - getImpl(seq[byte]) + var res: RocksDBResult[seq[byte]] + proc onData(data: openArray[byte]) = + res.ok(@data) -proc put*(db: RocksDBInstance, key, val: KeyValueType): RocksDBResult[void] = + if ? db.get(key, onData): + res + else: + err("") + +proc put*(db: RocksDBInstance, key, val: openArray[byte]): RocksDBResult[void] = if key.len <= 0: return err("rocksdb: key cannot be empty on put") @@ -140,7 +164,7 @@ proc put*(db: RocksDBInstance, key, val: KeyValueType): RocksDBResult[void] = bailOnErrors() ok() -proc del*(db: RocksDBInstance, key: KeyValueType): RocksDBResult[void] = +proc del*(db: RocksDBInstance, key: openArray[byte]): RocksDBResult[void] = if key.len <= 0: return err("rocksdb: key cannot be empty on del") @@ -151,7 +175,7 @@ proc del*(db: RocksDBInstance, key: KeyValueType): RocksDBResult[void] = bailOnErrors() ok() -proc contains*(db: RocksDBInstance, key: KeyValueType): RocksDBResult[bool] = +proc contains*(db: RocksDBInstance, key: openArray[byte]): RocksDBResult[bool] = if key.len <= 0: return err("rocksdb: key cannot be empty on contains") diff --git a/tests/test_rocksdb.nim b/tests/test_rocksdb.nim index 701bd0e..397e1f5 100644 --- a/tests/test_rocksdb.nim +++ b/tests/test_rocksdb.nim @@ -42,29 +42,36 @@ suite "Nim API tests": removeDir(dbDir) test "Basic operations": - let key = @[byte(1), 2, 3, 4, 5] - let otherKey = @[byte(1), 2, 3, 4, 5, 6] - let val = @[byte(1), 2, 3, 4, 5] + proc test() = + let key = @[byte(1), 2, 3, 4, 5] + let otherKey = @[byte(1), 2, 3, 4, 5, 6] + let val = @[byte(1), 2, 3, 4, 5] - var s = db.rocksdb.put(key, val) - check s.isok + var s = db.rocksdb.put(key, val) + check s.isok - var r1 = db.rocksdb.getBytes(key) - check r1.isok and r1.value == val + var bytes: seq[byte] + check db.rocksdb.get(key, proc(data: openArray[byte]) = bytes = @data)[] + check not db.rocksdb.get( + otherkey, proc(data: openArray[byte]) = bytes = @data)[] - var r2 = db.rocksdb.getBytes(otherKey) - # there's no error string for missing keys - check r2.isok == false and r2.error.len == 0 + var r1 = db.rocksdb.getBytes(key) + check r1.isok and r1.value == val - var e1 = db.rocksdb.contains(key) - check e1.isok and e1.value == true + var r2 = db.rocksdb.getBytes(otherKey) + # there's no error string for missing keys + check r2.isok == false and r2.error.len == 0 - var e2 = db.rocksdb.contains(otherKey) - check e2.isok and e2.value == false + var e1 = db.rocksdb.contains(key) + check e1.isok and e1.value == true - s = db.rocksdb.del(key) - check s.isok + var e2 = db.rocksdb.contains(otherKey) + check e2.isok and e2.value == false - e1 = db.rocksdb.contains(key) - check e1.isok and e1.value == false + s = db.rocksdb.del(key) + check s.isok + e1 = db.rocksdb.contains(key) + check e1.isok and e1.value == false + + test()