diff --git a/.gitignore b/.gitignore index f2d1c98..08260ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ zig-cache/ -deps.zig \ No newline at end of file +zig-out/ +deps.zig +.zigmod/ +zigmod.lock diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f17bff6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lmdb"] + path = lmdb + url = https://github.com/LMDB/lmdb diff --git a/README.md b/README.md index ffb1db3..e66f8b9 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Refer to the 12 extensive unit tests provided [here](lmdb.zig#L874) for usage in Built and tested against Zig's master branch over all possible optimization modes. +For Zig 0.11, see the branch `0.11`. ## Motivation @@ -24,19 +25,16 @@ As a result, extensive effort was put into exposing and testing as many differen ## Setup -These bindings were built with first-class support for the [zigmod](https://github.com/nektro/zigmod) package manager. +1. Add this repo as git submodule +2. Add the following code to your `build.zig` -To incorporate these bindings into your project, include the following into your project's `zig.mod`: - -```yml -- type: git - path: https://github.com/lithdew/lmdb-zig -``` - -Afterwards, run: - -```shell -zigmod fetch +```zig +pub fn build(b: *std.Build) void { + ... + const dep_lmdb = b.anonymousDependency("lib/lmdb-zig", @import("lib/lmdb-zig/build.zig"), .{}); + exe.linkLibrary(dep_lmdb.artifact("lmdb")); + exe.addModule("lmdb", dep_lmdb.module("lmdb")); +} ``` ## Status diff --git a/build.zig b/build.zig index 3498edb..24c84f1 100644 --- a/build.zig +++ b/build.zig @@ -1,19 +1,36 @@ const std = @import("std"); -const deps = @import("deps.zig"); -const Builder = std.build.Builder; - -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { var target = b.standardTargetOptions(.{}); if (target.isGnuLibC()) target.abi = .musl; - const mode = b.standardReleaseOptions(); + const optimize = b.standardOptimizeOption(.{}); + + const lib = b.addStaticLibrary(.{ + .name = "lmdb", + .target = target, + .optimize = optimize, + }); + lib.linkLibC(); + lib.addCSourceFiles(.{ .files = &.{ "lmdb/libraries/liblmdb/mdb.c", "lmdb/libraries/liblmdb/midl.c" }, .flags = &.{"-fno-sanitize=undefined"} }); + + b.installArtifact(lib); + + const pkg = b.addModule("lmdb", .{ + .source_file = .{ .path = "lmdb.zig" }, + }); + + const tests = b.addTest(.{ + .name = "test", + .root_source_file = pkg.source_file, + .target = target, + .optimize = optimize, + }); + tests.addIncludePath(.{ .path = "lmdb/libraries/liblmdb" }); + tests.linkLibrary(lib); - const tests = b.addTest("lmdb.zig"); - tests.setTarget(target); - tests.setBuildMode(mode); - deps.addAllTo(tests); + b.installArtifact(tests); const test_step = b.step("test", "Run libary tests"); - test_step.dependOn(&tests.step); + test_step.dependOn(&b.addRunArtifact(tests).step); } diff --git a/lmdb b/lmdb new file mode 160000 index 0000000..30288c7 --- /dev/null +++ b/lmdb @@ -0,0 +1 @@ +Subproject commit 30288c72573ceac719627183f1058cad1dd08b74 diff --git a/lmdb.zig b/lmdb.zig index a60c4bc..d23259d 100644 --- a/lmdb.zig +++ b/lmdb.zig @@ -12,6 +12,35 @@ const testing = std.testing; const panic = debug.panic; const assert = debug.assert; +threadlocal var _addZ_buffer: [fs.MAX_PATH_BYTES + 1]u8 = undefined; +fn addZ(slice: []const u8) [*:0]const u8 { + if (slice.ptr[slice.len] == 0) { + return @ptrCast(slice.ptr); + } + + @memcpy(_addZ_buffer[0..slice.len], slice); + _addZ_buffer[slice.len] = 0; + return @ptrCast(&_addZ_buffer); +} + +// functions std.meta.trait that are no longer there +fn is(comptime expected: std.meta.Tag(std.builtin.Type), comptime T: type) bool { + const actual = std.meta.activeTag(@typeInfo(T)); + return expected == actual; +} +fn isTuple(comptime T: type) bool { + return is(.Struct, T) and @typeInfo(T).Struct.is_tuple; +} +fn isIndexable(comptime T: type) bool { + if (is(.Pointer, T)) { + if (@typeInfo(T).Pointer.size == .One) { + return (is(.Array, meta.Child(T))); + } + return true; + } + return is(.Array, T) or is(.Vector, T) or isTuple(T); +} + pub const Environment = packed struct { pub const Statistics = struct { page_size: usize, @@ -78,23 +107,13 @@ pub const Environment = packed struct { try call(c.mdb_env_set_mapsize, .{ inner, map_size }); } if (flags.max_num_readers) |max_num_readers| { - try call(c.mdb_env_set_maxreaders, .{ inner, @intCast(c_uint, max_num_readers) }); + try call(c.mdb_env_set_maxreaders, .{ inner, @as(c_uint, @intCast(max_num_readers)) }); } if (flags.max_num_dbs) |max_num_dbs| { - try call(c.mdb_env_set_maxdbs, .{ inner, @intCast(c_uint, max_num_dbs) }); + try call(c.mdb_env_set_maxdbs, .{ inner, @as(c_uint, @intCast(max_num_dbs)) }); } - if (!mem.endsWith(u8, env_path, &[_]u8{0})) { - assert(env_path.len + 1 <= fs.MAX_PATH_BYTES); - - var fixed_path: [fs.MAX_PATH_BYTES + 1]u8 = undefined; - mem.copy(u8, &fixed_path, env_path); - fixed_path[env_path.len] = 0; - - try call(c.mdb_env_open, .{ inner, fixed_path[0 .. env_path.len + 1].ptr, flags.into(), flags.mode }); - } else { - try call(c.mdb_env_open, .{ inner, env_path.ptr, flags.into(), flags.mode }); - } + try call(c.mdb_env_open, .{ inner, addZ(env_path), flags.into(), flags.mode }); return Self{ .inner = inner }; } @@ -111,30 +130,20 @@ pub const Environment = packed struct { } }; pub inline fn copyTo(self: Self, backup_path: []const u8, flags: CopyFlags) !void { - if (!mem.endsWith(u8, backup_path, &[_]u8{0})) { - assert(backup_path.len + 1 <= fs.MAX_PATH_BYTES); - - var fixed_path: [fs.MAX_PATH_BYTES + 1]u8 = undefined; - mem.copy(u8, &fixed_path, backup_path); - fixed_path[backup_path.len] = 0; - - try call(c.mdb_env_copy2, .{ self.inner, fixed_path[0 .. backup_path.len + 1].ptr, flags.into() }); - } else { - try call(c.mdb_env_copy2, .{ self.inner, backup_path.ptr, flags.into() }); - } + try call(c.mdb_env_copy2, .{ self.inner, addZ(backup_path), flags.into() }); } pub inline fn pipeTo(self: Self, fd_handle: os.fd_t, flags: CopyFlags) !void { try call(c.mdb_env_copyfd2, .{ self.inner, fd_handle, flags.into() }); } pub inline fn getMaxKeySize(self: Self) usize { - return @intCast(usize, c.mdb_env_get_maxkeysize(self.inner)); + return @as(usize, @intCast(c.mdb_env_get_maxkeysize(self.inner))); } pub inline fn getMaxNumReaders(self: Self) usize { var max_num_readers: c_uint = 0; call(c.mdb_env_get_maxreaders, .{ self.inner, &max_num_readers }) catch |err| { panic("Environment.getMaxNumReaders(): {}", .{err}); }; - return @intCast(usize, max_num_readers); + return @as(usize, @intCast(max_num_readers)); } pub inline fn setMapSize(self: Self, map_size: ?usize) !void { try call(c.mdb_env_set_mapsize, .{ self.inner, if (map_size) |size| size else 0 }); @@ -217,11 +226,11 @@ pub const Environment = packed struct { } pub inline fn path(self: Self) []const u8 { var env_path: [:0]const u8 = undefined; - call(c.mdb_env_get_path, .{ self.inner, @ptrCast([*c][*c]const u8, &env_path.ptr) }) catch |err| { + call(c.mdb_env_get_path, .{ self.inner, @as([*c][*c]const u8, @ptrCast(&env_path.ptr)) }) catch |err| { panic("Environment.path(): {}", .{err}); }; env_path.len = mem.indexOfSentinel(u8, 0, env_path.ptr); - return mem.span(env_path); + return env_path; } pub inline fn stat(self: Self) Statistics { var inner: c.MDB_stat = undefined; @@ -229,12 +238,12 @@ pub const Environment = packed struct { panic("Environment.stat(): {}", .{err}); }; return Statistics{ - .page_size = @intCast(usize, inner.ms_psize), - .tree_height = @intCast(usize, inner.ms_depth), - .num_branch_pages = @intCast(usize, inner.ms_branch_pages), - .num_leaf_pages = @intCast(usize, inner.ms_leaf_pages), - .num_overflow_pages = @intCast(usize, inner.ms_overflow_pages), - .num_entries = @intCast(usize, inner.ms_entries), + .page_size = @as(usize, @intCast(inner.ms_psize)), + .tree_height = @as(usize, @intCast(inner.ms_depth)), + .num_branch_pages = @as(usize, @intCast(inner.ms_branch_pages)), + .num_leaf_pages = @as(usize, @intCast(inner.ms_leaf_pages)), + .num_overflow_pages = @as(usize, @intCast(inner.ms_overflow_pages)), + .num_entries = @as(usize, @intCast(inner.ms_entries)), }; } pub inline fn fd(self: Self) os.fd_t { @@ -250,12 +259,12 @@ pub const Environment = packed struct { panic("Environment.info(): {}", .{err}); }; return Info{ - .map_address = @ptrCast(?[*]u8, inner.me_mapaddr), - .map_size = @intCast(usize, inner.me_mapsize), - .last_page_num = @intCast(usize, inner.me_last_pgno), - .last_tx_id = @intCast(usize, inner.me_last_txnid), - .max_num_reader_slots = @intCast(usize, inner.me_maxreaders), - .num_used_reader_slots = @intCast(usize, inner.me_numreaders), + .map_address = @as(?[*]u8, @ptrCast(inner.me_mapaddr)), + .map_size = @as(usize, @intCast(inner.me_mapsize)), + .last_page_num = @as(usize, @intCast(inner.me_last_pgno)), + .last_tx_id = @as(usize, @intCast(inner.me_last_txnid)), + .max_num_reader_slots = @as(usize, @intCast(inner.me_maxreaders)), + .num_used_reader_slots = @as(usize, @intCast(inner.me_numreaders)), }; } pub inline fn begin(self: Self, flags: Transaction.Flags) !Transaction { @@ -270,7 +279,7 @@ pub const Environment = packed struct { pub inline fn purge(self: Self) !usize { var count: c_int = undefined; try call(c.mdb_reader_check, .{ self.inner, &count }); - return @intCast(usize, count); + return @as(usize, @intCast(count)); } }; @@ -282,27 +291,9 @@ pub const Database = struct { duplicate_entries_are_fixed_size: bool = false, duplicate_keys_are_integers: bool = false, compare_duplicate_keys_in_reverse_order: bool = false, - pub inline fn into(self: Self.OpenFlags) c_uint { - var flags: c_uint = 0; - if (self.compare_keys_in_reverse_order) flags |= c.MDB_REVERSEKEY; - if (self.allow_duplicate_keys) flags |= c.MDB_DUPSORT; - if (self.keys_are_integers) flags |= c.MDB_INTEGERKEY; - if (self.duplicate_entries_are_fixed_size) flags |= c.MDB_DUPFIXED; - if (self.duplicate_keys_are_integers) flags |= c.MDB_INTEGERDUP; - if (self.compare_duplicate_keys_in_reverse_order) flags |= c.MDB_REVERSEDUP; - return flags; - } - }; - - pub const UseFlags = packed struct { - compare_keys_in_reverse_order: bool = false, - allow_duplicate_keys: bool = false, - keys_are_integers: bool = false, - duplicate_entries_are_fixed_size: bool = false, - duplicate_keys_are_integers: bool = false, - compare_duplicate_keys_in_reverse_order: bool = false, + /// only use with named database create_if_not_exists: bool = false, - pub inline fn into(self: Self.UseFlags) c_uint { + pub inline fn into(self: @This()) c_uint { var flags: c_uint = 0; if (self.compare_keys_in_reverse_order) flags |= c.MDB_REVERSEKEY; if (self.allow_duplicate_keys) flags |= c.MDB_DUPSORT; @@ -342,16 +333,11 @@ pub const Transaction = packed struct { inner: ?*c.MDB_txn, pub inline fn id(self: Self) usize { - return @intCast(usize, c.mdb_txn_id(self.inner)); + return @as(usize, @intCast(c.mdb_txn_id(self.inner))); } - pub inline fn open(self: Self, flags: Database.OpenFlags) !Database { + pub inline fn open(self: Self, name: ?[]const u8, flags: Database.OpenFlags) !Database { var inner: c.MDB_dbi = 0; - try call(c.mdb_dbi_open, .{ self.inner, null, flags.into(), &inner }); - return Database{ .inner = inner }; - } - pub inline fn use(self: Self, name: []const u8, flags: Database.UseFlags) !Database { - var inner: c.MDB_dbi = 0; - try call(c.mdb_dbi_open, .{ self.inner, name.ptr, flags.into(), &inner }); + try call(c.mdb_dbi_open, .{ self.inner, if (name) |s| s.ptr else null, flags.into(), &inner }); return Database{ .inner = inner }; } pub inline fn cursor(self: Self, db: Database) !Cursor { @@ -362,8 +348,8 @@ pub const Transaction = packed struct { pub inline fn setKeyOrder(self: Self, db: Database, comptime order: fn (a: []const u8, b: []const u8) math.Order) !void { const S = struct { fn cmp(a: ?*const c.MDB_val, b: ?*const c.MDB_val) callconv(.C) c_int { - const slice_a = @ptrCast([*]const u8, a.?.mv_data)[0..a.?.mv_size]; - const slice_b = @ptrCast([*]const u8, b.?.mv_data)[0..b.?.mv_size]; + const slice_a = @as([*]const u8, @ptrCast(a.?.mv_data))[0..a.?.mv_size]; + const slice_b = @as([*]const u8, @ptrCast(b.?.mv_data))[0..b.?.mv_size]; return switch (order(slice_a, slice_b)) { .eq => 0, .lt => -1, @@ -376,8 +362,8 @@ pub const Transaction = packed struct { pub inline fn setItemOrder(self: Self, db: Database, comptime order: fn (a: []const u8, b: []const u8) math.Order) !void { const S = struct { fn cmp(a: ?*const c.MDB_val, b: ?*const c.MDB_val) callconv(.C) c_int { - const slice_a = @ptrCast([*]const u8, a.?.mv_data)[0..a.?.mv_size]; - const slice_b = @ptrCast([*]const u8, b.?.mv_data)[0..b.?.mv_size]; + const slice_a = @as([*]const u8, @ptrCast(a.?.mv_data))[0..a.?.mv_size]; + const slice_b = @as([*]const u8, @ptrCast(b.?.mv_data))[0..b.?.mv_size]; return switch (order(slice_a, slice_b)) { .eq => 0, .lt => -1, @@ -388,11 +374,11 @@ pub const Transaction = packed struct { try call(c.mdb_set_dupsort, .{ self.inner, db.inner, S.cmp }); } pub inline fn get(self: Self, db: Database, key: []const u8) ![]const u8 { - var k = &c.MDB_val{ .mv_size = key.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(key.ptr)) }; + var k = c.MDB_val{ .mv_size = key.len, .mv_data = @constCast(key.ptr) }; var v: c.MDB_val = undefined; - try call(c.mdb_get, .{ self.inner, db.inner, k, &v }); + try call(c.mdb_get, .{ self.inner, db.inner, &k, &v }); - return @ptrCast([*]const u8, v.mv_data)[0..v.mv_size]; + return @as([*]const u8, @ptrCast(v.mv_data))[0..v.mv_size]; } pub const PutFlags = packed struct { @@ -409,21 +395,17 @@ pub const Transaction = packed struct { return flags; } }; - pub inline fn putItem(self: Self, db: Database, key: []const u8, val: anytype, flags: PutFlags) !void { - const bytes = if (meta.trait.isIndexable(@TypeOf(val))) mem.span(val) else mem.asBytes(&val); - return self.put(db, key, bytes, flags); - } pub inline fn put(self: Self, db: Database, key: []const u8, val: []const u8, flags: PutFlags) !void { - var k = &c.MDB_val{ .mv_size = key.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(key.ptr)) }; - var v = &c.MDB_val{ .mv_size = val.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(val.ptr)) }; - try call(c.mdb_put, .{ self.inner, db.inner, k, v, flags.into() }); + var k = c.MDB_val{ .mv_size = key.len, .mv_data = @constCast(key.ptr) }; + var v = c.MDB_val{ .mv_size = val.len, .mv_data = @constCast(val.ptr) }; + try call(c.mdb_put, .{ self.inner, db.inner, &k, &v, flags.into() }); } - pub inline fn getOrPut(self: Self, db: Database, key: []const u8, val: []const u8) !?[]const u8 { - var k = &c.MDB_val{ .mv_size = key.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(key.ptr)) }; - var v = &c.MDB_val{ .mv_size = val.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(val.ptr)) }; + pub inline fn getOrPut(self: Self, db: Database, key: []const u8, val: []const u8) MDB_errorset!?[]const u8 { + var k = c.MDB_val{ .mv_size = key.len, .mv_data = @constCast(key.ptr) }; + var v = c.MDB_val{ .mv_size = val.len, .mv_data = @constCast(val.ptr) }; - call(c.mdb_put, .{ self.inner, db.inner, k, v, c.MDB_NOOVERWRITE }) catch |err| switch (err) { - error.AlreadyExists => return @ptrCast([*]u8, v.mv_data)[0..v.mv_size], + call(c.mdb_put, .{ self.inner, db.inner, &k, &v, c.MDB_NOOVERWRITE }) catch |err| switch (err) { + error.AlreadyExists => return @as([*]u8, @ptrCast(v.mv_data))[0..v.mv_size], else => return err, }; @@ -445,34 +427,34 @@ pub const Transaction = packed struct { successful: []u8, found_existing: []const u8, }; - pub inline fn reserve(self: Self, db: Database, key: []const u8, val_len: usize, flags: ReserveFlags) !ReserveResult { - var k = &c.MDB_val{ .mv_size = key.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(key.ptr)) }; - var v = &c.MDB_val{ .mv_size = val_len, .mv_data = null }; + pub inline fn reserve(self: Self, db: Database, key: []const u8, val_len: usize, flags: ReserveFlags) MDB_errorset!ReserveResult { + var k = c.MDB_val{ .mv_size = key.len, .mv_data = @constCast(key.ptr) }; + var v = c.MDB_val{ .mv_size = val_len, .mv_data = null }; - call(c.mdb_put, .{ self.inner, db.inner, k, v, flags.into() }) catch |err| switch (err) { + call(c.mdb_put, .{ self.inner, db.inner, &k, &v, flags.into() }) catch |err| switch (err) { error.AlreadyExists => return ReserveResult{ - .found_existing = @ptrCast([*]const u8, v.mv_data)[0..v.mv_size], + .found_existing = @as([*]const u8, @ptrCast(v.mv_data))[0..v.mv_size], }, else => return err, }; return ReserveResult{ - .successful = @ptrCast([*]u8, v.mv_data)[0..v.mv_size], + .successful = @as([*]u8, @ptrCast(v.mv_data))[0..v.mv_size], }; } pub inline fn del(self: Self, db: Database, key: []const u8, op: union(enum) { key: void, item: []const u8 }) !void { - var k = &c.MDB_val{ .mv_size = key.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(key.ptr)) }; - var v: ?*c.MDB_val = switch (op) { + var k = c.MDB_val{ .mv_size = key.len, .mv_data = @constCast(key.ptr) }; + const v: ?*c.MDB_val = switch (op) { .key => null, .item => |item| &c.MDB_val{ .mv_size = item.len, - .mv_data = @intToPtr(?*c_void, @ptrToInt(item.ptr)), + .mv_data = @ptrFromInt(@intFromPtr(item.ptr)), }, }; - try call(c.mdb_del, .{ self.inner, db.inner, k, v }); + try call(c.mdb_del, .{ self.inner, db.inner, &k, @as([*c]c.MDB_val, @constCast(v)) }); } pub inline fn drop(self: Self, db: Database, method: enum(c_int) { empty = 0, delete = 1 }) !void { - try call(c.mdb_drop, .{ self.inner, db.inner, @enumToInt(method) }); + try call(c.mdb_drop, .{ self.inner, db.inner, @intFromEnum(method) }); } pub inline fn deinit(self: Self) void { call(c.mdb_txn_abort, .{self.inner}); @@ -521,7 +503,7 @@ pub const Cursor = packed struct { call(c.mdb_cursor_count, .{ self.inner, &inner }) catch |err| { panic("cursor is initialized, or database does not support duplicate keys: {}", .{err}); }; - return @intCast(usize, inner); + return @as(usize, @intCast(inner)); } pub fn updateItemInPlace(self: Self, current_key: []const u8, new_val: anytype) !void { @@ -530,17 +512,17 @@ pub const Cursor = packed struct { } pub fn updateInPlace(self: Self, current_key: []const u8, new_val: []const u8) !void { - var k = &c.MDB_val{ .mv_size = current_key.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(current_key.ptr)) }; - var v = &c.MDB_val{ .mv_size = new_val.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(new_val.ptr)) }; - try call(c.mdb_cursor_put, .{ self.inner, k, v, c.MDB_CURRENT }); + var k = c.MDB_val{ .mv_size = current_key.len, .mv_data = @constCast(current_key.ptr) }; + var v = c.MDB_val{ .mv_size = new_val.len, .mv_data = @constCast(new_val.ptr) }; + try call(c.mdb_cursor_put, .{ self.inner, &k, &v, c.MDB_CURRENT }); } /// May not be used with databases supporting duplicate keys. pub fn reserveInPlace(self: Self, current_key: []const u8, new_val_len: usize) ![]u8 { - var k = &c.MDB_val{ .mv_size = current_key.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(current_key.ptr)) }; - var v = &c.MDB_val{ .mv_size = new_val_len, .mv_data = null }; - try call(c.mdb_cursor_put, .{ self.inner, k, v, c.MDB_CURRENT | c.MDB_RESERVE }); - return @ptrCast([*]u8, v.mv_data)[0..v.mv_size]; + var k = c.MDB_val{ .mv_size = current_key.len, .mv_data = @constCast(current_key.ptr) }; + var v = c.MDB_val{ .mv_size = new_val_len, .mv_data = null }; + try call(c.mdb_cursor_put, .{ self.inner, &k, &v, c.MDB_CURRENT | c.MDB_RESERVE }); + return @as([*]u8, @ptrCast(v.mv_data))[0..v.mv_size]; } pub const PutFlags = packed struct { @@ -557,33 +539,33 @@ pub const Cursor = packed struct { return flags; } }; - pub inline fn putItem(self: Self, key: []const u8, val: anytype, flags: PutFlags) !void { - const bytes = if (meta.trait.isIndexable(@TypeOf(val))) mem.span(val) else mem.asBytes(&val); - return self.put(key, bytes, flags); - } pub inline fn put(self: Self, key: []const u8, val: []const u8, flags: PutFlags) !void { - var k = &c.MDB_val{ .mv_size = key.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(key.ptr)) }; - var v = &c.MDB_val{ .mv_size = val.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(val.ptr)) }; - try call(c.mdb_cursor_put, .{ self.inner, k, v, flags.into() }); + var k = c.MDB_val{ .mv_size = key.len, .mv_data = @constCast(key.ptr) }; + var v = c.MDB_val{ .mv_size = val.len, .mv_data = @constCast(val.ptr) }; + try call(c.mdb_cursor_put, .{ self.inner, &k, &v, flags.into() }); + } + pub inline fn putItem(self: Self, key: []const u8, value: anytype, flags: PutFlags) !usize { + var k = c.MDB_val{ .mv_size = key.len, .mv_data = @constCast(key.ptr) }; + var v = c.MDB_val{ .mv_size = @sizeOf(@TypeOf(value)), .mv_data = @as(*anyopaque, @constCast(&value)) }; + try call(c.mdb_cursor_put, .{ self.inner, &k, &v, flags.into() }); } + /// Insert multiple values for a key + /// value must be contiguous in memory, like []Foo pub inline fn putBatch(self: Self, key: []const u8, batch: anytype, flags: PutFlags) !usize { - comptime assert(meta.trait.isIndexable(@TypeOf(batch))); + comptime assert(isIndexable(@TypeOf(batch))); - var k = &c.MDB_val{ .mv_size = key.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(key.ptr)) }; - var v = [_]c.MDB_val{ - .{ .mv_size = @sizeOf(meta.Elem(@TypeOf(batch))), .mv_data = @intToPtr(?*c_void, @ptrToInt(&batch[0])) }, - .{ .mv_size = mem.len(batch), .mv_data = undefined }, - }; - try call(c.mdb_cursor_put, .{ self.inner, k, &v, @intCast(c_uint, c.MDB_MULTIPLE) | flags.into() }); + var k = c.MDB_val{ .mv_size = key.len, .mv_data = @constCast(key.ptr) }; + var v = [_]c.MDB_val{ .{ .mv_size = @sizeOf(meta.Elem(@TypeOf(batch))), .mv_data = &batch[0] }, .{ .mv_size = batch.len, .mv_data = undefined } }; + try call(c.mdb_cursor_put, .{ self.inner, &k, &v, @as(c_uint, @intCast(c.MDB_MULTIPLE)) | flags.into() }); - return @intCast(usize, v[1].mv_size); + return @as(usize, @intCast(v[1].mv_size)); } pub inline fn getOrPut(self: Self, key: []const u8, val: []const u8) !?[]const u8 { - var k = &c.MDB_val{ .mv_size = key.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(key.ptr)) }; - var v = &c.MDB_val{ .mv_size = val.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(val.ptr)) }; + var k = c.MDB_val{ .mv_size = key.len, .mv_data = key.ptr }; + var v = c.MDB_val{ .mv_size = val.len, .mv_data = @constCast(val.ptr) }; - call(c.mdb_cursor_put, .{ self.inner, k, v, c.MDB_NOOVERWRITE }) catch |err| switch (err) { - error.AlreadyExists => return @ptrCast([*]u8, v.mv_data)[0..v.mv_size], + call(c.mdb_cursor_put, .{ self.inner, &k, &v, c.MDB_NOOVERWRITE }) catch |err| switch (err) { + error.AlreadyExists => return @as([*]u8, @ptrCast(v.mv_data))[0..v.mv_size], else => return err, }; @@ -606,22 +588,22 @@ pub const Cursor = packed struct { found_existing: []const u8, }; pub inline fn reserve(self: Self, key: []const u8, val_len: usize, flags: ReserveFlags) !ReserveResult { - var k = &c.MDB_val{ .mv_size = key.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(key.ptr)) }; - var v = &c.MDB_val{ .mv_size = val_len, .mv_data = null }; + var k = c.MDB_val{ .mv_size = key.len, .mv_data = @constCast(key.ptr) }; + var v = c.MDB_val{ .mv_size = val_len, .mv_data = null }; - call(c.mdb_cursor_put, .{ self.inner, k, v, flags.into() }) catch |err| switch (err) { + call(c.mdb_cursor_put, .{ self.inner, &k, &v, flags.into() }) catch |err| switch (err) { error.AlreadyExists => return ReserveResult{ - .found_existing = @ptrCast([*]const u8, v.mv_data)[0..v.mv_size], + .found_existing = @as([*]const u8, @ptrCast(v.mv_data))[0..v.mv_size], }, else => return err, }; return ReserveResult{ - .successful = @ptrCast([*]u8, v.mv_data)[0..v.mv_size], + .successful = @as([*]u8, @ptrCast(v.mv_data))[0..v.mv_size], }; } - pub inline fn del(self: Self, op: enum(c_uint) { key = c.MDB_NODUPDATA, item = 0 }) !void { - call(c.mdb_cursor_del, .{ self.inner, @enumToInt(op) }) catch |err| switch (err) { + pub inline fn del(self: Self, op: enum(c_uint) { key = c.MDB_NODUPDATA, item = 0 }) MDB_errorset!void { + call(c.mdb_cursor_del, .{ self.inner, @intFromEnum(op) }) catch |err| switch (err) { error.InvalidParameter => return error.NotFound, else => return err, }; @@ -643,14 +625,14 @@ pub const Cursor = packed struct { pub inline fn get(self: Self, pos: Position) !?Entry { var k: c.MDB_val = undefined; var v: c.MDB_val = undefined; - call(c.mdb_cursor_get, .{ self.inner, &k, &v, @enumToInt(pos) }) catch |err| switch (err) { + call(c.mdb_cursor_get, .{ self.inner, &k, &v, @intFromEnum(pos) }) catch |err| switch (err) { error.InvalidParameter => return if (pos == .current) null else err, error.NotFound => return null, else => return err, }; return Entry{ - .key = @ptrCast([*]const u8, k.mv_data)[0..k.mv_size], - .val = @ptrCast([*]const u8, v.mv_data)[0..v.mv_size], + .key = @as([*]const u8, @ptrCast(k.mv_data))[0..k.mv_size], + .val = @as([*]const u8, @ptrCast(v.mv_data))[0..v.mv_size], }; } @@ -659,42 +641,43 @@ pub const Cursor = packed struct { next = c.MDB_NEXT_MULTIPLE, prev = c.MDB_PREV_MULTIPLE, }; - pub inline fn getPage(self: Self, comptime T: type, pos: PagePosition) !?Page(T) { + pub inline fn getPage(self: Self, comptime T: type, pos: PagePosition) MDB_errorset!?Page(T) { var k: c.MDB_val = undefined; var v: c.MDB_val = undefined; - call(c.mdb_cursor_get, .{ self.inner, &k, &v, @enumToInt(pos) }) catch |err| switch (err) { + call(c.mdb_cursor_get, .{ self.inner, &k, &v, @intFromEnum(pos) }) catch |err| switch (err) { error.NotFound => return null, else => return err, }; return Page(T){ - .key = @ptrCast([*]const u8, k.mv_data)[0..k.mv_size], - .items = mem.bytesAsSlice(T, @ptrCast([*]const u8, v.mv_data)[0..v.mv_size]), + .key = @as([*]const u8, @ptrCast(k.mv_data))[0..k.mv_size], + .items = mem.bytesAsSlice(T, @as([*]const u8, @ptrCast(v.mv_data))[0..v.mv_size]), }; } pub inline fn seekToItem(self: Self, key: []const u8, val: []const u8) !void { - var k = &c.MDB_val{ .mv_size = key.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(key.ptr)) }; - var v = &c.MDB_val{ .mv_size = val.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(val.ptr)) }; - try call(c.mdb_cursor_get, .{ self.inner, k, v, .MDB_GET_BOTH }); + var k = c.MDB_val{ .mv_size = key.len, .mv_data = @constCast(key.ptr) }; + var v = c.MDB_val{ .mv_size = val.len, .mv_data = @constCast(val.ptr) }; + try call(c.mdb_cursor_get, .{ self.inner, &k, &v, .MDB_GET_BOTH }); } pub inline fn seekFromItem(self: Self, key: []const u8, val: []const u8) ![]const u8 { - var k = &c.MDB_val{ .mv_size = key.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(key.ptr)) }; - var v = &c.MDB_val{ .mv_size = val.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(val.ptr)) }; - try call(c.mdb_cursor_get, .{ self.inner, k, v, c.MDB_GET_BOTH_RANGE }); - return @ptrCast([*]const u8, v.mv_data)[0..v.mv_size]; + var k = c.MDB_val{ .mv_size = key.len, .mv_data = @constCast(key.ptr) }; + var v = c.MDB_val{ .mv_size = val.len, .mv_data = @constCast(val.ptr) }; + try call(c.mdb_cursor_get, .{ self.inner, &k, &v, c.MDB_GET_BOTH_RANGE }); + return @as([*]const u8, @ptrCast(v.mv_data))[0..v.mv_size]; } pub inline fn seekTo(self: Self, key: []const u8) ![]const u8 { - var k = &c.MDB_val{ .mv_size = key.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(key.ptr)) }; + @setEvalBranchQuota(10000); + var k = c.MDB_val{ .mv_size = key.len, .mv_data = @constCast(key.ptr) }; var v: c.MDB_val = undefined; - try call(c.mdb_cursor_get, .{ self.inner, k, &v, c.MDB_SET_KEY }); - return @ptrCast([*]const u8, v.mv_data)[0..v.mv_size]; + try call(c.mdb_cursor_get, .{ self.inner, &k, &v, c.MDB_SET_KEY }); + return @as([*]const u8, @ptrCast(v.mv_data))[0..v.mv_size]; } pub inline fn seekFrom(self: Self, key: []const u8) !Entry { - var k = &c.MDB_val{ .mv_size = key.len, .mv_data = @intToPtr(?*c_void, @ptrToInt(key.ptr)) }; + var k = c.MDB_val{ .mv_size = key.len, .mv_data = @constCast(key.ptr) }; var v: c.MDB_val = undefined; - try call(c.mdb_cursor_get, .{ self.inner, k, &v, c.MDB_SET_RANGE }); + try call(c.mdb_cursor_get, .{ self.inner, &k, &v, c.MDB_SET_RANGE }); return Entry{ - .key = @ptrCast([*]const u8, k.mv_data)[0..k.mv_size], - .val = @ptrCast([*]const u8, v.mv_data)[0..v.mv_size], + .key = @as([*]const u8, @ptrCast(k.mv_data))[0..k.mv_size], + .val = @as([*]const u8, @ptrCast(v.mv_data))[0..v.mv_size], }; } pub inline fn first(self: Self) !?Entry { @@ -741,12 +724,43 @@ pub const Cursor = packed struct { } }; +pub const MDB_errorset = error{ + AlreadyExists, + NotFound, + PageNotFound, + PageCorrupted, + Panic, + VersionMismatch, + FileNotDatabase, + MapSizeLimitReached, + MaxNumDatabasesLimitReached, + MaxNumReadersLimitReached, + TooManyEnvironmentsOpen, + TransactionTooBig, + CursorStackLimitReached, + OutOfPageMemory, + DatabaseExceedsMapSizeLimit, + IncompatibleOperation, + InvalidReaderLocktableSlotReuse, + TransactionNotAborted, + UnsupportedSize, + BadDatabaseHandle, + NoSuchFileOrDirectory, + InputOutputError, + OutOfMemory, + ReadOnly, + DeviceOrResourceBusy, + InvalidParameter, + NoSpaceLeftOnDevice, + FileAlreadyExists, +}; + inline fn ResultOf(comptime function: anytype) type { - return if (@typeInfo(@TypeOf(function)).Fn.return_type == c_int) anyerror!void else void; + return if (@typeInfo(@TypeOf(function)).Fn.return_type == c_int) MDB_errorset!void else void; } inline fn call(comptime function: anytype, args: anytype) ResultOf(function) { - const rc = @call(.{}, function, args); + const rc = @call(.auto, function, args); if (ResultOf(function) == void) return rc; return switch (rc) { @@ -771,14 +785,14 @@ inline fn call(comptime function: anytype, args: anytype) ResultOf(function) { c.MDB_BAD_TXN => error.TransactionNotAborted, c.MDB_BAD_VALSIZE => error.UnsupportedSize, c.MDB_BAD_DBI => error.BadDatabaseHandle, - @enumToInt(os.E.NOENT) => error.NoSuchFileOrDirectory, - @enumToInt(os.E.IO) => error.InputOutputError, - @enumToInt(os.E.NOMEM) => error.OutOfMemory, - @enumToInt(os.E.ACCES) => error.ReadOnly, - @enumToInt(os.E.BUSY) => error.DeviceOrResourceBusy, - @enumToInt(os.E.INVAL) => error.InvalidParameter, - @enumToInt(os.E.NOSPC) => error.NoSpaceLeftOnDevice, - @enumToInt(os.E.EXIST) => error.FileAlreadyExists, + @intFromEnum(os.E.NOENT) => error.NoSuchFileOrDirectory, + @intFromEnum(os.E.IO) => error.InputOutputError, + @intFromEnum(os.E.NOMEM) => error.OutOfMemory, + @intFromEnum(os.E.ACCES) => error.ReadOnly, + @intFromEnum(os.E.BUSY) => error.DeviceOrResourceBusy, + @intFromEnum(os.E.INVAL) => error.InvalidParameter, + @intFromEnum(os.E.NOSPC) => error.NoSpaceLeftOnDevice, + @intFromEnum(os.E.EXIST) => error.FileAlreadyExists, else => panic("({}) {s}", .{ rc, c.mdb_strerror(rc) }), }; } @@ -792,7 +806,7 @@ test "Environment.init() / Environment.deinit(): query environment stats, flags, defer tmp.cleanup(); var buf: [fs.MAX_PATH_BYTES]u8 = undefined; - var path = try tmp.dir.realpath("./", &buf); + const path = try tmp.dir.realpath("./", &buf); const env = try Environment.init(path, .{ .use_writable_memory_map = true, @@ -851,8 +865,8 @@ test "Environment.copyTo(): backup environment and check environment integrity" var buf_a: [fs.MAX_PATH_BYTES]u8 = undefined; var buf_b: [fs.MAX_PATH_BYTES]u8 = undefined; - var path_a = try tmp_a.dir.realpath("./", &buf_a); - var path_b = try tmp_b.dir.realpath("./", &buf_b); + const path_a = try tmp_a.dir.realpath("./", &buf_a); + const path_b = try tmp_b.dir.realpath("./", &buf_b); const env_a = try Environment.init(path_a, .{}); { @@ -861,7 +875,7 @@ test "Environment.copyTo(): backup environment and check environment integrity" const tx = try env_a.begin(.{}); errdefer tx.deinit(); - const db = try tx.open(.{}); + const db = try tx.open(null, .{}); defer db.close(env_a); var i: u8 = 0; @@ -881,7 +895,7 @@ test "Environment.copyTo(): backup environment and check environment integrity" const tx = try env_b.begin(.{}); defer tx.deinit(); - const db = try tx.open(.{}); + const db = try tx.open(null, .{}); defer db.close(env_b); var i: u8 = 0; @@ -896,7 +910,7 @@ test "Environment.sync(): manually flush system buffers" { defer tmp.cleanup(); var buf: [fs.MAX_PATH_BYTES]u8 = undefined; - var path = try tmp.dir.realpath("./", &buf); + const path = try tmp.dir.realpath("./", &buf); const env = try Environment.init(path, .{ .dont_sync = true, @@ -909,7 +923,7 @@ test "Environment.sync(): manually flush system buffers" { const tx = try env.begin(.{}); errdefer tx.deinit(); - const db = try tx.open(.{}); + const db = try tx.open(null, .{}); defer db.close(env); var i: u8 = 0; @@ -926,7 +940,7 @@ test "Environment.sync(): manually flush system buffers" { const tx = try env.begin(.{}); defer tx.deinit(); - const db = try tx.open(.{}); + const db = try tx.open(null, .{}); defer db.close(env); var i: u8 = 0; @@ -941,7 +955,7 @@ test "Transaction: get(), put(), reserve(), delete(), and commit() several entri defer tmp.cleanup(); var buf: [fs.MAX_PATH_BYTES]u8 = undefined; - var path = try tmp.dir.realpath("./", &buf); + const path = try tmp.dir.realpath("./", &buf); const env = try Environment.init(path, .{}); defer env.deinit(); @@ -949,7 +963,7 @@ test "Transaction: get(), put(), reserve(), delete(), and commit() several entri const tx = try env.begin(.{}); errdefer tx.deinit(); - const db = try tx.open(.{}); + const db = try tx.open(null, .{}); defer db.close(env); // Transaction.put() / Transaction.get() @@ -973,7 +987,7 @@ test "Transaction: get(), put(), reserve(), delete(), and commit() several entri { const result = try tx.reserve(db, "hello", "new_value".len, .{}); try testing.expectEqual("new_value".len, result.successful.len); - mem.copy(u8, result.successful, "new_value"); + @memcpy(result.successful, "new_value"); } try testing.expectEqualStrings("new_value", try tx.get(db, "hello")); @@ -997,7 +1011,7 @@ test "Transaction: reserve, write, and attempt to reserve again with dont_overwr defer tmp.cleanup(); var buf: [fs.MAX_PATH_BYTES]u8 = undefined; - var path = try tmp.dir.realpath("./", &buf); + const path = try tmp.dir.realpath("./", &buf); const env = try Environment.init(path, .{}); defer env.deinit(); @@ -1005,12 +1019,12 @@ test "Transaction: reserve, write, and attempt to reserve again with dont_overwr const tx = try env.begin(.{}); errdefer tx.deinit(); - const db = try tx.open(.{}); + const db = try tx.open(null, .{}); defer db.close(env); switch (try tx.reserve(db, "hello", "world!".len, .{ .dont_overwrite_key = true })) { .found_existing => try testing.expect(false), - .successful => |dst| std.mem.copy(u8, dst, "world!"), + .successful => |dst| @memcpy(dst, "world!"), } switch (try tx.reserve(db, "hello", "world!".len, .{ .dont_overwrite_key = true })) { @@ -1026,7 +1040,7 @@ test "Transaction: getOrPut() twice" { defer tmp.cleanup(); var buf: [fs.MAX_PATH_BYTES]u8 = undefined; - var path = try tmp.dir.realpath("./", &buf); + const path = try tmp.dir.realpath("./", &buf); const env = try Environment.init(path, .{}); defer env.deinit(); @@ -1034,7 +1048,7 @@ test "Transaction: getOrPut() twice" { const tx = try env.begin(.{}); errdefer tx.deinit(); - const db = try tx.open(.{}); + const db = try tx.open(null, .{}); defer db.close(env); try testing.expectEqual(@as(?[]const u8, null), try tx.getOrPut(db, "hello", "world")); @@ -1049,7 +1063,7 @@ test "Transaction: use multiple named databases in a single transaction" { defer tmp.cleanup(); var buf: [fs.MAX_PATH_BYTES]u8 = undefined; - var path = try tmp.dir.realpath("./", &buf); + const path = try tmp.dir.realpath("./", &buf); const env = try Environment.init(path, .{ .max_num_dbs = 2 }); defer env.deinit(); @@ -1058,10 +1072,10 @@ test "Transaction: use multiple named databases in a single transaction" { const tx = try env.begin(.{}); errdefer tx.deinit(); - const a = try tx.use("A", .{ .create_if_not_exists = true }); + const a = try tx.open("A", .{ .create_if_not_exists = true }); defer a.close(env); - const b = try tx.use("B", .{ .create_if_not_exists = true }); + const b = try tx.open("B", .{ .create_if_not_exists = true }); defer b.close(env); try tx.put(a, "hello", "this is in A!", .{}); @@ -1074,10 +1088,10 @@ test "Transaction: use multiple named databases in a single transaction" { const tx = try env.begin(.{}); errdefer tx.deinit(); - const a = try tx.use("A", .{}); + const a = try tx.open("A", .{}); defer a.close(env); - const b = try tx.use("B", .{}); + const b = try tx.open("B", .{}); defer b.close(env); try testing.expectEqualStrings("this is in A!", try tx.get(a, "hello")); @@ -1092,7 +1106,7 @@ test "Transaction: nest transaction inside transaction" { defer tmp.cleanup(); var buf: [fs.MAX_PATH_BYTES]u8 = undefined; - var path = try tmp.dir.realpath("./", &buf); + const path = try tmp.dir.realpath("./", &buf); const env = try Environment.init(path, .{}); defer env.deinit(); @@ -1100,7 +1114,7 @@ test "Transaction: nest transaction inside transaction" { const parent = try env.begin(.{}); errdefer parent.deinit(); - const db = try parent.open(.{}); + const db = try parent.open(null, .{}); defer db.close(env); { @@ -1140,7 +1154,7 @@ test "Transaction: custom key comparator" { defer tmp.cleanup(); var buf: [fs.MAX_PATH_BYTES]u8 = undefined; - var path = try tmp.dir.realpath("./", &buf); + const path = try tmp.dir.realpath("./", &buf); const env = try Environment.init(path, .{ .max_num_dbs = 2 }); defer env.deinit(); @@ -1148,7 +1162,7 @@ test "Transaction: custom key comparator" { const tx = try env.begin(.{}); errdefer tx.deinit(); - const db = try tx.open(.{}); + const db = try tx.open(null, .{}); defer db.close(env); const items = [_][]const u8{ "a", "b", "c" }; @@ -1178,7 +1192,7 @@ test "Cursor: move around a database and add / delete some entries" { defer tmp.cleanup(); var buf: [fs.MAX_PATH_BYTES]u8 = undefined; - var path = try tmp.dir.realpath("./", &buf); + const path = try tmp.dir.realpath("./", &buf); const env = try Environment.init(path, .{}); defer env.deinit(); @@ -1186,7 +1200,7 @@ test "Cursor: move around a database and add / delete some entries" { const tx = try env.begin(.{}); errdefer tx.deinit(); - const db = try tx.open(.{}); + const db = try tx.open(null, .{}); defer db.close(env); { @@ -1251,7 +1265,7 @@ test "Cursor: move around a database and add / delete some entries" { try cursor.updateInPlace(item, "???"); try testing.expectEqualStrings("???", (try cursor.current()).?.val); - mem.copy(u8, try cursor.reserveInPlace(item, item.len), item); + @memcpy(try cursor.reserveInPlace(item, item.len), item); try testing.expectEqualStrings(item, (try cursor.current()).?.val); } @@ -1276,7 +1290,7 @@ test "Cursor: interact with variable-sized items in a database with duplicate ke defer tmp.cleanup(); var buf: [fs.MAX_PATH_BYTES]u8 = undefined; - var path = try tmp.dir.realpath("./", &buf); + const path = try tmp.dir.realpath("./", &buf); const env = try Environment.init(path, .{ .max_num_dbs = 1 }); defer env.deinit(); @@ -1284,7 +1298,7 @@ test "Cursor: interact with variable-sized items in a database with duplicate ke const tx = try env.begin(.{}); errdefer tx.deinit(); - const db = try tx.open(.{ .allow_duplicate_keys = true }); + const db = try tx.open(null, .{ .allow_duplicate_keys = true }); defer db.close(env); const expected = comptime .{ @@ -1295,7 +1309,7 @@ test "Cursor: interact with variable-sized items in a database with duplicate ke inline for (expected) |entry| { inline for (entry[1]) |val| { - try tx.putItem(db, entry[0], val, .{ .dont_overwrite_item = true }); + try tx.put(db, entry[0], val, .{ .dont_overwrite_item = true }); } } @@ -1338,7 +1352,7 @@ test "Cursor: interact with batches of fixed-sized items in a database with dupl defer tmp.cleanup(); var buf: [fs.MAX_PATH_BYTES]u8 = undefined; - var path = try tmp.dir.realpath("./", &buf); + const path = try tmp.dir.realpath("./", &buf); const env = try Environment.init(path, .{ .max_num_dbs = 1 }); defer env.deinit(); @@ -1346,7 +1360,7 @@ test "Cursor: interact with batches of fixed-sized items in a database with dupl const tx = try env.begin(.{}); errdefer tx.deinit(); - const db = try tx.open(.{ + const db = try tx.open(null, .{ .allow_duplicate_keys = true, .duplicate_entries_are_fixed_size = true, }); @@ -1355,7 +1369,7 @@ test "Cursor: interact with batches of fixed-sized items in a database with dupl try tx.setItemOrder(db, U64.order); comptime var items: [512]u64 = undefined; - inline for (items) |*item, i| item.* = @as(u64, i); + inline for (&items, 0..) |*item, i| item.* = @as(u64, i); const expected = comptime .{ .{ "Set A", &items }, @@ -1367,7 +1381,7 @@ test "Cursor: interact with batches of fixed-sized items in a database with dupl defer cursor.deinit(); inline for (expected) |entry| { - try testing.expectEqual(entry[1].len, try cursor.putBatch(entry[0], entry[1], .{})); + try testing.expectEqual(entry[1].len, try cursor.putBatch(entry[0], @as(*[512][8]u8, @ptrCast(entry[1])), .{})); } } diff --git a/zig.mod b/zig.mod index 017f85c..5c5a02c 100644 --- a/zig.mod +++ b/zig.mod @@ -3,7 +3,7 @@ name: lmdb main: lmdb.zig dependencies: - type: git - path: https://git.openldap.org/openldap/openldap + path: lmdb version: tag-LMDB_0.9.29 c_include_dirs: - libraries/liblmdb