tigerbeetle-node 0.10.0 → 0.11.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 (92) hide show
  1. package/README.md +302 -101
  2. package/dist/index.d.ts +70 -72
  3. package/dist/index.js +70 -72
  4. package/dist/index.js.map +1 -1
  5. package/package.json +6 -6
  6. package/scripts/download_node_headers.sh +14 -7
  7. package/src/index.ts +6 -10
  8. package/src/node.zig +6 -3
  9. package/src/tigerbeetle/scripts/benchmark.sh +4 -4
  10. package/src/tigerbeetle/scripts/confirm_image.sh +44 -0
  11. package/src/tigerbeetle/scripts/fuzz_loop.sh +15 -0
  12. package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +7 -0
  13. package/src/tigerbeetle/scripts/install.sh +19 -4
  14. package/src/tigerbeetle/scripts/install_zig.bat +5 -1
  15. package/src/tigerbeetle/scripts/install_zig.sh +24 -14
  16. package/src/tigerbeetle/scripts/pre-commit.sh +9 -0
  17. package/src/tigerbeetle/scripts/shellcheck.sh +5 -0
  18. package/src/tigerbeetle/scripts/tests_on_alpine.sh +10 -0
  19. package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +14 -0
  20. package/src/tigerbeetle/src/benchmark.zig +4 -2
  21. package/src/tigerbeetle/src/benchmark_array_search.zig +3 -3
  22. package/src/tigerbeetle/src/c/tb_client/thread.zig +8 -9
  23. package/src/tigerbeetle/src/c/tb_client.h +100 -80
  24. package/src/tigerbeetle/src/c/tb_client.zig +4 -1
  25. package/src/tigerbeetle/src/cli.zig +1 -1
  26. package/src/tigerbeetle/src/config.zig +48 -16
  27. package/src/tigerbeetle/src/demo.zig +3 -1
  28. package/src/tigerbeetle/src/eytzinger_benchmark.zig +3 -3
  29. package/src/tigerbeetle/src/io/linux.zig +1 -1
  30. package/src/tigerbeetle/src/lsm/README.md +214 -0
  31. package/src/tigerbeetle/src/lsm/binary_search.zig +137 -10
  32. package/src/tigerbeetle/src/lsm/bloom_filter.zig +43 -0
  33. package/src/tigerbeetle/src/lsm/compaction.zig +352 -398
  34. package/src/tigerbeetle/src/lsm/composite_key.zig +2 -0
  35. package/src/tigerbeetle/src/lsm/eytzinger.zig +1 -1
  36. package/src/tigerbeetle/src/lsm/forest.zig +21 -447
  37. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +412 -0
  38. package/src/tigerbeetle/src/lsm/grid.zig +145 -69
  39. package/src/tigerbeetle/src/lsm/groove.zig +196 -133
  40. package/src/tigerbeetle/src/lsm/k_way_merge.zig +40 -18
  41. package/src/tigerbeetle/src/lsm/level_iterator.zig +28 -9
  42. package/src/tigerbeetle/src/lsm/manifest.zig +81 -181
  43. package/src/tigerbeetle/src/lsm/manifest_level.zig +210 -454
  44. package/src/tigerbeetle/src/lsm/manifest_log.zig +77 -28
  45. package/src/tigerbeetle/src/lsm/posted_groove.zig +64 -76
  46. package/src/tigerbeetle/src/lsm/segmented_array.zig +561 -241
  47. package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +148 -0
  48. package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +9 -0
  49. package/src/tigerbeetle/src/lsm/set_associative_cache.zig +62 -12
  50. package/src/tigerbeetle/src/lsm/table.zig +83 -48
  51. package/src/tigerbeetle/src/lsm/table_immutable.zig +30 -23
  52. package/src/tigerbeetle/src/lsm/table_iterator.zig +25 -14
  53. package/src/tigerbeetle/src/lsm/table_mutable.zig +63 -12
  54. package/src/tigerbeetle/src/lsm/test.zig +49 -55
  55. package/src/tigerbeetle/src/lsm/tree.zig +407 -402
  56. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +457 -0
  57. package/src/tigerbeetle/src/main.zig +28 -6
  58. package/src/tigerbeetle/src/message_bus.zig +2 -2
  59. package/src/tigerbeetle/src/message_pool.zig +14 -17
  60. package/src/tigerbeetle/src/simulator.zig +145 -112
  61. package/src/tigerbeetle/src/state_machine.zig +338 -228
  62. package/src/tigerbeetle/src/static_allocator.zig +65 -0
  63. package/src/tigerbeetle/src/storage.zig +3 -7
  64. package/src/tigerbeetle/src/test/accounting/auditor.zig +577 -0
  65. package/src/tigerbeetle/src/test/accounting/workload.zig +819 -0
  66. package/src/tigerbeetle/src/test/cluster.zig +18 -48
  67. package/src/tigerbeetle/src/test/conductor.zig +365 -0
  68. package/src/tigerbeetle/src/test/fuzz.zig +121 -0
  69. package/src/tigerbeetle/src/test/id.zig +89 -0
  70. package/src/tigerbeetle/src/test/priority_queue.zig +645 -0
  71. package/src/tigerbeetle/src/test/state_checker.zig +93 -69
  72. package/src/tigerbeetle/src/test/state_machine.zig +11 -35
  73. package/src/tigerbeetle/src/test/storage.zig +29 -8
  74. package/src/tigerbeetle/src/tigerbeetle.zig +14 -16
  75. package/src/tigerbeetle/src/unit_tests.zig +7 -0
  76. package/src/tigerbeetle/src/vopr.zig +494 -0
  77. package/src/tigerbeetle/src/vopr_hub/README.md +58 -0
  78. package/src/tigerbeetle/src/vopr_hub/SETUP.md +199 -0
  79. package/src/tigerbeetle/src/vopr_hub/go.mod +3 -0
  80. package/src/tigerbeetle/src/vopr_hub/main.go +1022 -0
  81. package/src/tigerbeetle/src/vopr_hub/scheduler/go.mod +3 -0
  82. package/src/tigerbeetle/src/vopr_hub/scheduler/main.go +403 -0
  83. package/src/tigerbeetle/src/vsr/client.zig +13 -0
  84. package/src/tigerbeetle/src/vsr/journal.zig +16 -13
  85. package/src/tigerbeetle/src/vsr/replica.zig +924 -491
  86. package/src/tigerbeetle/src/vsr/superblock.zig +55 -37
  87. package/src/tigerbeetle/src/vsr/superblock_client_table.zig +7 -10
  88. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +2 -2
  89. package/src/tigerbeetle/src/vsr/superblock_manifest.zig +18 -3
  90. package/src/tigerbeetle/src/vsr.zig +75 -55
  91. package/src/tigerbeetle/scripts/vopr.bat +0 -48
  92. package/src/tigerbeetle/scripts/vopr.sh +0 -33
