fix(query): client-side ORDER BY for PartiQL SELECTs - #66
Merged
Conversation
fuleinist
pushed a commit
that referenced
this pull request
Aug 24, 2026
…rip (#66) The Tabularis GUI wraps a filtered browse in a derived table when a LIMIT is applied: SELECT * FROM (<base> <where> <order_by> <limit>) AS limited_subset DynamoDB PartiQL has no subquery support, so ExecuteStatement rejects the wrapper outright. Worse, strip_partiql_order_by / strip_partiql_limit found the ORDER BY / LIMIT *inside* the parens and sliced off the subquery's closing paren, producing ValidationException: Statement wasn't well formed, can't be processed: Expected RIGHT_PAREN Unwrap the wrapper first (unwrap_derived_table) and run the normal pipeline on the inner statement. A wrapper is only unwrapped when it is a single derived table with nothing else at top level — a following JOIN or outer WHERE is left untouched. Parens are balanced with single-quoted string literals respected, so function calls and parens inside string values in the inner WHERE don't fool the balancer. Reproduced against DynamoDB Local with the exact GUI form for both `WHERE id > 5` and `WHERE status = 'active'` variants; both now return sorted, limited rows. 178 unit + 12 integration tests green; clippy clean. Co-authored-by: Cursor <cursoragent@cursor.com>
DynamoDB's ExecuteStatement only accepts ORDER BY when a WHERE clause pins the partition key and the ordered column is the sort key; every other form is rejected with `ValidationException: Must have WHERE clause in the statement when using ORDER BY clause.` That blocks the common GUI flow of clicking a column header to sort a table without a sort key (or ordering by a non-key column), which surfaces as a hard error. Strip a trailing `ORDER BY <col> [ASC|DESC]` from the PartiQL statement (after LIMIT stripping, so `ORDER BY ... LIMIT n` chains cleanly) and re-apply the ordering client-side over a bounded paged read of the result set. Numeric columns (DynamoDB `N` attributes serialised as JSON strings) sort numerically, not lexically; null/missing values sink to the end on ascending order. A 1000-row cap keeps the client-side sort bounded, with a warning returned when the cap is hit explaining how to get server-side ordering instead. A client-side sort is one-shot (no next_token), so the GUI re-issues the full statement to advance. Bumps version to 0.1.5. Co-authored-by: Cursor <cursoragent@cursor.com>
…rip (#66) The Tabularis GUI wraps a filtered browse in a derived table when a LIMIT is applied: SELECT * FROM (<base> <where> <order_by> <limit>) AS limited_subset DynamoDB PartiQL has no subquery support, so ExecuteStatement rejects the wrapper outright. Worse, strip_partiql_order_by / strip_partiql_limit found the ORDER BY / LIMIT *inside* the parens and sliced off the subquery's closing paren, producing ValidationException: Statement wasn't well formed, can't be processed: Expected RIGHT_PAREN Unwrap the wrapper first (unwrap_derived_table) and run the normal pipeline on the inner statement. A wrapper is only unwrapped when it is a single derived table with nothing else at top level — a following JOIN or outer WHERE is left untouched. Parens are balanced with single-quoted string literals respected, so function calls and parens inside string values in the inner WHERE don't fool the balancer. Reproduced against DynamoDB Local with the exact GUI form for both `WHERE id > 5` and `WHERE status = 'active'` variants; both now return sorted, limited rows. 178 unit + 12 integration tests green; clippy clean. Co-authored-by: Cursor <cursoragent@cursor.com>
fuleinist
force-pushed
the
fix/order-by-client-side
branch
from
August 24, 2026 05:01
a901066 to
a19dfb0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two related fixes for PartiQL
SELECTordering/limiting in the Tabularis GUI:Client-side
ORDER BY—ORDER BYclauses in PartiQLSELECTstatements no longer fail withValidationException: Must have WHERE clause in the statement when using ORDER BY clause.DynamoDB'sExecuteStatementonly acceptsORDER BYwhen aWHEREpins the partition key and the ordered column is the sort key — so any other form (sorting a table without a sort key, or ordering by a non-key column, exactly what the GUI emits when a column header is clicked) was rejected outright. The clause is now detected, stripped from the statement (afterLIMITstripping, soORDER BY ... LIMIT nchains cleanly), and re-applied client-side over a bounded paged read of the result set. Numeric columns (DynamoDBNattributes serialised as JSON strings) sort numerically, not lexically; null/missing values sink to the end on ascending order. A 1000-row cap keeps the client-side sort bounded, with a warning returned when the cap is hit explaining how to get server-side ordering instead. A client-side sort is one-shot (nonext_token), so the GUI re-issues the full statement to advance.Unwrap GUI derived-table wrapper (fix(query): client-side ORDER BY for PartiQL SELECTs #66 follow-up) — filtered browses with a LIMIT no longer fail with
ValidationException: Statement wasn't well formed, can't be processed: Expected RIGHT_PAREN. The GUI wraps such browses in a derived table —SELECT * FROM (<base> <where> <order_by> <limit>) AS limited_subset— which DynamoDB PartiQL does not support. The wrapper is now unwrapped before the LIMIT/ORDER BY strippers run, so they operate on the inner statement instead of slicing into the subquery and dropping its closing paren. A wrapper is only unwrapped when it is a single derived table with nothing else at the top level (a following JOIN or outer WHERE is left untouched). Parens are balanced with single-quoted string literals respected, so function calls and parens inside string values in the inner WHERE don't fool the balancer.Root cause (reproduced against DynamoDB Local)
Client-side ORDER BY
SELECT * FROM "users" ORDER BY "age"ValidationException: Must have WHERE clause...SELECT * FROM "users" ORDER BY "age" DESCSELECT * FROM "users" ORDER BY "age" DESC LIMIT 2truncated=trueSELECT * FROM "events" WHERE "pk" = 'p1' ORDER BY "ts" DESCSELECT * FROM "users" LIMIT 2Derived-table unwrap
SELECT * FROM (SELECT * FROM "production-venue" WHERE id > 5 ORDER BY created_at DESC LIMIT 10) AS limited_subsetValidationException: ... Expected RIGHT_PARENcreated_atdesc, limited to 10SELECT * FROM (SELECT * FROM "production-venue" WHERE status = 'active' ORDER BY created_at DESC LIMIT 10) AS limited_subsetSELECT * FROM (SELECT * FROM "a") AS x JOIN "b" ON x.id = b.idLIMITalone was never broken —strip_partiql_limithas handled it since #34.ORDER BYnever had any interception in the plugin (verified viagit log -S "order_by" -- src), so it never actually "worked before"; what changed in v0.1.0-era was that real DynamoDB error messages started surfacing (commit 92d615e), making the previously opaque"service error"visible as the actionableValidationException. TheExpected RIGHT_PARENregression appeared once the ORDER BY stripper was in place, because it found theORDER BYinside the GUI's subquery wrapper and sliced off the closing paren.Test plan
cargo test --lib— 178 passed (13 ORDER BY/sort + 8 derived-table-unwrap unit tests)DYNAMODB_ENDPOINT=http://localhost:8000 cargo test --test dynamodb_local_test -- --test-threads=1— 12 passedcargo clippy --all-targets -- -D warnings— cleancargo fmt --all— appliedWHERE id > 5+ORDER BY created_at DESC+LIMIT 10returns sorted, limited rows (no error)Notes
0.1.6(Cargo.toml, Cargo.lock, .tabularium) and adds a CHANGELOG entry covering both fixes.Client::MAX_SCAN_ITEMS; beyond it the sort is partial and a warning is returned. Server-side ordering remains available to users who write the supported form (WHERE pk = ? ORDER BY sk).unwrap_derived_tableis conservative: it only fires onSELECT ... FROM (<inner>) [AS <alias>]with a single derived table and nothing else at top level. A JOIN or outer WHERE after the wrapper is left untouched (and would surface a clear DynamoDB parse error rather than a mangled statement).Made with Cursor