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 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 ruleResourceType?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.