Skip to content

fix(query): refuse a key condition with no HASH equality the way the service does - #479

Open
LeeroyHannigan wants to merge 1 commit into
mainfrom
fix/427-lone-non-equality
Open

LeeroyHannigan wants to merge 1 commit into
mainfrom
fix/427-lone-non-equality

Conversation

@LeeroyHannigan

@LeeroyHannigan LeeroyHannigan commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #427.

Summary

A Query whose KeyConditionExpression has no equality clause on the HASH attribute was refused with a parser sentence of ExtendDB's own. The service chooses the sentence from the key schema, and this change does the same.

Measured on the service on 2026-10-23 (every row repeated, identical answers):

Shape Service sentence
Single HASH attribute, a clause names it (pk > :x, pk BETWEEN :x AND :y, begins_with(pk, :x), pk > :x AND sk = :y, sk = :x AND pk > :y, zz = :x AND pk > :y, GSI forms) Query key condition not supported
Single HASH attribute, no clause names it (sk > :x, zz > :x, sk > :x AND zz > :y, zz = :x AND yy = :y, GSI forms) Query condition missed key schema element: <HASH>
Two HASH attributes, or one HASH with several RANGE attributes, any operator, any attribute, including an N value on the HASH attribute HASH key attributes h1, h2 must have equality conditions specified in the query
Two conditions on one attribute Invalid KeyConditionExpression: KeyConditionExpressions must only contain one condition per key
Undefined #name or :value in the key condition, equality shapes included Invalid KeyConditionExpression: An expression attribute name used in the document path is not defined; attribute name: #n (value form likewise), reported before any of the above

Ordering, measured: unused values, filter syntax, projection syntax, the Select conflicts and undefined key references come before this refusal; an ExclusiveStartKey fault and an oversized key value come after it.

Changes

  • crates/core/src/expression/key_condition/no_hash_equality.rs: KeyCondition::lacks_hash_equality and refuse_without_hash_equality, choosing the sentence from the key schema.
  • crates/core/src/expression/key_condition/references.rs: KeyCondition::validate_references, the undefined-reference refusal with the prefix.
  • The parser records two non-equality clauses instead of refusing them so the engine can choose the sentence.
  • handle_query calls validate_references for every key condition, then the refusal after validate_select_projection and before the partition key resolution.

Tests

  • Unit tests in no_hash_equality/tests.rs and references.rs.
  • tests/python/test_query_lone_non_equality.py, 67 rows, each asserting the exact sentence: 67 passed against the service and on a SQLite build of this head. The previous head fails 25 of them.
  • Regression on the same server: test_query_scan.py, test_multipart_gsi.py, test_read_paging.py, test_vector_index_query_scan.py and the expression validation files, 191 passed.

Merge note

This branch conflicts with #429 in crates/engine/src/query.rs (both touch the partition key block). Whichever merges second gets rebased; the resolution is prepared and keeps both orders: on a single-attribute schema the type check in #429 precedes this refusal (row 427-8c, pk > :n with an N value on an S key, Condition parameter type does not match schema type), and on a multi-attribute index the HASH wording precedes the type check (h > :n measured as the HASH sentence).

Pre-existing, measured, not changed here

  • <>, NOT, OR, IN and contains in a key condition: the service says Invalid KeyConditionExpression: Invalid operator used in KeyConditionExpression; ExtendDB's text differs.
  • pk = :x AND z > :x (second clause on a non-key attribute): Query condition missed key schema element: sk on the service, 200 here.
  • begins_with(pk, :n) on an N key: the operand type sentence on the service; covered by fix(query): classify and order key conditions on a multi-attribute GSI the way the service does #429's type check.
  • Select=ALL_PROJECTED_ATTRIBUTES without an index: ExtendDB's text lacks the 1 validation error detected: prefix.
  • An undefined :value in a FilterExpression is refused up front by the service and only noticed at evaluation here.

These will be filed as one issue.

Overlap with other open PRs

#484 refuses a second condition on one key attribute (KeyConditionExpressions must only contain one condition per key) in parse_key_condition for every key schema, which covers pk > :x AND pk < :y. Once #484 merges, the copy of that refusal in no_hash_equality.rs can go.

…service does

Closes #427.

A Query whose KeyConditionExpression has no equality clause on the HASH
attribute was refused with a parser sentence of ExtendDB's own. The service
chooses the sentence from the key schema. Measured on 2026-10-23, every row
repeated with identical answers:

- Single HASH attribute, any clause names it (so the clause on it is a
  non-equality): "Query key condition not supported". Rows: pk > :x,
  pk BETWEEN :x AND :y, begins_with(pk, :x), pk > :x AND sk = :y,
  sk = :x AND pk > :y, zz = :x AND pk > :y, and the GSI forms.
- Single HASH attribute, no clause names it: "Query condition missed key
  schema element: <HASH>". Rows: sk > :x, zz > :x, sk > :x AND zz > :y,
  zz = :x AND yy = :y, and the GSI forms naming the index HASH attribute.
- Several key attributes (two HASH attributes, or one HASH with several
  RANGE attributes): "HASH key attributes h1, h2 must have equality
  conditions specified in the query", naming every HASH attribute, for
  every operator and for an N value on the HASH attribute.
- Two conditions on one attribute: "Invalid KeyConditionExpression:
  KeyConditionExpressions must only contain one condition per key".
- An undefined #name or :value in the key condition is refused first, with
  the "Invalid KeyConditionExpression: " prefix, for equality shapes too.
- Legacy KeyConditions is desugared with every reference defined and an
  equality on the HASH attribute, or refused there; no change.

Ordering, measured: unused values, filter syntax, projection syntax, the
Select conflicts and undefined key references are reported before this
refusal; an ExclusiveStartKey fault and an oversized key value after it.

The check lives in crates/core/src/expression/key_condition/no_hash_equality.rs
with KeyCondition::lacks_hash_equality and refuse_without_hash_equality, and
KeyCondition::validate_references in key_condition/references.rs. The parser
now records two non-equality clauses instead of refusing them, so the engine
can choose the sentence. handle_query calls validate_references for every
key condition, then the refusal after validate_select_projection and before
the partition key resolution.

Tests: unit tests in no_hash_equality/tests.rs and references.rs; wire file
tests/python/test_query_lone_non_equality.py (67 rows) passes against the
service and on SQLite. The previous head fails 25 of the 67 rows.

Pre-existing, measured, not changed here: "<>", NOT, OR, IN and contains in
a key condition get "Invalid operator used in KeyConditionExpression" on the
service; pk = :x AND z > :x gets "missed key schema element: sk" on the
service and 200 here; begins_with on an N key is the operand type sentence;
the Select ALL_PROJECTED_ATTRIBUTES text lacks the "1 validation error
detected: " prefix here. Row 427-8c (pk > :n with an N value on an S key,
"Condition parameter type does not match schema type") needs the type check
in #429, which precedes this refusal on a single-attribute schema and
follows the HASH wording on a multi-attribute index.

This branch has not been deployed

No deployments
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.

Query: a lone non-equality condition on the partition key gets the parser's message, not the service's

2 participants