The FHIR search compiler (SP3)

Featurekamo-shared-library
Shipped
August 25, 2026 at 4:01 PM UTC
Author
Kamo
Commit
b1a410f

Compiles a search into one SQL statement over the typed index tables. Every rule below fails by returning the WRONG ROWS rather than by raising anything, which is why each has a test that says why. Every criterion becomes an EXISTS, never a JOIN, and that is a correctness decision rather than a performance one. Index tables are multi-valued: an Observation with three categories has three token rows, so an inner join returns it three times and a page of twenty could hold six distinct resources. Adding DISTINCT to paper over it then breaks keyset pagination, because the sort columns must join the select list and duplicates still consume page slots. Paging is keyset, never OFFSET. OFFSET re-scans and discards, so cost grows with depth — and worse, a resource written between two page requests shifts every later row, so a client walking a Bundle silently SKIPS records. For a bulk export or a reconciliation that is missing clinical data with no error. logical_id is a mandatory tiebreaker: without it two resources sharing a timestamp order arbitrarily, so one appears twice and another never. Date search is INTERVAL OVERLAP, because a FHIR date IS an interval — eq2026 means "overlaps 2026", not "equals midnight on 1 January". A null range_high means STILL RUNNING and extends forever; coalescing it to the low bound makes an active condition stop matching ge the day after it started. Token has three forms that are three different questions: bare code, system|code, and |code meaning explicitly NO system. Collapsing the third lets |male match a male coded in an unrelated system. Quantity compares ONLY the canonical column, composite matches both halves on ONE row — pairing across tables would match a systolic code to a diastolic value and report a hypertensive reading that was never taken. :contains and :above are REFUSED rather than served slowly. Both need a match no b-tree can use, so they would scan the largest tables in the schema; an honest error beats an endpoint that works in testing and times out on real data, and the CapabilityStatement declares them unsupported. Deleted resources are excluded — tombstones keep answering vread and _history, but a search asks what IS, and returning them puts deleted problems back on a problem list. Only enum-controlled table names are interpolated; every client value is bound, and a test pins that with an injection string. Mutation-proven: treating a date as an instant and swapping keyset for OFFSET each failed with their own explanation before the revert. 1689 tests green.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing