[Domain] Orders search: find an order by the SKU it was bought under - #244
Merged
Conversation
`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.
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.
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
customerkey). A substring match would pull an entire variant family into asearch 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
searchquery param. Nothing that matched beforestops matching.
How the hazard is closed
order_itemsis a 1:N table offorders. Reaching it with a join would return an order once permatching line — a two-line order would appear twice on a page, the
limit + 1next-page probewould count the duplicate as a real row, and the page would silently shrink. So the SKU arm is
expressed as a boolean
EXISTSsubquery in theWHEREclause, never a join, on every adapter(and as
lines.some(...)on the in-memory fake).countOrdersandlistOrdersbuild theirpredicates 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, andpnpm format:checkall pass clean.targets: the in-memory fake (47/47), SQLite (47/47), and SQLite plus Postgres together
(94/94).
and the new arm is reachable through the existing
searchquery param.search box renders the new copy (1/1).
Postgres instance, the compiled SQL for a SKU search was captured along with its
EXPLAINplan, confirming
order_itemsis reached via a hashedSubPlan/EXISTS, never aJoin.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
EXISTSas a per-row correlated probe — it de-correlates it into ahashed subplan, which means it scans
order_itemsonlower(sku)once per statement regardlessof 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 thatsequential 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.