commands: createIndexes/listIndexes/dropIndexes + planner wiring
Adds the three driver commands, parameterized E11000 messages (engine dup_index carries the index name into writeErrors), the scan_matching planner wiring (_id fast path → index plan → scan) and the first-$match aggregate pushdown. The equivalence test (mixed-type corpus x 27 filters, non-sparse and sparse indexes) drove out three real bugs: the two-bound range under-approximation on multikey indexes (fall back to scan), a dangling single-value option array in the planner, and update-time unique violations now reporting writeErrors instead of corrupting state.
This commit is contained in:
@@ -677,6 +677,14 @@ fn evaluate_index(gpa: std.mem.Allocator, ix: *const Index, clauses: []const Cla
|
||||
}
|
||||
if (run == 0 and lo == null and hi == null) return null;
|
||||
|
||||
// A two-sided range on a multikey index can under-approximate: a doc
|
||||
// like {a: [1, 2]} satisfies {a: {$gt: 5, $lt: 25}} with the array for
|
||||
// the lower bound (rank 5 > 5) and an element for the upper (1 < 25),
|
||||
// so no single entry lies inside (5, 25) — the range scan would miss
|
||||
// it. One-sided ranges are safe: whichever candidate satisfies the
|
||||
// bound is itself an entry inside it. Equality/$in are unaffected.
|
||||
if (ix.multikey and lo != null and hi != null) return null;
|
||||
|
||||
// A sparse index skips documents missing a field; {a: null} would
|
||||
// otherwise miss them. Never use a sparse index for a null component.
|
||||
if (ix.sparse) {
|
||||
@@ -692,15 +700,13 @@ fn evaluate_index(gpa: std.mem.Allocator, ix: *const Index, clauses: []const Cla
|
||||
if (hi) |v| if (v == .null) return null;
|
||||
}
|
||||
|
||||
var opts: [max_index_keys][]const bson.Value = undefined;
|
||||
var counts: [max_index_keys]usize = undefined;
|
||||
for (0..run) |i| {
|
||||
if (infos[i].eq) |v| {
|
||||
opts[i] = &.{v};
|
||||
} else opts[i] = infos[i].in_values.?;
|
||||
counts[i] = if (infos[i].eq != null) 1 else infos[i].in_values.?.len;
|
||||
}
|
||||
var combos: u64 = 1;
|
||||
for (0..run) |i| {
|
||||
combos *= opts[i].len;
|
||||
combos *= counts[i];
|
||||
if (combos > max_combos) return null;
|
||||
}
|
||||
|
||||
@@ -724,14 +730,19 @@ fn evaluate_index(gpa: std.mem.Allocator, ix: *const Index, clauses: []const Cla
|
||||
while (true) {
|
||||
const key = try gpa.alloc(bson.Value, run);
|
||||
errdefer gpa.free(key);
|
||||
for (0..run) |i| key[i] = opts[i][choice[i]];
|
||||
// An eq component contributes its single value; an $in
|
||||
// component the choice-th member. (Never materialize the
|
||||
// single-value option as a temporary array: it would dangle.)
|
||||
for (0..run) |i| {
|
||||
key[i] = if (infos[i].eq) |v| v else infos[i].in_values.?[choice[i]];
|
||||
}
|
||||
try pl.lookup_keys.append(gpa, key);
|
||||
var i: usize = run;
|
||||
var carry = true;
|
||||
while (carry and i > 0) {
|
||||
i -= 1;
|
||||
choice[i] += 1;
|
||||
if (choice[i] < opts[i].len) carry = false else choice[i] = 0;
|
||||
if (choice[i] < counts[i]) carry = false else choice[i] = 0;
|
||||
}
|
||||
if (carry) break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user