A => .gitignore +2 -0
@@ 1,2 @@
+htmldocs
+test_tkrzw
A => README.md +14 -0
@@ 1,14 @@
+# Tkrzw: a set of implementations of DBM
+
+Nim wrappers over some of the [Tkrzw](https://dbmx.net/tkrzw/) C++ library.
+See the [tests](./tests/test_tkrzw.nim) for examples.
+
+Provides wrappers to the following DBM clases:
+* HashDBM : File database manager implementation based on hash table.
+* TreeDBM : File database manager implementation based on B+ tree.
+* SkipDBM : File database manager implementation based on skip list.
+* TinyDBM : On-memory database manager implementation based on hash table.
+* BabyDBM : On-memory database manager implementation based on B+ tree.
+* CacheDBM : On-memory database manager implementation with LRU deletion.
+
+Some performance tunning features are left unexposed, patches are apppreciated.
A => flake.lock +57 -0
@@ 1,57 @@
+{
+ "nodes": {
+ "nimble": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ },
+ "locked": {
+ "lastModified": 1625134608,
+ "narHash": "sha256-mCOGDdVNuGFRXfwpYOWhzUlFI57lwYVEGWXdSMEUdkA=",
+ "owner": "nix-community",
+ "repo": "flake-nimble",
+ "rev": "8c4a056c22fa4efa358dc74048cbce70d5cf074e",
+ "type": "github"
+ },
+ "original": {
+ "id": "nimble",
+ "type": "indirect"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1623747334,
+ "narHash": "sha256-yGU7k1XzLhESNc9a6V84gzij6dybTzldgT5b5aU1RWU=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "84a4536e58ec7974fd08ee75ec1a4d40031088e4",
+ "type": "github"
+ },
+ "original": {
+ "id": "nixpkgs",
+ "type": "indirect"
+ }
+ },
+ "nixpkgs_2": {
+ "locked": {
+ "lastModified": 1628775176,
+ "narHash": "sha256-TR1D/Fs0g2bHrPRgYFGdTEkA9ujCH+ZilzFcl8f4wBI=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "3c471e76e881261f4610a57b10d929fe7ae3def9",
+ "type": "github"
+ },
+ "original": {
+ "id": "nixpkgs",
+ "type": "indirect"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nimble": "nimble",
+ "nixpkgs": "nixpkgs_2"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
A => flake.nix +24 -0
@@ 1,24 @@
+{
+ description = "A set of implementations of DBM";
+
+ outputs = { self, nixpkgs, nimble }:
+ let
+ systems = [ "aarch64-linux" "x86_64-linux" ];
+ forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
+ in {
+
+ defaultPackage = forAllSystems (system:
+ let
+ pkgs = nixpkgs.legacyPackages.${system};
+ nimpkgs = nimble.packages.${system};
+ in with pkgs;
+ stdenv.mkDerivation {
+ pname = "tkrzw";
+ version = "unstable-" + self.lastModifiedDate;
+ src = self;
+ nativeBuildInputs = [ nimpkgs.nim pkgconfig ];
+ buildInputs = [ tkrzw ];
+ });
+
+ };
+}
A => src/tkrzw.nim +170 -0
@@ 1,170 @@
+# SPDX-License-Identifier: Apache-2.0
+
+## See https://dbmx.net/tkrzw/
+
+{.passC: "-std=c++17".}
+{.passC: staticExec("pkg-config --cflags tkrzw").}
+{.passL: staticExec("pkg-config --libs tkrzw").}
+
+from macros import genSym, strVal
+
+type
+ RW* = enum readonly, writeable
+ OpenOption* = enum
+ ooTruncate = 1 shl 0,
+ ooNoCreate = 1 shl 1,
+ ooNoWait = 1 shl 2,
+ ooNoLock = 1 shl 3
+
+ HashDBM* {.
+ header: "tkrzw_dbm_hash.h",
+ importcpp: "tkrzw::HashDBM", byref.} = object
+ ## File database manager implementation based on hash table.
+
+ TreeDBM* {.
+ header: "tkrzw_dbm_tree.h",
+ importcpp: "tkrzw::TreeDBM", byref.} = object
+ ## File database manager implementation based on B+ tree.
+
+ SkipDBM* {.
+ header: "tkrzw_dbm_skip.h",
+ importcpp: "tkrzw::SkipDBM", byref.} = object
+ ## File database manager implementation based on skip list.
+
+ TinyDBM* {.
+ header: "tkrzw_dbm_tiny.h",
+ importcpp: "tkrzw::TinyDBM", byref.} = object
+ ## TinyDBM : On-memory database manager implementation based on hash table.
+
+ BabyDBM* {.
+ header: "tkrzw_dbm_baby.h",
+ importcpp: "tkrzw::BabyDBM", byref.} = object
+ ## On-memory database manager implementation based on B+ tree.
+
+ CacheDBM* {.
+ header: "tkrzw_dbm_cache.h",
+ importcpp: "tkrzw::CacheDBM", byref.} = object
+ ## On-memory database manager implementation with LRU deletion.
+
+ FileDBM = HashDBM | TreeDBM | SkipDBM
+ MemoryDBM = TinyDBM | BabyDBM | CacheDBM
+ DBM = FileDBM | MemoryDBM
+
+using dbm: DBM
+
+type Status {.importcpp: "tkrzw::Status".} = object
+proc IsOK(st: Status): bool {.importcpp: "#.IsOK()".}
+proc GetMessage(st: Status): cstring {.importcpp: "#.GetMessage().c_str()".}
+template check(st: Status) =
+ if not IsOK(st): raise newException(IOError, $GetMessage(st))
+
+type Iterator {.
+ importcpp: "std::unique_ptr<tkrzw::DBM::Iterator>".} = object
+proc First(iter: Iterator): Status {.importcpp: "#->First()".}
+proc Next(iter: Iterator): Status {.importcpp: "#->Next()".}
+proc GetKey(iter: Iterator): cstring {.importcpp: "#->GetKey().c_str()".}
+proc GetValue(iter: Iterator): cstring {.importcpp: "#->GetValue().c_str()".}
+
+template importDBM(T: untyped): untyped =
+ proc Close(dbm: T) {.importcpp: "#.Close()".}
+ proc GetSimple(dbm: T; key, default: cstring): cstring {.
+ importcpp: "#.GetSimple(@).c_str()".}
+ proc Set(dbm: T; key, value: cstring; overwrite: bool) {.importcpp: "#.Set(@)".}
+ proc MakeIterator(dbm: T): Iterator {.importcpp: "#.MakeIterator()".}
+ proc Synchronize(dbm: T; hard: bool): Status {.importcpp: "#.Synchronize(@)".}
+ proc Rebuild(dbm: T): Status {.importcpp: "#.Rebuild()".}
+ proc ShouldBeRebuilt(dbm: T; toBe: ptr bool): Status {.importcpp: "#.ShouldBeRebuilt()".}
+ proc IsOpen(dbm: T): bool {.importcpp: "#.IsOpen()".}
+ proc IsWritable(dbm: T): bool {.importcpp: "#.IsWritable()".}
+ proc IsHealthy(dbm: T): bool {.importcpp: "#.IsHealthy()".}
+ proc IsOrdered(dbm: T): bool {.importcpp: "#.IsOrdered()".}
+
+template importFileDBM(T: untyped): untyped =
+ proc Open(dbm: T; path: cstring; writeable: bool; options: int32) {.
+ importcpp: "#.Open(@)".}
+ importDBM(T)
+
+importFileDBM(HashDBM)
+importFileDBM(TreeDBM)
+importFileDBM(SkipDBM)
+importDBM(TinyDBM)
+importDBM(BabyDBM)
+importDBM(CacheDBM)
+
+proc open*(dbm: FileDBM; path: string; rw: RW; options: set[OpenOption] = {}) =
+ ## Opens a database file.
+ var opt: int32
+ for o in options.items: opt.inc o.int32
+ dbm.Open(path, rw == writeable, opt)
+
+proc close*(dbm: DBM) =
+ ## Closes the database file.
+ dbm.Close()
+
+proc get*(dbm; key: string; default = ""): string =
+ $dbm.GetSimple(key, default)
+
+proc set*(dbm; key, value: string; overwrite = true) =
+ dbm.Set(key, value, overwrite)
+
+proc `[]`*(dbm; key: string): string =
+ const def = genSym().strVal
+ result = dbm.get(key, def)
+ if result == def:
+ when compiles($key):
+ raise newException(KeyError, "key not found: " & $key)
+ else:
+ raise newException(KeyError, "key not found")
+
+proc `[]=`*(dbm; key, value: string) = dbm.set(key, value, true)
+
+iterator pairs*(dbm): (string, string) =
+ ## Iterate over any `(key, value)` pair in `dbm`.
+ var
+ iter = dbm.MakeIterator()
+ status = iter.First()
+ key = iter.GetKey()
+ val = iter.GetValue()
+ while status.IsOK and key != "" and val != "":
+ yield($key, $val)
+ status = iter.Next()
+ key = iter.GetKey()
+ val = iter.GetValue()
+
+proc synchronize*(dbm; hard: bool) =
+ ## Synchronizes the content of the database to the file system.
+ check dbm.Synchronize(hard)
+
+proc shouldBeRebuilt*(dbm): bool =
+ ## Checks whether the database should be rebuilt, in a simple way.
+ discard dbm.ShouldBeRebuilt(addr result)
+
+proc rebuild*(dbm) =
+ ## Rebuilds the entire database.
+ check dbm.Rebuild()
+
+proc rebuild*(dbm: TinyDBM; numBuckets = -1) =
+ ## Rebuilds the entire database.
+ ## When `numBuckets` is calculated implicitly when -1.
+ proc RebuildAdvanced(dbm: TinyDBM; nb: int64): Status {.importcpp: "#.RebuildAdvanced(@)".}
+ check dbm.RebuildAdvanced(numBuckets)
+
+proc rebuild*(dbm: CacheDBM; capRecNum = -1; capMemSize = -1) =
+ ## Rebuilds the entire database.
+ ## * `capRecNum` is the maximum number of records.
+ ## * `capMemSize` is the total memory size to use
+ ## When `numBuckets` is calculated implicitly when -1.
+ proc RebuildAdvanced(dbm: CacheDBM; crn, cms: int64): Status {.importcpp: "#.RebuildAdvanced(@)".}
+ check dbm.RebuildAdvanced(capRecNum, capMemSize)
+
+proc isOpen*(dbm): bool = dbm.IsOpen()
+ ## Checks whether the database is open.
+
+proc isWritable*(dbm): bool = dbm .IsWritable()
+ ## Checks whether the database is writable.
+
+proc isHealthy*(dbm): bool = dbm.IsHealthy()
+ ## Checks whether the database condition is healthy.
+
+proc isOrdered*(dbm): bool = dbm.IsOrdered()
+ ## Checks whether ordered operations are supported.
A => tests/config.nims +1 -0
@@ 1,1 @@
+switch("path", "$projectDir/../src")<
\ No newline at end of file
A => tests/test_tkrzw.nim +50 -0
@@ 1,50 @@
+import std/[os, unittest]
+
+import tkrzw
+
+template testEx1(T: untyped; isFile: static[bool]): untyped =
+ test "ex1":
+ const path = "casket.test"
+ var dbm: T
+ when isFIle:
+ dbm.open(path, writeable, {ooTruncate})
+
+ dbm["foo"] = "hop"
+ dbm["bar"] = "step"
+ dbm["baz"] = "jump"
+
+ dbm.synchronize(hard=false)
+
+ check dbm["foo"] == "hop"
+ check dbm["bar"] == "step"
+ dbm.rebuild()
+ check dbm["baz"] == "jump"
+
+ expect KeyError:
+ echo dbm["outlier"]
+
+ var count: int
+ for (key, value) in dbm.pairs:
+ inc count
+ check(count == 3)
+
+ dbm.close()
+ removeFile(path)
+
+suite "HashDBM":
+ testEx1(HashDBM, true)
+
+suite "TreeDBM":
+ testEx1(TreeDBM, true)
+
+suite "SkipDBM":
+ testEx1(SkipDBM, true)
+
+suite "TinyDBM":
+ testEx1(TinyDBM, false)
+
+suite "BabyDBM":
+ testEx1(BabyDBM, false)
+
+suite "CacheDBM":
+ testEx1(CacheDBM, false)
A => tkrzw.nimble +16 -0
@@ 1,16 @@
+# Package
+
+version = "0.1.0"
+author = "Emery Hemingway"
+description = "Wrapper of the Tkrzw key-value database library"
+license = "Apache-2.0"
+srcDir = "src"
+backend = "cpp"
+
+# Dependencies
+
+requires "nim >= 1.4.2"
+
+import distros
+if detectOs(NixOS):
+ foreignDep "tkrzw"