AQL Generation & Configurable Queries
When openFHIR receives a /toAql request with the FHIR Query/path (e.g. Observation?category=laboratory), it must translate that request into an openEHR AQL query. By default, openFHIR dynamically builds this AQL from the FHIR Connect mapping definitions. However, there are cases where this dynamic generation is insufficient or too generic — for example, when a complex IPS $summary operation requires a hand-crafted AQL that spans multiple archetypes, or when a specific query pattern needs a fine-tuned AQL for performance or correctness reasons.
The _query property in context mapping files solves this problem by allowing administrators to pin specific FHIR request patterns to hand-crafted AQL templates that are returned directly, bypassing the dynamic AQL generation.
The _query Property
_query is an optional list defined inside the context block of a .context.yaml file. Each entry contains:
aql— the hardcoded AQL string to return when the rule matchesrules— a list of one or more matching patterns; if any rule matches the incoming request, theaqlis returned immediately
Example:
context:
template:
id: International Patient Summary
start: openEHR-EHR-COMPOSITION.health_summary.v1
_query:
- aql: "SELECT c FROM EHR e[ehr_id/value='{{ehrid}}'] CONTAINS COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1]"
rules:
- "$summary"
- aql: "SELECT o FROM EHR e[ehr_id/value='{{ehrid}}'] CONTAINS COMPOSITION c CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.body_weight.v2]"
rules:
- "Observation?category=body-weight"
- aql: "SELECT o FROM EHR e[ehr_id/value='{{ehrid}}'] CONTAINS COMPOSITION c CONTAINS OBSERVATION o"
rules:
- "Observation"
Rule Matching
Rules are evaluated in order. The first entry whose rules list contains at least one match wins and its aql is returned. Rule strings follow one of three formats:
1. Operation rule — starts with $
Matches when the incoming FHIR URL contains the named operation.
rules:
- "$summary" # matches GET /Patient/123/$summary
2. Resource-only rule — resource type name, no query parameters
Matches any request for the given resource type, regardless of query parameters.
rules:
- "Observation" # matches GET /Observation, GET /Observation?patient=xyz, etc.
3. Resource with parameters rule — ResourceType?param=value[¶m2=value2]
Matches when the resource type matches and all specified key-value pairs are present in the request query string. Extra query parameters in the request are ignored; the rule only requires its own params to be present.
rules:
- "Observation?category=laboratory" # matches GET /Observation?category=laboratory
- "Observation?code=abc&category=lab" # all listed params must be present
AQL Template Variables
The aql string supports the {{ehrid}} placeholder, which is replaced at runtime with the EHR ID of the patient being queried. Use this to scope the query to a specific patient.
aql: "SELECT c FROM EHR e[ehr_id/value='{{ehrid}}'] CONTAINS COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1]"
Why Use _query
Escape hatch for complex scenarios — dynamic AQL generation works well for simple mappings, but operations like
$summaryor$everythingtypically need a single AQL that retrieves data across many archetypes at once. Hand-crafting this AQL and binding it via_queryis far more practical.Performance — a carefully written AQL with explicit archetype paths and
CONTAINSchains can be significantly faster than the generically generated one.Correctness — some openEHR templates have subtleties (slot usage, optional sections) that the generic AQL builder cannot account for. A hand-written AQL ensures the right data is retrieved.
Predictability — when you need deterministic behavior for a specific request pattern (e.g. a national use case with a fixed AQL),
_querypins that behavior regardless of mapping file changes.
Precedence
_query entries are evaluated before dynamic AQL generation. If any rule matches, the hardcoded AQL is returned immediately and no dynamic mapping is performed. If no rule matches, openFHIR falls back to its standard AQL generation from the mapping definitions.
This means _query is purely additive — it does not affect requests that do not match any rule.