Repository navigation
Refuse a document path of more than 32 elements - #597
Open
LeeroyHannigan wants to merge 4 commits into
Open
LeeroyHannigan wants to merge 4 commits into
LeeroyHannigan wants to merge 4 commits into
Conversation
LeeroyHannigan
requested review from
amrith,
c33howard,
jcshepherd,
pdf-amzn,
robinnsc and
yesyayen
as code owners
October 26, 2026 06:32
LeeroyHannigan
force-pushed
the
fix/document-path-length-on-399
branch
from
October 30, 2026 09:07
9e2754c to
90b673b
Compare
Amazon DynamoDB refuses a document path of more than 32 elements, each attribute name and each list index counting as one, in every expression kind, with `Invalid <Kind>: The document path has too many nesting levels; nesting levels: <n>` (measured 2026-10-26 at 02:26, 02:29, 03:38 and 03:39 UTC, each request twice). It checks each path's `#name` references and then its length, path by path in text order. ExtendDB had no limit: a 33-element path was evaluated, and an ADD on one created the missing maps. The check runs where each path's names are resolved: in the semantic walk's `check_path_refs` for update and condition expressions, in `resolve_paths` for projections, and in the key condition parser before the nested-attribute refusal. The grammar pass still runs first, so a syntax error is reported before the length. Two checks move to the service's position so the new refusal does not answer ahead of them (measured 2026-10-26 at 04:40, 04:41 and 04:43 UTC): - The BETWEEN bounds check (both bounds placeholders of different types, or the lower above the upper) runs in the semantic walk, so it answers at request time on a missing key, after a false AND and on an empty Query, as the service does; it ran only while evaluating an item. - UpdateItem and PutItem check the nesting depth of ExpressionAttributeValues right after the table name, before the expressions, the table lookup and the key, as DeleteItem already did.
Dual-target wire tests from the 2026-10-26 measurements: 32 elements accepted and 33 or more refused in every expression kind and operation; an undefined `#name` before or inside a long path, and a filter or condition fault, reported before the length; a value nested too deep reported before a long path or a syntax error; BETWEEN bounds refused on a missing key, after a false AND and on an empty Query. The at-the-limit condition uses `size()` of the 32-element path. PutItem, UpdateItem and DeleteItem prefix expression errors with `1 validation error detected: `, so those tests accept the message either way.
The service reports an invalid number in the Item or the Key, then legacy and expression parameters used together, before an ExpressionAttributeValue nested more than 32 levels (measured 2026-10-26 at 05:15 and 05:20 UTC, each request twice). The earlier move put the nesting check ahead of both: UpdateItem now runs it right after the check for mixed parameters, and PutItem checks the Item's numbers just before it. DeleteItem already ran it after the mixed-parameter check. The path_length module comment now says the key condition parser checks the length before the names are checked, and a unit test covers the BETWEEN bounds in the semantic walk.
UpdateItem with `AttributeUpdates`, `Expected` or `ConditionalOperator`
beside an expression and a 32-level value answers the mixed-parameter
error, and PutItem with `{N: abc}` in the Item, at the top level or
nested, answers the number error (measured 2026-10-26 at 05:15 and 05:20
UTC).
LeeroyHannigan
force-pushed
the
fix/transact-validation-order
branch
from
October 31, 2026 17:50
17b805d to
1454cc9
Compare
LeeroyHannigan
force-pushed
the
fix/document-path-length-on-399
branch
from
October 31, 2026 17:50
90b673b to
8549bc0
Compare
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.
Stacked on #399, whose per-path semantic walk this change uses. After #399 merges, retarget it to
main.An expression whose document path has more than 32 elements is now refused, as DynamoDB refuses it. Before, ExtendDB had no limit: a 33-element path was evaluated, and an UpdateItem
ADDon one created the missing maps and stored the result.What the service answers
Measured on 2026-10-26 at 02:26, 02:29, 03:38, 03:39, 04:21, 04:40, 04:41, 04:43, 05:13, 05:15, 05:20 and 05:40 UTC in us-east-1, each request twice with the same answer, apart from the case noted below.
Invalid <Kind>: The document path has too many nesting levels; nesting levels: <n>. Each attribute name and each list index counts as one element. It holds in UpdateExpression (every action, the value side of SET,if_not_exists,list_append), ConditionExpression (PutItem, UpdateItem, DeleteItem, TransactWriteItems), ProjectionExpression (GetItem, BatchGetItem, TransactGetItems, Query, Scan), FilterExpression (Query, Scan) and KeyConditionExpression. Counts up to 2,040 are reported exactly.#namereferences and then its length, path by path in the order the paths appear. An undefined#namein an earlier path or inside the long path is reported instead; one in a later path is reported after the length. An undefined:value, an unused name or value and overlapping paths are reported after the length.KeyConditionExpressions cannot have conditions on nested attributes.AND, and in a Query of an empty partition.The change
crates/core/src/expression/path_length.rs(new): the limit and the sentence.crates/core/src/expression/semantics/refs.rs:check_path_refs, which fix(expressions): validate in the service's order, with its messages #399's walk calls for every path of an update or condition expression in text order, checks the length after the path's names.crates/core/src/expression/projection.rs:resolve_pathschecks each projection path's length after resolving its names.crates/core/src/expression/key_condition.rs: the length of every key condition path is checked before the nested-attribute refusal.crates/core/src/expression/evaluator.rsandsemantics/condition_rules.rs: the BETWEEN bounds check moves into a function the semantic walk calls; the evaluator keeps calling it too. Without this move, a request with a long update path and a condition with bounds out of order answered the length error, where the service and fix(expressions): validate in the service's order, with its messages #399 answer the bounds error.crates/engine/src/update_item.rsandput_item.rs: the ExpressionAttributeValues nesting check moves earlier. On UpdateItem it runs right after the check for legacy and expression parameters used together; on PutItem right after the table name check, with the Item's numbers checked first. DeleteItem already runs it after the mixed-parameter check. Without this move, a long path beside a value nested too deep answered the length error.docs/dynamodb-limits.md: a row for the limit.Where ExtendDB still answers differently
SET <33 elements> = :s, status = :sanswers the reserved word error on ExtendDB and the length on the service.<>, a function, IN, NOT or a path as the value, andADDwith a path as its value. ExtendDB answers its own syntax messages there, with or without this change.#zz = :p AND <33> = :swith#zzundefined answers the length error, where the service answers the undefined name. fix(expressions): validate in the service's order, with its messages #399's head answers a different message for it as well.Syntax error; token: ..., near: ...(fix(expression): operator cap, syntax error wording, PutItem map keys #558).Tests
crates/core/src/expression/path_length.rs: the limit, list indexes, the key condition order, the projection and update order against#namereferences.semantics/tests.rs: the BETWEEN bounds in the walk.tests/test_document_path_length.py: 53 dual-target wire tests from the measurements. The at-the-limit condition usessize()of the 32-element path, which the MongoDB condition pushdown does not handle, so the test does not depend on MongoDB: a condition on a nested map member fails when it holds #572.Run locally against SQLite on 2026-10-26, with #399's head (4f4fc21) as the base:
1 validation error detected:prefix.tests/whose test names matchexpression,condition,projection,query,scan,update,transact,batch_get,reserved,nested,key_condition,put_item,delete_item,validation,between,number,legacy,expectedorattribute_updates: 1,125 passed, 9 skipped at the head; on fix(expressions): validate in the service's order, with its messages #399's head the same tests pass apart from the new file's 33.tests/pythontests whose names matchexpression,condition,projection,query,scan,update,transact,putordelete: 218 passed on both.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,790 passed each), andcargo test -p extenddb-storage-cassandra --lib(61 passed). The 47-test version of the new file also passed on PostgreSQL and MongoDB.CI runs the new file in
run-integration,run-integration-sqlite,mongodb-pytestandcassandra-pytest.Files
crates/core/src/expression/key_condition.rs(1,912 lines on #399's head) grows by 7 andprojection.rs(693) by 4, the calls. The rest stays under 600 lines or shrinks; the check lives in the newpath_length.rs.Merging
This is based on #399's branch. After #399 merges, retarget it to
main. Since #399's previous base, 9d13202, three main PRs changed files this change also changes: #511key_condition.rsandexpression/mod.rs, #543docs/dynamodb-limits.md, and #429key_condition.rs. This change's diff applies to #399 rebased onto main at f49c5f8.git range-diff 17b805da..90b673b8 1454cc93..8549bc02shows the same diff for all four commits; in the first, thekey_condition.rshunk now sits inparse_clauses, and themod.rshunk follows #511's widerkey_conditionre-export.git merge-tree --write-treeagainst the 11 open PRs that changecrates/core/src/expression/,update_item.rsorput_item.rsadds no conflict: each merges with this change as it merges with #399's head. Of the open PRs that changedocs/dynamodb-limits.md, #335 and #372 conflict in it with main as well, and this change adds no conflict there with any of them.Rebase on main
Restacked on 31 October onto #399's head 1454cc9, which is rebased onto main at f49c5f8. The main PRs that changed files this change also changes are #511, #543 and #429 (see Merging). The four commits apply without conflicts, and none of this change's own lines changed (the range-diff above). The runs in the Tests section were made on 2026-10-26, before the rebase, with #399's head of that day (4f4fc21) as the base.
At 8549bc0:
cargo fmt --all --checkandcargo clippy --locked --workspace --all-targets -- -D warningson 1.97.0 clean;cargo check --locked --workspaceon 1.88.0 passes;cargo test --locked --libpasses for core (732), engine (192) and server (237).tests/test_document_path_length.py(53),tests/test_fgac_leading_keys.py(31) and #429'stests/test_gsi_multi_range_query.py(63, and one case skipped that runs only on a backend that refuses a condition on a later RANGE attribute) pass on a SQLite server and on a PostgreSQL server built from this head.CI passed all 30 checks on
8549bc023.