M3: the update operators, and 's modifiers #8

Merged
dev merged 9 commits from m3-operators into main 2026-08-10 18:41:26 +00:00
Showing only changes of commit 301a4538fd - Show all commits

View File

@@ -610,40 +610,67 @@ fn apply_operator(
value: bson.Value, value: bson.Value,
opts: Options, opts: Options,
) UpdateError!void { ) UpdateError!void {
const diag = opts.diag; // Every operator's argument is a document of path/operand pairs, so the
if (std.mem.eql(u8, op, "$set")) { // shape is checked once here rather than at the top of each.
const ops = doc_pairs(value) orelse return error.InvalidUpdate; const ops = doc_pairs(value) orelse return error.InvalidUpdate;
if (std.mem.eql(u8, op, "$set")) return op_set(arena, pairs, ops, opts);
if (std.mem.eql(u8, op, "$unset")) return op_unset(arena, pairs, ops, opts);
if (std.mem.eql(u8, op, "$inc")) return op_inc(arena, pairs, ops, opts);
if (std.mem.eql(u8, op, "$push")) return op_push(arena, pairs, ops, opts);
if (std.mem.eql(u8, op, "$pull")) return op_pull(arena, pairs, ops, opts);
if (std.mem.eql(u8, op, "$rename")) return op_rename(arena, pairs, ops, opts);
return error.InvalidUpdate;
}
fn op_set(
arena: std.mem.Allocator,
pairs: *std.ArrayListUnmanaged(bson.Pair),
ops: []const bson.Pair,
opts: Options,
) UpdateError!void {
for (ops) |p| { for (ops) |p| {
if (std.mem.eql(u8, p.key, "_id")) return error.ImmutableId; if (std.mem.eql(u8, p.key, "_id")) return error.ImmutableId;
for (try resolve(arena, pairs.items, p.key, opts)) |segs| { for (try resolve(arena, pairs.items, p.key, opts)) |segs| {
try set_path(arena, pairs, segs, try bson.copy_value(arena, p.value), p.key, diag); try set_path(arena, pairs, segs, try bson.copy_value(arena, p.value), p.key, opts.diag);
} }
} }
return;
} }
if (std.mem.eql(u8, op, "$unset")) {
const ops = doc_pairs(value) orelse return error.InvalidUpdate; fn op_unset(
arena: std.mem.Allocator,
pairs: *std.ArrayListUnmanaged(bson.Pair),
ops: []const bson.Pair,
opts: Options,
) UpdateError!void {
for (ops) |p| { for (ops) |p| {
for (try resolve(arena, pairs.items, p.key, opts)) |segs| { for (try resolve(arena, pairs.items, p.key, opts)) |segs| {
unset_path(arena, pairs, segs); unset_path(arena, pairs, segs);
} }
} }
return;
} }
if (std.mem.eql(u8, op, "$inc")) {
const ops = doc_pairs(value) orelse return error.InvalidUpdate; fn op_inc(
arena: std.mem.Allocator,
pairs: *std.ArrayListUnmanaged(bson.Pair),
ops: []const bson.Pair,
opts: Options,
) UpdateError!void {
for (ops) |p| { for (ops) |p| {
for (try resolve(arena, pairs.items, p.key, opts)) |segs| { for (try resolve(arena, pairs.items, p.key, opts)) |segs| {
const current = get_value(pairs.items, segs) orelse bson.Value{ .int32 = 0 }; const current = get_value(pairs.items, segs) orelse bson.Value{ .int32 = 0 };
if (!current.is_number() or !p.value.is_number()) return error.InvalidUpdate; if (!current.is_number() or !p.value.is_number()) return error.InvalidUpdate;
const sum = try numeric_add(current, p.value); const sum = try numeric_add(current, p.value);
try set_path(arena, pairs, segs, sum, p.key, diag); try set_path(arena, pairs, segs, sum, p.key, opts.diag);
} }
} }
return;
} }
if (std.mem.eql(u8, op, "$push")) {
const ops = doc_pairs(value) orelse return error.InvalidUpdate; fn op_push(
arena: std.mem.Allocator,
pairs: *std.ArrayListUnmanaged(bson.Pair),
ops: []const bson.Pair,
opts: Options,
) UpdateError!void {
for (ops) |p| { for (ops) |p| {
for (try resolve(arena, pairs.items, p.key, opts)) |segs| { for (try resolve(arena, pairs.items, p.key, opts)) |segs| {
const current_opt = get_value(pairs.items, segs); const current_opt = get_value(pairs.items, segs);
@@ -663,18 +690,22 @@ fn apply_operator(
else => return error.InvalidUpdate, else => return error.InvalidUpdate,
}; };
for (arr) |item| try items.append(arena, try bson.copy_value(arena, item)); for (arr) |item| try items.append(arena, try bson.copy_value(arena, item));
try set_path(arena, pairs, segs, .{ .array = try items.toOwnedSlice(arena) }, p.key, diag); try set_path(arena, pairs, segs, .{ .array = try items.toOwnedSlice(arena) }, p.key, opts.diag);
continue; continue;
} }
} }
try items.append(arena, try bson.copy_value(arena, p.value)); try items.append(arena, try bson.copy_value(arena, p.value));
try set_path(arena, pairs, segs, .{ .array = try items.toOwnedSlice(arena) }, p.key, diag); try set_path(arena, pairs, segs, .{ .array = try items.toOwnedSlice(arena) }, p.key, opts.diag);
} }
} }
return;
} }
if (std.mem.eql(u8, op, "$pull")) {
const ops = doc_pairs(value) orelse return error.InvalidUpdate; fn op_pull(
arena: std.mem.Allocator,
pairs: *std.ArrayListUnmanaged(bson.Pair),
ops: []const bson.Pair,
opts: Options,
) UpdateError!void {
for (ops) |p| { for (ops) |p| {
for (try resolve(arena, pairs.items, p.key, opts)) |segs| { for (try resolve(arena, pairs.items, p.key, opts)) |segs| {
const current = get_value(pairs.items, segs) orelse continue; const current = get_value(pairs.items, segs) orelse continue;
@@ -689,15 +720,19 @@ fn apply_operator(
try items.append(arena, elem); try items.append(arena, elem);
} }
} }
try set_path(arena, pairs, segs, .{ .array = try items.toOwnedSlice(arena) }, p.key, diag); try set_path(arena, pairs, segs, .{ .array = try items.toOwnedSlice(arena) }, p.key, opts.diag);
} }
} }
return;
} }
// `$rename` alone keeps the plain split: `validate` has already refused a
// positional path on either end of it, which is what mongod does too. /// `$rename` alone keeps the plain split: `validate` has already refused a
if (std.mem.eql(u8, op, "$rename")) { /// positional path on either end of it, which is what mongod does too.
const ops = doc_pairs(value) orelse return error.InvalidUpdate; fn op_rename(
arena: std.mem.Allocator,
pairs: *std.ArrayListUnmanaged(bson.Pair),
ops: []const bson.Pair,
opts: Options,
) UpdateError!void {
for (ops) |p| { for (ops) |p| {
if (p.value != .string) return error.InvalidUpdate; if (p.value != .string) return error.InvalidUpdate;
if (std.mem.eql(u8, p.key, "_id") or std.mem.eql(u8, p.value.string, "_id")) return error.ImmutableId; if (std.mem.eql(u8, p.key, "_id") or std.mem.eql(u8, p.value.string, "_id")) return error.ImmutableId;
@@ -707,11 +742,8 @@ fn apply_operator(
unset_path(arena, pairs, old_segs[0..old_n]); unset_path(arena, pairs, old_segs[0..old_n]);
var new_segs: [max_path_segments][]const u8 = undefined; var new_segs: [max_path_segments][]const u8 = undefined;
const new_n = split_path(p.value.string, &new_segs) orelse return error.InvalidUpdate; const new_n = split_path(p.value.string, &new_segs) orelse return error.InvalidUpdate;
try set_path(arena, pairs, new_segs[0..new_n], v, p.value.string, diag); try set_path(arena, pairs, new_segs[0..new_n], v, p.value.string, opts.diag);
} }
return;
}
return error.InvalidUpdate;
} }
fn doc_pairs(v: bson.Value) ?[]const bson.Pair { fn doc_pairs(v: bson.Value) ?[]const bson.Pair {