// Dev stress test for the overflow slab (records > 1024 bytes): // zig run -O ReleaseFast src/spill.zig // Keys straddling the spill threshold (10 B .. 100 KB) through insert, // lookup, delete and iteration. const std = @import("std"); const index = @import("index.zig"); const bson = @import("bson.zig"); fn doc_of(gpa: std.mem.Allocator, pairs: []const bson.Pair) ![]u8 { var out: std.ArrayListUnmanaged(u8) = .empty; defer out.deinit(gpa); try bson.write_doc(pairs, gpa, &out); return out.toOwnedSlice(gpa); } pub fn main() !void { const gpa = std.heap.page_allocator; var prng = std.Random.DefaultPrng.init(0x1234_5678); const rand = prng.random(); var keys = [_]index.IndexKey{.{ .path = "tag", .descending = false }}; var ix = try index.Index.init(gpa, "tag", &keys, false, false, null); // Keys straddling the spill threshold: inline, exactly at the limit, // just over, and one very long. Each id is a short static string. const lens = [_]usize{ 10, 1023, 1024, 1025, 2000, 100_000 }; var strings: [lens.len][]u8 = undefined; var docs: [lens.len][]u8 = undefined; var pairs: [2]bson.Pair = undefined; for (lens, 0..) |len, i| { strings[i] = try gpa.alloc(u8, len); for (strings[i]) |*c| c.* = 'a' + @as(u8, @intCast(rand.intRangeAtMost(u8, 0, 25))); // add a distinguishing suffix so keys are unique std.mem.copyForwards(u8, strings[i][len - 4 ..], &[_]u8{ @intCast(i), 0xff, 0x00, 0x00 }); pairs[0] = .{ .key = "_id", .value = .{ .int32 = @intCast(i) } }; pairs[1] = .{ .key = "tag", .value = .{ .string = strings[i] } }; docs[i] = try doc_of(gpa, &pairs); _ = try ix.add_doc(gpa, docs[i], @intCast(i + 1), true); } std.debug.print("count={d} leaves={d} depth={d} overflow={d}\n", .{ ix.count(), ix.leaf_count, ix.depth, ix.overflow.items.len, }); if (ix.overflow.items.len < 100_000) return error.NoSpill; // Every entry is found by exact key. for (lens, 0..) |_, i| { var out: std.ArrayListUnmanaged(u64) = .empty; defer out.deinit(gpa); try ix.lookup_eq(gpa, &.{.{ .string = strings[i] }}, &out); if (out.items.len != 1) { std.debug.print("lookup {d} got {d}\n", .{ i, out.items.len }); return error.Bad; } if (out.items[0] != i + 1) return error.Bad; } // Delete the spilled ones and the inline ones alternately. for (lens, 0..) |_, i| { if (i % 2 == 0) continue; ix.remove_doc(gpa, docs[i], @intCast(i + 1)); } if (ix.count() != 3) return error.Bad; for (lens, 0..) |_, i| { if (i % 2 == 0) continue; var out: std.ArrayListUnmanaged(u64) = .empty; defer out.deinit(gpa); try ix.lookup_eq(gpa, &.{.{ .string = strings[i] }}, &out); if (out.items.len != 0) return error.Bad; } // Iteration still sees the survivors in order. var it = ix.iter(); var seen: usize = 0; while (it.next()) |e| { seen += 1; _ = e; } if (seen != 3) return error.Bad; std.debug.print("SPILL OK (seen={d})\n", .{seen}); }