SQL on FHIR WG Meetings
SQL on FHIR WG Meeting — March 25, 2025
Gino Canessa
Gino Canessa
Principal Software Engineer at Microsoft
Nikolai Ryzhikov
Nikolai Ryzhikov
CTO at Health Samurai
Arjun Sanyal
Arjun Sanyal
Principal Antidote Solutions
John Grimes
John Grimes
Principal Research Consultant CSIRO
Mar 25, 2025

Topics discussed:

  • Gino's answer to describing the export operation was: profile the Parameters resource. John Grimes pushed back — why do you need a profile at all, isn't that what the OperationDefinition is for? Gino conceded the information mirrors almost exactly, but you can't validate against an OperationDefinition and developers get no example to copy, so they ask for a profile anyway; and only the profile can carry an invariant like "you can't supply both a URL and a canonical". The R6 build has added inputProfile and outputProfile for exactly this; older versions need an extension. Gino's own verdict on the situation was that if you could go back in time, OperationDefinition's parameter block would just be a Parameters you could profile — but it went normative, so all they could add was a slot for one.
  • Returning CSV or Parquet turned out to have no clean answer, because nothing in FHIR describes returning something outside its own type system. Gino's route is to declare the return type as Binary and let content negotiation do the work. Nikolai Ryzhikov said the spec is a little bit lying about GraphQL — everyone returns plain GraphQL JSON rather than a Binary wrapper, because otherwise GraphQL clients break — and Gino agreed nobody is expected to ever actually hand back a Binary resource. John came round after reading the Binary page, which has a whole content-negotiation section, and made the point that settled it: you never have to implement Binary storage, ids or lifecycle, you're only using the type to describe a response. They agreed to file a ticket for a better way to say it.
  • Gino floated defining a logical model for the response instead — a view run response with the shape you actually want, rather than a Parameters full of names and parts — which fits R6's push to treat logical models as first class. Nikolai pushed back: Parameters is trivial to write helpers against, needs no generated types, and stays open when an implementation wants to add a field, so a logical model sounds like overkill. Gino's compromise was that from an OperationDefinition, generating either a profile or a logical model should be a trivial transform, and he made himself a note because he doesn't know why the tool doesn't already exist.
  • Nikolai proposed that an async export return a Parameters body alongside the 202 and the Location header — the export id, an echo of what was requested, maybe an estimate — and that the same shape simply grow as the job runs: status, progress, partial outputs, and finally the outputs themselves. John pointed out this is the FHIR async pattern, which in Pathling he implemented once generically for every operation via the respond-async header. On progress he was sceptical: today it's an unstructured string and it's hard to make it more, because progress is implementation-specific — Spark counts jobs it knows about against jobs finished, another engine has nothing to report — and a string is only good for displaying. Nikolai's answer is that as Parameters it can be a percentage, an estimated finish, the views already done, the rows processed; Gino added that all of it stays optional, so a client copes with a server that says nothing at all.
  • On advertising ViewDefinition in a capability statement, Gino's advice was to accept being on the bleeding edge of what R6 is still defining: pick an approach, try it, and keep track of what works and what doesn't so it can steer the spec. When Nikolai said they'd be backward compatible forever so they wouldn't need a version on it, Gino's reply was: sure, but everyone's not using Clojure.