Key-values inside key-values for $run parameters, and nobody has a good answer for authorising arbitrary SQL — Feb 24, 2026
Nikolai's aesthetic objection to $run parameters
A Parameters resource is already a name/value pair, so passing query parameters as name/value pairs inside it means writing name, name, value — key-values stuffed into key-values.
The alternative, an OperationDefinition per query the way FHIR's named queries work, felt too heavy and drags in min/max and other properties you don't want.
It resolved when someone found that CQL's evaluate operation already types a parameter as Parameters, even though Parameters is a resource type and not a data type. If it's an official hack it isn't a hack any more, so the decision was to align with CQL.
Separately, SQL query $export was ticketed as the asynchronous counterpart to the synchronous $run.
Running arbitrary SQL safely — harder on some engines than others
- Postgres — read-only connections natively; no DDL, no inserts, no index creation
- Spark — nothing equivalent. You can tell DDL and DML apart from the logical plan, but plenty of things that are neither still have side effects, including arbitrary UDFs and calling Java methods through reflection
Hence a whitelist approach — enumerate what's allowed rather than chase everything that isn't — which is more work but fails safe.
The suggestion: the spec carry an implementation note warning about this, since it will hit every implementer immediately.
Authorisation — the unsolved one
If your server enforces scopes at the top, that has to hold inside this operation too, and enforcing it on an arbitrary SQL query is not obvious.
The Aidbox trick: turn each query into an operation with its own endpoint and then apply access policies to that — which points at each query being its own operation rather than one generic one, except FHIR still has no standard way to say a custom operation is allowed.
The other direction — view-level scopes: resource-type permission inherits to every view derived from that type, and you can narrow from there to specific de-identified views.
Where it breaks down: combining a view permission with a row filter — you have access to the view, but should only see patient one's rows.
A gap in SMART App Launch — no group/ scope
Even though bulk export is group-scoped and research runs on cohorts of patients who consented.
For the cohort app people have in mind — assemble ViewDefinitions, run SQL over them, apply permissions across the whole lot — that's the missing piece.
The caveat: a Group resource stops being usable once the cohort is large enough, so what you really want is criteria-based membership rather than a list, which few have implemented.
Security labels — an orthogonal layer for sophisticated cases
Tag the data, enforce the tags server-side, and because labels aren't bound to resource type you can express hide all my mental health information across everything at once. It's an extra layer of indirection that collapses into a simple query parameter at the end.
The catch: all the complexity moves to whoever labels the data — you need a labelling service and a rules engine somewhere, and the classic test is that you can hide a depression diagnosis while the medications still give it away.
Related: the Permission resource was maturity level zero in R5 and has vanished from the R6 build, part of a broader trend of moving immature things out of core into incubators.