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.

Generated WHERE conditions

Each search parameter becomes a condition on the openEHR path of the mapping it matches, comparing one attribute of a data value on its strict AQL path:

  • a mapping whose path ends in an attribute (…/value/magnitude, …/defining_code/code_string, …/|code) compares that attribute

  • a mapping on a whole data value compares the attribute its type is compared by: magnitude of a quantity or count, defining_code/code_string of a coded text (a token search names a code; a manual value is compared to the text), numerator of a proportion, id of an identifier, value of a text, date, time, boolean or ordinal. On an element allowing several types, with.type picks the type

  • through the element’s value for an ELEMENT, directly on a node that is a data value itself (context/start_time)

For example Observation?value-quantity=500 on a body weight mapping gives …/items[at0004]/value/magnitude = 500. A parameter whose mappings address no data value (an EVENT, a CLUSTER, a participation …) is left out of the AQL and returned in unhandledParams as a WARNING.

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 matches

  • rules — a list of one or more matching patterns; if any rule matches the incoming request, the aql is 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[&param2=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 $summary or $everything typically need a single AQL that retrieves data across many archetypes at once. Hand-crafting this AQL and binding it via _query is far more practical.

  • Performance — a carefully written AQL with explicit archetype paths and CONTAINS chains 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), _query pins 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.