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:
86
src/wire.zig
86
src/wire.zig
@@ -13,9 +13,6 @@ pub const op_code_reply: i32 = 2001;
|
||||
pub const max_message_size: usize = 48 * 1024 * 1024;
|
||||
pub const max_bson_object_size: i32 = 16 * 1024 * 1024;
|
||||
|
||||
pub const flag_checksum_present: u32 = 1 << 1;
|
||||
pub const flag_more_to_come: u32 = 1 << 0;
|
||||
|
||||
pub const Seq = struct {
|
||||
name: []const u8,
|
||||
docs: []bson.Document,
|
||||
@@ -175,6 +172,37 @@ pub const Message = struct {
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Documents of a batch argument (`documents`, `updates`, `deletes`).
|
||||
/// Drivers send them either as an OP_MSG document sequence or as an array
|
||||
/// inside the command body; callers should not have to care which. The
|
||||
/// result borrows this message's storage — never deinit the documents.
|
||||
pub fn batch(self: *Message, name: []const u8) BatchError![]const bson.Document {
|
||||
for (self.seqs) |seq| {
|
||||
if (std.mem.eql(u8, seq.name, name) and seq.docs.len > 0) return seq.docs;
|
||||
}
|
||||
const arr = switch (bson.get_pair(self.body.pairs, name) orelse return error.MissingBatch) {
|
||||
.array => |a| a,
|
||||
else => return error.BatchNotArray,
|
||||
};
|
||||
const docs = try self.arena.allocator().alloc(bson.Document, arr.len);
|
||||
for (arr, 0..) |item, i| {
|
||||
docs[i] = switch (item) {
|
||||
// Pairs are borrowed from the body document, which owns the
|
||||
// arena; these views must never be deinited.
|
||||
.doc => |pairs| .{ .arena = undefined, .pairs = pairs },
|
||||
else => return error.BatchElementNotDoc,
|
||||
};
|
||||
}
|
||||
return docs;
|
||||
}
|
||||
};
|
||||
|
||||
pub const BatchError = error{
|
||||
MissingBatch, // no sequence and no body field of that name
|
||||
BatchNotArray, // body field is present but not an array
|
||||
BatchElementNotDoc, // an array element is not a document
|
||||
OutOfMemory,
|
||||
};
|
||||
|
||||
/// Builder for a command reply document. Strings for keys and values must
|
||||
@@ -221,6 +249,32 @@ pub const Reply = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// Write the 16-byte message header with a zero length placeholder. Returns
|
||||
/// the offset `end_message` needs to patch the length in.
|
||||
fn begin_message(
|
||||
gpa: std.mem.Allocator,
|
||||
out: *std.ArrayListUnmanaged(u8),
|
||||
request_id: u32,
|
||||
response_to: u32,
|
||||
op_code: i32,
|
||||
) !usize {
|
||||
const len_pos = out.items.len;
|
||||
var header: [16]u8 = undefined;
|
||||
std.mem.writeInt(u32, header[0..4], 0, .little); // patched by end_message
|
||||
std.mem.writeInt(u32, header[4..8], request_id, .little);
|
||||
std.mem.writeInt(u32, header[8..12], response_to, .little);
|
||||
std.mem.writeInt(i32, header[12..16], op_code, .little);
|
||||
try out.appendSlice(gpa, &header);
|
||||
return len_pos;
|
||||
}
|
||||
|
||||
/// Patch in the total length of the message started at `len_pos`.
|
||||
fn end_message(out: *std.ArrayListUnmanaged(u8), len_pos: usize) !void {
|
||||
const total = out.items.len - len_pos;
|
||||
if (total > std.math.maxInt(u32)) return error.MessageTooLarge;
|
||||
std.mem.writeInt(u32, out.items[len_pos..][0..4], @intCast(total), .little);
|
||||
}
|
||||
|
||||
/// Serialize an OP_MSG reply: header + flags + single body section.
|
||||
pub fn write_message(
|
||||
gpa: std.mem.Allocator,
|
||||
@@ -230,18 +284,13 @@ pub fn write_message(
|
||||
body: []const bson.Pair,
|
||||
out: *std.ArrayListUnmanaged(u8),
|
||||
) !void {
|
||||
const len_pos = out.items.len;
|
||||
var header: [20]u8 = undefined;
|
||||
std.mem.writeInt(u32, header[4..8], request_id, .little);
|
||||
std.mem.writeInt(u32, header[8..12], response_to, .little);
|
||||
std.mem.writeInt(i32, header[12..16], op_code_msg, .little);
|
||||
std.mem.writeInt(u32, header[16..20], flags, .little);
|
||||
try out.appendSlice(gpa, &header);
|
||||
const len_pos = try begin_message(gpa, out, request_id, response_to, op_code_msg);
|
||||
var flag_bytes: [4]u8 = undefined;
|
||||
std.mem.writeInt(u32, &flag_bytes, flags, .little);
|
||||
try out.appendSlice(gpa, &flag_bytes);
|
||||
try out.append(gpa, 0x00); // single body section
|
||||
try bson.write_doc(body, gpa, out);
|
||||
const total = out.items.len - len_pos;
|
||||
if (total > std.math.maxInt(u32)) return error.MessageTooLarge;
|
||||
std.mem.writeInt(u32, out.items[len_pos..][0..4], @intCast(total), .little);
|
||||
try end_message(out, len_pos);
|
||||
}
|
||||
|
||||
/// Serialize an OP_REPLY (legacy): header + responseFlags | cursorID |
|
||||
@@ -254,19 +303,12 @@ pub fn write_reply_query(
|
||||
body: []const bson.Pair,
|
||||
out: *std.ArrayListUnmanaged(u8),
|
||||
) !void {
|
||||
const len_pos = out.items.len;
|
||||
var header: [16]u8 = undefined;
|
||||
std.mem.writeInt(u32, header[4..8], request_id, .little);
|
||||
std.mem.writeInt(u32, header[8..12], response_to, .little);
|
||||
std.mem.writeInt(i32, header[12..16], op_code_reply, .little);
|
||||
try out.appendSlice(gpa, &header);
|
||||
const len_pos = try begin_message(gpa, out, request_id, response_to, op_code_reply);
|
||||
var reply_fields: [20]u8 = [_]u8{0} ** 20; // responseFlags | cursorID | startingFrom | numberReturned
|
||||
std.mem.writeInt(i32, reply_fields[16..20], 1, .little); // numberReturned = 1
|
||||
try out.appendSlice(gpa, &reply_fields);
|
||||
try bson.write_doc(body, gpa, out);
|
||||
const total = out.items.len - len_pos;
|
||||
if (total > std.math.maxInt(u32)) return error.MessageTooLarge;
|
||||
std.mem.writeInt(u32, out.items[len_pos..][0..4], @intCast(total), .little);
|
||||
try end_message(out, len_pos);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user