Reject unsupported modifiers after $each in $addToSet#8
Conversation
MongoDB rejects $addToSet specs that contain any field after $each (e.g. $position, $slice, $sort), but DocumentDB silently accepted and applied them as if only $each was provided. Add validation in ValidateAddToSetWithDollarEach to error with 'Found unexpected fields after $each in $addToSet' matching MongoDB behavior, and add regression tests. Fixes documentdb#505 Signed-off-by: richardsimmonds <richardsimmonds314@gmail.com>
richardsimmonds
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: LGTM — recommend merge. No blocking issues. One minor, non-blocking divergence from MongoDB noted inline.
Correctness
- Validation is correctly scoped: it only runs when
TryGetBsonValueToPgbsonElementconfirms$eachis the first field of the update value, so literal-document adds like{a: 1, b: 2}are untouched — matches MongoDB, and the PR description calls this out explicitly. - The call site (
HandleUpdateDollarAddToSet) guaranteesupdateValueis a non-emptyBSON_TYPE_DOCUMENTbeforeValidateAddToSetWithDollarEachruns, soBsonValueInitIteratorcannot hit its non-document error path. No null-deref or iterator-init risk. - The double
bson_iter_nextcorrectly detects any second field, including a duplicate$each. - Error text
Found unexpected fields after $each in $addToSet: {...}andERRCODE_DOCUMENTDB_BADVALUEmatch mongod's message and BadValue code.
Safety
- No leaks:
BsonValueToJsonForLoggingallocates only in the error path, cleaned up by PG error handling. Iterator is stack-local over borrowed data.
Tests
- SQL + expected output updated in both
pg_documentdbandinternal/pg_documentdb_distributedcopies, covering$position,$slice,$sort, and an arbitrary trailing field (x). Matches the existing$addToSettest patterns in the file, including the NULL-arg signature differences between the two copies — correctly handled.
Scope / DCO / CI
- Diff is minimal: 18 lines of C + 28 lines of tests, nothing unrelated.
- Commit c7931f6 has a valid
Signed-off-byline. - CI: no status checks reported on the fork for this SHA — nothing to verify.
Reviewed by Hermes reviewer profile
| BsonValueInitIterator(updateValue, &updateValueIter); | ||
|
|
||
| /* Skip the first field ($each) and check for any trailing fields. */ | ||
| if (bson_iter_next(&updateValueIter) && bson_iter_next(&updateValueIter)) |
There was a problem hiding this comment.
Minor (non-blocking): mongod's AddToSetNode::init validates that $each is an array before checking for unexpected trailing fields. Here the order is inverted, so a spec like {$addToSet: {letters: {$each: "c", $position: 1}}} raises BadValue (Found unexpected fields...) in DocumentDB but TypeMismatch (The argument to $each... must be an array) in MongoDB. Both reject the spec, so this is cosmetic — but for exact parity, move this block below the existing $each-must-be-array check, and consider a regression test for the combined case to pin whichever precedence you choose.
Summary
Fixes documentdb#505 —
$addToSetincorrectly accepted$position,$slice, and$sortmodifiers (and any other field after$each), silently applying the update as if only$eachwas given. MongoDB rejects these specs.Changes
pg_documentdb/src/update/bson_update_operators.c: inValidateAddToSetWithDollarEach, after detecting$eachas the first field, iterate the update value and error if any additional field is present:Found unexpected fields after $each in $addToSet: { "$each" : [...], "$position" : 1 }using
ERRCODE_DOCUMENTDB_BADVALUE, matching MongoDB's error text.bson_update_document_tests(bothpg_documentdbandinternal/pg_documentdb_distributedcopies) covering$position,$slice,$sort, and an arbitrary trailing field after$each.Behavior
Before:
{$addToSet: {key: {$each: [...], $position: 1}}}succeeded and added elements.After: errors with
Found unexpected fields after $each in $addToSet: ...— same as MongoDB.Note:
$addToSetwith a document value whose first field is NOT$each(e.g.{a: 1, b: 2}) is still treated as a literal element to add, unchanged — this matches MongoDB, which only engages modifier parsing when$eachis the first field.