M3: the update operators, and 's modifiers #8
234
src/update.zig
234
src/update.zig
@@ -610,110 +610,142 @@ 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;
|
||||||
for (ops) |p| {
|
if (std.mem.eql(u8, op, "$set")) return op_set(arena, pairs, ops, opts);
|
||||||
if (std.mem.eql(u8, p.key, "_id")) return error.ImmutableId;
|
if (std.mem.eql(u8, op, "$unset")) return op_unset(arena, pairs, ops, opts);
|
||||||
for (try resolve(arena, pairs.items, p.key, opts)) |segs| {
|
if (std.mem.eql(u8, op, "$inc")) return op_inc(arena, pairs, ops, opts);
|
||||||
try set_path(arena, pairs, segs, try bson.copy_value(arena, p.value), p.key, diag);
|
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;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, op, "$unset")) {
|
|
||||||
const ops = doc_pairs(value) orelse return error.InvalidUpdate;
|
|
||||||
for (ops) |p| {
|
|
||||||
for (try resolve(arena, pairs.items, p.key, opts)) |segs| {
|
|
||||||
unset_path(arena, pairs, segs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, op, "$inc")) {
|
|
||||||
const ops = doc_pairs(value) orelse return error.InvalidUpdate;
|
|
||||||
for (ops) |p| {
|
|
||||||
for (try resolve(arena, pairs.items, p.key, opts)) |segs| {
|
|
||||||
const current = get_value(pairs.items, segs) orelse bson.Value{ .int32 = 0 };
|
|
||||||
if (!current.is_number() or !p.value.is_number()) return error.InvalidUpdate;
|
|
||||||
const sum = try numeric_add(current, p.value);
|
|
||||||
try set_path(arena, pairs, segs, sum, p.key, diag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, op, "$push")) {
|
|
||||||
const ops = doc_pairs(value) orelse return error.InvalidUpdate;
|
|
||||||
for (ops) |p| {
|
|
||||||
for (try resolve(arena, pairs.items, p.key, opts)) |segs| {
|
|
||||||
const current_opt = get_value(pairs.items, segs);
|
|
||||||
var items: std.ArrayListUnmanaged(bson.Value) = .empty;
|
|
||||||
defer items.deinit(arena);
|
|
||||||
if (current_opt) |current| {
|
|
||||||
switch (current) {
|
|
||||||
.array => |arr| try items.appendSlice(arena, arr),
|
|
||||||
.null => {},
|
|
||||||
else => return error.InvalidUpdate, // non-array field
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (p.value == .doc) {
|
|
||||||
if (bson.get_pair(p.value.doc, "$each")) |each| {
|
|
||||||
const arr = switch (each) {
|
|
||||||
.array => |a| a,
|
|
||||||
else => return error.InvalidUpdate,
|
|
||||||
};
|
|
||||||
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);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, op, "$pull")) {
|
|
||||||
const ops = doc_pairs(value) orelse return error.InvalidUpdate;
|
|
||||||
for (ops) |p| {
|
|
||||||
for (try resolve(arena, pairs.items, p.key, opts)) |segs| {
|
|
||||||
const current = get_value(pairs.items, segs) orelse continue;
|
|
||||||
const arr = switch (current) {
|
|
||||||
.array => |a| a,
|
|
||||||
else => return error.InvalidUpdate,
|
|
||||||
};
|
|
||||||
var items: std.ArrayListUnmanaged(bson.Value) = .empty;
|
|
||||||
defer items.deinit(arena);
|
|
||||||
for (arr) |elem| {
|
|
||||||
if (!pull_matches(arena, p.value, elem)) {
|
|
||||||
try items.append(arena, elem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try set_path(arena, pairs, segs, .{ .array = try items.toOwnedSlice(arena) }, p.key, 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.
|
|
||||||
if (std.mem.eql(u8, op, "$rename")) {
|
|
||||||
const ops = doc_pairs(value) orelse return error.InvalidUpdate;
|
|
||||||
for (ops) |p| {
|
|
||||||
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;
|
|
||||||
var old_segs: [max_path_segments][]const u8 = undefined;
|
|
||||||
const old_n = split_path(p.key, &old_segs) orelse return error.InvalidUpdate;
|
|
||||||
const v = get_value(pairs.items, old_segs[0..old_n]) orelse continue; // no-op when absent
|
|
||||||
unset_path(arena, pairs, old_segs[0..old_n]);
|
|
||||||
var new_segs: [max_path_segments][]const u8 = undefined;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return error.InvalidUpdate;
|
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| {
|
||||||
|
if (std.mem.eql(u8, p.key, "_id")) return error.ImmutableId;
|
||||||
|
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, opts.diag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op_unset(
|
||||||
|
arena: std.mem.Allocator,
|
||||||
|
pairs: *std.ArrayListUnmanaged(bson.Pair),
|
||||||
|
ops: []const bson.Pair,
|
||||||
|
opts: Options,
|
||||||
|
) UpdateError!void {
|
||||||
|
for (ops) |p| {
|
||||||
|
for (try resolve(arena, pairs.items, p.key, opts)) |segs| {
|
||||||
|
unset_path(arena, pairs, segs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op_inc(
|
||||||
|
arena: std.mem.Allocator,
|
||||||
|
pairs: *std.ArrayListUnmanaged(bson.Pair),
|
||||||
|
ops: []const bson.Pair,
|
||||||
|
opts: Options,
|
||||||
|
) UpdateError!void {
|
||||||
|
for (ops) |p| {
|
||||||
|
for (try resolve(arena, pairs.items, p.key, opts)) |segs| {
|
||||||
|
const current = get_value(pairs.items, segs) orelse bson.Value{ .int32 = 0 };
|
||||||
|
if (!current.is_number() or !p.value.is_number()) return error.InvalidUpdate;
|
||||||
|
const sum = try numeric_add(current, p.value);
|
||||||
|
try set_path(arena, pairs, segs, sum, p.key, opts.diag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op_push(
|
||||||
|
arena: std.mem.Allocator,
|
||||||
|
pairs: *std.ArrayListUnmanaged(bson.Pair),
|
||||||
|
ops: []const bson.Pair,
|
||||||
|
opts: Options,
|
||||||
|
) UpdateError!void {
|
||||||
|
for (ops) |p| {
|
||||||
|
for (try resolve(arena, pairs.items, p.key, opts)) |segs| {
|
||||||
|
const current_opt = get_value(pairs.items, segs);
|
||||||
|
var items: std.ArrayListUnmanaged(bson.Value) = .empty;
|
||||||
|
defer items.deinit(arena);
|
||||||
|
if (current_opt) |current| {
|
||||||
|
switch (current) {
|
||||||
|
.array => |arr| try items.appendSlice(arena, arr),
|
||||||
|
.null => {},
|
||||||
|
else => return error.InvalidUpdate, // non-array field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (p.value == .doc) {
|
||||||
|
if (bson.get_pair(p.value.doc, "$each")) |each| {
|
||||||
|
const arr = switch (each) {
|
||||||
|
.array => |a| a,
|
||||||
|
else => return error.InvalidUpdate,
|
||||||
|
};
|
||||||
|
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, opts.diag);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try items.append(arena, try bson.copy_value(arena, p.value));
|
||||||
|
try set_path(arena, pairs, segs, .{ .array = try items.toOwnedSlice(arena) }, p.key, opts.diag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op_pull(
|
||||||
|
arena: std.mem.Allocator,
|
||||||
|
pairs: *std.ArrayListUnmanaged(bson.Pair),
|
||||||
|
ops: []const bson.Pair,
|
||||||
|
opts: Options,
|
||||||
|
) UpdateError!void {
|
||||||
|
for (ops) |p| {
|
||||||
|
for (try resolve(arena, pairs.items, p.key, opts)) |segs| {
|
||||||
|
const current = get_value(pairs.items, segs) orelse continue;
|
||||||
|
const arr = switch (current) {
|
||||||
|
.array => |a| a,
|
||||||
|
else => return error.InvalidUpdate,
|
||||||
|
};
|
||||||
|
var items: std.ArrayListUnmanaged(bson.Value) = .empty;
|
||||||
|
defer items.deinit(arena);
|
||||||
|
for (arr) |elem| {
|
||||||
|
if (!pull_matches(arena, p.value, elem)) {
|
||||||
|
try items.append(arena, elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try set_path(arena, pairs, segs, .{ .array = try items.toOwnedSlice(arena) }, p.key, opts.diag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `$rename` alone keeps the plain split: `validate` has already refused a
|
||||||
|
/// positional path on either end of it, which is what mongod does too.
|
||||||
|
fn op_rename(
|
||||||
|
arena: std.mem.Allocator,
|
||||||
|
pairs: *std.ArrayListUnmanaged(bson.Pair),
|
||||||
|
ops: []const bson.Pair,
|
||||||
|
opts: Options,
|
||||||
|
) UpdateError!void {
|
||||||
|
for (ops) |p| {
|
||||||
|
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;
|
||||||
|
var old_segs: [max_path_segments][]const u8 = undefined;
|
||||||
|
const old_n = split_path(p.key, &old_segs) orelse return error.InvalidUpdate;
|
||||||
|
const v = get_value(pairs.items, old_segs[0..old_n]) orelse continue; // no-op when absent
|
||||||
|
unset_path(arena, pairs, old_segs[0..old_n]);
|
||||||
|
var new_segs: [max_path_segments][]const u8 = undefined;
|
||||||
|
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, opts.diag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn doc_pairs(v: bson.Value) ?[]const bson.Pair {
|
fn doc_pairs(v: bson.Value) ?[]const bson.Pair {
|
||||||
return switch (v) {
|
return switch (v) {
|
||||||
.doc => |pairs| pairs,
|
.doc => |pairs| pairs,
|
||||||
|
|||||||
Reference in New Issue
Block a user