@@ -3,31 +3,72 @@ const std = @import("std");
3
3
  const assert = std.debug.assert;
4
4
  const math = std.math;
5
5
  const mem = std.mem;
6
- const meta = std.meta;
7
6
 
8
7
  const div_ceil = @import("../util.zig").div_ceil;
9
-
8
+ const binary_search_values_raw = @import("binary_search.zig").binary_search_values_raw;
9
+ const binary_search_keys = @import("binary_search.zig").binary_search_keys;
10
10
  const Direction = @import("direction.zig").Direction;
11
11
 
12
- pub const Cursor = struct {
13
- node: u32,
14
- relative_index: u32,
12
+ /// A "segmented array" is an array with efficient (amortized) random-insert/remove operations.
13
+ /// Also known as an "unrolled linked list": https://en.wikipedia.org/wiki/Unrolled_linked_list
14
+ ///
15
+ /// The structure consists of an array list of "nodes". Each node is a non-empty array of T.
16
+ /// When a node fills, it is split into two adjacent, partially-full nodes.
17
+ /// When a node empties, it is joined with a nearby node.
18
+ ///
19
+ /// An absolute index is offset from the start of the segmented array.
20
+ /// A relative index is offset from the start of a node.
21
+ pub fn SegmentedArray(
22
+ comptime T: type,
23
+ comptime NodePool: type,
24
+ comptime element_count_max: u32,
25
+ comptime options: Options,
26
+ ) type {
27
+ return SegmentedArrayType(T, NodePool, element_count_max, null, {}, {}, options);
28
+ }
29
+
30
+ pub fn SortedSegmentedArray(
31
+ comptime T: type,
32
+ comptime NodePool: type,
33
+ comptime element_count_max: u32,
34
+ comptime Key: type,
35
+ comptime key_from_value: fn (*const T) callconv(.Inline) Key,
36
+ comptime compare_keys: fn (Key, Key) callconv(.Inline) math.Order,
37
+ comptime options: Options,
38
+ ) type {
39
+ return SegmentedArrayType(T, NodePool, element_count_max, Key, key_from_value, compare_keys, options);
40
+ }
41
+
42
+ pub const Options = struct {
43
+ /// Assert all invariants before/after every public function.
44
+ /// Very expensive - only enable for debugging/fuzzing.
45
+ verify: bool = false,
15
46
  };
16
47
 
17
- pub fn SegmentedArray(
48
+ fn SegmentedArrayType(
18
49
  comptime T: type,
19
50
  comptime NodePool: type,
20
51
  comptime element_count_max: u32,
52
+ // Set when the SegmentedArray is ordered:
53
+ comptime Key: ?type,
54
+ comptime key_from_value: if (Key) |K| (fn (*const T) callconv(.Inline) K) else void,
55
+ comptime compare_keys: if (Key) |K| (fn (K, K) callconv(.Inline) math.Order) else void,
56
+ comptime options: Options,
21
57
  ) type {
22
58
  return struct {
23
59
  const Self = @This();
24
60
 
61
+ pub const Cursor = struct {
62
+ node: u32,
63
+ relative_index: u32,
64
+ };
65
+
25
66
  // We can't use @divExact() here as we store TableInfo structs of various sizes in this
26
67
  // data structure. This means that there may be padding at the end of the node.
27
68
  pub const node_capacity = blk: {
28
- const max = NodePool.node_size / @sizeOf(T);
69
+ const max = @divFloor(NodePool.node_size, @sizeOf(T));
29
70
 
30
- // We require that the node is evenly divisible by 2 to simplify our code
71
+ // We require that the node capacity is evenly divisible by 2 to simplify our code
31
72
  // that splits/joins nodes at the midpoint.
32
73
  const capacity = if (max % 2 == 0) max else max - 1;
33
74
 
@@ -39,9 +80,15 @@ pub fn SegmentedArray(
39
80
  comptime {
40
81
  // If this assert fails, we should be using a non-segmented array instead!
41
82
  assert(element_count_max > node_capacity);
83
+
84
+ // We use u32 for indexes and counts.
85
+ assert(element_count_max <= std.math.maxInt(u32));
86
+
87
+ // The buffers returned from the node_pool must be able to store T with correct alignment.
88
+ assert(NodePool.node_alignment >= @alignOf(T));
42
89
  }
43
90
 
44
- pub const node_count_max = blk: {
91
+ pub const node_count_max_naive = blk: {
45
92
  // If a node fills up it is divided into two new nodes. Therefore,
46
93
  // the worst possible space overhead is when all nodes are half full.
47
94
  // This uses flooring division, we want to examine the worst case here.
@@ -49,8 +96,23 @@ pub fn SegmentedArray(
49
96
  break :blk div_ceil(element_count_max, elements_per_node_min);
50
97
  };
51
98
 
99
+ // We can't always actually reach node_count_max_naive in all configurations.
100
+ // If we're at node_count_max_naive-1 nodes, in order to split one more node we need:
101
+ pub const node_count_max = if (element_count_max >=
102
+ // * The node that we split must be full.
103
+ node_capacity +
104
+ // * The last node must have at least one element.
105
+ 1 +
106
+ // * All other nodes must be at least half-full.
107
+ ((node_count_max_naive -| 3) * @divExact(node_capacity, 2)) +
108
+ // * And then we insert one more element into the full node.
109
+ 1)
110
+ node_count_max_naive
111
+ else
112
+ node_count_max_naive - 1;
113
+
52
114
  node_count: u32 = 0,
53
- /// This is the segmented array, the first key_node_count pointers are non-null.
115
+ /// This is the segmented array. The first node_count pointers are non-null.
54
116
  /// The rest are null. We only use optional pointers here to get safety checks.
55
117
  nodes: *[node_count_max]?*[node_capacity]T,
56
118
  /// Since nodes in a segmented array are usually not full, computing the absolute index
@@ -72,13 +134,19 @@ pub fn SegmentedArray(
72
134
  mem.set(?*[node_capacity]T, nodes, null);
73
135
  indexes[0] = 0;
74
136
 
75
- return Self{
137
+ const array = Self{
76
138
  .nodes = nodes,
77
139
  .indexes = indexes,
78
140
  };
141
+
142
+ if (options.verify) array.verify();
143
+
144
+ return array;
79
145
  }
80
146
 
81
147
  pub fn deinit(array: Self, allocator: mem.Allocator, node_pool: ?*NodePool) void {
148
+ if (options.verify) array.verify();
149
+
82
150
  for (array.nodes[0..array.node_count]) |node| {
83
151
  node_pool.?.release(
84
152
  @ptrCast(NodePool.Node, @alignCast(NodePool.node_alignment, node.?)),
@@ -88,7 +156,78 @@ pub fn SegmentedArray(
88
156
  allocator.free(array.indexes);
89
157
  }
90
158
 
91
- pub fn insert_elements(
159
+ pub fn verify(array: Self) void {
160
+ assert(array.node_count <= node_count_max);
161
+ for (array.nodes) |node, node_index| {
162
+ if (node_index < array.node_count) {
163
+ // The first node_count pointers are non-null.
164
+ assert(node != null);
165
+ } else {
166
+ // The rest are non-null.
167
+ assert(node == null);
168
+ }
169
+ }
170
+ for (array.nodes[0..array.node_count]) |_, node_index| {
171
+ const c = array.count(@intCast(u32, node_index));
172
+ // Every node is at most full.
173
+ assert(c <= node_capacity);
174
+ // Every node is at least half-full, except the last.
175
+ if (node_index < array.node_count - 1) {
176
+ assert(c >= @divTrunc(node_capacity, 2));
177
+ }
178
+ }
179
+ if (Key) |K| {
180
+ // If Key is not null then the elements must be sorted by key_from_value (but not necessarily unique).
181
+ var key_prior_or_null: ?K = null;
182
+ for (array.nodes[0..array.node_count]) |_, node_index| {
183
+ for (array.node_elements(@intCast(u32, node_index))) |*value| {
184
+ const key = key_from_value(value);
185
+ if (key_prior_or_null) |key_prior| {
186
+ assert(compare_keys(key_prior, key) != .gt);
187
+ }
188
+ key_prior_or_null = key;
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ pub usingnamespace if (Key) |_| struct {
195
+ /// Returns the absolute index of the element being inserted.
196
+ pub fn insert_element(
197
+ array: *Self,
198
+ node_pool: *NodePool,
199
+ element: T,
200
+ ) u32 {
201
+ if (options.verify) array.verify();
202
+
203
+ const cursor = array.search(key_from_value(&element));
204
+ const absolute_index = array.absolute_index_for_cursor(cursor);
205
+ array.insert_elements_at_absolute_index(node_pool, absolute_index, &[_]T{element});
206
+
207
+ if (options.verify) array.verify();
208
+
209
+ return absolute_index;
210
+ }
211
+ } else struct {
212
+ pub fn insert_elements(
213
+ array: *Self,
214
+ node_pool: *NodePool,
215
+ absolute_index: u32,
216
+ elements: []const T,
217
+ ) void {
218
+ if (options.verify) array.verify();
219
+
220
+ array.insert_elements_at_absolute_index(
221
+ node_pool,
222
+ absolute_index,
223
+ elements,
224
+ );
225
+
226
+ if (options.verify) array.verify();
227
+ }
228
+ };
229
+
230
+ fn insert_elements_at_absolute_index(
92
231
  array: *Self,
93
232
  node_pool: *NodePool,
94
233
  absolute_index: u32,
@@ -136,6 +275,7 @@ pub fn SegmentedArray(
136
275
 
137
276
  const a = cursor.node;
138
277
  const a_pointer = array.nodes[a].?;
278
+ assert(cursor.relative_index <= array.count(a));
139
279
 
140
280
  const total = array.count(a) + @intCast(u32, elements.len);
141
281
  if (total <= node_capacity) {
@@ -164,86 +304,99 @@ pub fn SegmentedArray(
164
304
  // The 5th case can be seen as a special case of the 4th.
165
305
  //
166
306
  // elements: [yyyyyy], relative_index: 0
167
- // [xxxxx_]
168
- // [yyyyyy][xxxxx_]
307
+ // [xxxxx_][______]
308
+ // [______][xxxxx_] // after first copy_backwards
309
+ // [______][xxxxx_] // skip mem.copyBackwards (a_half >= relative_index)
310
+ // [yyyyyy][xxxxx_] // after second copy_backwards
169
311
  //
170
312
  // elements: [yy], relative_index: 1
171
- // [xxxxx_]
172
- // [xyyx__][xxx___]
313
+ // [xxxxx_][______]
314
+ // [x__x__][xxx___] // after first copy_backwards
315
+ // [x__x__][xxx___] // skip mem.copyBackwards (a_half >= relative_index)
316
+ // [xyyx__][xxx___] // after second copy_backwards
173
317
  //
174
318
  // elements: [yy], relative_index: 2
175
- // [xxx_]
176
- // [xxy_][yx__]
319
+ // [xxx_][____]
320
+ // [xx__][_x__] // after first copy_backwards
321
+ // [xx__][_x__] // skip mem.copyBackwards (a_half >= relative_index)
322
+ // [xxy_][yx__] // after second copy_backwards
177
323
  //
178
324
  // elements: [yy], relative_index: 5
179
- // [xxxxxx]
180
- // [xxxx__][xyyx__]
325
+ // [xxxxxx][______]
326
+ // [xxxxx_][___x__] // after first copy_backwards
327
+ // [xxxx__][x__x__] // after mem.copyBackwards (a_half < relative_index)
328
+ // [xxxx__][xyyx__] // after second copy_backwards
181
329
  //
182
330
  // elements: [yyyyy_], relative_index: 5
183
- // [xxxxx_]
184
- // [xxxxx_][yyyyy_]
185
-
186
- assert(cursor.relative_index <= array.count(a));
331
+ // [xxxxx_][______]
332
+ // [xxxxx_][______] // after first copy_backwards
333
+ // [xxxxx_][______] // skip mem.copyBackwards (a_half >= relative_index)
334
+ // [xxxxx_][yyyyy_] // after second copy_backwards
335
+
336
+ const a_half_pointer = a_pointer[0..a_half];
337
+ const b_half_pointer = b_pointer[0..b_half];
338
+
339
+ // Move part of `a` forwards to make space for elements.
340
+ copy_backwards(
341
+ a_half_pointer,
342
+ b_half_pointer,
343
+ cursor.relative_index + elements.len,
344
+ a_pointer[cursor.relative_index..array.count(a)],
345
+ );
187
346
 
188
- const existing_a_head = a_pointer[0..@minimum(a_half, cursor.relative_index)];
189
- const existing_b_head = a_pointer[existing_a_head.len..cursor.relative_index];
190
-
191
- const existing_a_tail = a_pointer[existing_a_head.len..][0..a_half -|
192
- (cursor.relative_index + elements.len)];
193
- const existing_b_tail = a_pointer[existing_a_head.len + existing_a_tail.len +
194
- existing_b_head.len .. array.count(a)];
195
-
196
- const elements_a = elements[0 .. a_half - existing_a_head.len - existing_a_tail.len];
197
- const elements_b = elements[elements_a.len..];
198
-
199
- assert(array.count(a) == existing_a_head.len + existing_b_head.len +
200
- existing_a_tail.len + existing_b_tail.len);
201
- assert(elements.len == elements_a.len + elements_b.len);
202
- assert(a_half == existing_a_head.len + elements_a.len + existing_a_tail.len);
203
- assert(b_half == existing_b_head.len + elements_b.len + existing_b_tail.len);
204
- assert(total == a_half + b_half);
205
-
206
- if (existing_a_tail.len > 0) assert(existing_b_head.len == 0 and elements_b.len == 0);
207
- if (existing_b_head.len > 0) assert(elements_a.len == 0 and existing_a_tail.len == 0);
208
- if (elements_a.len > 0) assert(existing_b_head.len == 0);
209
- if (elements_b.len > 0) assert(existing_a_tail.len == 0);
210
-
211
- assert(existing_a_head.ptr == a_pointer);
212
- assert(existing_a_head.ptr + existing_a_head.len == existing_a_tail.ptr);
213
- assert(existing_a_head.ptr + existing_a_head.len == existing_b_head.ptr);
214
- if (existing_b_head.len > 0) {
215
- assert(existing_b_head.ptr + existing_b_head.len == existing_b_tail.ptr);
216
- } else {
217
- assert(existing_a_tail.ptr + existing_a_tail.len == existing_b_tail.ptr);
347
+ if (a_half < cursor.relative_index) {
348
+ // Move the part of `a` that is past the half-way point into `b`.
349
+ mem.copyBackwards(
350
+ T,
351
+ b_half_pointer,
352
+ a_pointer[a_half..cursor.relative_index],
353
+ );
218
354
  }
219
355
 
220
- mem.copy(T, b_pointer[existing_b_head.len + elements_b.len ..], existing_b_tail);
221
- mem.copy(T, b_pointer[existing_b_head.len..], elements_b);
222
- mem.copy(T, b_pointer, existing_b_head);
223
-
224
- mem.copyBackwards(
225
- T,
226
- a_pointer[existing_a_head.len + elements_a.len ..],
227
- existing_a_tail,
356
+ // Move `elements` into `a` and/or `b`.
357
+ copy_backwards(
358
+ a_half_pointer,
359
+ b_half_pointer,
360
+ cursor.relative_index,
361
+ elements,
228
362
  );
229
- mem.copy(T, a_pointer[existing_a_head.len..], elements_a);
230
363
 
231
364
  array.indexes[b] = array.indexes[a] + a_half;
232
365
  array.increment_indexes_after(b, @intCast(u32, elements.len));
233
366
  }
234
367
 
368
+ /// Behaves like mem.copyBackwards, but as if `a` and `b` were a single contiguous slice.
369
+ /// `target` is the destination index within the concatenation of `a` and `b`.
370
+ fn copy_backwards(
371
+ a: []T,
372
+ b: []T,
373
+ target: usize,
374
+ source: []const T,
375
+ ) void {
376
+ assert(target + source.len <= a.len + b.len);
377
+ const target_a = a[@minimum(target, a.len)..@minimum(target + source.len, a.len)];
378
+ const target_b = b[target -| a.len..(target + source.len) -| a.len];
379
+ assert(target_a.len + target_b.len == source.len);
380
+ const source_a = source[0..target_a.len];
381
+ const source_b = source[target_a.len..];
382
+ if (target_b.ptr != source_b.ptr) {
383
+ mem.copyBackwards(T, target_b, source_b);
384
+ }
385
+ if (target_a.ptr != source_a.ptr) {
386
+ mem.copyBackwards(T, target_a, source_a);
387
+ }
388
+ }
389
+
235
390
  /// Insert an empty node at index `node`.
236
391
  fn insert_empty_node_at(array: *Self, node_pool: *NodePool, node: u32) void {
237
- assert(array.node_count + 1 < node_count_max);
238
-
239
392
  assert(node <= array.node_count);
240
- if (node < array.node_count) {
241
- mem.copyBackwards(
242
- ?*[node_capacity]T,
243
- array.nodes[node + 1 .. array.node_count + 1],
244
- array.nodes[node..array.node_count],
245
- );
246
- }
393
+ assert(array.node_count + 1 <= node_count_max);
394
+
395
+ mem.copyBackwards(
396
+ ?*[node_capacity]T,
397
+ array.nodes[node + 1 .. array.node_count + 1],
398
+ array.nodes[node..array.node_count],
399
+ );
247
400
  mem.copyBackwards(
248
401
  u32,
249
402
  array.indexes[node + 1 .. array.node_count + 2],
@@ -251,7 +404,13 @@ pub fn SegmentedArray(
251
404
  );
252
405
 
253
406
  array.node_count += 1;
254
- array.nodes[node] = @ptrCast(*[node_capacity]T, node_pool.acquire());
407
+ const node_pointer = node_pool.acquire();
408
+ comptime {
409
+ // @ptrCast does not check that the size or alignment agree
410
+ assert(std.meta.alignment(@TypeOf(node_pointer)) >= @alignOf(T));
411
+ assert(@sizeOf(@TypeOf(node_pointer.*)) >= @sizeOf([node_capacity]T));
412
+ }
413
+ array.nodes[node] = @ptrCast(*[node_capacity]T, node_pointer);
255
414
  assert(array.indexes[node] == array.indexes[node + 1]);
256
415
  }
257
416
 
@@ -261,6 +420,8 @@ pub fn SegmentedArray(
261
420
  absolute_index: u32,
262
421
  remove_count: u32,
263
422
  ) void {
423
+ if (options.verify) array.verify();
424
+
264
425
  assert(array.node_count > 0);
265
426
  assert(remove_count > 0);
266
427
  assert(absolute_index + remove_count <= element_count_max);
@@ -274,6 +435,8 @@ pub fn SegmentedArray(
274
435
  array.remove_elements_batch(node_pool, absolute_index, batch);
275
436
  i -= batch;
276
437
  }
438
+
439
+ if (options.verify) array.verify();
277
440
  }
278
441
 
279
442
  fn remove_elements_batch(
@@ -351,7 +514,11 @@ pub fn SegmentedArray(
351
514
  array.decrement_indexes_after(b, remove_count);
352
515
 
353
516
  array.remove_empty_node_at(node_pool, b);
354
- array.maybe_remove_or_merge_node_with_next(node_pool, a);
517
+
518
+ // Either:
519
+ // * `b` was the last node so now `a` is the last node
520
+ // * both `a` and `b` were at least half-full so now `a` is at least half-full
521
+ assert(b == array.node_count or array.count(a) >= half);
355
522
  }
356
523
  }
357
524
 
@@ -442,14 +609,11 @@ pub fn SegmentedArray(
442
609
  @ptrCast(NodePool.Node, @alignCast(NodePool.node_alignment, array.nodes[node].?)),
443
610
  );
444
611
 
445
- assert(node <= array.node_count - 1);
446
- if (node < array.node_count - 1) {
447
- mem.copy(
448
- ?*[node_capacity]T,
449
- array.nodes[node .. array.node_count - 1],
450
- array.nodes[node + 1 .. array.node_count],
451
- );
452
- }
612
+ mem.copy(
613
+ ?*[node_capacity]T,
614
+ array.nodes[node .. array.node_count - 1],
615
+ array.nodes[node + 1 .. array.node_count],
616
+ );
453
617
  mem.copy(
454
618
  u32,
455
619
  array.indexes[node..array.node_count],
@@ -535,27 +699,31 @@ pub fn SegmentedArray(
535
699
  assert(absolute_index < element_count_max);
536
700
  assert(absolute_index <= array.len());
537
701
 
538
- var node: u32 = 0;
539
- while (node + 1 < array.node_count and
540
- absolute_index >= array.indexes[node + 1])
541
- {
542
- node += 1;
543
- }
544
- assert(node < array.node_count);
545
-
546
- const relative_index = absolute_index - array.indexes[node];
702
+ const result = binary_search_keys(u32, struct {
703
+ inline fn compare(a: u32, b: u32) math.Order {
704
+ return math.order(a, b);
705
+ }
706
+ }.compare, array.indexes[0..array.node_count], absolute_index, .{});
547
707
 
548
- if (node == array.node_count - 1) {
549
- // Insertion may target the index one past the end of the array.
550
- assert(relative_index <= array.count(node));
708
+ if (result.exact) {
709
+ return .{
710
+ .node = result.index,
711
+ .relative_index = 0,
712
+ };
551
713
  } else {
552
- assert(relative_index < array.count(node));
714
+ const node = result.index - 1;
715
+ const relative_index = absolute_index - array.indexes[node];
716
+ if (node == array.node_count - 1) {
717
+ // Insertion may target the index one past the end of the array.
718
+ assert(relative_index <= array.count(node));
719
+ } else {
720
+ assert(relative_index < array.count(node));
721
+ }
722
+ return .{
723
+ .node = node,
724
+ .relative_index = relative_index,
725
+ };
553
726
  }
554
-
555
- return .{
556
- .node = node,
557
- .relative_index = relative_index,
558
- };
559
727
  }
560
728
 
561
729
  pub const Iterator = struct {
@@ -607,19 +775,15 @@ pub fn SegmentedArray(
607
775
  }
608
776
  };
609
777
 
610
- pub fn iterator(
778
+ pub fn iterator_from_cursor(
611
779
  array: *const Self,
612
- /// Absolute index to start iteration at.
613
- absolute_index: u32,
614
- /// The start node allows us to skip over nodes as an optimization.
615
- /// If ascending start from the first element of the start node and ascend.
616
- /// If descending start from the last element of the start node and descend.
617
- start_node: u32,
780
+ /// First element of iteration.
781
+ cursor: Cursor,
618
782
  direction: Direction,
619
783
  ) Iterator {
620
784
  if (array.node_count == 0) {
621
- assert(absolute_index == 0);
622
- assert(start_node == 0);
785
+ assert(cursor.node == 0);
786
+ assert(cursor.relative_index == 0);
623
787
 
624
788
  return Iterator{
625
789
  .array = array,
@@ -627,58 +791,111 @@ pub fn SegmentedArray(
627
791
  .cursor = .{ .node = 0, .relative_index = 0 },
628
792
  .done = true,
629
793
  };
794
+ } else {
795
+ assert(cursor.node < array.node_count);
796
+ assert(cursor.relative_index < array.count(cursor.node));
797
+
798
+ return .{
799
+ .array = array,
800
+ .direction = direction,
801
+ .cursor = cursor,
802
+ };
630
803
  }
804
+ }
631
805
 
632
- assert(start_node < array.node_count);
806
+ pub fn iterator_from_index(
807
+ array: *const Self,
808
+ /// First element of iteration.
809
+ absolute_index: u32,
810
+ direction: Direction,
811
+ ) Iterator {
633
812
  assert(absolute_index < element_count_max);
634
- assert(absolute_index < array.indexes[array.node_count]);
635
813
 
636
- switch (direction) {
637
- .ascending => {
638
- assert(absolute_index >= array.indexes[start_node]);
814
+ if (array.node_count == 0) {
815
+ assert(absolute_index == 0);
639
816
 
640
- var node = start_node;
641
- while (node + 1 < array.node_count and
642
- absolute_index >= array.indexes[node + 1])
643
- {
644
- node += 1;
645
- }
646
- assert(node < array.node_count);
817
+ return Iterator{
818
+ .array = array,
819
+ .direction = direction,
820
+ .cursor = .{ .node = 0, .relative_index = 0 },
821
+ .done = true,
822
+ };
823
+ } else {
824
+ assert(absolute_index < array.len());
647
825
 
648
- const relative_index = absolute_index - array.indexes[node];
649
- assert(relative_index < array.count(node));
826
+ return Iterator{
827
+ .array = array,
828
+ .direction = direction,
829
+ .cursor = array.cursor_for_absolute_index(absolute_index),
830
+ };
831
+ }
832
+ }
650
833
 
834
+ pub usingnamespace if (Key) |K| struct {
835
+ /// Returns a cursor to the index of the key either exactly equal to the target key or,
836
+ /// if there is no exact match, the next greatest key.
837
+ pub fn search(
838
+ array: *const Self,
839
+ key: K,
840
+ ) Cursor {
841
+ if (array.node_count == 0) {
651
842
  return .{
652
- .array = array,
653
- .direction = direction,
654
- .cursor = .{
655
- .node = node,
656
- .relative_index = relative_index,
657
- },
843
+ .node = 0,
844
+ .relative_index = 0,
658
845
  };
659
- },
660
- .descending => {
661
- assert(absolute_index < array.indexes[start_node + 1]);
846
+ }
662
847
 
663
- var node = start_node;
664
- while (node > 0 and absolute_index < array.indexes[node]) {
665
- node -= 1;
666
- }
848
+ var offset: usize = 0;
849
+ var length: usize = array.node_count;
850
+ while (length > 1) {
851
+ const half = length / 2;
852
+ const mid = offset + half;
667
853
 
668
- const relative_index = absolute_index - array.indexes[node];
669
- assert(relative_index < array.count(node));
854
+ const node = &array.nodes[mid].?[0];
855
+ // This trick seems to be what's needed to get llvm to emit branchless code for this,
856
+ // a ternary-style if expression was generated as a jump here for whatever reason.
857
+ const next_offsets = [_]usize{ offset, mid };
858
+ offset = next_offsets[@boolToInt(compare_keys(key_from_value(node), key) == .lt)];
670
859
 
860
+ length -= half;
861
+ }
862
+
863
+ // Unlike a normal binary search, don't increment the offset when "key" is higher
864
+ // than the element — "round down" to the previous node.
865
+ // This guarantees that the node result is never "== node_count".
866
+ //
867
+ // (If there are two adjacent nodes starting with keys A and C, and we search B,
868
+ // we want to pick the A node.)
869
+ const node = @intCast(u32, offset);
870
+ assert(node < array.node_count);
871
+
872
+ const relative_index = binary_search_values_raw(
873
+ K,
874
+ T,
875
+ key_from_value,
876
+ compare_keys,
877
+ array.node_elements(node),
878
+ key,
879
+ .{},
880
+ );
881
+
882
+ // Follow the same rule as absolute_index_for_cursor:
883
+ // only return relative_index==array.count() at the last node.
884
+ if (node + 1 < array.node_count and
885
+ relative_index == array.count(node))
886
+ {
671
887
  return .{
672
- .array = array,
673
- .direction = direction,
674
- .cursor = .{
675
- .node = node,
676
- .relative_index = relative_index,
677
- },
888
+ .node = node + 1,
889
+ .relative_index = 0,
678
890
  };
679
- },
891
+ } else {
892
+ return .{
893
+ .node = node,
894
+ .relative_index = relative_index,
895
+ };
896
+ }
680
897
  }
681
- }
898
+ } else struct {};
682
899
  };
683
900
  }
684
901
 
@@ -686,19 +903,26 @@ fn TestContext(
686
903
  comptime T: type,
687
904
  comptime node_size: u32,
688
905
  comptime element_count_max: u32,
906
+ comptime Key: type,
907
+ comptime key_from_value: fn (*const T) callconv(.Inline) Key,
908
+ comptime compare_keys: fn (Key, Key) callconv(.Inline) math.Order,
909
+ comptime element_order: enum { sorted, unsorted },
910
+ comptime options: Options,
689
911
  ) type {
690
912
  return struct {
691
913
  const Self = @This();
692
914
 
693
915
  const testing = std.testing;
694
-
695
916
  const log = false;
696
917
 
697
918
  const NodePool = @import("node_pool.zig").NodePool;
698
919
 
699
920
  // Test overaligned nodes to catch compile errors for missing @alignCast()
700
921
  const TestPool = NodePool(node_size, 2 * @alignOf(T));
701
- const TestArray = SegmentedArray(T, TestPool, element_count_max);
922
+ const TestArray = switch (element_order) {
923
+ .sorted => SortedSegmentedArray(T, TestPool, element_count_max, Key, key_from_value, compare_keys, options),
924
+ .unsorted => SegmentedArray(T, TestPool, element_count_max, options),
925
+ };
702
926
 
703
927
  random: std.rand.Random,
704
928
 
@@ -761,6 +985,27 @@ fn TestContext(
761
985
  }
762
986
 
763
987
  try context.remove_all();
988
+
989
+ if (element_order == .unsorted) {
990
+ // Insert at the beginning of the array until the array is full.
991
+ while (context.array.len() < element_count_max) {
992
+ try context.insert_before_first();
993
+ }
994
+ assert(context.array.node_count >= TestArray.node_count_max - 1);
995
+
996
+ // Remove all-but-one elements from the last node and insert them into the first node.
997
+ const element_count_last = context.array.count(context.array.node_count - 1);
998
+ var element_index: usize = 0;
999
+ while (element_index < element_count_last - 1) : (element_index += 1) {
1000
+ try context.remove_last();
1001
+ try context.insert_before_first();
1002
+ }
1003
+
1004
+ // We should now have maxed out our node count.
1005
+ assert(context.array.node_count == TestArray.node_count_max);
1006
+
1007
+ try context.remove_all();
1008
+ }
764
1009
  }
765
1010
 
766
1011
  fn insert(context: *Self) !void {
@@ -770,19 +1015,29 @@ fn TestContext(
770
1015
  if (count_free == 0) return;
771
1016
 
772
1017
  var buffer: [TestArray.node_capacity * 3]T = undefined;
773
-
774
1018
  const count_max = @minimum(count_free, TestArray.node_capacity * 3);
775
1019
  const count = context.random.uintAtMostBiased(u32, count_max - 1) + 1;
776
1020
  context.random.bytes(mem.sliceAsBytes(buffer[0..count]));
777
1021
 
778
1022
  assert(context.reference.items.len <= element_count_max);
779
- const index = context.random.uintAtMostBiased(u32, reference_len);
780
-
781
- context.array.insert_elements(&context.pool, index, buffer[0..count]);
782
1023
 
783
- // TODO the standard library could use an AssumeCapacity variant of this.
784
- context.reference.insertSlice(index, buffer[0..count]) catch unreachable;
1024
+ switch (element_order) {
1025
+ .unsorted => {
1026
+ const index = context.random.uintAtMostBiased(u32, reference_len);
785
1027
 
1028
+ context.array.insert_elements(&context.pool, index, buffer[0..count]);
1029
+ // TODO the standard library could use an AssumeCapacity variant of this.
1030
+ context.reference.insertSlice(index, buffer[0..count]) catch unreachable;
1031
+ },
1032
+ .sorted => {
1033
+ for (buffer[0..count]) |value| {
1034
+ const index_actual = context.array.insert_element(&context.pool, value);
1035
+ const index_expect = context.reference_index(key_from_value(&value));
1036
+ context.reference.insert(index_expect, value) catch unreachable;
1037
+ try std.testing.expectEqual(index_expect, index_actual);
1038
+ }
1039
+ },
1040
+ }
786
1041
  context.inserts += count;
787
1042
 
788
1043
  try context.verify();
@@ -807,6 +1062,35 @@ fn TestContext(
807
1062
  try context.verify();
808
1063
  }
809
1064
 
1065
+ fn insert_before_first(context: *Self) !void {
1066
+ assert(element_order == .unsorted);
1067
+
1068
+ const insert_index = context.array.absolute_index_for_cursor(context.array.first());
1069
+
1070
+ var element: T = undefined;
1071
+ context.random.bytes(mem.asBytes(&element));
1072
+
1073
+ context.array.insert_elements(&context.pool, insert_index, &.{element});
1074
+ context.reference.insert(insert_index, element) catch unreachable;
1075
+
1076
+ context.inserts += 1;
1077
+
1078
+ try context.verify();
1079
+ }
1080
+
1081
+ fn remove_last(context: *Self) !void {
1082
+ assert(element_order == .unsorted);
1083
+
1084
+ const remove_index = context.array.absolute_index_for_cursor(context.array.last());
1085
+
1086
+ context.array.remove_elements(&context.pool, remove_index, 1);
1087
+ context.reference.replaceRange(remove_index, 1, &[0]T{}) catch unreachable;
1088
+
1089
+ context.removes += 1;
1090
+
1091
+ try context.verify();
1092
+ }
1093
+
810
1094
  fn remove_all(context: *Self) !void {
811
1095
  while (context.reference.items.len > 0) try context.remove();
812
1096
 
@@ -830,7 +1114,7 @@ fn TestContext(
830
1114
  for (context.reference.items) |i| std.debug.print("{}, ", .{i});
831
1115
 
832
1116
  std.debug.print("\nactual: ", .{});
833
- var it = context.array.iterator(0, 0, .ascending);
1117
+ var it = context.array.iterator_from_index(0, .ascending);
834
1118
  while (it.next()) |i| std.debug.print("{}, ", .{i.*});
835
1119
  std.debug.print("\n", .{});
836
1120
  }
@@ -838,7 +1122,7 @@ fn TestContext(
838
1122
  try testing.expectEqual(context.reference.items.len, context.array.len());
839
1123
 
840
1124
  {
841
- var it = context.array.iterator(0, 0, .ascending);
1125
+ var it = context.array.iterator_from_index(0, .ascending);
842
1126
 
843
1127
  for (context.reference.items) |expect| {
844
1128
  const actual = it.next() orelse return error.TestUnexpectedResult;
@@ -848,9 +1132,8 @@ fn TestContext(
848
1132
  }
849
1133
 
850
1134
  {
851
- var it = context.array.iterator(
1135
+ var it = context.array.iterator_from_index(
852
1136
  @intCast(u32, context.reference.items.len) -| 1,
853
- context.array.last().node,
854
1137
  .descending,
855
1138
  );
856
1139
 
@@ -865,7 +1148,26 @@ fn TestContext(
865
1148
  try testing.expectEqual(@as(?*const T, null), it.next());
866
1149
  }
867
1150
 
868
- try testing.expectEqual(context.reference.items.len, context.array.len());
1151
+ {
1152
+ for (context.reference.items) |_, i| {
1153
+ try testing.expect(std.meta.eql(
1154
+ i,
1155
+ context.array.absolute_index_for_cursor(
1156
+ context.array.cursor_for_absolute_index(@intCast(u32, i)),
1157
+ ),
1158
+ ));
1159
+ }
1160
+ }
1161
+
1162
+ if (element_order == .sorted) {
1163
+ for (context.reference.items) |*expect, i| {
1164
+ if (i == 0) continue;
1165
+ try testing.expect(compare_keys(
1166
+ key_from_value(&context.reference.items[i - 1]),
1167
+ key_from_value(expect),
1168
+ ) != .gt);
1169
+ }
1170
+ }
869
1171
 
870
1172
  if (context.array.len() == 0) {
871
1173
  try testing.expectEqual(@as(u32, 0), context.array.node_count);
@@ -875,45 +1177,6 @@ fn TestContext(
875
1177
  try testing.expectEqual(@as(?*[TestArray.node_capacity]T, null), node);
876
1178
  }
877
1179
 
878
- if (context.reference.items.len > 0) {
879
- const reference_len = @intCast(u32, context.reference.items.len);
880
- const index = context.random.uintLessThanBiased(u32, reference_len);
881
- const cursor = context.array.cursor_for_absolute_index(index);
882
-
883
- {
884
- const start_node = context.random.uintAtMostBiased(u32, cursor.node);
885
-
886
- var it = context.array.iterator(index, start_node, .ascending);
887
-
888
- for (context.reference.items[index..]) |expect| {
889
- const actual = it.next() orelse return error.TestUnexpectedResult;
890
- try testing.expectEqual(expect, actual.*);
891
- }
892
- try testing.expectEqual(@as(?*const T, null), it.next());
893
- }
894
-
895
- {
896
- const start_node = cursor.node + context.random.uintAtMostBiased(
897
- u32,
898
- context.array.node_count - 1 - cursor.node,
899
- );
900
- assert(start_node >= cursor.node);
901
- assert(start_node < context.array.node_count);
902
-
903
- var it = context.array.iterator(index, start_node, .descending);
904
-
905
- var i = index + 1;
906
- while (i > 0) {
907
- i -= 1;
908
-
909
- const expect = context.reference.items[i];
910
- const actual = it.next() orelse return error.TestUnexpectedResult;
911
- try testing.expectEqual(expect, actual.*);
912
- }
913
- try testing.expectEqual(@as(?*const T, null), it.next());
914
- }
915
- }
916
-
917
1180
  {
918
1181
  var i: u32 = 0;
919
1182
  while (i < context.array.node_count -| 1) : (i += 1) {
@@ -921,38 +1184,83 @@ fn TestContext(
921
1184
  @divExact(TestArray.node_capacity, 2));
922
1185
  }
923
1186
  }
1187
+ if (element_order == .sorted) try context.verify_search();
1188
+ }
1189
+
1190
+ fn verify_search(context: *Self) !void {
1191
+ var queries: [20]Key = undefined;
1192
+ context.random.bytes(mem.sliceAsBytes(&queries));
1193
+
1194
+ // Test min/max exceptional values on different SegmentedArray shapes.
1195
+ queries[0] = 0;
1196
+ queries[1] = math.maxInt(Key);
1197
+
1198
+ for (queries) |query| {
1199
+ try testing.expectEqual(
1200
+ context.reference_index(query),
1201
+ context.array.absolute_index_for_cursor(context.array.search(query)),
1202
+ );
1203
+ }
1204
+ }
1205
+
1206
+ fn reference_index(context: *const Self, key: Key) u32 {
1207
+ return binary_search_values_raw(
1208
+ Key,
1209
+ T,
1210
+ key_from_value,
1211
+ compare_keys,
1212
+ context.reference.items,
1213
+ key,
1214
+ .{},
1215
+ );
924
1216
  }
925
1217
  };
926
1218
  }
927
1219
 
928
- test "SegmentedArray" {
929
- const seed = 42;
930
-
1220
+ pub fn run_tests(seed: u64, comptime options: Options) !void {
931
1221
  var prng = std.rand.DefaultPrng.init(seed);
932
1222
  const random = prng.random();
933
1223
 
934
- // TODO Import this type from lsm/tree.zig.
935
- const TableInfo = extern struct {
936
- checksum: u128,
937
- key_min: u128,
938
- key_max: u128,
1224
+ const Key = @import("composite_key.zig").CompositeKey(u64);
1225
+ const TableType = @import("table.zig").TableType;
1226
+ const TableInfoType = @import("manifest.zig").TableInfoType;
1227
+ const TableInfo = TableInfoType(TableType(
1228
+ Key,
1229
+ Key.Value,
1230
+ Key.compare_keys,
1231
+ Key.key_from_value,
1232
+ Key.sentinel_key,
1233
+ Key.tombstone,
1234
+ Key.tombstone_from_key,
1235
+ ));
1236
+
1237
+ const CompareInt = struct {
1238
+ inline fn compare_keys(a: u32, b: u32) std.math.Order {
1239
+ return std.math.order(a, b);
1240
+ }
1241
+
1242
+ inline fn key_from_value(value: *const u32) u32 {
1243
+ return value.*;
1244
+ }
1245
+ };
939
1246
 
940
- address: u64,
1247
+ const CompareTable = struct {
1248
+ inline fn compare_keys(a: u64, b: u64) std.math.Order {
1249
+ return std.math.order(a, b);
1250
+ }
941
1251
 
942
- /// Set to the current snapshot tick on creation.
943
- snapshot_min: u64,
944
- /// Initially math.maxInt(u64) on creation, set to the current
945
- /// snapshot tick on deletion.
946
- snapshot_max: u64,
947
- flags: u64 = 0,
1252
+ inline fn key_from_value(value: *const TableInfo) u64 {
1253
+ return value.address;
1254
+ }
948
1255
  };
949
1256
 
950
1257
  comptime {
951
1258
  assert(@sizeOf(TableInfo) == 48 + @sizeOf(u128) * 2);
952
1259
  assert(@alignOf(TableInfo) == 16);
1260
+ assert(@bitSizeOf(TableInfo) == @sizeOf(TableInfo) * 8);
953
1261
  }
954
1262
 
955
- const Options = struct {
1263
+ const TestOptions = struct {
956
1264
  element_type: type,
957
1265
  node_size: u32,
958
1266
  element_count_max: u32,
@@ -964,35 +1272,47 @@ test "SegmentedArray" {
964
1272
  // We want to explore not just the bottom boundary but also the surrounding area
965
1273
  // as it may also have interesting edge cases.
966
1274
  inline for (.{
967
- Options{ .element_type = u32, .node_size = 8, .element_count_max = 3 },
968
- Options{ .element_type = u32, .node_size = 8, .element_count_max = 4 },
969
- Options{ .element_type = u32, .node_size = 8, .element_count_max = 5 },
970
- Options{ .element_type = u32, .node_size = 8, .element_count_max = 6 },
971
- Options{ .element_type = u32, .node_size = 8, .element_count_max = 1024 },
972
- Options{ .element_type = u32, .node_size = 16, .element_count_max = 1024 },
973
- Options{ .element_type = u32, .node_size = 32, .element_count_max = 1024 },
974
- Options{ .element_type = u32, .node_size = 64, .element_count_max = 1024 },
975
- Options{ .element_type = TableInfo, .node_size = 256, .element_count_max = 3 },
976
- Options{ .element_type = TableInfo, .node_size = 256, .element_count_max = 4 },
977
- Options{ .element_type = TableInfo, .node_size = 256, .element_count_max = 1024 },
978
- Options{ .element_type = TableInfo, .node_size = 512, .element_count_max = 1024 },
979
- Options{ .element_type = TableInfo, .node_size = 1024, .element_count_max = 1024 },
980
- }) |options| {
981
- const Context = TestContext(
982
- options.element_type,
983
- options.node_size,
984
- options.element_count_max,
985
- );
986
-
987
- var context = try Context.init(random);
988
- defer context.deinit();
989
-
990
- try context.run();
991
-
992
- if (options.node_size % @sizeOf(options.element_type) != 0) tested_padding = true;
993
- if (Context.TestArray.node_capacity == 2) tested_node_capacity_min = true;
1275
+ TestOptions{ .element_type = u32, .node_size = 8, .element_count_max = 3 },
1276
+ TestOptions{ .element_type = u32, .node_size = 8, .element_count_max = 4 },
1277
+ TestOptions{ .element_type = u32, .node_size = 8, .element_count_max = 5 },
1278
+ TestOptions{ .element_type = u32, .node_size = 8, .element_count_max = 6 },
1279
+ TestOptions{ .element_type = u32, .node_size = 8, .element_count_max = 1024 },
1280
+ TestOptions{ .element_type = u32, .node_size = 16, .element_count_max = 1024 },
1281
+ TestOptions{ .element_type = u32, .node_size = 32, .element_count_max = 1024 },
1282
+ TestOptions{ .element_type = u32, .node_size = 64, .element_count_max = 1024 },
1283
+ TestOptions{ .element_type = TableInfo, .node_size = 256, .element_count_max = 3 },
1284
+ TestOptions{ .element_type = TableInfo, .node_size = 256, .element_count_max = 4 },
1285
+ TestOptions{ .element_type = TableInfo, .node_size = 256, .element_count_max = 1024 },
1286
+ TestOptions{ .element_type = TableInfo, .node_size = 512, .element_count_max = 1024 },
1287
+ TestOptions{ .element_type = TableInfo, .node_size = 1024, .element_count_max = 1024 },
1288
+ }) |test_options| {
1289
+ inline for (.{ .sorted, .unsorted }) |order| {
1290
+ const Context = TestContext(
1291
+ test_options.element_type,
1292
+ test_options.node_size,
1293
+ test_options.element_count_max,
1294
+ if (test_options.element_type == u32) u32 else u64,
1295
+ if (test_options.element_type == u32) CompareInt.key_from_value else CompareTable.key_from_value,
1296
+ if (test_options.element_type == u32) CompareInt.compare_keys else CompareTable.compare_keys,
1297
+ order,
1298
+ options,
1299
+ );
1300
+
1301
+ var context = try Context.init(random);
1302
+ defer context.deinit();
1303
+
1304
+ try context.run();
1305
+
1306
+ if (test_options.node_size % @sizeOf(test_options.element_type) != 0) tested_padding = true;
1307
+ if (Context.TestArray.node_capacity == 2) tested_node_capacity_min = true;
1308
+ }
994
1309
  }
995
1310
 
996
1311
  assert(tested_padding);
997
1312
  assert(tested_node_capacity_min);
998
1313
  }
1314
+
1315
+ test "SegmentedArray" {
1316
+ const seed = 42;
1317
+ try run_tests(seed, .{ .verify = true });
1318
+ }