Repository navigation
Description
Describe the bug
Follow-up to #411. The service refuses a PartiQL UPDATE whose SET or REMOVE path has 41 levels, for the nesting of the path, before it reads the item. ExtendDB at 0e4b4a8 does not count the levels of a document path. The SET form reaches the update evaluation and gets the invalid-path text, because the item's a holds a number. The REMOVE form runs and changes nothing.
The path is a.b0.b1.b2 ... .b39: the name a and 40 steps b0 to b39.
Measured against Amazon DynamoDB in us-east-1 on 2026-11-06. Table: pk S hash key, sk S range key. The item pk = 'h', sk = 's' holds a as {"N": "1"}. The ExtendDB answers are from 0e4b4a8 with the SQLite backend, debug build.
| Statement | Operation | DynamoDB | ExtendDB at 0e4b4a8 |
|---|---|---|---|
UPDATE "<t>" SET a.b0.b1 ... .b39 = 1 WHERE pk = 'h' AND sk = 's' |
ExecuteStatement | 400 ValidationException: The document path has too many nesting levels; nesting levels: 41 |
400 ValidationException: The document path provided in the update expression is invalid for update |
the same SET statement between two INSERT statements |
BatchExecuteStatement | the two inserts run, and the SET statement carries ValidationError with the nesting text and no TableName |
the two inserts run, and the SET statement carries ValidationError with the invalid-path text and a TableName |
UPDATE "<t>" REMOVE a.b0.b1 ... .b39 WHERE pk = 'h' AND sk = 's' |
ExecuteStatement | 400 ValidationException with the nesting text |
200, {"Items": []} |
the same REMOVE statement between two INSERT statements |
BatchExecuteStatement | the two inserts run, and the REMOVE statement carries ValidationError with the nesting text |
all three statements run |
Not measured: ExecuteTransaction, the same path on a classic UpdateItem, and a SET path into an attribute that holds a map. Also not measured: a path with fewer than 41 levels, so the depth at which the service starts to refuse is unknown.
To Reproduce
- Start ExtendDB at 0e4b4a8 with the SQLite backend.
- Create the table above and put the item
pk = 'h', sk = 's', a = 1. - Send the four requests in the table.
- Compare the answers with the DynamoDB column.
Expected behavior
The DynamoDB column. The service refuses both statements with The document path has too many nesting levels; nesting levels: 41. In a batch the refusal is the statement's own error.
Actual behavior
The ExtendDB column. ExtendDB refuses the SET statement with the invalid-path text, with a TableName in a batch, and runs the REMOVE statement.
Environment
- ExtendDB version: the feat(partiql): implement ExecuteStatement, BatchExecuteStatement, ExecuteTransaction #411 branch at 0e4b4a8, SQLite backend, debug build
- Operating system: Linux
- Rust version (if building from source): 1.97.0
- Client SDK/driver and version: boto3
- Deployment method (binary, container, source): source
Proposed change
Add a depth check of every SET and REMOVE path to the translation of an UPDATE. The check answers the service's text with the level count. It runs before ExtendDB parses the classic update expression and before it reads the item.
The translation builds the paths in build_update_parts (crates/engine/src/partiql/write/update.rs) through ExprBuilder::path (crates/engine/src/partiql/write/expr_builder.rs). request_actions (update.rs) parses the result with parse_update_expr. Neither counts the steps of a path. The invalid-path text comes from the update evaluation in crates/core/src/expression/update_evaluator.rs, when a step meets a value that is not a map. The check could also go with the other checks of written values in check_written_values (crates/core/src/partiql/values.rs).
Measure the depth at which the service starts to refuse before writing the check. 41 levels was the only depth measured.
A search of the crates for the service's text finds no producer, so the classic UpdateItem has no document path depth check either. Whether the service refuses a classic UpdateItem with the same path, and with which text, was not measured. If it does, the check belongs in the classic update expression parser, and the PartiQL translation gets it from there.
Additional context
The four cases come from the measurements made for #411. #813 covers another depth limit on the same branch, a parameter nested 32 levels deep that the statement never stores.
Activity