Skip to content

PartiQL

Dynoxide supports PartiQL across all three operations: ExecuteStatement, BatchExecuteStatement, and ExecuteTransaction. SELECT, INSERT, UPDATE and DELETE all work, with the full WHERE grammar.

aws dynamodb execute-statement \
  --endpoint-url http://localhost:8000 \
  --statement "SELECT * FROM Users WHERE pk = 'user#1'"

Parameters work in every position, including inside nested lists and maps:

aws dynamodb execute-statement \
  --endpoint-url http://localhost:8000 \
  --statement "SELECT * FROM Users WHERE pk = ?" \
  --parameters '[{"S": "user#1"}]'

Supported grammar

Category Support
Comparisons =, <>, <, >, <=, >=
Range and membership BETWEEN, IN
Functions EXISTS, NOT EXISTS, BEGINS_WITH, CONTAINS
Existence IS MISSING, IS NOT MISSING
Logical AND, OR, NOT, and parenthesised grouping
Projections Nested dot-notation paths, COUNT(*)
Pagination LIMIT and NextToken
Literals Set literals (<< >>), negative numbers, escaped quotes

INSERT supports IF NOT EXISTS and rejects duplicates. UPDATE supports SET with expressions and REMOVE, including real list-index writes: SET tags[0] = :v updates that element (appending when the index is at or past the end) and REMOVE tags[0] deletes it and shifts the rest down.

RETURNING

New in 0.12.0. Before that Dynoxide parsed the clause and silently dropped it, so a statement that should have returned an item returned nothing.

UPDATE Users SET status = 'active' WHERE pk = 'user#1' RETURNING ALL NEW *
DELETE FROM Users WHERE pk = 'user#1' AND sk = 'profile' RETURNING ALL OLD *

What each operation accepts:

Statement Accepted Rejected
UPDATE All four of ALL OLD, ALL NEW, MODIFIED OLD, MODIFIED NEW -
DELETE ALL OLD * only MODIFIED OLD *, ALL NEW *, MODIFIED NEW *

The variants DynamoDB does not allow on DELETE are rejected with its exact validation message rather than ignored.

The MODIFIED projections return only the paths that changed, and exclude the primary key. A nested SET a.b returns just the changed leaf rather than the whole a attribute, and when nothing was projected you get an empty Items array rather than no field. Over list-index paths the changed elements come back packed into a dense list in ascending index order, so SET a[0], a[2] yields {a: [v0, v2]}, matching DynamoDB.

DELETE ... RETURNING ALL OLD * against a key that isn't there returns a present but empty Items array. That differs from the classic DeleteItem path, and it matches real DynamoDB.

BatchExecuteStatement honours RETURNING on each member. ExecuteTransaction rejects it outright with a top-level ValidationException.

Where PartiQL differs from the classic API

Three behaviours catch people out, and all three match real DynamoDB:

  • UPDATE is not an upsert. The target has to exist already. Updating a key that isn't there fails with ConditionalCheckFailedException ("The conditional request failed") and creates nothing.
  • DELETE requires the full key. On a table with a composite key you must supply the sort key as well as the partition key.
  • SELECT without a key condition scans. The same cost model as the classic API applies, so a WHERE clause on a non-key attribute reads the whole table and filters afterwards.

Errors

Parse failures use DynamoDB's wording: Statement wasn't well formed, can't be processed: <detail>. A statement that doesn't begin with a DML keyword reports Expected data manipulation.

On BatchExecuteStatement, a member that fails to parse carries the short-form ValidationError code, matching a per-statement execution error rather than a request-level one. Each successful member echoes its TableName.

Over MCP

The MCP server exposes all three operations as tools: execute_partiql, batch_execute_partiql, and execute_transaction_partiql. See the MCP docs.