Skip to content

[Domain] Orders search: find an order by the SKU it was bought under - #244

Merged
vedanshujain merged 2 commits into
mainfrom
feat/orders-search-by-snapshot-sku
Aug 12, 2026
Merged

[Domain] Orders search: find an order by the SKU it was bought under#244
vedanshujain merged 2 commits into
mainfrom
feat/orders-search-by-snapshot-sku

Conversation

@vedanshujain

Copy link
Copy Markdown
Contributor

What

Orders search gains a third arm: an exact, case-folded match against the SKU frozen onto an
order's line items at purchase time
. An operator can now find an order by the SKU it was bought
under even after the product's live SKU has since been renamed — the match reads the order's own
snapshot, never the current catalogue, so renaming a product's SKU leaves every earlier order
findable under the SKU it was bought as and moves none of them onto the new one.

This is unlike the two existing arms: the order-id and buyer-email arms are prefix/substring
matches that forgive a fragment. The SKU arm is an exact match — a SKU is an identifier an
operator pastes whole, and exactness is already the house rule for SKUs elsewhere (the products
list, the orders customer key). A substring match would pull an entire variant family into a
search for one member. The search box label now says so: "Search order ID, buyer email, or exact
SKU" — a search axis the label doesn't name ships dark.

Wire format is unchanged: one search box, one search query param. Nothing that matched before
stops matching.

How the hazard is closed

order_items is a 1:N table off orders. Reaching it with a join would return an order once per
matching line — a two-line order would appear twice on a page, the limit + 1 next-page probe
would count the duplicate as a real row, and the page would silently shrink. So the SKU arm is
expressed as a boolean EXISTS subquery in the WHERE clause, never a join, on every adapter
(and as lines.some(...) on the in-memory fake). countOrders and listOrders build their
predicates from the same shared function, so the two statements agree on what "matches" means by
construction — there's no way for the count to drift from the page it captions.

Verification

  • pnpm lint, pnpm typecheck, and pnpm format:check all pass clean.
  • The order-store contract suite — the behavioral spec for this port — passes against all three
    targets: the in-memory fake (47/47), SQLite (47/47), and SQLite plus Postgres together
    (94/94).
  • The service's admin-orders HTTP suite passes (23/23), confirming the wire format is unchanged
    and the new arm is reachable through the existing search query param.
  • The admin-presentation suite passes (134/134), and a mounted DOM test confirms the relabelled
    search box renders the new copy (1/1).
  • The join-vs-EXISTS hazard was verified empirically, not just by inspection: against a real
    Postgres instance, the compiled SQL for a SKU search was captured along with its EXPLAIN
    plan, confirming order_items is reached via a hashed SubPlan/EXISTS, never a Join.
  • No browser pass was run. The only operator-visible surface this change touches is the search
    box's copy, and the DOM test mounts the real component and asserts on the rendered label —
    a browser pass would add no coverage a headless mount doesn't already give.

Known cost

Postgres doesn't run the EXISTS as a per-row correlated probe — it de-correlates it into a
hashed subplan, which means it scans order_items on lower(sku) once per statement regardless
of what the operator actually typed. So a plain order-id search now also pays for that scan, and
pays it twice per page (once for the list, once for the count). Measured on the test dataset: the
id-prefix count went from roughly 2.7ms to roughly 5.6ms.

The lever to close this is a functional index on lower(order_items.sku), which would turn that
sequential scan into an index scan. It's deliberately not added in this change — the cost is
small on the current data volume, and the right call is to measure against real data volume
before reaching for the index, rather than add one pre-emptively. Recording it here so the
trade-off is visible outside a source comment.

Reviewed

Reviewed independently by two reviewers, both approving with no blocking findings.

`OrderListFilter.search` gains a third arm: an EXACT, case-folded match on a
SKU frozen onto an order's lines at purchase time, ORed with the existing
order-id prefix and buyer_ref substring. One search box, one `search` param,
no wire or schema change.

Exact rather than prefix/substring because a sku is an identifier an operator
pastes whole, and because exactness is already the house rule for skus: the
products list matches an exact-lower sku beside its substring title, and the
orders customer key keeps exact-lower buyer_ref for the same identity reason.
A substring would pull a whole variant family into a search for one member.

The sku read is the order's own line snapshot, never the live catalogue, so a
later product rename leaves old orders findable under the sku they were bought
as and moves none of them onto the new one. Reached with a correlated EXISTS
and never a join: order_items is 1:N, and a join would return a two-line order
twice, inflate the limit + 1 next-page probe and make countOrders — which
shares the predicate — over-count the page it captions.

Contract cases land on the fake, SQLite and Postgres alike: exactness, the
fold, the frozen snapshot, a duplicate-sku order returning once across a page
boundary, and count/list agreement. The cost was measured rather than assumed
and the port records what EXPLAIN actually says — Postgres de-correlates the
EXISTS into a hashed subplan, one sequential pass over order_items that every
search pays, not the per-row index probe the shape suggests.
The Orders search box now reads "Search order ID, buyer email, or exact SKU".
An axis the label does not name ships dark — nobody types into a box for a
thing they have no reason to think it reads. It spends one mode word, on the
one axis whose mode changes what to type: a partial id or email still finds
the order, a partial SKU finds nothing. Same principle as the products list's
"Search (SKU exact, or title contains)", and both labels are now pinned side
by side, with a mounted check that the sentence reaches the control itself.

The plan note was Postgres-only but stated as if universal. It now says both:
pg de-correlates the EXISTS into a hashed subplan (one extra sequential pass
over order_items, paid by every search and by both statements a page issues),
while SQLite keeps it correlated, probes the (order_id, product_id) index per
row, and skips the arm entirely for a row the two cheaper arms — written
first, deliberately — already matched. Every figure is re-measured off the
statement's own Execution Time, including the count baselines that were
previously inferred rather than captured.

Three cross-references said the two searches diverge on sku; they now agree
on it, differing only in which table they read it from. The coupon and index
notes enumerate all three arms.

Two contract cases close the gaps: a sku spelled with LIKE metacharacters
matches itself and nothing else, and one search string that reaches one order
by id prefix and another by line SKU returns each exactly once.
@vedanshujain
vedanshujain merged commit ae2b837 into main Aug 12, 2026
4 checks passed
@vedanshujain
vedanshujain deleted the feat/orders-search-by-snapshot-sku branch August 12, 2026 13:35
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.

1 participant