Skip to content

fix(query): check legacy conditions against the key schema - #507

Open
LeeroyHannigan wants to merge 14 commits into
mainfrom
fix/legacy-condition-parity
Open

LeeroyHannigan wants to merge 14 commits into
mainfrom
fix/legacy-condition-parity

Conversation

@LeeroyHannigan

@LeeroyHannigan LeeroyHannigan commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #399 (fix/transact-validation-order): this branch starts at its head, 4f4fc21, and the diff against main includes that PR until it merges.

Summary

Issue #477 lists eight differences left on the legacy QueryFilter, ScanFilter and KeyConditions paths after #399. Items 2 to 8 are fixed here: a QueryFilter on a key attribute (2), a legacy N value that is not a number (3, 4), a KeyConditions value of the wrong schema type (5 and 7; the range key answered 500), an empty key string (6), and BatchGetItem capacity for absent keys (8). The checks live in the engine, and the wire suite was run on SQLite, PostgreSQL and MongoDB; CI also runs the new file on Cassandra. The non-indexable operators on the hash key, which the same request-time pass covers, get the service's message as well. Item 1, which entry the service names when two legacy entries are both invalid, changed between two runs of the same request, so no rule can be encoded; the differences document records it.

Fixes #477.

What changed

  • crates/engine/src/legacy_filter.rs
    • validate_legacy_condition checks every number in the value list first (N, NS, and the numbers inside L and M) and answers The parameter cannot be converted to a numeric value: <raw> or the overflow, underflow and 38-digit messages, before the operator name, count, type, same-type and range checks on that entry. An empty N answers the same text with nothing after the separator (... numeric value: ), as the service does on this path; the item path keeps its own text, which has no separator.
    • New validate_key_conditions: the value checks plus the non-indexable refusal (Attempted conditional constraint is not an indexable operation) on any KeyConditions entry, hash key included. Query runs it before the table lookup. Before, a hash key with NE, IN, NULL, NOT_NULL, CONTAINS or NOT_CONTAINS answered Query key condition not supported.
    • desugar_key_conditions takes the key schema and the attribute definitions of the queried table or index and walks the keys in schema order: an absent hash key, then the value type against the key's AttributeDefinitions type (One or more parameter values were invalid: Condition parameter type does not match schema type), then the hash key's operator, then conditions on non-key attributes, then an empty S or B key value (One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: <name>, hash key named first).
    • New reject_query_filter_on_key: a legacy QueryFilter entry on a key of the queried table or index answers QueryFilter can only contain non-primary key attributes: Primary key attribute: <name>, hash key first. Query runs it after the Select checks and before the ExclusiveStartKey check. The service reports it after the table and index lookup, the key schema, empty key and Select checks, and before the key value size check.
  • crates/engine/src/query.rs: the three call sites.
  • crates/engine/src/batch_get_item.rs: an absent key is charged the minimum read (0.5, or 1.0 with ConsistentRead) and keeps its table in ConsumedCapacity. The per-table map is a BTreeMap, so ExtendDB lists the tables in name order. The service's order is not stable: twelve calls over three tables returned all six orders, and one request order came back in two different orders, so the tests check that each table is listed once and do not check the order.
  • crates/core/src/validation/mod.rs: validate_attribute_number and validate_no_empty_key_value are now pub so the engine reuses the item-path checks.
  • docs/differences-from-dynamodb.md: a "Query and Scan" section with one row for the entry-naming difference.
  • crates/storage-cassandra/src/data/index.rs: an unknown IndexName on Cassandra answered The table does not have the specified index: Index nope not found, where the other backends and the service answer ... specified index: nope. The new tests are the first to check that text on Cassandra. The one-line fix is the same change as in fix(query): refuse key condition values over the key size limits #481 and fix(query): refuse a Query ExclusiveStartKey the key condition cannot reach #483, so the three merge cleanly in any order.

Service measurements

All against Amazon DynamoDB, us-east-1, 2026-10-24 between 08:51 and 12:08 UTC, on an S/S table, a hash-only table and a new S/N table with a GSI on two S attributes.

