Skip to content

fix: SET snapshot semantics and parenthesised arithmetic#158

Merged
jcshepherd merged 3 commits into
mainfrom
fix/set-evaluation-snapshot
Jun 11, 2026
Merged

fix: SET snapshot semantics and parenthesised arithmetic#158
jcshepherd merged 3 commits into
mainfrom
fix/set-evaluation-snapshot

Conversation

@LeeroyHannigan

Copy link
Copy Markdown
Collaborator

What

Fix two UpdateItem expression evaluation bugs:

  1. SET snapshot semantics: Multiple SET clauses now evaluate their RHS against the pre-update item snapshot. Previously SET a = :v, b = a would assign b the new value of a instead of the original.

  2. Parenthesised arithmetic: SET c = (c - :v) now parses correctly. The SET value parser previously rejected ( as an unexpected token.

Why

DynamoDB evaluates the entire UpdateExpression against a single pre-update snapshot, no SET clause can observe the effect of another SET clause in the same request. This is documented behavior that applications rely on (e.g., swap patterns like SET a = b, b = a).

Parenthesised arithmetic is valid DynamoDB syntax and used by SDKs and ORMs that defensively wrap expressions.

Testing done

  • Verified both behaviors against real DynamoDB first
  • cargo fmt --all -- --check — clean
  • cargo clippy --all-targets -- -D warnings — clean
  • cargo test --workspace — all pass (2 new unit tests)
  • New tests/test_set_evaluation.py — 3 integration tests (snapshot semantics, subtraction in parens, addition in parens), all pass against ExtendDB

Checklist

  • I have read CONTRIBUTING.md
  • All tests pass (cargo test --workspace)
  • Code is formatted (cargo fmt --check)
  • Clippy is clean (cargo clippy -- -W clippy::pedantic)
  • I have added or updated tests for new functionality
  • I have updated documentation if behavior changed
  • Breaking changes are noted below (if any)
  • If this changes the wire protocol, Storage trait, auth model, on-disk
    format, or public CLI surface, an RFC has been accepted or is linked
    below. Otherwise, an ADR captures the decision (link below).

ADR / RFC: n/a


By submitting this pull request, I confirm that my contribution is made under
the terms of the Apache License 2.0 and I agree to the Developer Certificate of
Origin (DCO). See CONTRIBUTING.md for details.

@pdf-amzn

pdf-amzn commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

This creates mutual recursion (parse_set_operand → parse_value_expr → parse_set_operand) without a depth limit. Deeply nested parens like SET c = ((((((c - :v)))))) would recurse unboundedly. In practice, DynamoDB's 4KB expression limit caps nesting at ~1000 levels, which shouldn't blow the stack on most systems. The condition parser has a 150-depth limit for reference. Should we do the same here?

@LeeroyHannigan LeeroyHannigan force-pushed the fix/set-evaluation-snapshot branch from 5c88ecd to 64254a1 Compare June 3, 2026 13:50
@LeeroyHannigan

Copy link
Copy Markdown
Collaborator Author

This creates mutual recursion (parse_set_operand → parse_value_expr → parse_set_operand) without a depth limit. Deeply nested parens like SET c = ((((((c - :v)))))) would recurse unboundedly. In practice, DynamoDB's 4KB expression limit caps nesting at ~1000 levels, which shouldn't blow the stack on most systems. The condition parser has a 150-depth limit for reference. Should we do the same here?

Good catch on both fronts. Updated: parse_update_from now calls check_redundant_parens upfront (same function used by the condition/filter parsers), so SET c = ((c - :v)) is rejected with the canonical "redundant parentheses" message. Verified against real DDB that single parens are accepted and double parens are rejected.

This also eliminates the unbounded recursion concern, since (( is rejected before parsing begins, the LParenparse_value_exprparse_set_operand path can only recurse once (for a single valid paren group).

}

#[test]
fn single_parens_accepted() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you wanted bonus points, could check for error on unmatched parens.

jcshepherd
jcshepherd previously approved these changes Jun 5, 2026

@jcshepherd jcshepherd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One suggestion for an additional unit test but looks good otherwise.

Adds the unit tests suggested on #158: an opening paren with no matching
close, and a stray closing paren, must each raise ValidationException
rather than panic or be silently accepted.
@jcshepherd jcshepherd added this pull request to the merge queue Jun 11, 2026
Merged via the queue into main with commit 5b63831 Jun 11, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants