2
0

keydir.zig 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. const std = @import("std");
  2. pub const RecordRef = struct {
  3. hash: u64,
  4. lsn: u64,
  5. key_offset: u64,
  6. value_offset: u64,
  7. key_len: u32,
  8. value_len: u32,
  9. flags: u32 = 0,
  10. reserved: u32 = 0,
  11. };
  12. pub const Reader = struct {
  13. context: *const anyopaque,
  14. readFn: *const fn (*const anyopaque, []u8, u64) anyerror!usize,
  15. fn read(self: Reader, destination: []u8, offset: u64) !usize {
  16. return self.readFn(self.context, destination, offset);
  17. }
  18. };
  19. const Slot = struct {
  20. record: RecordRef = undefined,
  21. distance: u32 = 0,
  22. used: bool = false,
  23. };
  24. pub const KeyDir = struct {
  25. allocator: std.mem.Allocator,
  26. slots: []Slot,
  27. count: usize = 0,
  28. pub fn init(allocator: std.mem.Allocator) !KeyDir {
  29. const slots = try allocator.alloc(Slot, 16);
  30. @memset(slots, .{});
  31. return .{ .allocator = allocator, .slots = slots };
  32. }
  33. pub fn deinit(self: *KeyDir) void {
  34. self.allocator.free(self.slots);
  35. self.* = undefined;
  36. }
  37. pub fn hash(key: []const u8) u64 {
  38. var value: u64 = 14695981039346656037;
  39. for (key) |byte| {
  40. value ^= byte;
  41. value *%= 1099511628211;
  42. }
  43. return value;
  44. }
  45. fn keysEqual(file: std.fs.File, record: RecordRef, key: []const u8) !bool {
  46. if (record.key_len != key.len) return false;
  47. var buffer: [4096]u8 = undefined;
  48. var done: usize = 0;
  49. while (done < key.len) {
  50. const amount = @min(buffer.len, key.len - done);
  51. const offset = try std.math.add(u64, record.key_offset, done);
  52. const got = try file.preadAll(buffer[0..amount], offset);
  53. if (got != amount or !std.mem.eql(u8, buffer[0..amount], key[done .. done + amount])) return false;
  54. done += amount;
  55. }
  56. return true;
  57. }
  58. fn keysEqualWithReader(reader: Reader, record: RecordRef, key: []const u8) !bool {
  59. if (record.key_len != key.len) return false;
  60. var buffer: [4096]u8 = undefined;
  61. var done: usize = 0;
  62. while (done < key.len) {
  63. const amount = @min(buffer.len, key.len - done);
  64. const offset = try std.math.add(u64, record.key_offset, done);
  65. const got = try reader.read(buffer[0..amount], offset);
  66. if (got != amount or !std.mem.eql(u8, buffer[0..amount], key[done .. done + amount])) return false;
  67. done += amount;
  68. }
  69. return true;
  70. }
  71. fn recordsEqual(file: std.fs.File, a: RecordRef, b: RecordRef) !bool {
  72. if (a.key_len != b.key_len) return false;
  73. var left: [4096]u8 = undefined;
  74. var right: [4096]u8 = undefined;
  75. var done: usize = 0;
  76. while (done < a.key_len) {
  77. const amount = @min(left.len, a.key_len - done);
  78. const left_offset = try std.math.add(u64, a.key_offset, done);
  79. const right_offset = try std.math.add(u64, b.key_offset, done);
  80. if (try file.preadAll(left[0..amount], left_offset) != amount) return error.Truncated;
  81. if (try file.preadAll(right[0..amount], right_offset) != amount) return error.Truncated;
  82. if (!std.mem.eql(u8, left[0..amount], right[0..amount])) return false;
  83. done += amount;
  84. }
  85. return true;
  86. }
  87. fn grow(self: *KeyDir, file: std.fs.File) anyerror!void {
  88. const old = self.slots;
  89. const old_count = self.count;
  90. const slots = try self.allocator.alloc(Slot, try std.math.mul(usize, old.len, 2));
  91. @memset(slots, .{});
  92. self.slots = slots;
  93. self.count = 0;
  94. errdefer {
  95. self.allocator.free(slots);
  96. self.slots = old;
  97. self.count = old_count;
  98. }
  99. for (old) |slot| if (slot.used) try self.put(file, slot.record);
  100. self.allocator.free(old);
  101. }
  102. pub fn ensureAdditional(self: *KeyDir, file: std.fs.File, additional: usize) anyerror!void {
  103. const desired = try std.math.add(usize, self.count, additional);
  104. while (desired >= self.slots.len - self.slots.len / 5) try self.grow(file);
  105. }
  106. pub fn clear(self: *KeyDir) void {
  107. @memset(self.slots, .{});
  108. self.count = 0;
  109. }
  110. pub fn put(self: *KeyDir, file: std.fs.File, record: RecordRef) anyerror!void {
  111. return self.putInternal(file, record, null);
  112. }
  113. pub fn putWithKey(self: *KeyDir, file: std.fs.File, key: []const u8, record: RecordRef) anyerror!void {
  114. return self.putInternal(file, record, key);
  115. }
  116. fn putInternal(self: *KeyDir, file: std.fs.File, record: RecordRef, key: ?[]const u8) anyerror!void {
  117. if (self.count + 1 >= self.slots.len - self.slots.len / 5) try self.grow(file);
  118. var incoming = Slot{ .record = record, .used = true };
  119. var index: usize = @intCast(record.hash & (self.slots.len - 1));
  120. while (true) {
  121. const slot = &self.slots[index];
  122. if (!slot.used) {
  123. slot.* = incoming;
  124. self.count += 1;
  125. return;
  126. }
  127. if (slot.record.hash == record.hash and if (key) |key_bytes| try keysEqual(file, slot.record, key_bytes) else try recordsEqual(file, slot.record, record)) {
  128. slot.record = record;
  129. return;
  130. }
  131. if (slot.distance < incoming.distance) std.mem.swap(Slot, slot, &incoming);
  132. incoming.distance += 1;
  133. index = (index + 1) & (self.slots.len - 1);
  134. }
  135. }
  136. fn findIndex(self: *const KeyDir, file: std.fs.File, key: []const u8, key_hash: u64) !?usize {
  137. var index: usize = @intCast(key_hash & (self.slots.len - 1));
  138. var distance: u32 = 0;
  139. while (true) {
  140. const slot = self.slots[index];
  141. if (!slot.used or slot.distance < distance) return null;
  142. if (slot.record.hash == key_hash and try keysEqual(file, slot.record, key)) return index;
  143. distance += 1;
  144. index = (index + 1) & (self.slots.len - 1);
  145. }
  146. }
  147. pub fn get(self: *const KeyDir, file: std.fs.File, key: []const u8) !?RecordRef {
  148. const index = try self.findIndex(file, key, hash(key)) orelse return null;
  149. return self.slots[index].record;
  150. }
  151. pub fn getWithReader(self: *const KeyDir, reader: Reader, key: []const u8) !?RecordRef {
  152. const key_hash = hash(key);
  153. var index: usize = @intCast(key_hash & (self.slots.len - 1));
  154. var distance: u32 = 0;
  155. while (true) {
  156. const slot = self.slots[index];
  157. if (!slot.used or slot.distance < distance) return null;
  158. if (slot.record.hash == key_hash and try keysEqualWithReader(reader, slot.record, key)) return slot.record;
  159. distance += 1;
  160. index = (index + 1) & (self.slots.len - 1);
  161. }
  162. }
  163. pub fn remove(self: *KeyDir, file: std.fs.File, key: []const u8) !bool {
  164. var index = try self.findIndex(file, key, hash(key)) orelse return false;
  165. while (true) {
  166. const next = (index + 1) & (self.slots.len - 1);
  167. if (!self.slots[next].used or self.slots[next].distance == 0) {
  168. self.slots[index] = .{};
  169. break;
  170. }
  171. self.slots[index] = self.slots[next];
  172. self.slots[index].distance -= 1;
  173. index = next;
  174. }
  175. self.count -= 1;
  176. return true;
  177. }
  178. pub fn records(self: *const KeyDir, allocator: std.mem.Allocator) ![]RecordRef {
  179. const result = try allocator.alloc(RecordRef, self.count);
  180. var index: usize = 0;
  181. for (self.slots) |slot| if (slot.used) {
  182. result[index] = slot.record;
  183. index += 1;
  184. };
  185. return result;
  186. }
  187. pub fn bytes(self: *const KeyDir) usize {
  188. return self.slots.len * @sizeOf(Slot);
  189. }
  190. };
  191. test "hash is 64 bit and deterministic" {
  192. try std.testing.expectEqual(KeyDir.hash("pizza"), KeyDir.hash("pizza"));
  193. try std.testing.expect(KeyDir.hash("pizza") != KeyDir.hash("pizzb"));
  194. }
  195. test "colliding hashes compare immutable key bytes" {
  196. var tmp = std.testing.tmpDir(.{});
  197. defer tmp.cleanup();
  198. const file = try tmp.dir.createFile("keys", .{ .read = true });
  199. defer file.close();
  200. try file.writeAll("alphaomega");
  201. var directory = try KeyDir.init(std.testing.allocator);
  202. defer directory.deinit();
  203. try directory.put(file, .{ .hash = 42, .lsn = 1, .key_offset = 0, .value_offset = 0, .key_len = 5, .value_len = 0 });
  204. try directory.put(file, .{ .hash = 42, .lsn = 2, .key_offset = 5, .value_offset = 0, .key_len = 5, .value_len = 0 });
  205. try std.testing.expect((try directory.findIndex(file, "alpha", 42)) != null);
  206. try std.testing.expect((try directory.findIndex(file, "omega", 42)) != null);
  207. try std.testing.expectEqual(@as(usize, 2), directory.count);
  208. }