Case Service response
ScanFilter EQ, BEGINS_WITH, FOO, NULL with N "abc"; EQ [N abc, S x] and [S x, N abc]; IN [S x, N abc]; BETWEEN [N 2, N abc] and [N abc, N 2]; EQ NS ["1","abc"], L [N abc], M {k: N abc}; LT L [N abc] ValidationException The parameter cannot be converted to a numeric value: abc
ScanFilter EQ N "" The parameter cannot be converted to a numeric value: (the separator with nothing after it)
ScanFilter EQ N "1e400", "1e-400", 39 digits Number overflow. Attempting to store a number with magnitude larger than supported range; Number underflow. Attempting to store a number with magnitude smaller than supported range; Attempting to store more than 38 significant digits in a Number
N abc on a missing table, with unknown IndexName, with Select COUNT and AttributesToGet, with a bad ExclusiveStartKey the number message
N abc with FilterExpression; with ConditionalOperator and one entry the both-forms conflict; ConditionalOperator can only be used when Filter or Expected has two or more elements
KeyConditions pk EQ N abc with a bad QueryFilter type the QueryFilter type message
KeyConditions pk EQ N, EQ B, EQ BOOL, EQ empty B on an S key; sk EQ N, LE B, BETWEEN N N, BEGINS_WITH B, BEGINS_WITH empty B on an S key; sk EQ S, BEGINS_WITH S, BETWEEN S S, LT S on an N key; gpk EQ N, gsk EQ N on the GSI One or more parameter values were invalid: Condition parameter type does not match schema type
pk EQ N + sk NE S; pk EQ S + sk NE N; pk NE S; pk IN S; pk NULL; pk NOT_NULL; pk CONTAINS S; sk NE S alone; sk NE S + foo; pk EQ "" + sk NE S; sk NE with Select SPECIFIC_ATTRIBUTES and no projection One or more parameter values were invalid: Attempted conditional constraint is not an indexable operation
pk LT N the schema type message
pk LT S + sk EQ N; pk LT ""; pk LT S + foo Query key condition not supported
pk EQ count 2 + sk EQ N; pk EQ N + sk EQ count 2 Invalid number of argument(s) for the EQ ComparisonOperator
pk EQ N + foo EQ S the schema type message
sk EQ N alone; sk EQ N + foo; pk EQ "" + foo Query condition missed key schema element: pk (first two), ... element: sk (third)
pk EQ N with unknown IndexName; gpk EQ N with ConsistentRead; pk EQ N on a missing table; pk EQ N with a bad ExclusiveStartKey The table does not have the specified index: nope; Consistent reads are not supported on global secondary indexes; ResourceNotFoundException; the schema type message
pk EQ ""; pk EQ "" + sk EQ ""; sk EQ, LE, LT, GE, GT, BEGINS_WITH ""; sk BETWEEN ["", "a"] and ["", ""]; gpk EQ ""; gsk EQ "" and BEGINS_WITH ""; hash-only pk EQ "" One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: <pk, sk, gpk or gsk>
sk BETWEEN ["a", ""] the inverted-range message
pk EQ "" + sk EQ N; pk EQ N + sk EQ ""; pk EQ "" with a bad QueryFilter type; pk EQ "" with unknown IndexName; pk EQ "" with a bad ExclusiveStartKey; pk EQ "" with QueryFilter on pk schema type; schema type; the QueryFilter type message; the index message; the empty key message; the empty key message
QueryFilter on pk; on sk; on pk and sk in both orders; pk NOT_NULL; pk NULL; pk with another entry and ConditionalOperator OR QueryFilter can only contain non-primary key attributes: Primary key attribute: pk (sk alone names sk)
GSI query, QueryFilter on gpk; on gsk; on table pk; on table sk. Table query, QueryFilter on gpk ... Primary key attribute: gpk; ... gsk; Count 0; Count 1; Count 2
QueryFilter on pk with: a missing table; unknown IndexName; pk EQ N; foo in KeyConditions; sk only; sk NE; pk LT; sk ""; no KeyConditions; a bad type on another entry; ConsistentRead on the GSI; Select COUNT with AttributesToGet; a bad ExclusiveStartKey ResourceNotFoundException; the index message; schema type; missed key schema element: sk; missed ... pk; non-indexable; not supported; empty key; Either the KeyConditions or KeyConditionExpression parameter must be specified in the request.; the type message; the ConsistentRead message; the Select message; the QueryFilter message
ScanFilter on pk Count 0
BatchGetItem TOTAL: 1 absent; 2 absent; 1 present + 1 absent; 1 present; 100 absent; absent with ConsistentRead; 2 absent with ConsistentRead; absent with ProjectionExpression; absent on a hash-only table [{TableName, CapacityUnits: 0.5}]; 1.0; 1.0; 0.5; 50.0; 1.0; 2.0; 0.5; 0.5
BatchGetItem TOTAL, two tables all absent, both request orders one 0.5 entry per table
BatchGetItem TOTAL, three tables all absent, all six request orders, twice one 0.5 entry per table; all six orders appeared across the twelve calls, and one request order came back in two different orders
QueryFilter on pk with a KeyConditions hash value of 2,049 bytes the QueryFilter message, not the key size message
BatchGetItem INDEXES 1 absent; NONE; no ReturnConsumedCapacity [{TableName, CapacityUnits: 0.5, Table: {CapacityUnits: 0.5}}]; no ConsumedCapacity; no ConsumedCapacity
GetItem TOTAL absent; with ConsistentRead 0.5; 1.0
Two invalid legacy entries, 44 maps across nine name pairs and three-entry maps, both insertion orders the entry named changed with the names, with the insertion order, and between two runs of the same request

