index/db/commands: fold duplicated index logic into single definitions
Cleanup pass over the secondary-index feature. add_doc is now the one entry-commit path. create_index and build_all_indexes each hand-rolled build -> check_unique -> reserve -> insert, and had already drifted on whether multikey is set before or after the unique check; add_doc gained an enforce_unique flag so the rebuild path keeps its tolerate-and-warn behavior. reserve_for and insert_entries are now the only way the engine touches Index.entries. One definition each for: prefix comparison and the prefix binary searches (prefix_order + std.sort), the cartesian-product odometer (advance_choice), the spec pair list (write_spec builds on spec_pairs, so the log format and the listIndexes reply share one schema), the _id clause parser (plan_id reuses analyze_clause), key-pattern direction (index.descending, which desc_dir already disagreed with on non-numeric values), option truthiness (query.truthy), the E11000 message, and index-removal-by-name (Collection.find_index/remove_index). Key-pattern matching moved out of the dispatcher into index.find_by_key_pattern. Dead or redundant: ParallelArraysError, the unread `dropped` counter, insert_entries' discarded gpa, a third pass computing multikey, the has_id/is_id_index flag pair, Plan.key_len (always lookup_keys[0].len, now a method), first_match_consumed (now stages = stages[1..]). Cheaper hot paths: remove_id compacts in one pass instead of an orderedRemove per hit; Plan.search skips the sort/dedupe when neither multikey nor multiple lookup keys can produce a repeat; the _id fast path reuses one scratch key buffer (bson.write_serialized_value); the plan loop uses the bound collection instead of re-resolving it through two hash lookups per candidate. Behavior is unchanged except that dropping plan_id's fixed 16-clause buffer enables the _id fast path on filters that previously exceeded it.
This commit is contained in:
96
src/db.zig
96
src/db.zig
@@ -17,6 +17,28 @@ pub const Collection = struct {
|
||||
fn init() Collection {
|
||||
return .{ .docs = .empty, .indexes = .empty };
|
||||
}
|
||||
|
||||
/// The secondary index with this name, or null. The single by-name
|
||||
/// lookup: index lifetime (who calls Index.deinit, and when) is decided
|
||||
/// here rather than at each caller.
|
||||
pub fn find_index(self: *Collection, name: []const u8) ?*index.Index {
|
||||
for (self.indexes.items) |*ix| {
|
||||
if (std.mem.eql(u8, ix.name, name)) return ix;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Remove and free the index with this name. Returns whether it existed.
|
||||
fn remove_index(self: *Collection, gpa: std.mem.Allocator, name: []const u8) bool {
|
||||
for (self.indexes.items, 0..) |ix, i| {
|
||||
if (std.mem.eql(u8, ix.name, name)) {
|
||||
var removed = self.indexes.orderedRemove(i);
|
||||
removed.deinit(gpa);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Db = struct {
|
||||
@@ -204,8 +226,7 @@ pub const Engine = struct {
|
||||
// 4. Reserve entry capacity — the last fallible step, so the entry
|
||||
// insertion after the log append is infallible.
|
||||
for (built_list.items) |*b| {
|
||||
if (b.built.entries.items.len == 0) continue;
|
||||
try b.ix.entries.ensureUnusedCapacity(self.gpa, b.built.entries.items.len);
|
||||
try b.ix.reserve_for(self.gpa, b.built.entries.items.len);
|
||||
}
|
||||
|
||||
// 5. Log (and sync) before anything becomes visible.
|
||||
@@ -221,7 +242,7 @@ pub const Engine = struct {
|
||||
try coll.docs.put(self.gpa, id_key, owned);
|
||||
for (built_list.items) |*b| {
|
||||
if (b.built.multikey) b.ix.multikey = true;
|
||||
b.ix.insert_entries(self.gpa, &b.built);
|
||||
b.ix.insert_entries(&b.built);
|
||||
}
|
||||
stored = true;
|
||||
try self.maybe_compact();
|
||||
@@ -293,32 +314,19 @@ pub const Engine = struct {
|
||||
// parsed spec is only owned by the collection once committed.
|
||||
defer if (!committed) ix.deinit(self.gpa);
|
||||
|
||||
for (coll.indexes.items) |*existing| {
|
||||
if (std.mem.eql(u8, existing.name, ix.name)) {
|
||||
if (index.Index.spec_equal(existing, &ix)) return existing;
|
||||
return error.IndexOptionsConflict;
|
||||
}
|
||||
if (coll.find_index(ix.name)) |existing| {
|
||||
if (index.Index.spec_equal(existing, &ix)) return existing;
|
||||
return error.IndexOptionsConflict;
|
||||
}
|
||||
|
||||
// Build entries over the existing documents, checking uniqueness as
|
||||
// we go (the index is not exposed until the end, so mutating it is
|
||||
// safe). Each batch is inserted into the index immediately (which
|
||||
// drains it), so on any later failure the errdefer ix.deinit frees
|
||||
// every inserted entry key; a batch that fails before insertion is
|
||||
// freed by its own errdefer. Nothing is persisted on failure.
|
||||
// safe). Each document's batch is inserted immediately, so on any
|
||||
// later failure the deferred ix.deinit frees every inserted entry
|
||||
// key. Nothing is persisted on failure.
|
||||
var doc_it = coll.docs.iterator();
|
||||
while (doc_it.next()) |entry| {
|
||||
var built = try ix.build_entries(self.gpa, entry.value_ptr.*, entry.key_ptr.*);
|
||||
// Runs on success too: insert_entries drains the keys, leaving
|
||||
// only the (now empty) ArrayList buffer to free.
|
||||
defer built.deinit(self.gpa);
|
||||
if (built.multikey) ix.multikey = true;
|
||||
if (ix.unique) {
|
||||
try ix.check_unique(built.entries.items, entry.key_ptr.*);
|
||||
}
|
||||
if (built.entries.items.len == 0) continue;
|
||||
try ix.entries.ensureUnusedCapacity(self.gpa, built.entries.items.len);
|
||||
ix.insert_entries(self.gpa, &built);
|
||||
_ = try ix.add_doc(self.gpa, entry.value_ptr.*, entry.key_ptr.*, true);
|
||||
}
|
||||
|
||||
// Reserve the collection slot, then persist and publish.
|
||||
@@ -339,14 +347,7 @@ pub const Engine = struct {
|
||||
pub fn drop_index(self: *Engine, db_name: []const u8, coll_name: []const u8, index_name: []const u8) !bool {
|
||||
const db = self.dbs.get(db_name) orelse return false;
|
||||
const coll = db.collections.getPtr(coll_name) orelse return false;
|
||||
var found = false;
|
||||
for (coll.indexes.items) |ix| {
|
||||
if (std.mem.eql(u8, ix.name, index_name)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) return false;
|
||||
if (coll.find_index(index_name) == null) return false;
|
||||
|
||||
const name_pairs = [_]bson.Pair{.{ .key = "name", .value = .{ .string = index_name } }};
|
||||
var name_doc: std.ArrayListUnmanaged(u8) = .empty;
|
||||
@@ -355,13 +356,7 @@ pub const Engine = struct {
|
||||
self.seq += 1;
|
||||
try self.log.append_index_drop(db_name, coll_name, name_doc.items, self.seq);
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < coll.indexes.items.len) {
|
||||
if (std.mem.eql(u8, coll.indexes.items[i].name, index_name)) {
|
||||
var removed = coll.indexes.orderedRemove(i);
|
||||
removed.deinit(self.gpa);
|
||||
} else i += 1;
|
||||
}
|
||||
_ = coll.remove_index(self.gpa, index_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -482,23 +477,16 @@ pub const Engine = struct {
|
||||
if (ix.entries.items.len > 0) continue; // defensive
|
||||
var doc_it = coll_entry.value_ptr.docs.iterator();
|
||||
while (doc_it.next()) |doc_entry| {
|
||||
var built = ix.build_entries(self.gpa, doc_entry.value_ptr.*, doc_entry.key_ptr.*) catch |err| switch (err) {
|
||||
const duplicate = ix.add_doc(self.gpa, doc_entry.value_ptr.*, doc_entry.key_ptr.*, false) catch |err| switch (err) {
|
||||
error.ParallelArrays => {
|
||||
std.debug.print("mongo-light: WARNING: index '{s}' cannot index an existing document; entry skipped\n", .{ix.name});
|
||||
continue;
|
||||
},
|
||||
else => return err,
|
||||
};
|
||||
defer built.deinit(self.gpa);
|
||||
if (built.multikey) ix.multikey = true;
|
||||
if (ix.unique) {
|
||||
ix.check_unique(built.entries.items, doc_entry.key_ptr.*) catch {
|
||||
std.debug.print("mongo-light: WARNING: unique index '{s}' has duplicate keys in existing data; duplicates not enforced for existing documents\n", .{ix.name});
|
||||
};
|
||||
if (duplicate) {
|
||||
std.debug.print("mongo-light: WARNING: unique index '{s}' has duplicate keys in existing data; duplicates not enforced for existing documents\n", .{ix.name});
|
||||
}
|
||||
if (built.entries.items.len == 0) continue;
|
||||
try ix.entries.ensureUnusedCapacity(self.gpa, built.entries.items.len);
|
||||
ix.insert_entries(self.gpa, &built);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -511,9 +499,7 @@ pub const Engine = struct {
|
||||
var ix = try index.parse_spec(self.gpa, spec_doc);
|
||||
var committed = false;
|
||||
defer if (!committed) ix.deinit(self.gpa);
|
||||
for (coll.indexes.items) |existing| {
|
||||
if (std.mem.eql(u8, existing.name, ix.name)) return;
|
||||
}
|
||||
if (coll.find_index(ix.name) != null) return;
|
||||
try coll.indexes.append(self.gpa, ix);
|
||||
committed = true;
|
||||
}
|
||||
@@ -559,13 +545,7 @@ fn apply_record(ctx: *anyopaque, record: storage.Record, doc: *bson.Document) an
|
||||
.string => |s| s,
|
||||
else => return,
|
||||
};
|
||||
var i: usize = 0;
|
||||
while (i < coll.indexes.items.len) {
|
||||
if (std.mem.eql(u8, coll.indexes.items[i].name, name)) {
|
||||
var removed = coll.indexes.orderedRemove(i);
|
||||
removed.deinit(self.gpa);
|
||||
} else i += 1;
|
||||
}
|
||||
_ = coll.remove_index(self.gpa, name);
|
||||
return;
|
||||
},
|
||||
else => {},
|
||||
|
||||
Reference in New Issue
Block a user