Repository navigation
Description
Describe the bug
Follow-up to #411. The PartiQL parser at 0e4b4a8 caps the nesting of a statement in crates/core/src/partiql/parser/limits.rs: 1000 parenthesized groups (MAX_GROUP_DEPTH), 1000 literal containers (MAX_LITERAL_DEPTH) and 100 nested operands (MAX_OPERAND_DEPTH). Past a cap, ExtendDB refuses the statement with Statement wasn't well formed, can't be processed: expression nesting depth exceeded. The service runs, or refuses for another reason, several forms that these caps refuse:
- Parentheses round an operand (
a = (((b))),a = ((('x'))),SET x = (((a)))) from 100 levels. Each level counts as a group and as an operand (operand_in_parentheses,crates/core/src/partiql/parser/operands.rs), so the statement meets the operand cap first. The service runs 100, 101, 999, 1000 and 1001 levels. - Parentheses round a path on the left of a comparison (
(((a))) = 1) and round anINSERTvalue ('x': ((('v')))) from 1001 levels, and round anINmember (a IN (((1)))) from 1002 levels. The service runs them. if_not_existsandlist_appendcalls nested in aSETvalue from 100 levels. The service runs 100 and 101.- Forms the service refuses for another reason at depths past the caps. The service reads
sizenested 101 to 1,355 times,NOT100 to 2,031 times before aSETvalue, andCASE100 to 1,626 times. It also reads 1,160 nested maps in anINSERTvalue, 2,033 nested bags, and 1,354 levels off(j[inside the bracket of a path.
Measured against Amazon DynamoDB in us-east-1 on 2026-11-06. Table: pk S hash key, sk S range key, with an item pk = 'h', sk = 's' that holds a = 1 and a map m. Operation: ExecuteStatement. A condition form in the table is the tail of SELECT * FROM "<t>" WHERE pk = 'h' AND . In a statement, x N after a code span means N copies of that span. ConditionalCheckFailedException on an UPDATE means the statement ran and found no item with sk = 'w'. DuplicateItemException on an INSERT means the statement ran and found an item with that key. The ExtendDB answers are from 0e4b4a8 with the SQLite backend, debug build.
| Statement | DynamoDB | ExtendDB at 0e4b4a8 |
|---|---|---|
a = ( x 99 b ) x 99 |
runs, 0 items | runs, 0 items |
a = ( x 100 b ) x 100 |
runs, 0 items | expression nesting depth exceeded |
a = ( x 1001 b ) x 1001 |
runs, 0 items | the depth refusal |
a = ( x 100 'x' ) x 100 |
runs, 0 items | the depth refusal |
UPDATE "<t>" SET x = ( x 100 a ) x 100 WHERE pk = 'h' AND sk = 'w' |
ConditionalCheckFailedException |
the depth refusal |
( x 1000 a ) x 1000 = 1 |
runs, 1 item | runs, 1 item |
( x 1001 a ) x 1001 = 1 |
runs, 1 item | the depth refusal |
INSERT INTO "<t>" VALUE {'pk': 'h', 'sk': 'i', 'x': ( x 1001 'v' ) x 1001 } |
DuplicateItemException |
the depth refusal |
a IN ( x 1001 1 ) x 1001 |
runs, 1 item | runs, 1 item |
a IN ( x 1002 1 ) x 1002 |
runs, 1 item | the depth refusal |
UPDATE "<t>" SET x = if_not_exists(x, x 99 1 ) x 99 WHERE pk = 'h' AND sk = 'w' |
ConditionalCheckFailedException |
ConditionalCheckFailedException |
the same with 100 and with 101 nested if_not_exists calls |
ConditionalCheckFailedException |
the depth refusal |
UPDATE "<t>" SET l = list_append( x 100 l , [1]) x 100 WHERE pk = 'h' AND sk = 'w', and the same with 101 |
ConditionalCheckFailedException |
the depth refusal |
(size( x 101 a )) x 101 = 1, and the same with 1,355 |
Incorrect operand type for operator or function; operator or functi (the measurement cut the text there) |
the depth refusal |
UPDATE "<t>" SET x = NOT x 100 a WHERE pk = 'h' AND sk = 'w', and the same with 2,031 |
Unsupported operator in Update Expression. Operator: not |
the depth refusal |
a = CASE x 100 b, and the same with 1,626 |
Statement wasn't well formed, can't be processed: Expected a WHEN clause in CASE |
the depth refusal |
INSERT INTO "<t>" VALUE {'pk': 'h', 'sk': 'i', 'x': {'a': x 1160 1 } x 1160 } |
Nesting Levels have exceeded supported limits under root.a with .a repeated to 32 levels |
the depth refusal |
a = << x 2033 1 >> x 2033 |
Unsupported data type in Bag. DynamoDB only supports either numbers or strings in bags |
the depth refusal |
1,354 levels of f(j[ inside the bracket of a path |
Unexpected path component at the first k |
the depth refusal |
The operand type and the operator rows show that the service read the whole statement at those depths.
To Reproduce
- Start ExtendDB at 0e4b4a8 with the SQLite backend.
- Create the table above and put the item.
- Send the statements in the table with ExecuteStatement.
- Compare the answers with the DynamoDB column.
Expected behavior
The DynamoDB column. The service runs 1,001 levels of parentheses round a path or a value, and 101 nested if_not_exists or list_append calls. It reads 1,355 nested size calls, 2,031 NOT operators, 1,626 CASE words, 1,160 nested maps and 2,033 nested bags to their end.
Actual behavior
The ExtendDB column. ExtendDB refuses an operand in parentheses from 100 levels, the other parenthesized forms from 1001 levels, and nested calls from 100 levels. It refuses every form in the last six rows for its depth.
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
Raise the operand cap (MAX_OPERAND_DEPTH, 100) and the group cap (MAX_GROUP_DEPTH, 1000). Stop counting an operand in parentheses against the operand cap. Size LARGE_STACK_BYTES (64 MiB) for the higher caps. Parser::nested (limits.rs) applies the caps, and parse_on_a_fitting_stack parses a deep statement again on a thread with the large stack.
The service's limit for nested operands is not measured (the comment on MAX_OPERAND_DEPTH). The deepest forms the service read in this pass are the ones in the table: 1,001 parentheses and 101 nested calls. It also read 1,355 nested size calls and refused them for the operand type. The comments in limits.rs record that the service fails 2000 parenthesized groups and 4000 nested lists with an internal error.
The module comment of limits.rs gives the stack use per level on a debug build. A parenthesized group uses 8.6 KB, a nested operand 6.8 KB, and a literal container 2.5 to 2.8 KB. The worst parse the present caps allow used 12.2 MB on its own thread. Three tests in crates/core/src/partiql/parser/limits/tests.rs check the bounds on a thread with a 1 MiB stack and need the new caps: every_nesting_form_at_the_length_limit_parses_on_a_small_stack, a_deep_statement_takes_little_of_the_callers_stack and the_deepest_statements_that_parse_are_walked_on_a_small_stack. The last one runs the checks the server makes on a parsed statement (check_condition_values, check_ordering_operands and check_written_values) and the plan walk over the deepest statements that parse. Its comment records 870 KB for the literal checks of a condition with a value 1,000 levels deep.
Additional context
At the statement length limit of 8,192 characters the service answers InternalFailure for most of these forms. ExtendDB refuses them with the depth text or runs them. limits.rs documents that refusal as the design for a statement the service fails with an internal error. This issue proposes no change for it.
Two groups of rows are parser forms, which #887 covers. In the size rows at 10 to 100 levels, ExtendDB runs the statement and the service refuses the operand type. In the CASE and NOT rows at 10 and 99 levels, the two syntax texts differ.
Activity