Tests

  • tests/test_legacy_condition_parity.py (new, 60 cases): number messages and their order on ScanFilter, QueryFilter and KeyConditions, the empty N included; schema type on S and N keys and on GSI keys; non-indexable on the hash key and its order; the hash key's type-then-operator order; empty key strings on hash, range and index keys under every operator; QueryFilter on a key and its placement after the key schema and Select checks; BatchGetItem capacity for absent keys in every measured shape. On main, 56 of the 60 fail (the four that pass are controls); the failures include the 11 InternalServerError answers of item 5.
  • crates/engine/src/legacy_filter_tests.rs: five new unit tests.

Interactions with open PRs

Checked with git merge-tree --write-tree --merge-base=origin/main fix/legacy-condition-parity origin/<branch> against every open branch that touches a file this PR changes.

Not changed here

  • Which entry the service names when several legacy entries are invalid: not a function of the request (see the docs row). ExtendDB names the first in attribute name order.
  • The number of query conditions (n) exceeds the number of key attributes defined in the schema (m) and the name in Query condition missed key schema element for a non-key entry (the service names the first absent key; ExtendDB names the hash key): both pre-existing on main, outside the eight.
  • The Select checks (Cannot specify the AttributesToGet when choosing to get only the Count, which ExtendDB does not have; SPECIFIC_ATTRIBUTES without a projection; ALL_PROJECTED_ATTRIBUTES without an IndexName) run before the table lookup on the service and after it in ExtendDB; pre-existing on main and on the expression path.
  • The key value size check on legacy KeyConditions: the service reports it after a QueryFilter on a key, and ExtendDB does not check it on this path. fix(query): refuse key condition values over the key size limits #481 adds it.
  • TransactGetItems capacity for an absent key (2.0 on the service; not measured on ExtendDB).

…ts messages

Issue #108 lists 106 requests whose response differs from Amazon DynamoDB.
Every expectation in this change was measured against the service
(us-east-1, 2026-10-14); the probes and captured responses are in the
issue's notes, and every new test file was run against the service before
it was run here.

Semantic validation of expressions. After the grammar pass, the service
walks the parsed expression once, in expression order, checking each node
after its operands, then checks overlapping paths, then unused names and
values. A new `validate_update_semantics` and `validate_condition_semantics`
in the core crate follow that walk; the parsers now do grammar only, keep
function names as written, and record section order for the walk.

- UpdateExpression: `+` and `-` take numbers, `list_append` takes lists,
  `ADD` takes a number or a set, `DELETE` takes a set; a placeholder of the
  wrong type is refused with the service's type vocabulary (`S`, `N`, `L` for
  operators and functions; `STRING`, `NUMBER`, `Binary`, `BOOL`, `NULL`,
  `LIST`, `MAP` for ADD and DELETE). Function names are case-sensitive:
  `IF_NOT_EXISTS(x, :v)` and `foo(x)` are `Invalid function name`, a
  condition function is `not allowed in an update expression`,
  `list_append` is refused as an operand of a function or of `+`, both
  functions take two operands, the first operand of `if_not_exists` is a
  document path. A repeated section is reported after that section's own
  actions (`SET a = :v SET b = :q` reports `:q`). Previously `IF_NOT_EXISTS`,
  `DELETE s :string`, and `SET a = :string + :n` were accepted and written.
- ConditionExpression and FilterExpression: the six condition functions are
  matched as written (`ATTRIBUTE_EXISTS(pk)` was accepted and evaluated);
  `if_not_exists` and `list_append` are `not allowed in a condition
  expression`; a boolean function in operand position and `size` in
  condition position are `not allowed to be used this way`; operand counts
  are fixed per function; `attribute_exists` and `attribute_not_exists` take
  a document path; `begins_with` takes a string or binary and
  `attribute_type` a string naming a type, both checked at request time with
  the operation's prefix.
