From: patrick-scho Date: Sun, 2 Mar 2025 18:23:41 +0000 (+0100) Subject: add db abstaction X-Git-Url: https://gitweb.ps.run/ziglmdb/commitdiff_plain/b418996b9b61fee6f1d2b8ca325637ea192cd3b8?ds=sidebyside add db abstaction --- diff --git a/build.zig b/build.zig index 0525e47..102a4c0 100644 --- 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); } diff --git a/build.zig.zon b/build.zig.zon index 7c223de..494f4b6 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -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 index 0000000..3e54060 --- /dev/null +++ b/src/db.zig @@ -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 }); + } +} diff --git a/src/lmdb.zig b/src/lmdb.zig index 3802ec6..637bd26 100644 --- a/src/lmdb.zig +++ b/src/lmdb.zig @@ -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 {