~ehmry/nim-tkrzw

e8a54d0fe6baf850341b958bcba62b40914bbe60 — Emery Hemingway 3 years ago v0.1.0
Initial commit
8 files changed, 334 insertions(+), 0 deletions(-)

A .gitignore
A README.md
A flake.lock
A flake.nix
A src/tkrzw.nim
A tests/config.nims
A tests/test_tkrzw.nim
A tkrzw.nimble
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"