tigerbeetle-node 0.11.12 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +212 -196
  2. package/dist/bin/aarch64-linux-gnu/client.node +0 -0
  3. package/dist/bin/aarch64-linux-musl/client.node +0 -0
  4. package/dist/bin/aarch64-macos/client.node +0 -0
  5. package/dist/bin/x86_64-linux-gnu/client.node +0 -0
  6. package/dist/bin/x86_64-linux-musl/client.node +0 -0
  7. package/dist/bin/x86_64-macos/client.node +0 -0
  8. package/dist/index.js +33 -1
  9. package/dist/index.js.map +1 -1
  10. package/package-lock.json +66 -0
  11. package/package.json +8 -17
  12. package/src/index.ts +56 -1
  13. package/src/node.zig +10 -9
  14. package/dist/.client.node.sha256 +0 -1
  15. package/scripts/build_lib.sh +0 -61
  16. package/scripts/download_node_headers.sh +0 -32
  17. package/src/tigerbeetle/scripts/benchmark.bat +0 -48
  18. package/src/tigerbeetle/scripts/benchmark.sh +0 -66
  19. package/src/tigerbeetle/scripts/confirm_image.sh +0 -44
  20. package/src/tigerbeetle/scripts/fuzz_loop.sh +0 -15
  21. package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +0 -7
  22. package/src/tigerbeetle/scripts/install.bat +0 -7
  23. package/src/tigerbeetle/scripts/install.sh +0 -21
  24. package/src/tigerbeetle/scripts/install_zig.bat +0 -113
  25. package/src/tigerbeetle/scripts/install_zig.sh +0 -90
  26. package/src/tigerbeetle/scripts/lint.zig +0 -199
  27. package/src/tigerbeetle/scripts/pre-commit.sh +0 -9
  28. package/src/tigerbeetle/scripts/scripts/benchmark.bat +0 -48
  29. package/src/tigerbeetle/scripts/scripts/benchmark.sh +0 -66
  30. package/src/tigerbeetle/scripts/scripts/confirm_image.sh +0 -44
  31. package/src/tigerbeetle/scripts/scripts/fuzz_loop.sh +0 -15
  32. package/src/tigerbeetle/scripts/scripts/fuzz_unique_errors.sh +0 -7
  33. package/src/tigerbeetle/scripts/scripts/install.bat +0 -7
  34. package/src/tigerbeetle/scripts/scripts/install.sh +0 -21
  35. package/src/tigerbeetle/scripts/scripts/install_zig.bat +0 -113
  36. package/src/tigerbeetle/scripts/scripts/install_zig.sh +0 -90
  37. package/src/tigerbeetle/scripts/scripts/lint.zig +0 -199
  38. package/src/tigerbeetle/scripts/scripts/pre-commit.sh +0 -9
  39. package/src/tigerbeetle/scripts/scripts/shellcheck.sh +0 -5
  40. package/src/tigerbeetle/scripts/scripts/tests_on_alpine.sh +0 -10
  41. package/src/tigerbeetle/scripts/scripts/tests_on_ubuntu.sh +0 -14
  42. package/src/tigerbeetle/scripts/scripts/upgrade_ubuntu_kernel.sh +0 -48
  43. package/src/tigerbeetle/scripts/scripts/validate_docs.sh +0 -23
  44. package/src/tigerbeetle/scripts/scripts/vr_state_enumerate +0 -46
  45. package/src/tigerbeetle/scripts/shellcheck.sh +0 -5
  46. package/src/tigerbeetle/scripts/tests_on_alpine.sh +0 -10
  47. package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +0 -14
  48. package/src/tigerbeetle/scripts/upgrade_ubuntu_kernel.sh +0 -48
  49. package/src/tigerbeetle/scripts/validate_docs.sh +0 -23
  50. package/src/tigerbeetle/scripts/vr_state_enumerate +0 -46
  51. package/src/tigerbeetle/src/benchmark.zig +0 -314
  52. package/src/tigerbeetle/src/config.zig +0 -234
  53. package/src/tigerbeetle/src/constants.zig +0 -436
  54. package/src/tigerbeetle/src/ewah.zig +0 -286
  55. package/src/tigerbeetle/src/ewah_benchmark.zig +0 -120
  56. package/src/tigerbeetle/src/ewah_fuzz.zig +0 -130
  57. package/src/tigerbeetle/src/fifo.zig +0 -120
  58. package/src/tigerbeetle/src/io/benchmark.zig +0 -213
  59. package/src/tigerbeetle/src/io/darwin.zig +0 -814
  60. package/src/tigerbeetle/src/io/linux.zig +0 -1062
  61. package/src/tigerbeetle/src/io/test.zig +0 -643
  62. package/src/tigerbeetle/src/io/windows.zig +0 -1183
  63. package/src/tigerbeetle/src/io.zig +0 -34
  64. package/src/tigerbeetle/src/iops.zig +0 -107
  65. package/src/tigerbeetle/src/lsm/README.md +0 -308
  66. package/src/tigerbeetle/src/lsm/binary_search.zig +0 -341
  67. package/src/tigerbeetle/src/lsm/bloom_filter.zig +0 -125
  68. package/src/tigerbeetle/src/lsm/compaction.zig +0 -603
  69. package/src/tigerbeetle/src/lsm/composite_key.zig +0 -77
  70. package/src/tigerbeetle/src/lsm/direction.zig +0 -11
  71. package/src/tigerbeetle/src/lsm/eytzinger.zig +0 -587
  72. package/src/tigerbeetle/src/lsm/eytzinger_benchmark.zig +0 -330
  73. package/src/tigerbeetle/src/lsm/forest.zig +0 -204
  74. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +0 -401
  75. package/src/tigerbeetle/src/lsm/grid.zig +0 -573
  76. package/src/tigerbeetle/src/lsm/groove.zig +0 -972
  77. package/src/tigerbeetle/src/lsm/k_way_merge.zig +0 -474
  78. package/src/tigerbeetle/src/lsm/level_iterator.zig +0 -332
  79. package/src/tigerbeetle/src/lsm/manifest.zig +0 -617
  80. package/src/tigerbeetle/src/lsm/manifest_level.zig +0 -877
  81. package/src/tigerbeetle/src/lsm/manifest_log.zig +0 -789
  82. package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +0 -691
  83. package/src/tigerbeetle/src/lsm/merge_iterator.zig +0 -106
  84. package/src/tigerbeetle/src/lsm/node_pool.zig +0 -235
  85. package/src/tigerbeetle/src/lsm/posted_groove.zig +0 -378
  86. package/src/tigerbeetle/src/lsm/segmented_array.zig +0 -1328
  87. package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +0 -148
  88. package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +0 -9
  89. package/src/tigerbeetle/src/lsm/set_associative_cache.zig +0 -850
  90. package/src/tigerbeetle/src/lsm/table.zig +0 -1031
  91. package/src/tigerbeetle/src/lsm/table_immutable.zig +0 -203
  92. package/src/tigerbeetle/src/lsm/table_iterator.zig +0 -340
  93. package/src/tigerbeetle/src/lsm/table_mutable.zig +0 -220
  94. package/src/tigerbeetle/src/lsm/test.zig +0 -438
  95. package/src/tigerbeetle/src/lsm/tree.zig +0 -1193
  96. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +0 -474
  97. package/src/tigerbeetle/src/message_bus.zig +0 -1012
  98. package/src/tigerbeetle/src/message_pool.zig +0 -156
  99. package/src/tigerbeetle/src/ring_buffer.zig +0 -399
  100. package/src/tigerbeetle/src/simulator.zig +0 -569
  101. package/src/tigerbeetle/src/state_machine/auditor.zig +0 -577
  102. package/src/tigerbeetle/src/state_machine/workload.zig +0 -883
  103. package/src/tigerbeetle/src/state_machine.zig +0 -1881
  104. package/src/tigerbeetle/src/static_allocator.zig +0 -65
  105. package/src/tigerbeetle/src/stdx.zig +0 -162
  106. package/src/tigerbeetle/src/storage.zig +0 -393
  107. package/src/tigerbeetle/src/testing/cluster/message_bus.zig +0 -82
  108. package/src/tigerbeetle/src/testing/cluster/network.zig +0 -237
  109. package/src/tigerbeetle/src/testing/cluster/state_checker.zig +0 -169
  110. package/src/tigerbeetle/src/testing/cluster/storage_checker.zig +0 -202
  111. package/src/tigerbeetle/src/testing/cluster.zig +0 -443
  112. package/src/tigerbeetle/src/testing/fuzz.zig +0 -140
  113. package/src/tigerbeetle/src/testing/hash_log.zig +0 -66
  114. package/src/tigerbeetle/src/testing/id.zig +0 -99
  115. package/src/tigerbeetle/src/testing/packet_simulator.zig +0 -364
  116. package/src/tigerbeetle/src/testing/priority_queue.zig +0 -645
  117. package/src/tigerbeetle/src/testing/reply_sequence.zig +0 -139
  118. package/src/tigerbeetle/src/testing/state_machine.zig +0 -249
  119. package/src/tigerbeetle/src/testing/storage.zig +0 -757
  120. package/src/tigerbeetle/src/testing/table.zig +0 -247
  121. package/src/tigerbeetle/src/testing/time.zig +0 -84
  122. package/src/tigerbeetle/src/tigerbeetle.zig +0 -227
  123. package/src/tigerbeetle/src/time.zig +0 -112
  124. package/src/tigerbeetle/src/tracer.zig +0 -529
  125. package/src/tigerbeetle/src/unit_tests.zig +0 -42
  126. package/src/tigerbeetle/src/vopr.zig +0 -495
  127. package/src/tigerbeetle/src/vsr/README.md +0 -209
  128. package/src/tigerbeetle/src/vsr/client.zig +0 -544
  129. package/src/tigerbeetle/src/vsr/clock.zig +0 -853
  130. package/src/tigerbeetle/src/vsr/journal.zig +0 -2413
  131. package/src/tigerbeetle/src/vsr/journal_format_fuzz.zig +0 -111
  132. package/src/tigerbeetle/src/vsr/marzullo.zig +0 -309
  133. package/src/tigerbeetle/src/vsr/replica.zig +0 -6381
  134. package/src/tigerbeetle/src/vsr/replica_format.zig +0 -219
  135. package/src/tigerbeetle/src/vsr/superblock.zig +0 -1631
  136. package/src/tigerbeetle/src/vsr/superblock_client_table.zig +0 -256
  137. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +0 -929
  138. package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +0 -334
  139. package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +0 -390
  140. package/src/tigerbeetle/src/vsr/superblock_manifest.zig +0 -615
  141. package/src/tigerbeetle/src/vsr/superblock_quorums.zig +0 -394
  142. package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +0 -314
  143. package/src/tigerbeetle/src/vsr.zig +0 -1352
