Found by the #448 sweep v2 (SQLite, Synthea data): a coherence matrix over every positively-matched parameter shows :not fails on 19 of 19 token parameters whose values live in nested or array elements, while top-level scalars behave.
Evidence (all manually re-verified)
| Query |
= total |
:not total |
Expected :not |
Cohort |
Patient?gender:not=male (top-level scalar) |
16 |
11 |
11 |
27 — ✅ correct |
| `Patient?language:not=urn:ietf:bcp:47 |
en-US` |
24 |
27 |
≤3 |
| `Observation?combo-code:not=http://loinc.org |
8302-2` |
85 |
1400 |
1315 |
ExplanationOfBenefit?status:not=active |
514 (all) |
1 |
0 |
514 — one phantom row |
| `Encounter?reason-code:not=sct |
36971009` |
3 |
245 |
≥370 |
MedicationAdministration?reason-given:not=… |
9 |
9 |
16 |
25 — returns the matches |
…plus 13 more in the sweep output (PractitionerRole specialty/role/telecom, Organization/Device type, Immunization vaccine-code, identifiers, DiagnosticReport category, Provenance agent-type, SearchParameter target).
Reading the shapes
FHIR :not means no value of the parameter equals X — per resource, and resources without the element count as matches. The observed behaviors fit a per-row predicate on the search index join (EXISTS(row != X) or plain value != X on a multi-row join) instead of NOT EXISTS(row = X):
- multi-row resources leak in whenever any of their rows differs (category arrays, codings),
- resources with no rows for the param drop out entirely (Encounter reason-code),
- the EOB oddity (all 514 active yet
:not finds 1) suggests an extra indexed row from a contained resource satisfying the !=.
The gender case passes only because single top-level scalars have exactly one row.
Part of the #448 sanity pass, iteration 2. Likely home: the sqlite token modifier handling (modifier_handlers.rs / token handler) — same neighborhood as the #456 date fix.
Found by the #448 sweep v2 (SQLite, Synthea data): a coherence matrix over every positively-matched parameter shows
:notfails on 19 of 19 token parameters whose values live in nested or array elements, while top-level scalars behave.Evidence (all manually re-verified)
=total:nottotal:notPatient?gender:not=male(top-level scalar)ExplanationOfBenefit?status:not=activeMedicationAdministration?reason-given:not=……plus 13 more in the sweep output (PractitionerRole specialty/role/telecom, Organization/Device type, Immunization vaccine-code, identifiers, DiagnosticReport category, Provenance agent-type, SearchParameter target).
Reading the shapes
FHIR
:notmeans no value of the parameter equals X — per resource, and resources without the element count as matches. The observed behaviors fit a per-row predicate on the search index join (EXISTS(row != X)or plainvalue != Xon a multi-row join) instead ofNOT EXISTS(row = X)::notfinds 1) suggests an extra indexed row from a contained resource satisfying the!=.The gender case passes only because single top-level scalars have exactly one row.
Part of the #448 sanity pass, iteration 2. Likely home: the sqlite token modifier handling (
modifier_handlers.rs/ token handler) — same neighborhood as the #456 date fix.