- Every unused name or value is listed in one set, sorted, so the message is
  the same on every backend; it named one key chosen by hash order.

Request stage on the read APIs. Query and Scan check the FilterExpression,
then the KeyConditionExpression (with its `Invalid KeyConditionExpression:`
prefix, and ahead of the key schema match, so an undefined `#n` is reported
as such), then the ProjectionExpression, then unused names and values, all
before the table is looked up. An undefined `:q` in a filter returned 200 on
an empty table. Limit and Segment checks precede the expressions on Scan.

Single-item order. UpdateItem checks the whole ConditionExpression before
the UpdateExpression. PutItem, UpdateItem, and DeleteItem wrap every
expression-stage error in `1 validation error detected: ` and omit the
`: ConditionExpression is null` suffix; the read, batch, and transaction
APIs return the messages bare. The ` expression size: N` suffix follows the
operation. Three existing dual-target tests asserted the unprefixed text and
fail against the service; they are corrected.

Transactions. TransactGetItems and TransactWriteItems validate in phases,
each over every item before the next: per item, expressions (UpdateExpression
before ConditionExpression), unused names and values, then the item's 400 KB
size; then table lookup; then the 4 MB total; then key schema, as a
cancellation with ValidationError on the first offending item only and None
on every other (it flagged every offender); then duplicates. An undefined
reference is the request error the service returns rather than a per-item
cancellation reason, and a key schema mismatch leaves other items'
conditions unevaluated. TransactWriteItems returns `"ItemCollectionMetrics":
{}` when SIZE is requested and no table has a local secondary index, and its
ConsumedCapacity entries are in first-appearance order rather than
per-process hash order.

Key-shaped messages. GetItem, DeleteItem, UpdateItem, BatchGetItem, and
BatchWriteItem (Put and Delete) refuse a key with a missing, mistyped, or
extra attribute with `The provided key element does not match the schema`;
PutItem keeps `Missing the key pk in the item` and `Type mismatch for key`.
BatchWriteItem returns `"ItemCollectionMetrics": {}` under SIZE with no LSI.

