index: secondary index core — entries, search, planner, _id fast path
Adds src/index.zig with the full secondary-index machinery: entry generation mirroring field_matches (array value + elements), BSON-order sorted entries with binary search, compound prefix and range lookups, unique/sparse options, the query planner (longest equality/$in run + optional range, $in cartesian cap, sparse/null bail), and the _id_ fast path guarded against serialization-ambiguous values (numbers, strings, symbols, codes, opaque payloads). query.collect_values is now pub so entry generation can mirror it exactly. storage.zig gains record_type_index_create/drop; lib.zig exports index.
This commit is contained in:
@@ -14,6 +14,8 @@ const bson = @import("bson.zig");
|
||||
|
||||
pub const record_type_upsert: u8 = 1;
|
||||
pub const record_type_delete: u8 = 2;
|
||||
pub const record_type_index_create: u8 = 3;
|
||||
pub const record_type_index_drop: u8 = 4;
|
||||
|
||||
pub const header_len: usize = 20; // len + crc + seq + type + reserved
|
||||
|
||||
@@ -26,7 +28,6 @@ pub const Record = struct {
|
||||
type: u8,
|
||||
db: []const u8, // transient: valid only during replay callback
|
||||
coll: []const u8,
|
||||
doc: []const u8, // raw bson bytes
|
||||
};
|
||||
|
||||
pub const Error = error{
|
||||
@@ -44,6 +45,9 @@ pub const Log = struct {
|
||||
path: []const u8,
|
||||
end_pos: u64,
|
||||
log_bytes: u64, // bytes written since the log was last rewritten
|
||||
// Reused record-framing buffer. Appends are single-writer (the engine's
|
||||
// exclusive lock), so one buffer avoids a realloc cycle per record.
|
||||
scratch: std.ArrayListUnmanaged(u8),
|
||||
|
||||
pub fn open(gpa: std.mem.Allocator, io: std.Io, path: []const u8) !Log {
|
||||
// Resolve to an absolute path so compaction can rename the file
|
||||
@@ -68,10 +72,12 @@ pub const Log = struct {
|
||||
.path = abs_path,
|
||||
.end_pos = 0,
|
||||
.log_bytes = 0,
|
||||
.scratch = .empty,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn close(self: *Log) void {
|
||||
self.scratch.deinit(self.gpa);
|
||||
self.file.close(self.io);
|
||||
self.gpa.free(self.path);
|
||||
}
|
||||
@@ -134,7 +140,6 @@ pub const Log = struct {
|
||||
.type = rtype,
|
||||
.db = db,
|
||||
.coll = coll,
|
||||
.doc = doc_bytes,
|
||||
}, doc);
|
||||
|
||||
pos += total;
|
||||
@@ -150,12 +155,23 @@ pub const Log = struct {
|
||||
try self.append(record_type_delete, db, coll, doc, seq);
|
||||
}
|
||||
|
||||
/// The payload is the canonical index spec document ({v, key, name,
|
||||
/// unique?, sparse?}); only apply_record interprets it.
|
||||
pub fn append_index_create(self: *Log, db: []const u8, coll: []const u8, doc: []const u8, seq: u64) !void {
|
||||
try self.append(record_type_index_create, db, coll, doc, seq);
|
||||
}
|
||||
|
||||
/// The payload is {name: "..."}; only apply_record interprets it.
|
||||
pub fn append_index_drop(self: *Log, db: []const u8, coll: []const u8, doc: []const u8, seq: u64) !void {
|
||||
try self.append(record_type_index_drop, db, coll, doc, seq);
|
||||
}
|
||||
|
||||
fn append(self: *Log, rtype: u8, db: []const u8, coll: []const u8, doc: []const u8, seq: u64) !void {
|
||||
if (std.mem.indexOfScalar(u8, db, 0) != null or std.mem.indexOfScalar(u8, coll, 0) != null) {
|
||||
return error.NulInName;
|
||||
}
|
||||
var buf: std.ArrayListUnmanaged(u8) = .empty;
|
||||
defer buf.deinit(self.gpa);
|
||||
const buf = &self.scratch;
|
||||
buf.clearRetainingCapacity();
|
||||
try buf.appendNTimes(self.gpa, 0, header_len);
|
||||
std.mem.writeInt(u64, buf.items[8..16], seq, .little);
|
||||
buf.items[16] = rtype;
|
||||
@@ -189,17 +205,19 @@ pub const Log = struct {
|
||||
|
||||
const testing = std.testing;
|
||||
|
||||
const TmpLog = struct {
|
||||
/// Throwaway log file in the test temp dir, shared by the storage and
|
||||
/// engine test suites.
|
||||
pub const TmpLog = struct {
|
||||
tmp: std.testing.TmpDir,
|
||||
path: []u8,
|
||||
|
||||
fn init(gpa: std.mem.Allocator) !TmpLog {
|
||||
pub fn init(gpa: std.mem.Allocator) !TmpLog {
|
||||
const tmp = std.testing.tmpDir(.{});
|
||||
const path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/test.log", .{tmp.sub_path});
|
||||
return .{ .tmp = tmp, .path = path };
|
||||
}
|
||||
|
||||
fn deinit(self: *TmpLog, gpa: std.mem.Allocator) void {
|
||||
pub fn deinit(self: *TmpLog, gpa: std.mem.Allocator) void {
|
||||
self.tmp.cleanup();
|
||||
gpa.free(self.path);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user