]> gitweb.ps.run Git - ziglmdb/commitdiff
add db abstaction
authorpatrick-scho <patrick.schoenberger@posteo.de>
Sun, 2 Mar 2025 18:23:41 +0000 (19:23 +0100)
committerpatrick-scho <patrick.schoenberger@posteo.de>
Sun, 2 Mar 2025 18:23:41 +0000 (19:23 +0100)
build.zig
build.zig.zon
src/db.zig [new file with mode: 0644]
src/lmdb.zig

index 0525e47fbb7c2a56b573afbb83a2291fa39e6a3a..102a4c02eb86310047baa31038e3e84db69be9bf 100644 (file)
--- a/build.zig
+++ b/build.zig
@@ -2,42 +2,56 @@ const std = @import("std");
 
 pub fn build(b: *std.Build) void {
     const target = b.standardTargetOptions(.{});
-
     const optimize = b.standardOptimizeOption(.{});
 
-    const mod = b.addModule("lmdb", .{
-        .root_source_file = .{ .src_path = .{ .owner = b, .sub_path = "src/lmdb.zig" } },
-        // .root_source_file = .{ .cwd_relative = "src/lmdb.zig" },
+    const lmdb = b.addModule("lmdb", .{
+        .root_source_file = b.path("src/lmdb.zig"),
         .target = target,
         .optimize = optimize,
     });
-
-    mod.addIncludePath(.{ .src_path = .{ .owner = b, .sub_path = "lmdb/libraries/liblmdb" } });
-
-    mod.addCSourceFiles(.{ .files = &.{
+    lmdb.addIncludePath(b.path("lmdb/libraries/liblmdb"));
+    lmdb.addCSourceFiles(.{ .files = &.{
         "./lmdb/libraries/liblmdb/midl.c",
         "./lmdb/libraries/liblmdb/mdb.c",
     } });
+    lmdb.link_libc = true;
+
+    const db = b.addModule("db", .{
+        .root_source_file = b.path("src/db.zig"),
+    });
+    db.addImport("lmdb", lmdb);
 
-    const unit_tests = b.addTest(.{
-        .root_source_file = .{ .cwd_relative = "src/lmdb.zig" },
+    const lmdb_tests = b.addTest(.{
+        .root_source_file = b.path("src/lmdb.zig"),
         .target = target,
         .optimize = optimize,
     });
-
-    unit_tests.addIncludePath(.{ .src_path = .{ .owner = b, .sub_path = "lmdb/libraries/liblmdb" } });
-    unit_tests.addCSourceFiles(.{ .files = &.{
+    lmdb_tests.addIncludePath(b.path("lmdb/libraries/liblmdb"));
+    lmdb_tests.addCSourceFiles(.{ .files = &.{
         "./lmdb/libraries/liblmdb/midl.c",
         "./lmdb/libraries/liblmdb/mdb.c",
     } });
-    unit_tests.linkLibC();
+    lmdb_tests.linkLibC();
+
+    const db_tests = b.addTest(.{
+        .root_source_file = b.path("src/db.zig"),
+        .target = target,
+        .optimize = optimize,
+    });
+    db_tests.root_module.addImport("lmdb", lmdb);
 
-    const test_bin = b.addInstallBinFile(unit_tests.getEmittedBin(), "./lmdb_test");
+    const lmdb_test_bin = b.addInstallBinFile(lmdb_tests.getEmittedBin(), "./lmdb_test");
+    const db_test_bin = b.addInstallBinFile(db_tests.getEmittedBin(), "./db_test");
 
-    const run_unit_tests = b.addRunArtifact(unit_tests);
+    const run_lmdb_tests = b.addRunArtifact(lmdb_tests);
+    const run_db_tests = b.addRunArtifact(db_tests);
 
     const test_step = b.step("test", "Run unit tests");
-    test_step.dependOn(&run_unit_tests.step);
-    test_step.dependOn(&unit_tests.step);
-    test_step.dependOn(&test_bin.step);
+    test_step.result_cached = false;
+    test_step.dependOn(&run_lmdb_tests.step);
+    test_step.dependOn(&lmdb_tests.step);
+    test_step.dependOn(&run_db_tests.step);
+    test_step.dependOn(&db_tests.step);
+    test_step.dependOn(&lmdb_test_bin.step);
+    test_step.dependOn(&db_test_bin.step);
 }
index 7c223de2bd100142373536daf7edadbdd25956b3..494f4b6a5ddca7f930c7282c5ed5ee580fb72380 100644 (file)
@@ -3,6 +3,7 @@
     .version = "0.0.0",
     .paths = .{
         "src/lmdb.zig",
+        "src/db.zig",
         "build.zig",
         "build.zig.zon",
     },
diff --git a/src/db.zig b/src/db.zig
new file mode 100644 (file)
index 0000000..3e54060
--- /dev/null
@@ -0,0 +1,620 @@
+const std = @import("std");
+const lmdb = @import("lmdb");
+
+const PRNG_SEED = 0;
+
+pub fn Db(comptime K: type, comptime V: type) type {
+    return struct {
+        const Self = @This();
+
+        dbi: lmdb.Dbi,
+
+        pub fn init(txn: lmdb.Txn, name: [:0]const u8) !Self {
+            return .{
+                .dbi = try txn.dbi(name),
+            };
+        }
+        pub fn put(self: Self, k: K, v: V) !void {
+            try self.dbi.put(k, v);
+        }
+        pub fn get(self: Self, k: K) !V {
+            return try self.dbi.get(k, V);
+        }
+        pub fn del(self: Self, k: K) !void {
+            try self.dbi.del(k);
+        }
+        pub fn has(self: Self, k: K) !bool {
+            return try self.dbi.has(k);
+        }
+        pub const Iterator = struct {
+            cursor: lmdb.Cursor,
+            k: ?K,
+            v: ?V,
+
+            pub fn next(self: *Iterator) ?struct { key: K, val: V } {
+                if (self.k != null and self.v != null) {
+                    const result = .{ .key = self.k.?, .val = self.v.? };
+
+                    var k = self.k.?;
+                    self.v = self.cursor.get(&k, V, .Next) catch return null;
+                    if (self.v != null) {
+                        self.k = k;
+                    }
+                    return result;
+                } else {
+                    return null;
+                }
+            }
+        };
+        pub fn iterator(self: Self) !Iterator {
+            var cursor = try self.dbi.cursor();
+
+            var k: K = undefined;
+            const v = try cursor.get(&k, V, .First);
+            return .{ .cursor = cursor, .k = k, .v = v };
+        }
+    };
+}
+
+pub const Prng = struct {
+    var prng = std.Random.DefaultPrng.init(PRNG_SEED);
+
+    pub fn gen(dbi: lmdb.Dbi, comptime T: type) !T {
+        var buf: [@sizeOf(T)]u8 = undefined;
+        // TODO: limit loop
+        while (true) {
+            prng.fill(&buf);
+            const t = std.mem.bytesToValue(T, &buf);
+            if (!try dbi.has(t)) {
+                return t;
+            }
+        }
+    }
+};
+pub fn Set(comptime K: type) type {
+    return struct {
+        idx: ?Index = null,
+
+        const Self = @This();
+        pub const Index = u64;
+        pub const View = SetView(K);
+
+        fn open_dbi(txn: lmdb.Txn) !lmdb.Dbi {
+            return try txn.dbi("SetList");
+        }
+        pub fn init(txn: lmdb.Txn) !Self {
+            const head = View.Head{};
+            const dbi = try open_dbi(txn);
+            const idx = try Prng.gen(dbi, Index);
+            try dbi.put(idx, head);
+            return .{ .idx = idx };
+        }
+        pub fn open(self: Self, txn: lmdb.Txn) !View {
+            // create new head
+            if (self.idx == null) {
+                return error.NotInitialized;
+            }
+            // get head from dbi
+            const dbi = try open_dbi(txn);
+            const head = try dbi.get(self.idx.?, View.Head);
+            return .{
+                .dbi = dbi,
+                .idx = self.idx.?,
+                .head = head,
+            };
+        }
+    };
+}
+
+pub fn SetView(comptime K: type) type {
+    return struct {
+        const Self = @This();
+        const ItemIndex = struct { Set(K).Index, K };
+
+        pub const Head = struct {
+            len: usize = 0,
+            first: ?K = null,
+            last: ?K = null,
+        };
+        pub const Item = struct {
+            next: ?K = null,
+            prev: ?K = null,
+        };
+
+        dbi: lmdb.Dbi,
+        idx: Set(K).Index,
+        head: Head,
+
+        fn item_idx(self: Self, k: K) ItemIndex {
+            return .{ self.idx, k };
+        }
+        fn item_get(self: Self, k: K) !Item {
+            return try self.dbi.get(self.item_idx(k), Item);
+        }
+        fn item_put(self: Self, k: K, item: Item) !void {
+            try self.dbi.put(self.item_idx(k), item);
+        }
+        fn head_update(self: Self) !void {
+            try self.dbi.put(self.idx, self.head);
+        }
+        pub fn append(self: *Self, k: K) !void {
+            if (self.head.len == 0) {
+                const item = Item{};
+                try self.item_put(k, item);
+
+                self.head.len = 1;
+                self.head.first = k;
+                self.head.last = k;
+                try self.head_update();
+            } else {
+                const prev_idx = self.head.last.?;
+                var prev = try self.item_get(prev_idx);
+
+                const item = Item{ .prev = prev_idx };
+                try self.item_put(k, item);
+
+                prev.next = k;
+                try self.item_put(prev_idx, prev);
+
+                self.head.last = k;
+                self.head.len += 1;
+                try self.head_update();
+            }
+        }
+        pub fn del(self: *Self, k: K) !void {
+            const item = try self.item_get(k);
+
+            if (item.prev != null) {
+                var prev = try self.item_get(item.prev.?);
+                prev.next = item.next;
+                try self.item_put(item.prev.?, prev);
+            }
+
+            if (item.next != null) {
+                var next = try self.item_get(item.next.?);
+                next.prev = item.prev;
+                try self.item_put(item.next.?, next);
+            }
+
+            if (self.head.first == k) self.head.first = item.next;
+            if (self.head.last == k) self.head.last = item.prev;
+            self.head.len -= 1;
+            try self.head_update();
+
+            try self.dbi.del(self.item_idx(k));
+        }
+        pub fn has(self: Self, k: K) !bool {
+            return self.dbi.has(self.item_idx(k));
+        }
+        pub fn len(self: Self) usize {
+            return self.head.len;
+        }
+        pub const Iterator = struct {
+            sv: SetView(K),
+            idx: ?K,
+            dir: enum { Forward, Backward },
+
+            pub fn next(self: *Iterator) ?K {
+                if (self.idx != null) {
+                    const k = self.idx.?;
+                    const item = self.sv.item_get(k) catch return null;
+                    self.idx = switch (self.dir) {
+                        .Forward => item.next,
+                        .Backward => item.prev,
+                    };
+                    return k;
+                } else {
+                    return null;
+                }
+            }
+        };
+        pub fn iterator(self: Self) Iterator {
+            return .{
+                .sv = self,
+                .idx = self.head.first,
+                .dir = .Forward,
+            };
+        }
+        pub fn reverse_iterator(self: Self) Iterator {
+            return .{
+                .sv = self,
+                .idx = self.head.last,
+                .dir = .Backward,
+            };
+        }
+    };
+}
+pub fn List(comptime V: type) type {
+    return struct {
+        idx: ?Index = null,
+
+        const Self = @This();
+        pub const Index = u64;
+        pub const View = ListView(V);
+
+        fn open_dbi(txn: lmdb.Txn) !lmdb.Dbi {
+            return try txn.dbi("SetList");
+        }
+        pub fn init(txn: lmdb.Txn) !Self {
+            const head = View.Head{};
+            const dbi = try open_dbi(txn);
+            const idx = try Prng.gen(dbi, Index);
+            try dbi.put(idx, head);
+            return .{ .idx = idx };
+        }
+        pub fn open(self: Self, txn: lmdb.Txn) !View {
+            // create new head
+            if (self.idx == null) {
+                return error.NotInitialized;
+            }
+            // get head from dbi
+            const dbi = try open_dbi(txn);
+            const head = try dbi.get(self.idx.?, View.Head);
+            return .{
+                .dbi = dbi,
+                .idx = self.idx.?,
+                .head = head,
+            };
+        }
+    };
+}
+
+pub fn ListView(comptime V: type) type {
+    return struct {
+        const Self = @This();
+        const K = u64;
+        const ItemIndex = struct { List(V).Index, K };
+
+        pub const Head = struct {
+            len: usize = 0,
+            first: ?K = null,
+            last: ?K = null,
+        };
+        pub const Item = struct {
+            next: ?K = null,
+            prev: ?K = null,
+            data: V,
+        };
+
+        dbi: lmdb.Dbi,
+        idx: List(V).Index,
+        head: Head,
+
+        fn item_idx(self: Self, k: K) ItemIndex {
+            return .{ self.idx, k };
+        }
+        fn item_get(self: Self, k: K) !Item {
+            return try self.dbi.get(self.item_idx(k), Item);
+        }
+        fn item_put(self: Self, k: K, item: Item) !void {
+            try self.dbi.put(self.item_idx(k), item);
+        }
+        fn head_update(self: Self) !void {
+            try self.dbi.put(self.idx, self.head);
+        }
+        fn gen(self: Self) !K {
+            // TODO: limit loop
+            while (true) {
+                const k = try Prng.gen(self.dbi, K);
+                if (!try self.dbi.has(self.item_idx(k))) {
+                    return k;
+                }
+            }
+        }
+        pub fn append(self: *Self, v: V) !K {
+            if (self.head.len == 0) {
+                const k = try self.gen();
+                const item = Item{ .data = v };
+                try self.item_put(k, item);
+
+                self.head.len = 1;
+                self.head.first = k;
+                self.head.last = k;
+                try self.head_update();
+
+                return k;
+            } else {
+                const prev_idx = self.head.last.?;
+                var prev = try self.item_get(prev_idx);
+
+                const k = try self.gen();
+                const item = Item{ .prev = prev_idx, .data = v };
+                try self.item_put(k, item);
+
+                prev.next = k;
+                try self.item_put(prev_idx, prev);
+
+                self.head.last = k;
+                self.head.len += 1;
+                try self.head_update();
+
+                return k;
+            }
+        }
+        pub fn get(self: Self, k: K) !V {
+            const item = try self.item_get(k);
+            return item.data;
+        }
+        pub fn del(self: *Self, k: K) !void {
+            const item = try self.item_get(k);
+
+            if (item.prev != null) {
+                var prev = try self.item_get(item.prev.?);
+                prev.next = item.next;
+                try self.item_put(item.prev.?, prev);
+            }
+
+            if (item.next != null) {
+                var next = try self.item_get(item.next.?);
+                next.prev = item.prev;
+                try self.item_put(item.next.?, next);
+            }
+
+            if (self.head.first == k) self.head.first = item.next;
+            if (self.head.last == k) self.head.last = item.prev;
+            self.head.len -= 1;
+            try self.head_update();
+
+            try self.dbi.del(self.item_idx(k));
+        }
+        pub fn len(self: Self) usize {
+            return self.head.len;
+        }
+        pub const Iterator = struct {
+            lv: ListView(V),
+            idx: ?K,
+            dir: enum { Forward, Backward },
+
+            pub fn next(self: *Iterator) ?struct { key: K, val: V } {
+                if (self.idx != null) {
+                    const k = self.idx.?;
+                    const item = self.lv.item_get(k) catch return null;
+                    self.idx = switch (self.dir) {
+                        .Forward => item.next,
+                        .Backward => item.prev,
+                    };
+                    return .{ .key = k, .val = item.data };
+                } else {
+                    return null;
+                }
+            }
+        };
+        pub fn iterator(self: Self) Iterator {
+            return .{
+                .lv = self,
+                .idx = self.head.first,
+                .dir = .Forward,
+            };
+        }
+        pub fn reverse_iterator(self: Self) Iterator {
+            return .{
+                .lv = self,
+                .idx = self.head.last,
+                .dir = .Backward,
+            };
+        }
+    };
+}
+
+pub fn SetList(comptime K: type, comptime V: type) type {
+    return struct {
+        idx: ?Index = null,
+
+        const Self = @This();
+        pub const Index = u64;
+        pub const View = SetListView(K, V);
+
+        fn open_dbi(txn: lmdb.Txn) !lmdb.Dbi {
+            return try txn.dbi("SetList");
+        }
+        pub fn init(txn: lmdb.Txn) !Self {
+            const head = View.Head{};
+            const dbi = try open_dbi(txn);
+            const idx = try Prng.gen(dbi, Index);
+            try dbi.put(idx, head);
+            return .{ .idx = idx };
+        }
+        pub fn open(self: Self, txn: lmdb.Txn) !View {
+            // create new head
+            if (self.idx == null) {
+                return error.NotInitialized;
+            }
+            // get head from dbi
+            const dbi = try open_dbi(txn);
+            const head = try dbi.get(self.idx.?, View.Head);
+            return .{
+                .dbi = dbi,
+                .idx = self.idx.?,
+                .head = head,
+            };
+        }
+    };
+}
+
+pub fn SetListView(comptime K: type, comptime V: type) type {
+    return struct {
+        const Self = @This();
+        const ItemIndex = struct { SetList(K, V).Index, K };
+
+        pub const Head = struct {
+            len: usize = 0,
+            first: ?K = null,
+            last: ?K = null,
+        };
+        pub const Item = struct {
+            next: ?K = null,
+            prev: ?K = null,
+            data: V,
+        };
+
+        dbi: lmdb.Dbi,
+        idx: SetList(K, V).Index,
+        head: Head,
+
+        fn item_idx(self: Self, k: K) ItemIndex {
+            return .{ self.idx, k };
+        }
+        fn item_get(self: Self, k: K) !Item {
+            return try self.dbi.get(self.item_idx(k), Item);
+        }
+        fn item_put(self: Self, k: K, item: Item) !void {
+            try self.dbi.put(self.item_idx(k), item);
+        }
+        fn head_update(self: Self) !void {
+            try self.dbi.put(self.idx, self.head);
+        }
+        pub fn append(self: *Self, k: K, v: V) !void {
+            if (self.head.len == 0) {
+                const item = Item{ .data = v };
+                try self.item_put(k, item);
+
+                self.head.len = 1;
+                self.head.first = k;
+                self.head.last = k;
+                try self.head_update();
+            } else {
+                const prev_idx = self.head.last.?;
+                var prev = try self.item_get(prev_idx);
+
+                const item = Item{ .prev = prev_idx, .data = v };
+                try self.item_put(k, item);
+
+                prev.next = k;
+                try self.item_put(prev_idx, prev);
+
+                self.head.last = k;
+                self.head.len += 1;
+                try self.head_update();
+            }
+        }
+        pub fn get(self: Self, k: K) !V {
+            const item = try self.item_get(k);
+            return item.data;
+        }
+        pub fn del(self: *Self, k: K) !void {
+            const item = try self.item_get(k);
+
+            if (item.prev != null) {
+                var prev = try self.item_get(item.prev.?);
+                prev.next = item.next;
+                try self.item_put(item.prev.?, prev);
+            }
+
+            if (item.next != null) {
+                var next = try self.item_get(item.next.?);
+                next.prev = item.prev;
+                try self.item_put(item.next.?, next);
+            }
+
+            if (self.head.first == k) self.head.first = item.next;
+            if (self.head.last == k) self.head.last = item.prev;
+            self.head.len -= 1;
+            try self.head_update();
+
+            try self.dbi.del(self.item_idx(k));
+        }
+        pub fn has(self: Self, k: K) !bool {
+            return self.dbi.has(self.item_idx(k));
+        }
+        pub fn len(self: Self) usize {
+            return self.head.len;
+        }
+        pub const Iterator = struct {
+            slv: SetListView(K, V),
+            idx: ?K,
+            dir: enum { Forward, Backward },
+
+            pub fn next(self: *Iterator) ?struct { key: K, val: V } {
+                if (self.idx != null) {
+                    const k = self.idx.?;
+                    const item = self.slv.item_get(k) catch return null;
+                    self.idx = switch (self.dir) {
+                        .Forward => item.next,
+                        .Backward => item.prev,
+                    };
+                    return .{ .key = k, .val = item.data };
+                } else {
+                    return null;
+                }
+            }
+        };
+        pub fn iterator(self: Self) Iterator {
+            return .{
+                .slv = self,
+                .idx = self.head.first,
+                .dir = .Forward,
+            };
+        }
+        pub fn reverse_iterator(self: Self) Iterator {
+            return .{
+                .slv = self,
+                .idx = self.head.last,
+                .dir = .Backward,
+            };
+        }
+    };
+}
+
+const DB_SIZE = 1024 * 1024 * 1;
+
+test "db" {
+    const env = try lmdb.Env.open("db", DB_SIZE);
+    defer env.close();
+
+    const txn = try env.txn();
+    defer txn.commit() catch {};
+
+    var db = try Db(u32, u32).init(txn, "123");
+    var n: u32 = 456;
+    if (try db.has(123)) {
+        n = try db.get(123);
+        n += 1;
+    }
+    try db.put(123, n);
+    std.debug.print("n: {}\n", .{n});
+}
+
+// test "list" {
+//     const env = try lmdb.Env.open("db", DB_SIZE);
+//     defer env.close();
+
+//     const txn = try env.txn();
+//     defer txn.commit();
+
+//     const db = List.init(txn, "b", u32);
+// }
+
+test "maplist" {
+    var env = try lmdb.Env.open("db", 1024 * 1024 * 1);
+    // env.sync();
+    defer env.close();
+
+    var txn = try env.txn();
+    defer txn.commit() catch {};
+
+    var dbi = try txn.dbi("abc");
+
+    const A = struct {
+        ml: SetList(usize, usize),
+    };
+
+    var a: A = undefined;
+    const a_idx: u64 = 27;
+    if (try dbi.has(a_idx)) {
+        a = try dbi.get(a_idx, A);
+    } else {
+        a = A{ .ml = try SetList(usize, usize).init(txn) };
+        try dbi.put(a_idx, a);
+    }
+
+    var ml = try a.ml.open(txn);
+
+    const len = ml.len();
+    std.debug.print("{}\n", .{len});
+    try ml.append(len, len * 10);
+    std.debug.print("{}\n", .{try ml.get(len)});
+    var it = ml.iterator();
+    while (it.next()) |i| {
+        std.debug.print("{}: {}\n", .{ i.key, i.val });
+    }
+}
index 3802ec60e39741912d761c8e4b081c7a05e97b26..637bd26411549496acccd2af0cccf57995c6537b 100644 (file)
@@ -3,196 +3,232 @@ const lmdb = @cImport(@cInclude("lmdb.h"));
 
 pub usingnamespace lmdb;
 
-pub fn Cursor(comptime K: type, comptime V: type) type {
-    return struct {
-        const Self = @This();
-
-        pub const Flags = enum(c_uint) {
-            First = lmdb.MDB_FIRST,
-            FirstDup = lmdb.MDB_FIRST_DUP,
-            GetBoth = lmdb.MDB_GET_BOTH,
-            GetBothRange = lmdb.MDB_GET_BOTH_RANGE,
-            GetCurrent = lmdb.MDB_GET_CURRENT,
-            GetMultiple = lmdb.MDB_GET_MULTIPLE,
-            Last = lmdb.MDB_LAST,
-            LastDup = lmdb.MDB_LAST_DUP,
-            Next = lmdb.MDB_NEXT,
-            NextDup = lmdb.MDB_NEXT_DUP,
-            NextMultiple = lmdb.MDB_NEXT_MULTIPLE,
-            NextNodup = lmdb.MDB_NEXT_NODUP,
-            Prev = lmdb.MDB_PREV,
-            PrevDup = lmdb.MDB_PREV_DUP,
-            PrevNodup = lmdb.MDB_PREV_NODUP,
-            Set = lmdb.MDB_SET,
-            SetKey = lmdb.MDB_SET_KEY,
-            SetRange = lmdb.MDB_SET_RANGE,
-        };
+pub const Cursor = struct {
+    const Self = @This();
+
+    pub const Flags = enum(c_uint) {
+        First = lmdb.MDB_FIRST,
+        FirstDup = lmdb.MDB_FIRST_DUP,
+        GetBoth = lmdb.MDB_GET_BOTH,
+        GetBothRange = lmdb.MDB_GET_BOTH_RANGE,
+        GetCurrent = lmdb.MDB_GET_CURRENT,
+        GetMultiple = lmdb.MDB_GET_MULTIPLE,
+        Last = lmdb.MDB_LAST,
+        LastDup = lmdb.MDB_LAST_DUP,
+        Next = lmdb.MDB_NEXT,
+        NextDup = lmdb.MDB_NEXT_DUP,
+        NextMultiple = lmdb.MDB_NEXT_MULTIPLE,
+        NextNodup = lmdb.MDB_NEXT_NODUP,
+        Prev = lmdb.MDB_PREV,
+        PrevDup = lmdb.MDB_PREV_DUP,
+        PrevNodup = lmdb.MDB_PREV_NODUP,
+        Set = lmdb.MDB_SET,
+        SetKey = lmdb.MDB_SET_KEY,
+        SetRange = lmdb.MDB_SET_RANGE,
+    };
 
-        ptr: ?*lmdb.MDB_cursor = undefined,
+    ptr: ?*lmdb.MDB_cursor = undefined,
 
-        pub fn close(self: *Self) void {
-            _ = lmdb.mdb_cursor_close(self.ptr);
-        }
+    pub fn close(self: Self) void {
+        lmdb.mdb_cursor_close(self.ptr);
+    }
 
-        pub fn put(self: *Self, k: K, v: V) void {
-            var key = lmdb.MDB_val{
-                .mv_size = @sizeOf(@TypeOf(k)),
-                .mv_data = @constCast(@ptrCast(&k)),
-            };
-            var val = lmdb.MDB_val{
-                .mv_size = @sizeOf(@TypeOf(v)),
-                .mv_data = @constCast(@ptrCast(&v)),
-            };
-            switch (lmdb.mdb_cursor_put(self.ptr, &key, &val, 0)) {
-                0 => {},
-                else => |err| {
-                    std.debug.print("put err: {}\n", .{err});
-                },
-            }
+    pub fn put(self: Self, k: anytype, v: anytype) !void {
+        var key = lmdb.MDB_val{
+            .mv_size = @sizeOf(@TypeOf(k)),
+            .mv_data = @constCast(@ptrCast(&k)),
+        };
+        var val = lmdb.MDB_val{
+            .mv_size = @sizeOf(@TypeOf(v)),
+            .mv_data = @constCast(@ptrCast(&v)),
+        };
+        switch (lmdb.mdb_cursor_put(self.ptr, &key, &val, 0)) {
+            0 => {},
+            else => |err| {
+                _ = err;
+                return error.CursorPut;
+            },
         }
+    }
 
-        pub fn get(self: *Self, k: *K, flags: Flags) ?V {
-            var key = lmdb.MDB_val{
-                .mv_size = @sizeOf(K),
-                .mv_data = @constCast(@ptrCast(k)),
-            };
-            var val: lmdb.MDB_val = undefined;
-            return switch (lmdb.mdb_cursor_get(self.ptr, &key, &val, @intFromEnum(flags))) {
-                0 => {
-                    k.* = std.mem.bytesToValue(K, key.mv_data.?);
-                    return std.mem.bytesToValue(V, val.mv_data.?);
-                    //k.* = @as(*align(1) K, @ptrCast(key.mv_data)).*;
-                    //return @as(?*align(1) V, @ptrCast(val.mv_data)).?.*;
-                },
-                lmdb.MDB_NOTFOUND => null,
-                else => |err| {
-                    std.debug.print("get err: {}\n", .{err});
-                    return null;
-                },
-            };
-        }
+    pub fn get(self: Self, k: anytype, comptime V: type, flags: Flags) !?V {
+        const k_ti = @typeInfo(@TypeOf(k));
+        const K = k_ti.Pointer.child;
 
-        pub fn del(self: *Self, k: K) void {
-            var key = lmdb.MDB_val{
-                .mv_size = @sizeOf(@TypeOf(k)),
-                .mv_data = @constCast(@ptrCast(&k)),
-            };
-            switch (lmdb.mdb_cursor_del(self.ptr, &key, 0)) {
-                0 => {},
-                else => |err| {
-                    std.debug.print("del err: {}\n", .{err});
-                },
-            }
-        }
+        var key = lmdb.MDB_val{
+            .mv_size = @sizeOf(K),
+            .mv_data = @constCast(@ptrCast(k)),
+        };
+        var val: lmdb.MDB_val = undefined;
+        return switch (lmdb.mdb_cursor_get(self.ptr, &key, &val, @intFromEnum(flags))) {
+            0 => {
+                const ptr = @as(*K, @constCast(k));
+                ptr.* = std.mem.bytesToValue(K, key.mv_data.?);
+                return std.mem.bytesToValue(V, val.mv_data.?);
+            },
+            lmdb.MDB_NOTFOUND => null,
+            else => |err| {
+                _ = err;
+                return error.CursorGet;
+            },
+        };
+    }
 
-        pub fn has(self: *Dbi, k: K, flags: Flags) bool {
-            return self.get(k, flags) != null;
+    pub fn del(self: Self, k: anytype) !void {
+        var key = lmdb.MDB_val{
+            .mv_size = @sizeOf(@TypeOf(k)),
+            .mv_data = @constCast(@ptrCast(&k)),
+        };
+        switch (lmdb.mdb_cursor_del(self.ptr, &key, 0)) {
+            0 => {},
+            else => |err| {
+                _ = err;
+                return error.CursorDel;
+            },
         }
-    };
-}
+    }
 
-pub fn Dbi(comptime K: type, comptime V: type) type {
-    return struct {
-        const Self = @This();
+    pub fn has(self: Self, k: anytype, flags: Flags) !bool {
+        const k_ti = @typeInfo(@TypeOf(k));
+        const K = k_ti.Pointer.child;
 
-        ptr: lmdb.MDB_dbi = undefined,
-        txn: *const Txn = undefined,
-        env: *const Env = undefined,
+        var key = lmdb.MDB_val{
+            .mv_size = @sizeOf(K),
+            .mv_data = @constCast(@ptrCast(k)),
+        };
+        var val: lmdb.MDB_val = undefined;
+        return switch (lmdb.mdb_cursor_get(self.ptr, &key, &val, @intFromEnum(flags))) {
+            0 => {
+                return true;
+            },
+            lmdb.MDB_NOTFOUND => {
+                return false;
+            },
+            else => {
+                return error.CursorHas;
+            },
+        };
+    }
+};
 
-        pub fn close(self: Self) void {
+pub const Dbi = struct {
+    const Self = @This();
 
-            // TODO: necessary?
-            lmdb.mdb_dbi_close(self.env.ptr, self.ptr);
-        }
+    ptr: lmdb.MDB_dbi = undefined,
+    txn: Txn = undefined,
+    env: Env = undefined,
 
-        pub fn cursor(self: Self) !Cursor(K, V) {
-            var result = Cursor(K, V){};
+    pub fn close(self: Self) void {
+        // TODO: necessary?
+        lmdb.mdb_dbi_close(self.env.ptr, self.ptr);
+    }
 
-            return switch (lmdb.mdb_cursor_open(self.txn.ptr, self.ptr, &result.ptr)) {
-                0 => result,
-                else => error.CursorOpen,
-            };
-        }
+    pub fn cursor(self: Self) !Cursor {
+        var result = Cursor{};
 
-        pub fn put(self: Self, k: K, v: V) void {
-            var key = lmdb.MDB_val{
-                .mv_size = @sizeOf(@TypeOf(k)),
-                .mv_data = @constCast(@ptrCast(&k)),
-            };
-            var val = lmdb.MDB_val{
-                .mv_size = @sizeOf(@TypeOf(v)),
-                .mv_data = @constCast(@ptrCast(&v)),
-            };
-            switch (lmdb.mdb_put(self.txn.ptr, self.ptr, &key, &val, 0)) {
-                0 => {},
-                else => |err| {
-                    std.debug.print("put err: {}\n", .{err});
-                },
-            }
-        }
+        return switch (lmdb.mdb_cursor_open(self.txn.ptr, self.ptr, &result.ptr)) {
+            0 => result,
+            else => error.DbiCursor,
+        };
+    }
 
-        pub fn get(self: Self, k: K) ?V {
-            var key = lmdb.MDB_val{
-                .mv_size = @sizeOf(@TypeOf(k)),
-                .mv_data = @constCast(@ptrCast(&k)),
-            };
-            var val: lmdb.MDB_val = undefined;
-            return switch (lmdb.mdb_get(self.txn.ptr, self.ptr, &key, &val)) {
-                0 => {
-                    return std.mem.bytesToValue(V, val.mv_data.?);
-                    //@as(?*align(1) V, @ptrCast(val.mv_data)).?.*
-                },
-                else => |err| {
-                    std.debug.print("get err: {}\n", .{err});
-                    return null;
-                },
-            };
+    pub fn put(self: Self, k: anytype, v: anytype) !void {
+        var key = lmdb.MDB_val{
+            .mv_size = @sizeOf(@TypeOf(k)),
+            .mv_data = @constCast(@ptrCast(&k)),
+        };
+        var val = lmdb.MDB_val{
+            .mv_size = @sizeOf(@TypeOf(v)),
+            .mv_data = @constCast(@ptrCast(&v)),
+        };
+        switch (lmdb.mdb_put(self.txn.ptr, self.ptr, &key, &val, 0)) {
+            0 => {},
+            else => |err| {
+                _ = err;
+                return error.DbiPut;
+            },
         }
+    }
 
-        pub fn del(self: Self, k: K) void {
-            var key = lmdb.MDB_val{
-                .mv_size = @sizeOf(@TypeOf(k)),
-                .mv_data = @constCast(@ptrCast(&k)),
-            };
-            switch (lmdb.mdb_del(self.txn.ptr, self.ptr, &key, null)) {
-                0 => {},
-                else => |err| {
-                    std.debug.print("del err: {}\n", .{err});
-                },
-            }
-        }
+    pub fn get(self: Self, k: anytype, comptime V: type) !V {
+        var key = lmdb.MDB_val{
+            .mv_size = @sizeOf(@TypeOf(k)),
+            .mv_data = @constCast(@ptrCast(&k)),
+        };
+        var val: lmdb.MDB_val = undefined;
+        return switch (lmdb.mdb_get(self.txn.ptr, self.ptr, &key, &val)) {
+            0 => {
+                return std.mem.bytesToValue(V, val.mv_data.?);
+            },
+            lmdb.MDB_NOTFOUND => error.NotFound,
+            else => |err| {
+                _ = err;
+                return error.DbiGet;
+            },
+        };
+    }
 
-        pub fn has(self: Self, k: K) bool {
-            return self.get(k) != null;
+    pub fn del(self: Self, k: anytype) !void {
+        var key = lmdb.MDB_val{
+            .mv_size = @sizeOf(@TypeOf(k)),
+            .mv_data = @constCast(@ptrCast(&k)),
+        };
+        switch (lmdb.mdb_del(self.txn.ptr, self.ptr, &key, null)) {
+            0 => {},
+            else => |err| {
+                _ = err;
+                return error.DbiDel;
+            },
         }
-    };
-}
+    }
+
+    pub fn has(self: Self, k: anytype) !bool {
+        var key = lmdb.MDB_val{
+            .mv_size = @sizeOf(@TypeOf(k)),
+            .mv_data = @constCast(@ptrCast(&k)),
+        };
+        var val: lmdb.MDB_val = undefined;
+        return switch (lmdb.mdb_get(self.txn.ptr, self.ptr, &key, &val)) {
+            0 => {
+                return true;
+            },
+            lmdb.MDB_NOTFOUND => {
+                return false;
+            },
+            else => |err| {
+                std.debug.print("[{}]\n", .{err});
+                return error.DbiHas;
+            },
+        };
+    }
+};
 
 pub const Txn = struct {
     ptr: ?*lmdb.MDB_txn = undefined,
-    env: *const Env = undefined,
+    env: Env = undefined,
 
-    pub fn dbi(self: *const Txn, name: [*c]const u8, comptime K: type, comptime V: type) !Dbi(K, V) {
-        var result = Dbi(K, V){ .env = self.env, .txn = self };
+    pub fn dbi(self: Txn, name: [:0]const u8) !Dbi {
+        var result = Dbi{ .env = self.env, .txn = self };
         // TODO: lmdb.MDB_INTEGERKEY?
-        switch (lmdb.mdb_dbi_open(self.ptr, name, lmdb.MDB_CREATE, &result.ptr)) {
+        switch (lmdb.mdb_dbi_open(self.ptr, @ptrCast(name), lmdb.MDB_CREATE, &result.ptr)) {
             0 => return result,
             else => |err| {
-                std.debug.print("dbi err: {}\n", .{err});
+                _ = err;
                 return error.DbiOpen;
             },
         }
     }
 
-    pub fn commit(self: Txn) void {
+    pub fn commit(self: Txn) !void {
         switch (lmdb.mdb_txn_commit(self.ptr)) {
             0 => {},
             lmdb.MDB_MAP_FULL => {
-                std.debug.print("resize\n", .{});
                 _ = lmdb.mdb_env_set_mapsize(self.env.ptr, 0);
+                return error.TxnCommitMapFull;
             },
             else => |err| {
-                std.debug.print("commit err: {}\n", .{err});
+                _ = err;
+                return error.TxnCommit;
             },
         }
     }
@@ -205,102 +241,106 @@ pub const Txn = struct {
 pub const Env = struct {
     ptr: ?*lmdb.MDB_env = undefined,
 
-    pub fn open(name: [*c]const u8, size: lmdb.mdb_size_t) Env {
+    pub fn open(name: [:0]const u8, size: lmdb.mdb_size_t) !Env {
         var result = Env{};
 
         _ = lmdb.mdb_env_create(&result.ptr);
         _ = lmdb.mdb_env_set_maxdbs(result.ptr, 10);
         _ = lmdb.mdb_env_set_mapsize(result.ptr, size);
-        _ = lmdb.mdb_env_open(result.ptr, name, lmdb.MDB_WRITEMAP, 0o664);
+        const res = lmdb.mdb_env_open(result.ptr, name, lmdb.MDB_WRITEMAP, 0o664);
         // _ = lmdb.mdb_env_open(result.ptr, name, lmdb.MDB_NOSYNC | lmdb.MDB_WRITEMAP, 0o664);
 
-        return result;
+        if (res != 0) {
+            return error.EnvOpen;
+        } else {
+            return result;
+        }
     }
 
     pub fn close(self: Env) void {
         lmdb.mdb_env_close(self.ptr);
     }
 
-    pub fn txn(self: *const Env) !Txn {
+    pub fn txn(self: Env) !Txn {
         var result = Txn{ .env = self };
         switch (lmdb.mdb_txn_begin(self.ptr, null, 0, &result.ptr)) {
             0 => return result,
             else => |err| {
-                std.debug.print("txn err: {}\n", .{err});
+                _ = err;
                 return error.TxnOpen;
             },
         }
     }
 
-    pub fn sync(self: *Env) void {
+    pub fn sync(self: Env) !void {
         switch (lmdb.mdb_env_sync(self.ptr, 1)) {
             0 => {},
             else => |err| {
-                std.debug.print("sync err: {}\n", .{err});
+                _ = err;
+                return error.EnvSync;
             },
         }
     }
 };
 
 test "basic" {
-    var env = Env.open("db", 1024 * 1024 * 1);
+    var env = try Env.open("db", 1024 * 1024 * 1);
+    // env.sync();
+    defer env.close();
 
     var txn = try env.txn();
+    defer txn.commit() catch {};
 
     const Value = struct {
         i: i64 = 123,
         s: [16]u8 = undefined,
     };
 
-    var dbi = try txn.dbi("abc", u64, Value);
+    var dbi = try txn.dbi("abc");
+
+    const idx: u64 = 1;
 
-    var val = dbi.get(1) orelse Value{ .i = 5 };
+    std.debug.print("has?: {}\n", .{try dbi.has(idx)});
+
+    var val = dbi.get(idx, Value) catch Value{ .i = 5 };
     std.debug.print("{}\n", .{val});
 
     val.i += 1;
 
-    dbi.put(1, val);
-
-    txn.commit();
-    dbi.close();
-    env.sync();
-    env.close();
+    try dbi.put(idx, val);
 }
 
 test "cursor" {
-    var env = Env.open("db", 1024 * 1024 * 1);
+    var env = try Env.open("db", 1024 * 1024 * 1);
+    // env.sync();
+    defer env.close();
 
     var txn = try env.txn();
+    defer txn.commit() catch {};
 
     const Value = struct {
         i: i64 = 123,
         s: [16]u8 = undefined,
     };
 
-    var dbi = try txn.dbi("def", u64, Value);
+    var dbi = try txn.dbi("def");
 
     for (0..10) |i| {
-        dbi.put(i, Value{ .i = @intCast(i + 23) });
+        try dbi.put(@as(u64, i), Value{ .i = @intCast(i + 23) });
     }
 
     var cursor = try dbi.cursor();
+    defer cursor.close();
 
     var key: u64 = undefined;
     {
-        const val = cursor.get(&key, .First);
+        const val = try cursor.get(&key, Value, .First);
         std.debug.print("{}: {?}\n", .{ key, val });
     }
 
-    while (cursor.get(&key, .Next)) |val| {
+    while (try cursor.get(&key, Value, .Next)) |val| {
         std.debug.print("{}: {?}\n", .{ key, val });
     }
-
-    cursor.close();
-
-    txn.commit();
-    dbi.close();
-    env.sync();
-    env.close();
 }
 
 // pub fn get(txn: ?*lmdb.MDB_txn, dbi: lmdb.MDB_dbi, k: anytype, comptime T: type) ?T {