The Query counterpart of #38. PR #47 fixed paginated Scan over a GSI on a hash-only base table, but the same defect remains on the Query path.
In src/actions/query.rs the base sort key for the composite cursor is read as:
let base_sk = table_key_schema
.sort_key
.as_ref()
.and_then(|sk_name| esk.get(sk_name))
.and_then(|v| v.to_key_string());
When the base table has only a partition key, sort_key is None, so base_sk is None. query_gsi_items then falls back to the two-column (gsi_pk, gsi_sk) cursor, which cannot advance past rows that share the same index key. src/actions/scan.rs already defaults base_sk to Some(String::new()) for exactly this reason (see the comment added in #47).
Repro: a hash-only base table, a GSI with hash and range keys, several items sharing one (gsi_pk, gsi_sk) but with distinct base partition keys. Page with Query IndexName=..., Limit=1 following LastEvaluatedKey. Only the first tied item comes back; the rest are silently dropped.
Fix: mirror the scan.rs fix in query.rs, defaulting base_sk to Some(String::new()) when the base table is hash-only.
The Query counterpart of #38. PR #47 fixed paginated
Scanover a GSI on a hash-only base table, but the same defect remains on theQuerypath.In
src/actions/query.rsthe base sort key for the composite cursor is read as:When the base table has only a partition key,
sort_keyisNone, sobase_skisNone.query_gsi_itemsthen falls back to the two-column(gsi_pk, gsi_sk)cursor, which cannot advance past rows that share the same index key.src/actions/scan.rsalready defaultsbase_sktoSome(String::new())for exactly this reason (see the comment added in #47).Repro: a hash-only base table, a GSI with hash and range keys, several items sharing one
(gsi_pk, gsi_sk)but with distinct base partition keys. Page withQuery IndexName=..., Limit=1followingLastEvaluatedKey. Only the first tied item comes back; the rest are silently dropped.Fix: mirror the scan.rs fix in query.rs, defaulting
base_sktoSome(String::new())when the base table is hash-only.