Table ARNs in `TableName` (eight #108 rows) stay unsupported by decision: an
ExtendDB table ARN names ExtendDB's account and region, so a table that
streams or replicates to Amazon DynamoDB would answer to two ARNs.
docs/differences-from-dynamodb.md records this.

Remaining #108 gaps after this change, all stated in the PR: the parser's
syntax wording (`Syntax error; token: X, near: Y`), the ResourceNotFound
message text, the 4 MB message (the service's carries its own payload size),
the service's rendering of over-limit list values, and the table ARN rows.

Testing: tests/test_transact_validation_order.py (31 cases),
tests/test_update_expression_rules.py (37), and
tests/test_expression_semantics.py (155) pass against the service and on
SQLite, PostgreSQL, and MongoDB; 164 of the 186 cases in the first and third
fail on main. The expression, validation, transaction, batch, query, and
scan suites pass on all three backends (767 on SQLite and PostgreSQL, 751 on
MongoDB with the four #387 cases unchanged). Of the 106 #108 rows, 83 now
return the service's status, error type, and message (30 on main). cargo
test --workspace 1,500 passed; fmt; clippy -D warnings on 1.97.0; cargo
+1.88.0 check.

Closes part of #108.
… three comments

The move of the FilterExpression operand-type checks into the semantic
pass dropped them for the legacy QueryFilter and ScanFilter parameters,
which bypass the parser. A ScanFilter with BEGINS_WITH and a number, or
LT and a boolean, was accepted at request time and refused only when a
row was evaluated, so an empty table returned 200. The desugared legacy
filter now runs the same begins_with and ordering operand checks as
before, with the FilterExpression prefix, before any row is read. Wire
tests cover both parameters on an empty table.

Comments reworded after review: the parsers state that grammar errors
are reported before semantic errors and why the semantic checks are
deferred; the empty operand list comment names the measured cases; the
PerTableCapacity doc states that first-appearance order is a firmer
guarantee than the service gives, with the measured orders.
Conflicted files and how each was resolved:

- crates/engine/src/transact_get_items.rs: main (#418) replaced the
  inline item-count checks with request_limits::check_transact_count and
  reads key info through OperationContext::table_key_info_uncached. The
  branch reorders the handler into the service's phases (projection
  checks, table lookup, key schema as a first-offender cancellation,
  duplicates). Kept the branch's phase order and applied both of main's
  changes inside it: the count check stays first and the key info loop
  in the table-lookup phase calls table_key_info_uncached.
- crates/engine/src/transact_write_items.rs: only the import block
  conflicted. The branch no longer imports parse_optional_condition;
  main adds request_limits. Kept build_expression_maps and added
  request_limits. Main's check_transact_count call and the four
  table_key_info_uncached lookups in prepare_write_op merged on their
  own and are kept.

Files changed on both sides that merged on their own were read for
overlap: crates/core/src/validation/mod.rs (main adds index projection
limits, the branch changes key-shape messages; different functions),
crates/engine/src/batch_write_item.rs (main replaces the count checks,
the branch changes ItemCollectionMetrics; different regions), and
docs/differences-from-dynamodb.md, where main's authorization row and
the branch's control-plane row both describe a table ARN as TableName;
the branch's row now points at the authorization row.
…'s message

A ScanFilter or QueryFilter condition whose AttributeValueList holds a value of
a type its ComparisonOperator does not take is refused at request time with the
service's text, `One or more parameter values were invalid: ComparisonOperator
<op> is not valid for <type> AttributeValue type`, naming the first such value.
Measured against Amazon DynamoDB on 2026-10-23 on an empty table: BEGINS_WITH
with N, LT and GT with BOOL, and BETWEEN with L then N, which names the L. The
check runs in desugar_filter on each condition before it is desugared; the
`Invalid FilterExpression:` wording that the previous commit restored is gone.
attribute_type_code is exported from extenddb-core for the type tag.

Pin the condition-expression order for a syntax error after a semantic error:
`foo(pk) AND`, `ATTRIBUTE_EXISTS(pk) AND`, `begins_with(pk, :n) AND` and
`attribute_exists(#x) AND` each answer the syntax error on the service
(measured 2026-10-23), and the test asserts that no semantic message is
returned.

Document the order of ConsumedCapacity entries in transaction responses. With
two tables the service returned the same table first for both request orders
(measured 2026-10-23), so the order does not follow the request; ExtendDB keeps
first appearance in the request on every backend. No wire test asserts a
multi-table order.
… and messages

Measured the full ComparisonOperator by AttributeValue type grid against
Amazon DynamoDB on 2026-10-23 for ScanFilter, QueryFilter and KeyConditions,
with the value counts, mixed-type lists, inverted BETWEEN ranges and unknown
operator names. validate_legacy_condition in legacy_filter.rs now applies the
measured rules to every legacy condition before it is desugared and before the
table is looked up: the operator name (`Invalid comparison operator`), the
value count (`Invalid number of argument(s) for the <op> ComparisonOperator`),
the first value's type (EQ and NE take every type; BEGINS_WITH takes S and B;
CONTAINS and NOT_CONTAINS take S, N, B, BOOL and NULL; LE, LT, GE, GT, BETWEEN
and IN take S, N and B), that every value has the first value's type
(`AttributeValues inside AttributeValueList must be of same type`), and that a
BETWEEN lower bound is not above its upper bound. Each message carries the
`One or more parameter values were invalid: ` prefix. KeyConditions get the
same checks ahead of the table lookup, and a range key operator the service
does not index (NE, NULL, NOT_NULL, CONTAINS, NOT_CONTAINS, IN) is refused
with `Attempted conditional constraint is not an indexable operation`. The
engine's own count messages for these parameters are gone.

compare_orderable is exported from extenddb-core for the range check. The
grid is pinned in legacy_filter_tests.rs and one wire row per message in
TestLegacyFilterOperandType.

The ConsumedCapacity order note now cites the three-table measurement: 16
transactions in eight request orders all listed the tables in name order.
Amazon DynamoDB documents no order for the ConsumedCapacity entries of
TransactGetItems and TransactWriteItems. Measured on 2026-10-23 with three
single-hash-key tables whose names sort opposite to their creation order,
in three request orders: both operations listed the tables by creation
time, oldest first, whatever order the request named them in.

TableKeyInfo now carries the table's creation time. Every backend reads it
from the catalog row or document it already fetches for the key schema
(SQLite and PostgreSQL creation_date_time, MongoDB creation_date_time,
Cassandra created_at), so no request makes another catalog read.

PerTableCapacity moves from capacity_helpers.rs to per_table_capacity.rs,
takes the TableKeyInfo of each operation, and lists tables oldest first,
with name order breaking a tie on the same creation time. Both transaction
handlers use it, including the idempotent replay path of
TransactWriteItems, which now has a key_info accessor on PreparedOp.

tests/test_transact_capacity_order.py creates three tables whose names
sort opposite to their creation order, waiting for ACTIVE between them,
and asserts the order for both operations at TOTAL and INDEXES over the
three measured request orders; it runs against the service and against
ExtendDB. The row in docs/differences-from-dynamodb.md states the measured
order and ExtendDB's tie rule.
create_table wrote the catalog creation time as whole seconds, so two
tables created inside the same second carried the same value. The
transaction ConsumedCapacity order sorts tables by that value and falls
back to name order on a tie, which the service does not do, and
tests/test_transact_capacity_order.py failed 5 of 6 on this backend.

The write now keeps the millisecond part, which is the precision
bson::DateTime stores. Every reader already converts through
timestamp_millis, so DescribeTable, ListTables, the backup and restore
paths and the transaction key-info fetcher return the sub-second value,
the same as the PostgreSQL backend. Catalog rows written by earlier
builds keep whole seconds and can still tie.
The ConsumedCapacity order row said two tables created in the same
millisecond are listed in name order. The precision of the stored
creation time differs per backend, so the tie does too: nanoseconds on
SQLite, microseconds on PostgreSQL, milliseconds on MongoDB and
Cassandra, and whole seconds for a MongoDB catalog row written before
the precision fix. The row now says which, and names the backends the
order was measured on.

The service-side measurement no longer claims the three tables had a
single hash key, which the recorded run does not state. Same wording in
the test docstring and the PerTableCapacity doc comment.

The empty-operand-list comment in the condition parser now cites the
2026-10-23 measurement as well as the 2026-10-14 one.
semantics.rs had grown to 819 lines. It is now a module directory with
one file per rule family: update_rules.rs for the update-expression
rules, condition_rules.rs for the condition-expression rules, refs.rs
for the name and value usage checks both passes share, errors.rs for
the message builders, and tests.rs for the tests. mod.rs keeps the
module documentation, the two function-name tables and the two public
entry points.

No behaviour change. The functions moved unedited; the only edits are
the per-file imports, pub(super) on the helpers the siblings call, and
the test module's imports in place of its use super::*. The nine
semantic-pass tests keep their names and pass.
update_parser.rs had grown from 571 lines to 630. It is now a module
directory: mod.rs holds the parser at 417 lines and tests.rs holds the
21 tests at 215 lines.

No behaviour change. The test module moved unedited, keeping its
use super::*, and the tests keep their names. One comment inside it
lost an em dash.
The update-parser ADD/DELETE doc comment and the scan Segment check
comment carried an em dash. Both are now two clauses.
…e service does

Legacy QueryFilter, ScanFilter and KeyConditions values are now held to the
service's rules that need the key schema, and to its number check, on every
backend (issue #477, stacked on #399):

- a legacy N value that is not a number answers the number message before
  the operator, count, type and range checks on that entry, inside NS, L
  and M too;
- a KeyConditions value whose type is not the key's AttributeDefinitions
  type answers "Condition parameter type does not match schema type" (the
  range key answered 500 before, the hash key 200);
- a non-indexable operator on any KeyConditions entry, the hash key
  included, is refused at request time;
- an empty string on a key through KeyConditions is refused with the shared
  key message, hash key first, after the schema checks;
- a QueryFilter entry on a key of the queried table or index is refused
  after every other Query check;
- BatchGetItem charges each absent key the minimum read and lists every
  requested table in ConsumedCapacity, in table name order.

Which entry the service names when several legacy entries are invalid is
not a function of the request; the differences document records it.
A legacy condition value {"N": ""} now answers "The parameter cannot be
converted to a numeric value: " with the separator kept, the text the
service returns on a ScanFilter EQ condition. The item path keeps its own
text, which has no separator.

BatchGetItem still lists ConsumedCapacity tables by name, but the comment
and the wire test no longer claim that is the service's order: twelve
calls over three tables on the service returned all six orders, and one
request order came back in two different orders. The test now checks that
each table is listed once with its units.

The comments on the QueryFilter key check now say it is reported after the
key schema and Select checks and before the key value size check, which
the service reports later.
…y sentence matches the other backends

(cherry picked from commit 0294106)

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.

Legacy QueryFilter, ScanFilter and KeyConditions: eight differences from DynamoDB

1 participant