Repository navigation
UpdateExpression ADD creates no missing map on its path - #596
Open
LeeroyHannigan wants to merge 3 commits into
Open
LeeroyHannigan wants to merge 3 commits into
LeeroyHannigan wants to merge 3 commits into
Conversation
Amazon DynamoDB applies `ADD #m.#a :n` only when every element above the
leaf exists and is the container the next element needs; otherwise it
answers `The document path provided in the update expression is invalid
for update` and writes nothing (measured 2026-10-26 at 02:38 and 02:54
UTC, each case twice). ExtendDB created each missing map, so
`ADD #z.#a :one` on an item without `z` stored `z: {a: 1}`, and on a
missing key created the item.
`apply_add` now reaches the leaf's parent through
`navigate_to_existing_parent_map`, which refuses a missing or wrong-type
element, and an index past the end of a list, with the service's text.
REMOVE and DELETE through a missing parent are unchanged: the service
accepts them when the key holds no item and refuses them otherwise, and
the evaluator does not know which case it is in.
Dual-target wire tests from the 2026-10-26 measurements: a missing top-level attribute, a missing map below an existing one, a parent of the wrong type for a name or an index, an index past the end of a list, a missing key, the request's other actions left unapplied, a failing and a passing condition, and the TransactWriteItems cancellation reason; and the applied cases, a member of an existing map and of a map inside a list.
An index past the end of a list was refused in the tests only where the
list held a number, which a parent of the wrong type refuses too, so an
index clamped to the last element passed them. The new rows put a map in
the list: `ADD #m.#l[3].#a :one` with `m: {l: [{}]}` and `ADD #l[0].#a
:one` with `l: []`, both refused by the service (measured 2026-10-26 at
03:33 UTC, twice), and the matching unit test.
LeeroyHannigan
requested review from
amrith,
c33howard,
jcshepherd,
pdf-amzn,
robinnsc and
yesyayen
as code owners
October 26, 2026 04:25
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An UpdateExpression
ADDon a nested path now needs every element above the leaf to exist, as DynamoDB requires. Before, ExtendDB created each missing map on the way, soADD #z.#a :oneon an item withoutzstoredz: {a: 1}, and on a missing key it created the item with that map.What the service answers
Measured on 2026-10-26 at 02:38, 02:54, 03:33 and 03:41 UTC in us-east-1, each case twice with the same answer.
ADD #m.#a :onewithman empty map is applied (m: {a: 1}), with a number or a set. So isADD #l[0].#a :onewithla list holding an empty map.The document path provided in the update expression is invalid for updateand writes nothing, the request's other actions included. That covers a missing top-level attribute, a missing map below an existing one, a string, number or list where a map is needed, a map or a string where a list is needed, and an index past the end of a list.ADD #z.#a :oneanswers the same and creates no item.ValidationErrorreason carrying the same text.The change
crates/core/src/expression/update_evaluator.rs:apply_addreaches the leaf's parent through the newnavigate_to_existing_parent_map, which refuses a missing or wrong-type element with the service's text, in place ofnavigate_to_parent_map_or_create. An index past the end of a list above the leaf was answered withThe provided expression refers to an attribute that does not exist in the item; it now has the service's text too. The file goes from 642 to 628 lines, and the new unit tests are in a file of their own. Every backend applies nestedADDthrough this function: the MongoDB native update path takes only one-element paths.What is not changed here
REMOVEandDELETEthrough a missing or wrong-type parent. The service refuses them on an existing item with the same text and, when the key holds no item, answers OK and stores nothing (measured at 02:54 UTC). ExtendDB treats the action as a no-op in both cases; on a missing key it then stores an item with only the key, which fix(update): write nothing for a removal-only UpdateItem on a missing key #508 addresses. Telling the two cases apart needs the evaluator to know whether the item existed, which is a separate change.ADDinto a list element (ADD #l[0] :one). The service adds to the element, or appends when the index is past the end; ExtendDB refuses it, before and after this change, now with the service's refusal text for a map parent.ADDpath against the item as it was before the update. ExtendDB applies SET and REMOVE first, soSET #l[5] = :m ADD #l[1].#a :oneonl: [1]is refused by the service and applied by ExtendDB, andREMOVE #l[0] ADD #l[1].#a :oneis applied by the service to the element that was at index 1 and by ExtendDB to the next one or refused.SET #z = :m ADD #z.#a :oneandREMOVE #m ADD #m.#a :oneanswer the service's overlapping paths error; ExtendDB has no overlap check on UpdateExpression (fix(expressions): validate in the service's order, with its messages #399 adds one).ADDof a string or a list answers the service's operand type error before the path is looked at; ExtendDB answers its own operand text where the parent exists and the path text where it does not (fix(expressions): validate in the service's order, with its messages #399 adds the service's operand check).Tests
crates/core/src/expression/update_add_parent_tests.rs(new): a member of an existing map, a missing parent, a parent of the wrong type, an index past the end of a list of maps, a map inside a list.tests/test_update_add_missing_parent.py: 23 dual-target wire tests from the measurements.Run locally against SQLite on 2026-10-26:
main: 15 failed, 8 passed. The 8 that pass onmainare the applied cases, the failing condition, and the four refusals wheremainalready refused a parent that is not a map (a string, a number, a list, or an index into a string).tests/whose test names matchupdate,add,transact,nested,item_operations,conditionalorset: 461 passed, 7 skipped, with the new file at 21 tests; onmainthe same 448 pass and the new file's 13 fail.tests/pythonfiles matchingupdate,add,transact,nestedorset: 73 passed.cargo +1.97.0 fmt --all -- --check,cargo +1.97.0 clippy --all-targets -- -D warnings,cargo test --workspace --exclude extenddb-storage-cassandraon stable and on 1.88.0 (1,772 passed each), andcargo test -p extenddb-storage-cassandra --lib(61 passed). The 21-test version of the new file passed on PostgreSQL 16, MongoDB 7 and Cassandra 4.1 as well; on PostgreSQL and MongoDB the related files fail the same tests at the head as onmain, apart from the new file's.CI runs the new file in
run-integration,run-integration-sqlite,mongodb-pytestandcassandra-pytest.Merging
git merge-tree --write-treeis clean against #590, the one open PR that changesupdate_evaluator.rs, and the merged tree passes both PRs' wire files. #590 checks the nesting depth of ADD targets because ADD created missing maps; after this change its testADD path(29).#a.#b.#c :oneis refused for the missingabefore the depth check, and #590's comment on ADD needs updating in whichever PR merges second. Its depth check on ADD still covers an item stored deeper than the limit before the check existed.