Skip to content

fix: KeyConditionExpression rejects parenthesized sub-expressions#7

Merged
hicksy merged 2 commits into
nubo-db:mainfrom
AnatolyRugalev:fix/key-condition-parens
Apr 24, 2026
Merged

fix: KeyConditionExpression rejects parenthesized sub-expressions#7
hicksy merged 2 commits into
nubo-db:mainfrom
AnatolyRugalev:fix/key-condition-parens

Conversation

@AnatolyRugalev

@AnatolyRugalev AnatolyRugalev commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Summary

The KeyConditionExpression parser rejects parentheses around sub-expressions, returning ValidationException: Expected attribute name, got (. Both DynamoDB and DynamoDB Local accept these expressions. ConditionExpression handles parentheses correctly — the bug is specific to the KeyConditionExpression parser.

Version: dynoxide 0.9.8

Reproduction

dynoxide --port 8000 &

export AWS_ACCESS_KEY_ID=fake AWS_SECRET_ACCESS_KEY=fake \
       AWS_DEFAULT_REGION=us-east-1 EP=http://localhost:8000

# Create table
aws dynamodb create-table --endpoint-url $EP \
  --table-name test --billing-mode PAY_PER_REQUEST \
  --attribute-definitions AttributeName=PK,AttributeType=S AttributeName=SK,AttributeType=S \
  --key-schema AttributeName=PK,KeyType=HASH AttributeName=SK,KeyType=RANGE

# Seed data
aws dynamodb put-item --endpoint-url $EP --table-name test \
  --item '{"PK":{"S":"user#1"},"SK":{"S":"profile"},"name":{"S":"Alice"}}'

# ✅ Works — no parens
aws dynamodb query --endpoint-url $EP --table-name test \
  --key-condition-expression "#pk = :pk AND #sk = :sk" \
  --expression-attribute-names '{"#pk":"PK","#sk":"SK"}' \
  --expression-attribute-values '{":pk":{"S":"user#1"},":sk":{"S":"profile"}}'

# ❌ Fails — parens around each condition
aws dynamodb query --endpoint-url $EP --table-name test \
  --key-condition-expression "(#pk = :pk) AND (#sk = :sk)" \
  --expression-attribute-names '{"#pk":"PK","#sk":"SK"}' \
  --expression-attribute-values '{":pk":{"S":"user#1"},":sk":{"S":"profile"}}'
# => ValidationException: Expected attribute name, got (

Root cause

parse_single_condition in key_condition.rs doesn't handle Token::LParen — it immediately tries to parse an attribute path, which fails on (.

Fix

Two levels of parenthesis support:

  1. Outer parensstrip_outer_parens removes balanced parens wrapping the entire expression before parsing (handles (pk = :pk AND sk = :sk) and ((expr)))
  2. Per-condition parensparse_single_condition counts and consumes wrapping parens around individual conditions (handles (pk = :pk) AND (sk = :sk))

Unit tests cover: per-condition parens, outer parens, nested parens ((pk = :pk)), mixed nesting (((pk = :pk)) AND ((begins_with(...)))), and parens with #name references.

Also submitted as a conformance test: nubo-db/dynamodb-conformance#1

Fixes #4

@hicksy

hicksy commented Apr 22, 2026

Copy link
Copy Markdown
Member

Nice - the nested and mixed parens coverage looks thorough. Reviewing alongside the other two.

@hicksy hicksy force-pushed the fix/key-condition-parens branch from 2bd42d4 to 233af0f Compare April 24, 2026 17:03
@hicksy

hicksy commented Apr 24, 2026

Copy link
Copy Markdown
Member

Two small edits pushed to your branch:

  1. Rebased onto current main as our workflows have been updated there recently.
  2. cargo fmt (two trailing semicolons in the new consume_close_parens). No logic change.

As you were a first time contributor the auto CI checks needed approval. They should run now. Will merge once green.

AnatolyRugalev and others added 2 commits April 24, 2026 18:55
DynamoDB accepts parentheses in KeyConditionExpression — e.g.
(#pk = :pk) AND (#sk = :sk) or ((pk = :pk AND sk = :sk)). The parser
rejected them with "Expected attribute name, got (".

Fix: strip balanced outer parens from the token list before parsing,
and handle per-condition parens in parse_single_condition.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@hicksy hicksy force-pushed the fix/key-condition-parens branch from 233af0f to 9eb867e Compare April 24, 2026 17:55
@hicksy

hicksy commented Apr 24, 2026

Copy link
Copy Markdown
Member

Quick follow-up: the earlier CI run failed on a pre-existing clippy lint on main (new iter_kv_map lint in Rust 1.95, unrelated to this PR). Pushed a fix on main and rebased your branch on top. CI re-running now.

@hicksy hicksy merged commit 57ae30d into nubo-db:main Apr 24, 2026
6 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.

KeyConditionExpression parser fails on parenthesized sub-expressions

2 participants