@@ -1,587 +0,0 @@
1
- const std = @import("std");
2
- const assert = std.debug.assert;
3
- const math = std.math;
4
- const mem = std.mem;
5
-
6
- /// keys_count must be one less than a power of two. This allows us to align the layout
7
- /// such that great great grandchildren of a node are not unnecessarily split across cache lines.
8
- pub fn eytzinger(comptime keys_count: u32, comptime values_max: u32) type {
9
- // This is not strictly necessary, but having less than 8 keys in the
10
- // Eytzinger layout would make having the layout at all somewhat pointless.
11
- assert(keys_count >= 3);
12
- assert(math.isPowerOfTwo(keys_count + 1));
13
- assert(values_max >= keys_count);
14
-
15
- return struct {
16
- const tree: [keys_count]u32 = blk: {
17
- @setEvalBranchQuota((keys_count + 1) * 4 * math.log2(keys_count));
18
- // n = 7:
19
- // sorted values: 0 1 2 3 4 5 6
20
- //
21
- // binary search tree:
22
- // 3
23
- // 1 5
24
- // 0 2 4 6
25
- //
26
- // Eytzinger layout:
27
- // 3 1 5 0 2 4 6
28
- //
29
- // Our Eytzinger layout construction exactly matches the indexes
30
- // used during a binary search, unlike common recursive implementations.
31
- // This gives us more consistent performance and it's easy to verify.
32
- //
33
- // n = 21, X = padding:
34
- // sorted values: 0 1 2 3 ... 20
35
- //
36
- // binary search tree:
37
- // 10
38
- // 4 15
39
- // 1 7 12 18
40
- // 0 2 5 8 11 13 16 19
41
- // X X X 3 X 6 X 9 X X X 14 X 17 X 20
42
- //
43
- // Eytzinger layout:
44
- // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
45
- // 10 4 15 1 7 12 1 0 2 5 8 11 13 16 19
46
- //
47
- // Note, we only support building a full Eytzinger tree of 2^k-1 keys.
48
- // The last layer in the binary search tree for n=21 is not included
49
- // in the Eytzinger layout. If we were to include it, our perfectly
50
- // balanced construction would require sentinel values for the padding
51
- // due to negative lookups.
52
-
53
- var nodes: [keys_count]u32 = undefined;
54
- var node: u32 = 0;
55
- while (node < nodes.len) : (node += 1) {
56
- // Left and right inclusive bounds for the children of this node,
57
- // as if we were doing a binary search.
58
- const l = if (left_ancestor(node)) |l| nodes[l] + 1 else 0;
59
- const r = if (right_ancestor(node)) |r| nodes[r] - 1 else values_max - 1;
60
-
61
- // The binary search index into source for this node in the Eytzinger layout.
62
- // This is (r + l) / 2 ... but without overflow bugs.
63
- nodes[node] = l + (r - l) / 2;
64
- }
65
-
66
- break :blk nodes;
67
- };
68
-
69
- fn left_ancestor(node: u32) ?u32 {
70
- var n = node;
71
- while (!is_right_child(n)) {
72
- n = parent(n) orelse return null;
73
- }
74
- return parent(n).?;
75
- }
76
-
77
- fn right_ancestor(node: u32) ?u32 {
78
- var n = node;
79
- while (!is_left_child(n)) {
80
- n = parent(n) orelse return null;
81
- }
82
- return parent(n).?;
83
- }
84
-
85
- fn parent(node: u32) ?u32 {
86
- if (node == 0) return null;
87
- return (node - 1) / 2;
88
- }
89
-
90
- fn is_right_child(node: u32) bool {
91
- return node != 0 and node % 2 == 0;
92
- }
93
-
94
- fn is_left_child(node: u32) bool {
95
- return node != 0 and node % 2 != 0;
96
- }
97
-
98
- /// Writes the Eytzinger layout to the passed layout buffer.
99
- /// The values slice must be sorted by key in ascending order.
100
- pub fn layout_from_keys_or_values(
101
- comptime Key: type,
102
- comptime Value: type,
103
- comptime key_from_value: fn (*const Value) callconv(.Inline) Key,
104
- /// This sentinel must compare greater than all actual keys.
105
- comptime sentinel_key: Key,
106
- values: []const Value,
107
- layout: *[keys_count + 1]Key,
108
- ) void {
109
- comptime assert(tree.len + 1 == layout.len);
110
- assert(values.len > 0);
111
- assert(values.len <= values_max);
112
-
113
- // We leave the first slot in layout empty for purposes of alignment.
114
- // If we did not do this, the level in the tree with 16 great great
115
- // grand childern would be split across cache lines with one child
116
- // in the first cache line and the other 15 in the second.
117
- mem.set(u8, mem.asBytes(&layout[0]), 0);
118
- // 0 8 4 12 2 6 10 14 1 3 5 7 9 11 13 15
119
- // ^
120
- // padding element
121
-
122
- for (tree) |values_index, i| {
123
- if (values_index < values.len) {
124
- layout[i + 1] = key_from_value(&values[values_index]);
125
- } else {
126
- layout[i + 1] = sentinel_key;
127
- }
128
- }
129
- }
130
-
131
- /// Returns a smaller slice into values where the target key may be found.
132
- /// If the target key is present in values, the returned slice is guaranteed to contain it.
133
- /// May return a slice of length one if an exact result is found.
134
- /// May return a slice of length zero if the key is definitely not in values.
135
- /// Otherwise, the caller will likely want to perform a binary search on the result.
136
- /// TODO examine the generated machine code for this function
137
- pub fn search_values(
138
- comptime Key: type,
139
- comptime Value: type,
140
- comptime compare_keys: fn (Key, Key) callconv(.Inline) math.Order,
141
- layout: *const [keys_count + 1]Key,
142
- values: []const Value,
143
- key: Key,
144
- ) []const Value {
145
- assert(values.len > 0);
146
- assert(values.len <= values_max);
147
-
148
- const keys = layout[1..];
149
-
150
- // "Array Layouts for Comparison-Based Search"
151
- //
152
- // Example using the n=21 tree above:
153
- //
154
- // If we search for 12.5 then we would have gone left at 13:
155
- // i = 25 = 0b0...0011001
156
- // i + 1 = 0b0...0011010
157
- //
158
- // Upper bound:
159
- // ~(i + 1) = 0b1...1100101
160
- // ffs(~(i + 1)) = index of least significant set bit + 1 = 0 + 1
161
- // j = (i + 1) >> ffs(~(i + 1)) = 0b11010 >> 1 = 0b1101 = 13
162
- // upper = if (j == 0) keys.len else j - 1 = 12
163
- //
164
- // Lower bound:
165
- // ffs(i + 1) = index of least significant set bit + 1 = 1 + 1 = 2
166
- // k = (i + 1) >> ffs(i + 1) = 0b11010 >> 2 = 0b110 = 6
167
- // lower = if (k == 0) -1 else k - 1 = 5;
168
- //
169
- // Search for 3 in the tree 10 4 15:
170
- // i = 2 * 1 + 1 = 3
171
- // i + 1 = 0b100
172
- //
173
- // Upper bound:
174
- // ~(i + 1) = 0b1...11011
175
- // ffs(~(i + 1)) = 0 + 1 = 1
176
- // j = (i + 1) >> ffs(~(i + 1)) = 0b100 >> 1 = 0b10 = 2
177
- // upper = if (j == 0) keys.len else j - 1 = 1
178
- //
179
- // Lower bound:
180
- // ffs(i + 1) = 2 + 1 = 3
181
- // k = (i + 1) >> ffs(i + 1) = 0b100 >> 3 = 0
182
- // lower = if (k == 0) -1 else k - 1 = -1;
183
-
184
- var i: u32 = 0;
185
- while (i < keys.len) {
186
- // TODO use @prefetch when available: https://github.com/ziglang/zig/issues/3600
187
- i = if (compare_keys(key, keys[i]) == .gt) 2 * i + 2 else 2 * i + 1;
188
- }
189
-
190
- // The upper_bound is the smallest key that is greater than or equal to the
191
- // target. This is due to the greater than comparison in the loop above.
192
- const upper = @as(u64, i + 1) >> ffs(~(i + 1));
193
- const upper_bound: ?u32 = if (upper == 0) null else @intCast(u32, upper - 1);
194
-
195
- // Because of the comparison used in the loop above, the lower bound is a < bound
196
- // not a <= bound. Therefore in the case of an exact match we must use the upper bound.
197
- const lower_bound: ?u32 = blk: {
198
- if (upper_bound) |u| {
199
- if (compare_keys(key, keys[u]) == .eq) break :blk u;
200
- }
201
- const lower = @as(u64, i + 1) >> ffs((i + 1));
202
- break :blk if (lower == 0) null else @intCast(u32, lower - 1);
203
- };
204
-
205
- // We want to exclude the bounding keys to avoid re-checking them, except in the case
206
- // of an exact match. This condition checks for an inexact match.
207
- const exclusion = @boolToInt(lower_bound == null or upper_bound == null or
208
- lower_bound.? != upper_bound.?);
209
-
210
- // The exclusion alone may result in an upper bound one less than the lower bound.
211
- // However, we add one to the upper bound to make it exclusive for slicing.
212
- // This case indicates that key is not present in values.
213
- const values_lower = if (lower_bound) |l| tree[l] + exclusion else 0;
214
- // This must be an exclusive upper bound but upper_bound is inclusive. Thus, add 1.
215
- const values_upper = if (upper_bound) |u| tree[u] + 1 - exclusion else values.len;
216
-
217
- return values[values_lower..math.min(values.len, values_upper)];
218
- }
219
-
220
- /// Returns an upper bound index into the corresponding values. The returned index is
221
- /// less than values_count, or null to indicate that all keys are less than the target key.
222
- /// TODO examine the generated machine code for this function
223
- pub fn search_keys(
224
- comptime Key: type,
225
- comptime compare_keys: fn (Key, Key) callconv(.Inline) math.Order,
226
- layout: *const [keys_count + 1]Key,
227
- values_count: u32,
228
- key: Key,
229
- ) ?u32 {
230
- // See search_values() for the explanation and full implementation of the algorithm.
231
- // This code is duplicated here to avoid unnecessary computation when only searching
232
- // for an upper bound and to keep search_values() as readable as possible. Using helper
233
- // functions would fragment the logic.
234
- const keys = layout[1..];
235
- var i: u32 = 0;
236
- while (i < keys.len) {
237
- // TODO use @prefetch when available: https://github.com/ziglang/zig/issues/3600
238
- i = if (compare_keys(key, keys[i]) == .gt) 2 * i + 2 else 2 * i + 1;
239
- }
240
- const upper = @as(u64, i + 1) >> ffs(~(i + 1));
241
-
242
- const out_of_bounds = upper == 0 or tree[upper - 1] >= values_count;
243
- return if (out_of_bounds) null else tree[upper - 1];
244
- }
245
-
246
- /// Returns one plus the index of the least significant 1-bit of x.
247
- /// Asserts that x is not 0.
248
- inline fn ffs(x: u32) u6 {
249
- // clang __builtin_ffs() output:
250
- // bsf ecx, edi
251
- // mov eax, -1
252
- // cmovne eax, ecx
253
- // add eax, 1
254
- // ret
255
- //
256
- // zig direct translation:
257
- // export fn ffs(num: i32) i32 {
258
- // return if (num == 0) 0 else @ctz(i32, num) + 1;
259
- // }
260
- //
261
- // zig ffs output:
262
- // tzcnt ecx, edi
263
- // mov eax, -1
264
- // cmovae eax, ecx
265
- // inc eax
266
- // ret
267
- //
268
- // tzcnt is essentially bsf but is defined to return the size
269
- // of the operand if the operand is 0, whereas the output of
270
- // bsf is undefined if the operand is 0.
271
-
272
- // Since x will never be 0 in practice, we can drop the cmove
273
- // above by not checking for 0.
274
- // This function is always called with argument ~(i + 1) or (i + 1).
275
- // Since i is unsigned, (i + 1) is always greater than 0. Therefore
276
- // we only need to check for the case where ~(i + 1) == 0 which happens
277
- // exactly when i + 1 is the maximum value for a u32.
278
- comptime {
279
- // Max i after the while loop in search_values() terminates.
280
- const max_i = 2 * (tree.len - 1) + 2;
281
- assert(max_i + 1 < math.maxInt(u32));
282
- assert(~(max_i + 1) != 0);
283
- }
284
- assert(x != 0);
285
-
286
- // Since we assert that x is not 0, we know that @ctz() returns
287
- // a value in the range 0 to 31 inclusive. This means that the return
288
- // value of @ctz() fits in a u5, but since we add 1 the return type of
289
- // our function must be a u6.
290
- comptime assert(31 + 1 <= math.maxInt(u6));
291
- return @ctz(u32, x) + 1;
292
- }
293
- };
294
- }
295
-
296
- const test_eytzinger = struct {
297
- const log = false;
298
-
299
- const Value = extern struct {
300
- key: u32,
301
-
302
- inline fn to_key(value: *const Value) u32 {
303
- return value.key;
304
- }
305
- };
306
-
307
- inline fn compare_keys(a: u32, b: u32) math.Order {
308
- return math.order(a, b);
309
- }
310
-
311
- const sentinel_key = math.maxInt(u32);
312
-
313
- pub const Bounds = struct {
314
- lower: ?u32,
315
- upper: ?u32,
316
- };
317
-
318
- fn layout_from_keys_or_values(
319
- comptime keys_count: usize,
320
- comptime values_max: usize,
321
- expect: []const u32,
322
- ) !void {
323
- const e = eytzinger(keys_count, values_max);
324
-
325
- var values: [values_max]Value = undefined;
326
- for (values) |*v, i| v.* = .{ .key = @intCast(u32, i) };
327
-
328
- var layout: [keys_count + 1]u32 = undefined;
329
-
330
- e.layout_from_keys_or_values(u32, Value, Value.to_key, sentinel_key, &values, &layout);
331
-
332
- try std.testing.expectEqualSlices(u32, expect, &layout);
333
- }
334
-
335
- fn search(comptime keys_count: usize, comptime values_max: usize, sentinels_max: usize) !void {
336
- assert(sentinels_max < values_max);
337
- const e = eytzinger(keys_count, values_max);
338
-
339
- var values_full: [values_max]Value = undefined;
340
- // This 3 * i + 7 ensures that keys don't line up perfectly with indexes
341
- // which could potentially catch bugs.
342
- for (values_full) |*v, i| v.* = .{ .key = @intCast(u32, 3 * i + 7) };
343
-
344
- var layout: [keys_count + 1]u32 = undefined;
345
-
346
- var sentinels: usize = 0;
347
- while (sentinels < sentinels_max) : (sentinels += 1) {
348
- const values = values_full[0 .. values_full.len - sentinels];
349
- e.layout_from_keys_or_values(u32, Value, Value.to_key, sentinel_key, values, &layout);
350
-
351
- const keys = layout[1..];
352
-
353
- if (log) {
354
- std.debug.print("keys count: {}, values max: {}, sentinels: {}\n", .{
355
- keys_count,
356
- values_max,
357
- sentinels,
358
- });
359
- std.debug.print("values: {any}\n", .{values});
360
- std.debug.print("eytzinger layout: {any}\n", .{e.tree});
361
- std.debug.print("keys: {any}\n", .{@as([]u32, keys)});
362
- }
363
-
364
- // This is a regression test for our test code. We added this after we originally failed
365
- // to test exactly the case this is checking for.
366
- var at_least_one_target_key_not_in_values = false;
367
-
368
- var target_key: u32 = 0;
369
- while (target_key < values[values.len - 1].to_key() + 13) : (target_key += 1) {
370
- if (log) std.debug.print("target key: {}\n", .{target_key});
371
- var expect_keys: Bounds = .{
372
- .lower = null,
373
- .upper = null,
374
- };
375
- for (keys) |key, index| {
376
- const i = @intCast(u32, index);
377
- if (key == sentinel_key) continue;
378
- switch (compare_keys(key, target_key)) {
379
- .eq => {
380
- expect_keys.lower = i;
381
- expect_keys.upper = i;
382
- break;
383
- },
384
- .gt => if (expect_keys.upper == null or
385
- compare_keys(key, keys[expect_keys.upper.?]) == .lt)
386
- {
387
- expect_keys.upper = i;
388
- },
389
- .lt => if (expect_keys.lower == null or
390
- compare_keys(key, keys[expect_keys.lower.?]) == .gt)
391
- {
392
- expect_keys.lower = i;
393
- },
394
- }
395
- }
396
-
397
- const expect_upper_bound = if (expect_keys.upper) |u| e.tree[u] else null;
398
- var actual_upper_bound = e.search_keys(
399
- u32,
400
- compare_keys,
401
- &layout,
402
- @intCast(u32, values.len),
403
- target_key,
404
- );
405
- try std.testing.expectEqual(expect_upper_bound, actual_upper_bound);
406
-
407
- var expect_values: Bounds = .{
408
- .lower = null,
409
- .upper = null,
410
- };
411
- var target_key_found = false;
412
- for (values) |value, i| {
413
- if (compare_keys(value.to_key(), target_key) == .eq) target_key_found = true;
414
-
415
- if (expect_keys.lower) |l| {
416
- if (compare_keys(value.to_key(), keys[l]) == .eq) {
417
- expect_values.lower = @intCast(u32, i);
418
- }
419
- }
420
- if (expect_keys.upper) |u| {
421
- if (compare_keys(value.to_key(), keys[u]) == .eq) {
422
- expect_values.upper = @intCast(u32, i);
423
- }
424
- }
425
- }
426
- if (!target_key_found) at_least_one_target_key_not_in_values = true;
427
- assert((expect_keys.lower == null) == (expect_values.lower == null));
428
- assert((expect_keys.upper == null) == (expect_values.upper == null));
429
-
430
- if (expect_values.lower != null) {
431
- if (compare_keys(values[expect_values.lower.?].to_key(), target_key) == .lt) {
432
- expect_values.lower.? += 1;
433
- } else {
434
- assert(compare_keys(values[expect_values.lower.?].to_key(), target_key) == .eq);
435
- }
436
- }
437
- if (expect_values.upper != null) {
438
- var exclusion: u32 = undefined;
439
- if (compare_keys(values[expect_values.upper.?].to_key(), target_key) == .gt) {
440
- exclusion = 1;
441
- } else {
442
- exclusion = 0;
443
- assert(compare_keys(values[expect_values.upper.?].to_key(), target_key) == .eq);
444
- }
445
- // Convert the inclusive upper bound to an exclusive upper bound for slicing.
446
- expect_values.upper.? += 1;
447
- // Now, apply the conditional exclusion. The order is important here to avoid
448
- // underflow if expect_values.upper is 0.
449
- expect_values.upper.? -= exclusion;
450
- }
451
-
452
- const expect_slice_lower = expect_values.lower orelse 0;
453
- const expect_slice_upper = expect_values.upper orelse values.len;
454
- const expect_slice = values[expect_slice_lower..expect_slice_upper];
455
- if (target_key_found and expect_slice.len == 1) {
456
- assert(compare_keys(expect_slice[0].to_key(), target_key) == .eq);
457
- }
458
-
459
- const actual_slice = e.search_values(
460
- u32,
461
- Value,
462
- compare_keys,
463
- &layout,
464
- values,
465
- target_key,
466
- );
467
-
468
- try std.testing.expectEqual(
469
- @as([*]const Value, expect_slice.ptr),
470
- actual_slice.ptr,
471
- );
472
- try std.testing.expectEqual(expect_slice.len, actual_slice.len);
473
- }
474
-
475
- assert(at_least_one_target_key_not_in_values);
476
- }
477
- }
478
- };
479
-
480
- test "eytzinger: equal key and value count" {
481
- // zig fmt: off
482
- try test_eytzinger.layout_from_keys_or_values(3, 3, &[_]u32{ 0,
483
- 1,
484
- 0, 2,
485
- });
486
- try test_eytzinger.layout_from_keys_or_values(7, 7, &[_]u32{ 0,
487
- 3,
488
- 1, 5,
489
- 0, 2, 4, 6,
490
- });
491
- try test_eytzinger.layout_from_keys_or_values(15, 15, &[_]u32{ 0,
492
- 7,
493
- 3, 11,
494
- 1, 5, 9, 13,
495
- 0, 2, 4, 6, 8, 10, 12, 14,
496
- });
497
- // zig fmt: on
498
- }
499
-
500
- test "eytzinger: power of two value count > key count" {
501
- // zig fmt: off
502
- try test_eytzinger.layout_from_keys_or_values(3, 8, &[_]u32{ 0,
503
- 3,
504
- 1, 5,
505
- });
506
- try test_eytzinger.layout_from_keys_or_values(3, 16, &[_]u32{ 0,
507
- 7,
508
- 3, 11,
509
- });
510
- try test_eytzinger.layout_from_keys_or_values(7, 16, &[_]u32{ 0,
511
- 7,
512
- 3, 11,
513
- 1, 5, 9, 13,
514
- });
515
- try test_eytzinger.layout_from_keys_or_values(15, 32, &[_]u32{ 0,
516
- 15,
517
- 7, 23,
518
- 3, 11, 19, 27,
519
- 1, 5, 9, 13, 17, 21, 25, 29,
520
- });
521
- // zig fmt: on
522
- }
523
-
524
- test "eytzinger: non power of two value count" {
525
- // zig fmt: off
526
- try test_eytzinger.layout_from_keys_or_values(7, 13, &[_]u32{ 0,
527
- 6,
528
- 2, 9,
529
- 0, 4, 7, 11,
530
- });
531
- try test_eytzinger.layout_from_keys_or_values(7, 19, &[_]u32{ 0,
532
- 9,
533
- 4, 14,
534
- 1, 6, 11, 16,
535
- });
536
- try test_eytzinger.layout_from_keys_or_values(7, 20, &[_]u32{ 0,
537
- 9,
538
- 4, 14,
539
- 1, 6, 11, 17,
540
- });
541
- try test_eytzinger.layout_from_keys_or_values(15, 21, &[_]u32{ 0,
542
- 10,
543
- 4, 15,
544
- 1, 7, 12, 18,
545
- 0, 2, 5, 8, 11, 13, 16, 19,
546
- });
547
- try test_eytzinger.layout_from_keys_or_values(7, 7919, &[_]u32{ 0,
548
- 3959,
549
- 1979, 5939,
550
- 989, 2969, 4949, 6929,
551
- });
552
- try test_eytzinger.layout_from_keys_or_values(7, 7920, &[_]u32{ 0,
553
- 3959,
554
- 1979, 5939,
555
- 989, 2969, 4949, 6929,
556
- });
557
- try test_eytzinger.layout_from_keys_or_values(7, 7921, &[_]u32{ 0,
558
- 3960,
559
- 1979, 5940,
560
- 989, 2969, 4950, 6930,
561
- });
562
- // zig fmt: on
563
- }
564
-
565
- test "eytzinger: handle classic binary search overflow" {
566
- // Many binary search implementations do mid = (l + r) / 2
567
- // However, if l + r is outside of the range of the integer type
568
- // used, this calculation will needlessly overflow.
569
- // https://en.wikipedia.org/wiki/Binary_search_algorithm#Implementation_issues
570
-
571
- // We don't use the utilities in test_eytzinger as they place the key/value arrays
572
- // on the stack, which the OS doesn't like if you have 2^32 values.
573
- const actual = eytzinger(3, math.maxInt(u32)).tree;
574
- const expect = [_]u32{ 2_147_483_647, 1_073_741_823, 3_221_225_471 };
575
- try std.testing.expectEqualSlices(u32, &expect, &actual);
576
- }
577
-
578
- test "eytzinger: search" {
579
- comptime var power_of_2: u32 = 4;
580
- inline while (power_of_2 <= 32) : (power_of_2 <<= 1) {
581
- comptime var values_count = power_of_2;
582
- inline while (values_count <= 64) : (values_count += 1) {
583
- const keys_count = power_of_2 - 1;
584
- try test_eytzinger.search(keys_count, values_count, values_count - 1);
585
- }
586
- }
587
- }