index.zig 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. const std = @import("std");
  2. const storage = @import("storage.zig");
  3. var tree_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
  4. const tree_allocator = tree_arena.allocator();
  5. var tree_mutex: std.Thread.Mutex = .{};
  6. const RadixNode = struct {
  7. edge: []const u8,
  8. children: std.StringHashMap(*RadixNode),
  9. is_terminal: bool,
  10. fn init(edge: []const u8) *RadixNode {
  11. const node = tree_allocator.create(RadixNode) catch unreachable;
  12. node.* = .{
  13. .edge = tree_allocator.dupe(u8, edge) catch unreachable,
  14. .children = std.StringHashMap(*RadixNode).init(tree_allocator),
  15. .is_terminal = false,
  16. };
  17. return node;
  18. }
  19. fn deinit(self: *RadixNode) void {
  20. var it = self.children.iterator();
  21. while (it.next()) |entry| {
  22. entry.value_ptr.*.deinit();
  23. }
  24. self.children.deinit();
  25. tree_allocator.free(self.edge);
  26. tree_allocator.destroy(self);
  27. }
  28. };
  29. var root: *RadixNode = undefined;
  30. var root_initialized = false;
  31. fn ensureRoot() void {
  32. if (!root_initialized) {
  33. root = RadixNode.init("");
  34. root_initialized = true;
  35. }
  36. }
  37. fn commonPrefixLen(a: []const u8, b: []const u8) usize {
  38. var i: usize = 0;
  39. while (i < a.len and i < b.len and a[i] == b[i]) {
  40. i += 1;
  41. }
  42. return i;
  43. }
  44. pub fn insert(key: []const u8) void {
  45. tree_mutex.lock();
  46. defer tree_mutex.unlock();
  47. ensureRoot();
  48. if (key.len == 0) return;
  49. var node = root;
  50. var remaining = key;
  51. while (remaining.len > 0) {
  52. var found = false;
  53. var it = node.children.iterator();
  54. while (it.next()) |entry| {
  55. const child = entry.value_ptr.*;
  56. const prefix_len = commonPrefixLen(child.edge, remaining);
  57. if (prefix_len > 0) {
  58. found = true;
  59. if (prefix_len == child.edge.len) {
  60. if (prefix_len == remaining.len) {
  61. child.is_terminal = true;
  62. return;
  63. }
  64. remaining = remaining[prefix_len..];
  65. node = child;
  66. break;
  67. } else {
  68. const old_edge = child.edge;
  69. const key_suffix = remaining[prefix_len..];
  70. // Dupe before freeing old_edge
  71. const child_suffix = tree_allocator.dupe(u8, old_edge[prefix_len..]) catch unreachable;
  72. const intermediate = RadixNode.init(old_edge[0..prefix_len]);
  73. // Remove old entry before freeing
  74. _ = node.children.remove(old_edge);
  75. tree_allocator.free(old_edge);
  76. child.edge = child_suffix;
  77. intermediate.children.put(child_suffix, child) catch unreachable;
  78. node.children.put(intermediate.edge, intermediate) catch unreachable;
  79. if (key_suffix.len == 0) {
  80. intermediate.is_terminal = true;
  81. return;
  82. } else {
  83. const new_child = RadixNode.init(key_suffix);
  84. new_child.is_terminal = true;
  85. intermediate.children.put(key_suffix, new_child) catch unreachable;
  86. return;
  87. }
  88. }
  89. }
  90. }
  91. if (!found) {
  92. const new_child = RadixNode.init(remaining);
  93. new_child.is_terminal = true;
  94. node.children.put(remaining, new_child) catch unreachable;
  95. return;
  96. }
  97. }
  98. node.is_terminal = true;
  99. }
  100. pub fn delete(key: []const u8) void {
  101. tree_mutex.lock();
  102. defer tree_mutex.unlock();
  103. ensureRoot();
  104. if (key.len == 0) return;
  105. const node = findNode(root, key);
  106. if (node) |n| {
  107. n.is_terminal = false;
  108. }
  109. }
  110. fn findNodeForPrefix(node: *RadixNode, prefix: []const u8, path_buf: *[MAX_KEY_LENGTH]u8, path_len: *usize) ?*RadixNode {
  111. if (prefix.len == 0) {
  112. path_len.* = 0;
  113. return node;
  114. }
  115. var current = node;
  116. var remaining = prefix;
  117. path_len.* = 0;
  118. while (remaining.len > 0) {
  119. var found = false;
  120. var it = current.children.iterator();
  121. while (it.next()) |entry| {
  122. const child = entry.value_ptr.*;
  123. const prefix_match_len = commonPrefixLen(child.edge, remaining);
  124. if (prefix_match_len > 0) {
  125. if (prefix_match_len == remaining.len) {
  126. return child;
  127. }
  128. if (prefix_match_len == child.edge.len) {
  129. if (path_len.* + prefix_match_len > MAX_KEY_LENGTH) return null;
  130. @memcpy(path_buf[path_len.* .. path_len.* + prefix_match_len], child.edge[0..prefix_match_len]);
  131. path_len.* += prefix_match_len;
  132. remaining = remaining[prefix_match_len..];
  133. current = child;
  134. found = true;
  135. break;
  136. }
  137. return null;
  138. }
  139. }
  140. if (!found) {
  141. return null;
  142. }
  143. }
  144. return current;
  145. }
  146. fn findNode(node: *RadixNode, key: []const u8) ?*RadixNode {
  147. var dummy_buf: [MAX_KEY_LENGTH]u8 = undefined;
  148. var dummy_len: usize = 0;
  149. return findNodeForPrefix(node, key, &dummy_buf, &dummy_len);
  150. }
  151. pub fn searchByPrefix(prefix: []const u8) ?*RadixNode {
  152. ensureRoot();
  153. if (prefix.len == 0) return root;
  154. return findNode(root, prefix);
  155. }
  156. fn countKeys(node: *RadixNode) usize {
  157. var count: usize = 0;
  158. if (node.is_terminal) {
  159. count += 1;
  160. }
  161. var it = node.children.iterator();
  162. while (it.next()) |entry| {
  163. count += countKeys(entry.value_ptr.*);
  164. }
  165. return count;
  166. }
  167. const MAX_KEYS_RETURN = 100_000_000;
  168. const MAX_KEY_LENGTH = 1024;
  169. fn collectKeysWithBuffer(node: *RadixNode, prefix_buffer: []u8, prefix_len: usize, keys: *std.ArrayListUnmanaged([]const u8), max_keys: usize, search_prefix: []const u8, include_node_edge: bool, allocator: std.mem.Allocator) void {
  170. if (keys.items.len >= max_keys) return;
  171. var current_len = prefix_len;
  172. if (include_node_edge and node.edge.len > 0) {
  173. if (current_len + node.edge.len > MAX_KEY_LENGTH) return;
  174. @memcpy(prefix_buffer[current_len .. current_len + node.edge.len], node.edge);
  175. current_len += node.edge.len;
  176. }
  177. if (node.is_terminal) {
  178. const key = prefix_buffer[0..current_len];
  179. if (key.len >= search_prefix.len and std.mem.eql(u8, key[0..search_prefix.len], search_prefix)) {
  180. const key_copy = allocator.dupe(u8, key) catch return;
  181. keys.append(allocator, key_copy) catch return;
  182. }
  183. }
  184. var it = node.children.iterator();
  185. while (it.next()) |entry| {
  186. if (keys.items.len >= max_keys) break;
  187. const child = entry.value_ptr.*;
  188. collectKeysWithBuffer(child, prefix_buffer, current_len, keys, max_keys, search_prefix, true, allocator);
  189. }
  190. }
  191. fn collectKeys(node: *RadixNode, prefix: []const u8, keys: *std.ArrayListUnmanaged([]const u8), max_keys: usize, search_prefix: []const u8, allocator: std.mem.Allocator) void {
  192. var prefix_buffer: [MAX_KEY_LENGTH]u8 = undefined;
  193. if (prefix.len > MAX_KEY_LENGTH) return;
  194. @memcpy(prefix_buffer[0..prefix.len], prefix);
  195. collectKeysWithBuffer(node, &prefix_buffer, prefix.len, keys, max_keys, search_prefix, true, allocator);
  196. }
  197. pub fn getKeysFromNode(node: *RadixNode, prefix: []const u8, allocator: std.mem.Allocator) [][]const u8 {
  198. var keys_list = std.ArrayListUnmanaged([]const u8){};
  199. collectKeys(node, prefix, &keys_list, MAX_KEYS_RETURN, prefix, allocator);
  200. return keys_list.toOwnedSlice(allocator) catch &[_][]const u8{};
  201. }
  202. pub fn getKeysByPrefix(prefix: []const u8, allocator: std.mem.Allocator) []const u8 {
  203. tree_mutex.lock();
  204. defer tree_mutex.unlock();
  205. ensureRoot();
  206. const node = searchByPrefix(prefix) orelse return "";
  207. const keys = getKeysFromNode(node, prefix, allocator);
  208. if (keys.len == 0) return "";
  209. return std.mem.join(allocator, "\n", keys) catch "";
  210. }
  211. pub fn getValuesByPrefix(prefix: []const u8, allocator: std.mem.Allocator) []const u8 {
  212. // Phase 1: Collect matching keys under tree_mutex
  213. var keys: [][]const u8 = &[_][]const u8{};
  214. {
  215. tree_mutex.lock();
  216. defer tree_mutex.unlock();
  217. ensureRoot();
  218. var path_buf: [MAX_KEY_LENGTH]u8 = undefined;
  219. var path_len: usize = 0;
  220. const node = findNodeForPrefix(root, prefix, &path_buf, &path_len) orelse {
  221. return "";
  222. };
  223. var keys_list = std.ArrayListUnmanaged([]const u8){};
  224. collectKeys(node, path_buf[0..path_len], &keys_list, MAX_KEYS_RETURN, prefix, allocator);
  225. keys = keys_list.toOwnedSlice(allocator) catch &[_][]const u8{};
  226. }
  227. if (keys.len == 0) return "";
  228. // Phase 2: Read values without tree_mutex to avoid deadlock with write/delete
  229. const values = allocator.alloc([]const u8, keys.len) catch return "";
  230. for (keys, 0..) |key, i| {
  231. const value = storage.read(key) orelse "";
  232. values[i] = value;
  233. }
  234. return std.mem.join(allocator, "\n", values) catch "";
  235. }
  236. pub fn getAllKeys(allocator: std.mem.Allocator) []const u8 {
  237. tree_mutex.lock();
  238. defer tree_mutex.unlock();
  239. ensureRoot();
  240. const keys = getKeysFromNode(root, &[_]u8{}, allocator);
  241. if (keys.len == 0) return "";
  242. return std.mem.join(allocator, "\n", keys) catch "";
  243. }
  244. // -- Tests --
  245. const test_allocator = std.heap.page_allocator;
  246. test "commonPrefixLen" {
  247. try std.testing.expectEqual(@as(usize, 3), commonPrefixLen("abc", "abcdef"));
  248. try std.testing.expectEqual(@as(usize, 3), commonPrefixLen("abcdef", "abc"));
  249. try std.testing.expectEqual(@as(usize, 0), commonPrefixLen("abc", "xyz"));
  250. try std.testing.expectEqual(@as(usize, 0), commonPrefixLen("", "abc"));
  251. try std.testing.expectEqual(@as(usize, 0), commonPrefixLen("abc", ""));
  252. try std.testing.expectEqual(@as(usize, 5), commonPrefixLen("hello", "hello"));
  253. }
  254. test "insert and searchByPrefix" {
  255. insert("idx_apple");
  256. insert("idx_app");
  257. insert("idx_banana");
  258. try std.testing.expect(searchByPrefix("idx_apple") != null);
  259. try std.testing.expect(searchByPrefix("idx_banana") != null);
  260. try std.testing.expect(searchByPrefix("idx_xyz") == null);
  261. }
  262. test "insert duplicate key does not crash" {
  263. insert("idx_dup");
  264. insert("idx_dup");
  265. // Node should exist and be terminal
  266. const node = searchByPrefix("idx_dup");
  267. try std.testing.expect(node != null);
  268. try std.testing.expect(node.?.is_terminal);
  269. }
  270. test "delete marks non-terminal" {
  271. insert("idx_delme");
  272. const node_before = searchByPrefix("idx_delme");
  273. try std.testing.expect(node_before != null);
  274. try std.testing.expect(node_before.?.is_terminal);
  275. delete("idx_delme");
  276. const node_after = searchByPrefix("idx_delme");
  277. try std.testing.expect(node_after != null);
  278. try std.testing.expect(!node_after.?.is_terminal);
  279. }
  280. test "getAllKeys returns inserted keys" {
  281. insert("idx_all_a");
  282. insert("idx_all_b");
  283. const result = getAllKeys(test_allocator);
  284. try std.testing.expect(result.len > 0);
  285. try std.testing.expect(std.mem.indexOf(u8, result, "idx_all_a") != null);
  286. try std.testing.expect(std.mem.indexOf(u8, result, "idx_all_b") != null);
  287. }
  288. test "insert empty key is no-op" {
  289. insert("");
  290. }
  291. test "radix tree prefix splitting" {
  292. insert("idx_test");
  293. insert("idx_testing");
  294. insert("idx_tested");
  295. insert("idx_tester");
  296. // All four keys should be findable
  297. try std.testing.expect(searchByPrefix("idx_test") != null);
  298. try std.testing.expect(searchByPrefix("idx_testing") != null);
  299. try std.testing.expect(searchByPrefix("idx_tested") != null);
  300. try std.testing.expect(searchByPrefix("idx_tester") != null);
  301. // Verify terminals
  302. const node_test = searchByPrefix("idx_test");
  303. try std.testing.expect(node_test.?.is_terminal);
  304. const node_testing = searchByPrefix("idx_testing");
  305. try std.testing.expect(node_testing.?.is_terminal);
  306. }
  307. test "searchByPrefix returns null for missing prefix" {
  308. try std.testing.expect(searchByPrefix("zzz_nonexistent") == null);
  309. }
  310. test "countKeys counts terminal nodes" {
  311. ensureRoot();
  312. insert("idx_cnt_a");
  313. insert("idx_cnt_b");
  314. insert("idx_cnt_c");
  315. const node = searchByPrefix("idx_cnt") orelse return error.TestUnexpectedResult;
  316. const count = countKeys(node);
  317. try std.testing.expect(count >= 3);
  318. }
  319. test "getValuesByPrefix with storage" {
  320. storage.init();
  321. _ = storage.restore("idx_pv_key1", "val1");
  322. _ = storage.restore("idx_pv_key2", "val2");
  323. const result = getValuesByPrefix("idx_pv_key", test_allocator);
  324. try std.testing.expect(result.len > 0);
  325. try std.testing.expect(std.mem.indexOf(u8, result, "val1") != null);
  326. try std.testing.expect(std.mem.indexOf(u8, result, "val2") != null);
  327. }