Skip to content

Commit

Permalink
implements query-iterator object
Browse files Browse the repository at this point in the history
  • Loading branch information
benbierens committed May 14, 2024
1 parent afab7b9 commit 760acd2
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ leveldbtool
*.css
build

*.exe
64 changes: 64 additions & 0 deletions leveldbstatic.nim
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ type

LevelDbException* = object of CatchableError

IterNext* = proc(): (string, string) {.gcsafe, closure.}
LevelDbQueryIter* = ref object
finished*: bool
next*: IterNext

const
version* = block:
const configFile = "leveldbstatic.nimble"
Expand Down Expand Up @@ -406,6 +411,65 @@ iterator iterRange*(self: LevelDb, start, limit: string): (string, string) =
break
yield (key, value)


proc getIterKey(iterPtr: ptr leveldb_iterator_t): string =

This comment has been minimized.

Copy link
@dryajov

dryajov May 14, 2024

Why is this here? This should not be part of the wrapper which should only expose the raw C interface.

This comment has been minimized.

Copy link
@benbierens

benbierens May 15, 2024

Author Collaborator

This proc and the one below it 'getIterValue' are not exported. They're only used by the exported proc 'queryIter' further down.

var len: csize_t
var str: cstring

str = leveldb_iter_key(iterPtr, addr len)
return newString(str, len)

proc getIterValue(iterPtr: ptr leveldb_iterator_t): string =
var len: csize_t
var str: cstring

str = leveldb_iter_value(iterPtr, addr len)
return newString(str, len)

proc queryIter*(self: LevelDb, prefix: string = "", keysOnly: bool = false): LevelDbQueryIter =
var iterPtr = leveldb_create_iterator(self.db, self.readOptions)

if prefix.len > 0:
leveldb_iter_seek(iterPtr, prefix, prefix.len.csize_t)
else:
leveldb_iter_seek_to_first(iterPtr)

var iter = LevelDbQueryIter()
let emptyResponse = ("", "")

proc getNext(): (string, string) {.gcsafe, closure.} =
if iter.finished:
return emptyResponse

if leveldb_iter_valid(iterPtr) == levelDbFalse:
iter.finished = true
leveldb_iter_destroy(iterPtr)
return emptyResponse

let
keyStr = getIterKey(iterPtr)
valueStr = if keysOnly: "" else: getIterValue(iterPtr)

var err: cstring = nil
leveldb_iter_get_error(iterPtr, addr err)
checkError(err)

leveldb_iter_next(iterPtr)

if prefix.len > 0:
if keyStr.startsWith(prefix):
return (keyStr, valueStr)
else:
iter.finished = true
leveldb_iter_destroy(iterPtr)
return emptyResponse
else:
return (keyStr, valueStr)

iter.finished = false
iter.next = getNext
return iter

proc removeDb*(name: string) =
## Remove the database `name`.
var err: cstring = nil
Expand Down
71 changes: 71 additions & 0 deletions tests/test.nim
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,77 @@ suite "leveldb":
nc.put("a", "1")
check(toSeq(nc.iter()) == @[("a", "1")])

suite "leveldb queryIter":

setup:
let env = leveldb_create_default_env()
let dbName = $(leveldb_env_get_test_directory(env))
let db = leveldb.open(dbName)
let
k1 = "k1"
k2 = "k2"
k3 = "l3"
v1 = "v1"
v2 = "v2"
v3 = "v3"
empty = ("", "")

db.put(k1, v1)
db.put(k2, v2)
db.put(k3, v3)

teardown:
db.close()
removeDb(dbName)

test "iterates all keys and values":
let iter = db.queryIter()
check:
not iter.finished
iter.next() == (k1, v1)
not iter.finished
iter.next() == (k2, v2)
not iter.finished
iter.next() == (k3, v3)
not iter.finished
iter.next() == empty
iter.finished

test "iterates only keys":
let iter = db.queryIter(keysOnly = true)
check:
not iter.finished
iter.next() == (k1, "")
not iter.finished
iter.next() == (k2, "")
not iter.finished
iter.next() == (k3, "")
not iter.finished
iter.next() == empty
iter.finished

test "iterates only 'k', both keys and values":
let iter = db.queryIter(prefix = "k")
check:
not iter.finished
iter.next() == (k1, v1)
not iter.finished
iter.next() == (k2, v2)
not iter.finished
iter.next() == empty
iter.finished

test "iterates only 'k', only keys":
let iter = db.queryIter(prefix = "k", keysOnly = true)
check:
not iter.finished
iter.next() == (k1, "")
not iter.finished
iter.next() == (k2, "")
not iter.finished
iter.next() == empty
iter.finished

suite "package":

setup:
Expand Down

0 comments on commit 760acd2

Please sign in to comment.