+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 });
+ }
+}