openEHR logo

Archetype Query Language (AQL)

Issuer: openEHR Specification Program

Release: QUERY Release-1.0.1

Status: STABLE

Revision: [latest_issue]

Date: [latest_issue_date]

Keywords: query, AQL, archetype, Xpath, openehr

openEHR components
© 2008 - 2020 The openEHR Foundation

The openEHR Foundation is an independent, non-profit foundation, facilitating the sharing of health records by consumers and clinicians via open specifications, clinical models and open platform implementations.

Licence

image Creative Commons Attribution-NoDerivs 3.0 Unported. https://creativecommons.org/licenses/by-nd/3.0/

Support

Issues: Problem Reports
Web: specifications.openEHR.org

Amendment Record

Issue Details Raiser Completed

Release 1.0.1

1.0.1

SPECQUERY-20 Clarify major keyword documentation and semantics; remove TIMEWINDOW.

openEHR SEC

15 Jun 2020

SPECQUERY-23 Improve introductory text; update section 2.

T Beale

15 Apr 2020

SPECQUERY-19 Clarify use of terminology 'identified path'.

P Pazos,
T Beale

10 Apr 2020

SPECQUERY-14: Correct typographical errors (SPECPR-320, SPECPR-345, SPECPR-212, SPECPR-332, SPECPR-331);
SPECQUERY-15: Add clarifications and fix typographical errors (SPECPR-359);
SPECQUERY-4: Improve documentation of 'matches' operator;
SPECQUERY-2: Clarify the return structure of an AQL query (SPECPR-320).

P Pazos,
B Næss,
S Arikan,
S Iancu,
M Polajnar,
I McNicoll,
T Beale

10 Apr 2020

Release 1.0.0

1.0.0

SPECQUERY-6 Publish original AQL wiki page as formal specification;
Various reformatting, adjustment of headings;
Fix missing square brackets on query example. (SPECPR-236)

openEHR SEC

10 Jan 2017

Add Antlr grammar for AQL

B Lah

13 Jan 2012

Initial Writing, based on openEHR AQL wiki page.

C Ma
H Frankel
T Beale

10 Dec 2008

Acknowledgements

Primary Author

  • Chunlan Ma, MD, Ocean Informatics, Australia.

Contributors

This specification benefited from wide formal and informal input from the openEHR and wider health informatics community. The openEHR Foundation would like to recognise the following people for their contributions.

  • Heath Frankel, Ocean Informatics, Australia

  • Bostjan Lah, Architect, Marand, Slovenia

Support

The work reported in this paper has been funded by the following organisations:

  • Ocean Informatics, Australia

  • Marand, Slovenia

Trademarks

  • 'openEHR' is a trademark of the openEHR Foundation

  • 'Java' is a registered trademark of Oracle Corporation

  • 'Microsoft' and '.Net' are trademarks of the Microsoft Corporation

1. Preface

1.1. Purpose

This document describes the syntax of the openEHR Archetype Query Language (AQL).

1.2. Status

This specification is in the STABLE state. The latest development version of this document can be found at https://specifications.openehr.org/releases/QUERY/Release-1.0.1/AQL.html.

Known omissions or questions are indicated in the text with a 'to be determined' paragraph, as follows:

TBD: (example To Be Determined paragraph)

1.3. Tools

Various tools that can be used to work with archetypes and templates, including extracting 'archetype paths' for use in AQL statements, are listed on the openEHR website modelling tools page.

1.4. Feedback

Feedback may be provided on the technical mailing list.

Issues may be raised on the specifications Problem Report tracker.

To see changes made due to previously reported issues, see the QUERY component Change Request tracker.

1.5. Original Source

The text of this specification was originally posted on the openEHR wiki, and was used by the global openEHR community in that form.

1.5.1. AQL features not yet supported

The following features are likely to be needed, but are not yet formalized by this specification:

  • XOR operator;

  • functions;

  • embedded queries.

2. Overview

2.1. Existing Query Languages

Currently mainstream database query languages such as Structured Query Language (SQL), W3C XQuery, and Object Query Language (OQL) have dependencies on particular data schemas and physical representations (e.g. relational tables). Users must know the physical data schema of a particular database in order to write a valid query. A query statement written for one schema will not usually work in other systems, which have generally have different data schemas, even for storing the same data. One reason schemas are different between systems relates to differing optimisation requirements and choices. Queries written in these languages therefore are not usually portable across systems.

More modern web-oriented languages such as W3C Sparql and GraphQL are not database- or system-oriented as such, and do not suffer from the problem of portability. However, both the database- and web-oriented languages suffer from another problem, which is being limited to a single level of semantic representation, i.e. they effectively assume an Entity-Attribute-Value (EAV) data meta-model. This prevents their direct use with multi-level models, such as those based on openEHR Archetype model, or its ISO equivalent ISO 13606-2:2019.

In order to overcome these limitations, this specification describes a query language designed to support portable queries based on multi-level models.

2.2. What is AQL?

Archetype Query Language (AQL) is a declarative query language developed specifically for expressing queries used for searching and retrieving the data found in archetype-based repositories. The examples used in this specification mostly relate to the openEHR Reference Model (RM) and the openEHR clinical archetypes, but the syntax is independent of information model, application, programming language, system environment, and storage model.

The minimum requirement for data to be queried using AQL (including with archetype structures and terminology) is that it be based on archetypes, which concretely means that it contains fine-grained semantic markings in the form of archetype and terminology codes. This may be native openEHR RM data, or legacy system data to which the relevant semantic markers (i.e. archetype and terminology codes) have been added. Consequently, AQL expresses queries in terms of a combination of archetype semantic elements and RM data structure elements on which the archetypes are based, rather than solely the latter, which is the case for EAV-based query languages such as SQL. This is the key in developing and sharing semantic queries across system and enterprise boundaries.

AQL has the following distinctive features:

  1. the utilization of openEHR path syntax in AQL: the path syntax is used to locate clinical statements and data values within them using archetypes; this syntax is used to represent the query criteria and returned results, and allows stating query criteria using archetype and node identifiers, data values within the archetypes, and class attributes defined within the Reference Model;

  2. returned results may be objects of any granularity from 'top-level' RM objects to primitive data items;

  3. the utilization of a contains operator to match data hierarchy relationships in order to constrain the source data to which the query is applied;

  4. the utilization of ADL-like operator syntaxes, such as matches, exists, in, and not;

  5. model-neutral syntax: AQL does not have any dependency on a Reference Model; it is also neutral to system implementation and environment;

  6. supports time-based conditions to query historical versions of data.

AQL also has features found in other query languages, including:

  1. naming returned results;

  2. query criteria parameters;

  3. arithmetic operators (such as +, -, *, and /), comparison operators (>, >=, =, !=, <=, <) and Boolean operators (OR, AND, XOR, NOT).

  4. some built-in functions that are supported in W3C XQuery, such as current-date();

  5. preferences on the result retrieval and structuring, such as ordering and total number of retrieved results.

2.3. AQL example

Below is an example of an AQL statement. This statement returns all blood pressure values contained in COMPOSITION instances defined by the openEHR-EHR-COMPOSITION.encounter.v1 archetype, which contain OBSERVATION instances defined by the openEHR-EHR-OBSERVATION.blood_pressure.v1 archetype, where the systolic value is greater than or equal to 140 or whose diastolic value is greater than or equal to 90, within a specified EHR (i.e. whose EHR id is the value of the variable $ehrId). The AQL syntax is a synthesis of SQL structural syntax and the openEHR path syntax.

SELECT                                                       -- Select clause
   o/data[at0001]/.../items[at0004]/value AS systolic,       -- Identified path with alias
   o/data[at0001]/.../items[at0005]/value AS diastolic,
FROM                                                         -- From clause
   EHR[ehr_id=$ehrId]                                        -- RM class expression
      CONTAINS                                               -- containment
         COMPOSITION c                                       -- RM class expression
            [openEHR-EHR-COMPOSITION.encounter.v1]           -- archetype predicate
         CONTAINS
            OBSERVATION o [openEHR-EHR-OBSERVATION.blood_pressure.v1]
WHERE                                                        -- Where clause
   o/data[at0001]/.../items[at0004]/value/value >= 140 OR    -- value comparison
   o/data[at0001]/.../items[at0005]/value/value >= 90

3. AQL Syntax Description

3.1. Overview

AQL has five clauses: SELECT, FROM, WHERE, and ORDER BY. The SELECT clause specifies the data elements to be returned. The FROM clause specifies the result source and the corresponding containment criteria. The WHERE clause specifies data value criteria within the result source. The ORDER BY clause indicates the data items used to order the returned result set.

In AQL, openEHR path syntax is used to locate any node or data values defined within archetypes. In the SELECT clause, the openEHR path syntax can be used to indicate expected objects or data values to be returned (similarly to how SQL 'projections' work). The FROM clause uses containment mechanisms indicating the scope of the query data source. WHERE clause uses archetype path to set query criteria on archetypes or any node within the archetypes. The following sections introduce the AQL syntax in detail.

Note
If no ORDER BY clause is specified, then the query result doesn’t have any default ordering criteria defined by this specification. Ordering could be defined by each implementation or be random. In terms of compliance to this specification, default ordering in results is undefined.

3.2. Reserved words and characters

Keywords in AQL are not case sensitive, so SELECT, Select, select, SeLeCt, etc. are processed in the same way. AQL has the following reserved keywords and characters:

  • SELECT, FROM, WHERE, ORDER BY

  • TOP, AND, OR, NOT, CONTAINS

  • max, now, current-date, current-date-time

  • matches, in, exists, not in

  • " and ': double and single quote characters are used to delimit string values;

  • |: bar characters are used to delimit intervals;

  • []: brackets are used to delimit coded terms, archetype id values or openEHR reference model class attribute values.

  • {}: brackets are used with matches delimiting matches criteria

  • <, >, =, %, !, $

3.3. openEHR path syntax

The openEHR path syntax has been described in detail (see the Architecture Overview specification). AQL utilises the basic openEHR path syntax to reference both coarse-grainedand fine-grained archetype nodes (objects and data values, respectively), and openEHR class instances and attributes, when referencing something that is not defined in an archetype but is part of the openEHR Reference Model. A set of openEHR path examples are provided on this section.

3.3.1. openEHR path examples - archetype path

An archetype path is the path referring to any node within an archetype. The following is an ADL workbench screen shot of apgar observation node map. The table below shows some examples.

Archetype RM type Archetype node name Archetype ID Path syntax Referenced RM Type

OBSERVATION

Apgar score

openEHR-EHR-OBSERVATION.apgar.v1

/

OBSERVATION

OBSERVATION

Heart rate

openEHR-EHR-OBSERVATION.pulse.v1

/data[at0002]/events[at0003]/data[at0001]/items[at0004]

ELEMENT

OBSERVATION

Systolic

openEHR-EHR-OBSERVATION.blood_pressure.v2

/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value

DV_QUANTITY

3.3.2. openEHR path examples - path to RM class attributes

Another type of openEHR path is the path pointing to an attribute of openEHR Reference Model classes. The examples shown below uses the attributes from the COMPOSITION class.

openEHR RM attribute name Path syntax

COMPOSITION.category

/category

COMPOSITION.context.start_time

/context/start_time

COMPOSITION.uid.value

/uid/value

3.4. AQL variables

AQL variables are used to refer a specific openEHR class defined within an AQL FROM clause. Consider the following example:

SELECT
   o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude,
   o/data[at0001]/events[at0006]/data[at0003]/items[at0005]/value/magnitude
FROM
   EHR [ehr_id/value='1234']
      CONTAINS COMPOSITION [openEHR-EHR-COMPOSITION.encounter.v1]
         CONTAINS OBSERVATION o [openEHR-EHR-OBSERVATION.blood_pressure.v1]
WHERE
   o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude >= 140 OR
   o/data[at0001]/events[at0006]/data[at0003]/items[at0005]/value/magnitude >= 90

This example defines only one AQL variable: o, which represents OBSERVATIONs that conform to (i.e. created using) the openEHR-EHR-OBSERVATION.blood_pressure.v1 archetype.

AQL variables have the following features:

  • AQL variable names are defined in the AQL FROM clause.

  • A variable name must be unique within an AQL statement.

  • Not all classes defined within the FROM clause need a variable name. It is required only when other AQL clauses need to refer to the class identified in the FROM clause. The example above doesn’t define a variable name for the EHR or COMPOSITION classes, only for the OBSERVATION class (variable o), to allow the latter to be referred to in the WHERE and SELECT clauses, as the root object of specific paths.

The combination of a variable reference and a subordinate path forms one kind of identified path (see Section 3.8), used to specify a fine-grained data element within a larger object matched by the FROM clause. In the above example, o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude specifies the systolic value of the Observation archetype openEHR-EHR-OBSERVATION.blood_pressure.v1.

A variable name is formed of an initial letter followed by any number of alphanumerics and underscores, as long as it does not clash with a reserved word. Variable names are not case-sensitive. Digits are not recommended at the beginning of a variable name although there is no formal restriction.

3.5. Parameters

AQL syntax supports parameterisation. AQL parameters are used to indicate criteria, which will be substituted with values at run time. Supporting parameterisation is one of the mechanisms used to realise sharing AQL statements within a system or across system boundaries.

The parameters can be EHR specific (such as the parameter of EHR identifier or EHR creation date/time), archetype specific (such as an archetype identifier, or archetype constraints), or template specific (such as a template identifier or template constraints).

3.5.1. Parameter syntax

A parameter always starts with a dollar sign $ followed by the parameter name, e.g. $ehrUid. The parameter name can consist of letters, digits and underscores. It cannot have spaces and it cannot be an AQL reserved word.

A parameter can be used for any criteria values within an AQL statement, e.g.:

  • within a predicate: [$archetypeId], [at0003, $nameValue], [ehr_id/value=$ehrId]

  • withing a WHERE criteria: o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/value > $systolicCriteria

Note
In the WHERE criteria, when the value of the parameter is not a number or boolean value, it should be substituted with the corresponding quotes added to the value, for instance o/../.. = $textVariable, when substituted should look like this o/../.. = "value" (single or double quotes could be used). So substituted values follow the same rules as each type when the value is specified as a constant in the AQL expression: strings, dates, times and datetimes should be quoted, numbers and booleans are not quoted.

3.5.2. When parameters are needed

Parameters are needed when the same AQL query statement is used with different criteria values. This AQL example is to return all abnormal blood pressure values for a single specific EHR. This query has a parameter $ehrUid. This parameter will be substituted by a specific EHR.ehr_id value at run time. Consequently, this query can be reused for all EHRs either within an EHR system or by other EHR systems (assuming these EHR systems support AQL).

3.5.3. Where and how parameters are resolved

AQL query parameters can be resolved at application level, or EHR system level. It depends on what the query parameters are used for and the design/implementation of the system or components. Query parameters would be normally resolved outside of a query engine.

If a query needs to be reusable across different EHR systems, any query parameters normally need to be registered in these EHR systems so that they can be resolved with real values from each environment. A typical example of this type of query parameter is $ehrUid. If the query is only used within an application, then the query parameters would be resolved by the application, such as the parameter of healthcare facility identifier or template identifier.

There are no specific guidelines on how to resolve query parameters. Generally speaking, a parameter name is used as a key (or a key is associated with a parameter name) and the key needs to be unique within the boundary where the parameters are resolved. The EHR system or application needs to have the API functions to get the real value with a given parameter name or key.

3.6. Predicates

AQL has three types of predicates: the standard predicate, the archetype predicate, and the node predicate.

3.6.1. Standard predicate

Standard predicates always have a left operand, operator and right operand, e.g. [ehr_id/value='123456'].

  • The left operand is normally an openEHR path, such as ehr_id/value, name/value.

  • The right operand is normally a criterion value or a parameter, such as '123456', $ehrUid. It can also be an openEHR path (based on the BNF).

  • The operator can be one of the following:

    >, >=, =, <, <=, !=

3.6.2. Archetype predicate

An archetype predicate is a shortcut of a standard predicate, i.e. the predicate does not have the left operand and operator. It only has an archetype id, e.g. [openEHR-EHR-COMPOSITION.encounter.v1]. The archetype predicate is a specific type of query criterion indicating which archetype instances are relevant to this query. It is used to scope the data source from which the query result data is to be retrieved. Therefore, an archetype predicate is only used within an AQL FROM clause, for example,

FROM EHR [ehr_id/value='1234']
   CONTAINS COMPOSITION c [openEHR-EHR-COMPOSITION.encounter.v1]
      CONTAINS OBSERVATION o [openEHR-EHR-OBSERVATION.blood_pressure.v1]

This predicates could also be written as Standard predicates:

FROM EHR e
   CONTAINS COMPOSITION c
      CONTAINS OBSERVATION o
WHERE
   e/ehr_id/value = '1234' AND
   c/archetype_node_id = 'openEHR-EHR-COMPOSITION.encounter.v1' AND
   o/archetype_node_id = 'openEHR-EHR-OBSERVATION.blood_pressure.v1'

This equivalence could be used by implementers of AQL to transform predicates into their standard form, as a canonical representation of the query, which can simplified its processing, validation, evaluation and transformation into specific database query languages.

3.6.3. Node predicate

A node predicate is also a shortcut of a standard predicate. It has the following forms:

  • Containing an archetype_node_id (i.e. an at-code) only, e.g.:

    [at0002]

    The corresponding standard predicate would be:

    [archetype_node_id=at0002]
  • Containing an archetype_node_id and a name value/criterion, e.g.:

    [at0002 and name/value=$nameValue]
    [at0002 and name/value='real name value']

    The corresponding standard predicates would be:

    [archetype_node_id=at0002 and name/value=$nameValue]
    [archetype_node_id=at0002 and name/value='real name value']
  • Containing an archetype_node_id and a shortcut of a name/value criterion, e.g.:

    [at0002, $nameValue]
    [at0002, 'real name value']

    The corresponding standard predicates would be:

    [archetype_node_id=at0002 and name/value=$nameValue]
    [archetype_node_id=at0002 and name/value='real name value']
  • The above three forms are the most common node predicates. A more advanced form is to include a general criterion instead of the name/value criterion within the predicate. The general criterion consists of left operand, operator, and right operand, e.g.:

    [at0002 and value/defining_code/terminology_id/value=$terminologyId]

A node predicate defines criteria on fine-grained data.

3.7. Operators

3.7.1. Comparison operators

The table below shows the supported AQL comparison operators, meaning and example.

Operator Meaning Example

=

Equal

name/value = $nameValue

>

Greater than

o/data[at0001]/…​/data[at0003]/items[at0004]/value/value >140

>=

Greater than or equal to

o/data[at0001]/…​./data[at0003]/items[at0004]/value/value >=140

<

Smaller than

o/data[at0001]/…​/data[at0003]/items[at0004]/value/value <160

<=

Smaller than or equal to

o/data[at0001]/…​/data[at0003]/items[at0004]/value/value ⇐160

!=

not equals to

c/archetype_details/template_id/value != ''

matches

advanced matcher

o/data[at0002]/…​/name/defining_code/code_string matches {'18919-1', '18961-3', '19000-9'}

3.7.2. matches operator definition

The matches binary operator is used in the WHERE clause. The left operand is an AQL identified path. The right operand is enclosed within braces ('{}'), and may take the following forms:

  1. cADL list constraint: a comma-separated value list, which is one of the cADL constraint forms used in the Archetype Definition Language (ADL). Below is an example using a string value list:

    SELECT
       o/data[at0002]/events[at0003]/data/items[at0015]/items[at0018]/name
    FROM
       EHR [uid=$ehrUid]
          CONTAINS Composition c
             CONTAINS Observation o[openEHR-EHR-OBSERVATION.microbiology.v1]
    WHERE
       o/data[at0002]/events[at0003]/data/items[at0015]/items[at0018]/items[at0019]/items[at0021]/name/defining_code/code_string matches {'18919-1', '18961-3', '19000-9'}

    Value list items may be of type string, date/time, integer, or real. Quotes are required for strings and date/times.

    A value list is only used in AQL WHERE clause when the criteria is to match one item of the list. The relationships among these value list items are "OR".

    Some examples of value list:

    matches {'string item 1', 'string item 2', 'string item3'}  // A string list, equivalent to
                                                                // matches 'string item1' or ..
                                                                // matches 'string item 2' or ..
                                                                // matches 'string item 3'
    matches {'2006-01-01', '2007-01-01', '2008-01-01'}          // a date value list
    matches {1, 2, 3}                                           // an integer list
    matches {1.1, 2.5, 3.8}                                     // a real value list
  2. URI: can be a terminology URI, openEHR EHR URI, or other URI. An example with a terminology URI is shown below:

    SELECT
       e/ehr_status/subject/external_ref/id/value, diagnosis/data/items[at0002.1]/value
    FROM
       EHR e
          CONTAINS Composition c[openEHR-EHR-COMPOSITION.problem_list.v1]
             CONTAINS Evaluation diagnosis[openEHR-EHR-EVALUATION.problem-diagnosis.v1]
    WHERE
       c/name/value='Current Problems' AND
       diagnosis/data/items[at0002.1]/value/defining_code matches { terminology://snomed-ct/hierarchy?rootConceptId=50043002 }

    URI data is enclosed within curly braces after matches operator. A URI is expressed in IETF RFC 3986 format. URIs are not case sensitive.

    A terminology URI consists of the following components:

    1. terminology: the URI schemes value;

    2. terminology service: the URI authority value, such as SNOMED-CT;

    3. terminology function name: the URI path, e.g. "hierarchy" is the function name in the example shown below;

    4. argument values required by the terminology functions - URI queries;

      This is an example of a terminology URI:

          terminology://snomed-CT/hierarchy?rootConceptId=50043002
          \_________/   \_______/ \_______/ \___________/ \______/
              |             |         |           |__________|
           scheme       authority   path          | queries  |
              |             |         |           |          |
          terminology  terminology function    argument   argument
             uri         service                 name      value

3.7.3. Logical operators

3.7.3.1. AND

AND is a binary boolean operator used to link two boolean expressions. It evaluates to true when both operands evaluate to true, and it evaluates to false otherwise.

3.7.3.2. OR

OR is a binary boolean operator used to link two boolean expressions. It evaluates to true when any of the operands evaluate to true, and it evaluates to false otherwise.

3.7.3.3. NOT

NOT is a unary Boolean operator which is always followed by either a Boolean identified expression (see Section 3.10.3.2) or other Boolean-valued operand, such as an expression using the EXISTS operator. It returns a Boolean result: true means the operand is false. A NOT expression is a kind of identified expression (see below).

The example below uses NOT operator followed by a Boolean identified expression:

SELECT
   e/ehr_id/value
FROM
   EHR e
      CONTAINS COMPOSITION c[openEHR-EHR-COMPOSITION.administrative_encounter.v1]
         CONTAINS ADMIN_ENTRY admission[openEHR-EHR-ADMIN_ENTRY.admission.v1]
WHERE
   NOT (EXISTS c/content[openEHR-EHR-ADMIN_ENTRY.discharge.v1] AND
   e/ehr_status/subject/external_ref/namespace = 'CEC')

The above example is equivalent to the two expressions shown in the following WHERE clause:

SELECT
   e/ehr_id/value
FROM
   EHR e
      CONTAINS COMPOSITION c[openEHR-EHR-COMPOSITION.administrative_encounter.v1]
         CONTAINS ADMIN_ENTRY admission[openEHR-EHR-ADMIN_ENTRY.admission.v1]
WHERE
   NOT EXISTS c/content[openEHR-EHR-ADMIN_ENTRY.discharge.v1] OR
   e/ehr_status/subject/external_ref/namespace != 'CEC'

These advanced operators are not yet supported by the grammar. The operator syntax is borrowed from ADL specifications. These are proposed to improve the richness and flexibility of AQL so that AQL syntax supports more complicated query scenarios.

3.7.3.4. EXISTS

EXISTS is a unary operator, whose operand is an identified path (described in Section 3.8). It returns a Boolean result: true means the data associated with the specified path exists, false otherwise. An EXISTS expression is a kind of identified expression (see below).

In the AQL example below, EXISTS is used in negated form to filter out COMPOSITIONs (and therefore EHRs) that do not contain a discharge ADMIN_ENTRY instance:

SELECT
   e/ehr_id/value
FROM
   EHR e
      CONTAINS COMPOSITION c[openEHR-EHR-COMPOSITION.administrative_encounter.v1]
         CONTAINS ADMIN_ENTRY admission[openEHR-EHR-ADMIN_ENTRY.admission.v1]
WHERE
   NOT EXISTS c/content[openEHR-EHR-ADMIN_ENTRY.discharge.v1]

3.8. AQL Identified Paths

3.8.1. Usage

In AQL, an identified path is the association of a variable reference (the identifier; see above) and an archetype path and/or predicate. Except for the FROM clause, an identified path may appear in any clause of an AQL statement. For example, it can be used to indicate the data to be returned in a SELECT clause, or the data item on which query criteria are applied in a WHERE clause.

3.8.2. Syntax

An AQL identified path can take any of the following forms:

  • a variable name defined in the FROM clause followed by an archetype path, which specifies a data item at that path within the object, as follows:

    o/data[at0001]/.../data[at0003]/items[at0004]/value/value
  • a variable name followed by a predicate, which specifies an object that satisfies the predicate, as follows:

    o[name/value=$nameValue]
  • a variable name followed by a predicate and an archetype path, which specifies a data item at a path within an object satisfying the predicate, as follows:

    o[name/value=$nameValue]/data[at0001]/.../data[at0003]/items[at0004]/value/value

3.9. Built-in Types

3.9.1. Integer data

Integers are represented as numeric literals, such as 1, 2, 365. Commas or periods for breaking long numbers are not allowed. Hexadecimal representation is not supported.

3.9.2. Real data

Real numbers are the decimal literals that include a decimal point, such as 3.1415926. Commas or periods for breaking long numbers are not allowed.

3.9.3. Boolean data

Boolean values are indicated using the case-insensitive literals true or false.

3.9.4. String Data

All strings are enclosed in double or single quotes. Line breaks are not supported.

3.9.5. Dates and Times

These types are treated as strings and should comply with the rules for string quoting. The format of the date, time and datetime types should comply with the ISO 8601 Date and Time format specification, which allow the basic or extended formats to be used. In the openEHR specification it is recommended to use the extended format for dates, times and datetimes. Complete or partial values are allowed.

Note
The underlying types of date/time strings are inferred by the AQL processor from the context (metadata associated with the path to which the date/time value is compared with, or by interpretation of the ISO 8601 format), enabling them to be processed as date/time quantities rather than literal strings by AQL engines.

Examples are as below:

WHERE composition/context/start_time <= '1986-01-01'    // complete date in ISO 8601 extended format. AQL grammar identifies this value as a date value.
WHERE composition/context/start_time > '19860101'       // a complete date in ISO 8601 basic format. AQL grammar identifies it as a string value.
WHERE composition/context/start_time < "12:00:00+0930"  // ISO 8601 time extended format. AQL grammar identifies it as a string value.

3.10. Query structure

3.10.1. Overview

AQL structure was briefly introduced in Section 2.3. This section describes the syntax in more formal detail. An AQL query may include the following clauses which must occur in the order shown.

  • SELECT (mandatory)

  • FROM (mandatory)

  • WHERE (optional)

  • ORDER BY (optional)

An AQL statement must at least contain the SELECT and FROM clauses.

3.10.2. FROM

The FROM clause is used to specify the subset of all the data available in a repository, that will be available for the rest of the clauses to filter and return specific data (WHERE, SELECT, etc). The data available should be defined by an information model, known here as the Reference Model (RM), that supports the use of archetype- and template-based constraint models, as described in the openEHR Architecture Overview and the Archetype Technology Overview.

All the classes referenced by the FROM clause should be defined by the RM. For instance, if the Reference Model is the openEHR Reference Model, the FROM clause should only include classes like EHR, COMPOSITION, OBSERVATION, EVALUATION, and so on.

Note
The AQL specification is not bound to a specific Reference Model, but to use a given RM, it should comply with some requirements: it should be an Object Oriented Model and should follow the dual-model approach.

Then, the expressions in the WHERE clause will filter data, but only from the subset defined by the FROM clause. And in the SELECT clause, a final projection is applied, selecting only the matched data that should be retrieved in the query result. In addition to the filtering done in the WHERE clause, the class expressions could hold predicates that also allow filtering data.

In summary:

  1. FROM: Defines the subset of data over which the query will be executed;

  2. WHERE: Filters data from the defined subset, leaving only the data that we need;

  3. predicates in class expressions: further filters for data in the subset;

  4. SELECT: picks the exact data that we need to return from the query, taken from the matched data in the previous two points.

3.10.2.1. FROM Syntax

A simple FROM clause consists of three parts: keyword FROM, class expression and/or containment constraints, e.g.

FROM
   EHR e [ehr_id/value=$ehrId]
      CONTAINS COMPOSITION c[openEHR-EHR-COMPOSITION.report.v1]
3.10.2.2. Class expressions

Two examples of a class expression are shown below:

-- EHR class, class identifier/variable, and a standard predicate
EHR e [ehr_id/value=$ehrId]

-- COMPOSITION class, class identifier/variable, and an archetype predicate
COMPOSITION c[openEHR-EHR-COMPOSITION.report.v1]

Class expressions are used for two purposes:

  1. indicating the constraints on RM classes so as to scope the data source for the query. For instance, EHR e[ehr_id/value='123456'] indicates that the required data must be from a specific EHR with ehr_id value '123456'; while COMPOSITION c[openEHR-EHR-COMPOSITION.report.v1] indicates the required data must be from or must be associated with a Composition instance with archetype id - openEHR-EHR-COMPOSITION.report.v1.

  2. defining an RM class variable that may be used by other clauses to indicate the required data or data items on which query criteria are applied. The example below uses the class expression to define a variable e which is used by the SELECT clause indicating all relevant ehr_id values are retrieved, and a variable c used by the WHERE clause indicating that the query criteria is set on the Composition template_id value.

    SELECT
       e/ehr_id/value
    FROM
       EHR e
          CONTAINS COMPOSITION c[openEHR-EHR-COMPOSITION.referral.v1]
    WHERE
       c/archetype_details/template_id/value = $templateId

Class expressions syntax include three parts. A class expression must have part one and at least one of part two or part three.

  1. part one (mandatory): openEHR RM class name, such as EHR, COMPOSITION, OBSERVATION etc.

  2. part two (optional): AQL variable name

  3. part three (optional): a standard predicate or an archetype predicate.

3.10.2.3. Containment

Since archetypes are in hierarchical structure, AQL has a containment constraint which specifies the hierarchical relationships between parent and child data items. The FROM clause utilises this hierarchical constraint along with class expression to determine the data source to which the AQL query is applied.

The syntax of containment constraint is very simple: using keyword CONTAINS between two class expressions. Left class expression is the the parent object of the right class expression, e.g.

EHR e CONTAINS COMPOSITION c [openEHR-EHR-COMPOSITION.referral.v1]

Boolean operators (AND, OR, NOT) and parentheses are used when multiple containment constrains are required, e.g.

EHR e
   CONTAINS COMPOSITION c [openEHR-EHR-COMPOSITION.referral.v1] AND COMPOSITION c1 [openEHR-EHR-COMPOSITION.report.v1]

EHR e
   CONTAINS COMPOSITION c [openEHR-EHR-COMPOSITION.referral.v1]
      CONTAINS (OBSERVATION o [openEHR-EHR-OBSERVATION-laboratory-hba1c.v1] AND OBSERVATION o1 [openEHR-EHR-OBSERVATION-laboratory-glucose.v1])

3.10.3. WHERE

An AQL WHERE clause is used to represent further criteria applied to the data items within the objects declared in the FROM clause. A WHERE clause expresses the query criteria that cannot be represented in other AQL clauses, such as criteria on archetype id, composition committal date/time, and the criteria on in which order the returned results should be listed.

3.10.3.1. Syntax

The WHERE clause syntax has the following parts (in order): keyword WHERE and identified expression(s). Boolean operators (AND, OR, NOT) and parenthesis can be used to represent multiple identified expressions. Examples:

WHERE
   c/name/value=$nameValue AND c/archetype_details/template_id/value=$templateId
WHERE
   (c/name/value = $nameValue OR c/archetype_details/template_id/value = $templateId) AND
   o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/value >= 140
3.10.3.2. Identified expression

An identified expression specifies matching criteria in the WHERE clause and comes in two forms. The first form is an expression formed by a unary operator, such as NOT or EXISTS, described earlier. The second is a binary operator expression, formed as follows:

  1. left operand: an identified path;

  2. operator: a comparison operator;

  3. right operand: one of:

    • a value criterion, normally of a primitive type, such as String, Integer, Boolean, Double, or Float; if a String value, single or double quotation marks are required; OR

    • a replaceable parameter (identified with the '$' symbol); OR

    • an identified path.

The following examples illustrate the binary form.

  • Left operand is an identified path; right operand is a primitive value:

    o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/value >= 140
    
    c/archetype_details/template_id/value = 'health_encounter'
  • Left operand is an identified path and right operand is a parameter:

    c/archetype_details/template_id/value = $templateParameter
  • Both left operand and right operand are identified paths:

    o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/value >
    o1/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/value

3.10.4. SELECT

A SELECT clause specifies what data is to be retrieved by the AQL query. The data can be any types from openEHR RM and any primitive data types. In addition, the SELECT clause uses TOP to indicate the number of result sets that should be returned and name alias to rename the retrieved data.

The SELECT syntax always starts with the keyword SELECT, followed by TOP (optional), and identified path(s) or plain variable name(s) defined in the FROM clause. Each variable name or identified path may have a name alias renaming the associated data.

Where a variable name is specified, the full object of the type associated with the variable is retrieved, such as a COMPOSITION, OBSERVATION etc. Where an identified path is specified, the data item(s) having that archetype path are returned. Multiple identifiers or identified paths are separated using a comma.

Some examples are shown below.

Example 1: retrieve all Compositions' name value, context start time and composer name from a specific EHR.

SELECT
   c/name/value AS Name, c/context/start_time AS date_time, c/composer/name AS Composer
FROM
   EHR e[ehr_id/value=$ehrUid]
       CONTAINS COMPOSITION c

Example 2: Retrieve all Composition objects of a specific EHR.

SELECT c
FROM EHR e[ehr_id/value=$ehrUid]
    CONTAINS COMPOSITION c
3.10.4.1. TOP

The TOP syntax was borrowed from SQL language for representing the number of result sets that should be returned by the AQL query. It uses BACKWARD and FORWARD to indicate the direction where to start to get the number of results to be returned.

It starts with keyword TOP, followed by an integer number and/or the direction (i.e. BACKWARD, FORWARD), e.g.

SELECT
   TOP 10 c/name/value AS Name, c/context/start_time AS date_time, c/composer/name AS Composer
FROM
   EHR e[ehr_id/value=$ehrUid]
      CONTAINS COMPOSITION c
3.10.4.2. Name alias

As in SQL, AQL supports the use of a name alias for the retrieved data. This is done with the keyword AS, followed by the name which conforms to the syntax rule of AQL variable.

3.10.5. ORDER BY

The ORDER BY clause is used to sort the returned results. The keyword ORDER BY is followed by an identified path and the keyword DESC, DESCENDING, ASCE, or ASCENDING, e.g.

ORDER BY c/name/value

4. Result structure

The result structure of an AQL query in its raw form is a 2-dimensional table, conceptually similar to the tabular projection generated by a SQL query. Formally, it may be thought of as having the type Array<Array<Any>>, where Any is understood as the superclass of all object types, including primitive and constructed types.

In practical use, AQL queries will normally be executed through a library or service API, which is likely to provide an 'annotated' result structure, i.e. a data structure that includes meta-data such as column descriptors, thus enabling efficient result processing. Such annotated results are not formally defined by this specification, and are considered an artefact of the relevant API or service definition.

An example of such an extended structure is the 'Result set' used in openEHR, described by the openEHR Abstract Platform Query Service. A concrete expression for use with the openEHR REST Query API includes this structure, mapped to HTTP and JSON structures.

5. AQL Operator Syntax Examples

The following sub-sections illustrate various AQL operators with examples.

5.1. ADL matches operator

The following examples show how the ADL matches operator may be used within AQL statements.

Note
the ADL expressions on the right-hand side of the matches operator are in ADL 1.4 format.
Clinical Scenario 1 Blood glucose > 11 mmol/L or blood glucose >= 200 mg/dL

EHR path

o/data[at0001]/events[at0002 and name/value='Any event']/data[at0003]/items[at0013.1]

AQL Fragment

ehr_path/value
    matches {
        C_DV_QUANTITY<
            list = <
               ["1"] = <
                   units = <"mmol/L">
                   magnitude = <|>=11|>
               >
               ["2"] = <
                   units=<"mg/dL">
                   magnitude=<|>=200|>
               >
            >
        >
    }
Clinical Scenario 2 Blood glucose level is between 5-6 mmol/L or between 90-110 mg/dL

EHR path

o/data[at0001]/events[at0002 and name/value='Any event']/data[at0003]/items[at0013.1]

AQL Fragment

ehr_path/value
    matches {
        C_DV_QUANTITY<
            list = <
                ["1"] = <
                    units = <"mmol/L">
                    magnitude = <|5.0..6.0|>
                >
                ["2"] = <
                    units = <"mg/dL">
                    magnitude = <|90..110|>
                >
            >
        >
    }
Clinical Scenario 3 HbA1c > 7.0%

EHR path

o/data[at0001]/events[at0002 and name/value='Any event']/data[at0003]/items[at0013.1]

AQL Fragment

ehr_path/value matches {
    DV_PROPORTION matches {
        numerator matches {|>7.0|}
        denominator matches {|100.0|}
    }
}
Clinical Scenario 4 Total cholesterol >= 5.0 mmol/L or LDL-C >= 3.0 mmol/L

Total cholesterol EHR path

o/data[at0001]/events[at0002 and name/value='Any event']/data[at0003]/items[at0013.1]

LDL-C EHR path

o/data[at0001]/events[at0002 and name/value='Any event']/data[at0003]/items[at0011.1 and
name/value='Fractions']/items[at0013.4 and name/value='LDL-Cholesterol']

AQL Fragment

Total cholesterol EHR path/value matches {
    DV_QUANTITY matches {
        units matches {"mmol/L"}
        magnitude matches {|>=5.0|}
    }
}

OR

LDL-C EHR path/value matches {
    DV_QUANTITY matches {
        units matches {"mmol/L"}
        magnitude matches {|>=3.0|}
    }
}

5.2. Arithmetic operators

The following example shows how arithmetic operators may be used within AQL statements.

Clinical Scenario 1 For each recorded administration of ampicillin check for problem diagnosis of skin rash that is within 2 days of the administration date

AQL Fragment

SELECT e/ehr_id
FROM EHR e CONTAINS (COMPOSITION c1
   CONTAINS ACTION a [openEHR-EHR-ACTION.medication.v1]
      CONTAINS ITEM_TREE it [openEHR-EHR-ITEM_TREE.medication.v1]) AND
      CONTAINS (COMPOSITION c2 CONTAINS EVALUATION eval [openEHR-EHR-EVALUATION.problem-diagnosis.v1])
WHERE
   it/description[openEHR-EHR-ITEM_TREE.medication.v1]/items[at0001]/value matches {"SNOMED::31087008"} AND
   eval/data[at0001]/items[at0002.1]/value/value/defining_code matches {
      CODE_PHRASE matches {[SNOMED::294506009, 21626009]}
   } AND
   eval/data[at0001]/items[at0010]/value - it/description[openEHR-EHR-ITEM_TREE.medication.v1]/items[at0018]/items[at0019]/value
   matches {
      DV_DURATION matches {
         value matches{<=P2d}
      }
   }

5.3. Nest Query and 'Not in'

The following example shows the use of a nested query and the not-in operator.

Clinical Scenario 1 All patients who have not been discharged

AQL Fragment

SELECT e/ehr_id
FROM
   EHR e CONTAINS
      ADMIN_ENTRY ae1 [openEHR-EHR-ADMIN_ENTRY.admission.v1]
WHERE
   ae1/encounter_id/value not in (
      SELECT ae2/encounter_id/value
      FROM
         EHR e CONTAINS
            ADMIN_ENTRY ae2 [openEHR-EHR-ADMIN_ENTRY.discharge.v1]
   )

6. How to write an AQL query statement manually

This section describes how to build an AQL query manually. The process described here may provide some hints for constructing AQL builder tools.

6.1. Query scenario

An example is used to illustrate how to write an AQL statement. The query scenario is:

"Get all abnormal blood pressure values that are recorded in a health encounter for a specific patient."

6.2. Step 1. The FROM clause

A FROM clause is to scope the data source for the query. Normally the first step is to determine the FROM clause, which has EHR class expression and archetype class expressions.

EHR class expression

This determines whether this query is applied to a single EHR or all EHRs. The latter is called a population query. If it is for all EHRs, there is no need to specify ehr_id/value in the FROM clause. Otherwise, you need to either specify a specific value or use a query parameter for ehr_id/value in a FROM clause. For this particular scenario, it is for a single EHR. A parameter called ehrUid is used for the ehr_id/value, giving this AQL query statement:

FROM EHR [ehr_id/value=$ehrUid]

Archetype expressions

  • To write archetype expressions in the FROM clause, the archetypes required for the scenario must be determined, since archetypes are used to represent clinical concepts. The easy way to identify archetypes for a scenario is to identify clinical concepts mentioned in the scenario. Each clinical concept is generally associated with one archetype. Two clinical concepts are mentioned in the above scenario: 'blood pressure' and 'health encounter'. From these concepts the two archetypes used in query are identified: an Observation archetype openEHR-EHR-OBSERVATION.blood_pressure.v1, and a Composition archetype openEHR-EHR-COMPOSITION.encounter.v1.

  • Determine whether a variable name is needed for each archetype class expression or not. This is useful if the reference to the archetype class expression is required by other AQL clauses. Without using variable names for the archetype expressions, the two archetype expressions are:

COMPOSITION [openEHR-EHR-COMPOSITION.encounter.v1]
OBSERVATION [openEHR-EHR-OBSERVATION.blood_pressure.v1]

Containment expression.

The openEHR RM is used to identify the hierarchical relationships among the found archetypes so as to define the containment expression. For example, a Composition archetype is the parent of an Observation archetype, so the AQL query looks as follows:

FROM EHR [ehr_id/value=$ehrUid] CONTAINS COMPOSITION [openEHR-EHR-COMPOSITION.encounter.v1]
CONTAINS OBSERVATION [openEHR-EHR-OBSERVATION.blood_pressure.v1]

6.3. Step 2. The WHERE clause

The WHERE clause represents all query criteria that cannot be represented in the FROM clause. To write a WHERE clause expression, the following is needed:

  1. Determine the criteria. The criteria required by the scenario are abnormal blood pressure values. Different guidelines may have different definitions for abnormal blood pressure values. Here abnormal blood pressure is interpreted to mean mean 1) the 'systolic pressure' value is greater than or equal to 140; OR 2) the 'diastolic pressure' value is greater than or equal to 90.

  2. State an identified expression for criterion 1), as follows:

    1. state the identified path for the systolic data value. A class variable name is needed as the reference to the blood pressure class expression defined in the FROM clause, so a variable name obs is added into the FROM clause. A path to systolic data value is also required to form the identified path. The path to the systolic data value is /data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/value.

    2. choose the appropriate operator: >= is used for criterion 1.

    3. add the criteria value, here, 140. The query statement including the identified expression for criterion 1) is shown below:

      FROM
         EHR [ehr_id/value=$ehrUid] CONTAINS COMPOSITION [openEHR-EHR-COMPOSITION.encounter.v1]
             CONTAINS OBSERVATION obs [openEHR-EHR-OBSERVATION.blood_pressure.v1]
      WHERE
         obs/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/value >= 140
  3. Write an identified expression for criterion 2). Following the previous instruction to write the identified expression for criterion 2) which is shown as:

    obs/data[at0001]/events[at0006]/data[at0003]/items[at0005]/value/value >= 90
  4. Find the appropriate boolean operator to join criterion 1) and 2) - OR. The query statement looks like:

    FROM
       EHR [ehr_id/value=$ehrUid] CONTAINS COMPOSITION [openEHR-EHR-COMPOSITION.encounter.v1]
          CONTAINS OBSERVATION obs [openEHR-EHR-OBSERVATION.blood_pressure.v1]
    WHERE
       obs/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/value >= 140 OR
       obs/data[at0001]/events[at0006]/data[at0003]/items[at0005]/value/value >= 90

6.4. Step 3. The SELECT clause

Writing a SELECT clause depends on determining:

  1. What return data is required. The afore-mentioned scenario requires abnormal blood pressure values as return values, including both systolic pressure and diastolic pressure.

  2. Write the appropriate identified path to the required item (variable name is required if the containing object is required). Multiple identified paths are separated using commas. For this particular scenario, two identified paths are found for systolic and diastolic data value by following Step 2. The completed query statement looks as follows:

    SELECT
       obs/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude,
       obs/data[at0001]/events[at0006]/data[at0003]/items[at0005]/value/magnitude
    FROM
       EHR [ehr_id/value=$ehrUid] CONTAINS COMPOSITION [openEHR-EHR-COMPOSITION.encounter.v1]
          CONTAINS OBSERVATION obs [openEHR-EHR-OBSERVATION.blood_pressure.v1]
    WHERE
       obs/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude >= 140 OR
       obs/data[at0001]/events[at0006]/data[at0003]/items[at0005]/value/magnitude >= 90

7. AQL Syntax Specification

The following Antlr grammar expresses the AQL syntax.

// Author: Bostjan Lah
// (c) Copyright, Marand, http://www.marand.com
// Licensed under LGPL: http://www.gnu.org/copyleft/lesser.html
// Based on AQL grammar by Ocean Informatics: http://www.openehr.org/wiki/download/attachments/2949295/EQL_v0.6.grm?version=1&modificationDate=1259650833000
grammar Aql;

options {
	output=AST;
	ASTLabelType=CommonTree;
	backtrack=true;
	memoize=true;
}

tokens {
	ORDERDESC;
	ORDERASC;
}

@header {
package com.marand.thinkehr.aql.antlr;
import com.marand.thinkehr.aql.AqlRecognitionException;
}

@lexer::header{
package com.marand.thinkehr.aql.antlr;
import com.marand.thinkehr.aql.AqlRecognitionException;
}

@members{
public void displayRecognitionError(String[] tokenNames, RecognitionException e)
{
  throw new AqlRecognitionException(e);
}
}

// Rule Definitions
//<Query> ::= <Select> <From>
//           | <Select> <From> <Where>
//            | <Select> <From> <OrderBy>   ! is this allowed?
//            | <Select> <From> <Where> <OrderBy>
query	:	select from where? orderBy? ';'!? EOF!;

//<Select> ::= 'SELECT' <SelectExpr>
//         | 'SELECT' <TOP> <SelectExpr>
select	:	SELECT top? selectExpr -> ^(SELECT top? selectExpr);

//<Top> ::= 'TOP' Integer
//          | 'TOP' Integer 'BACKWARD'
//          | 'TOP' Integer 'FORWARD'
top	:	TOP INTEGER FORWARD? -> ^(TOP INTEGER)
	|	TOP INTEGER BACKWARD -> ^(TOP INTEGER BACKWARD);


//<Where> ::= 'WHERE' <IdentifiedExpr>
where	:	WHERE identifiedExpr -> ^(WHERE identifiedExpr);

//<OrderBy> ::= 'ORDER BY' <OrderBySeq>
orderBy	:	ORDERBY orderBySeq -> ^(ORDERBY orderBySeq);

//<OrderBySeq>  ::= <OrderByExpr>
//		| <OrderByExpr> ',' <OrderBySeq>
orderBySeq
	:	orderByExpr (','! orderByExpr)*;

//<OrderByExpr> ::= <IdentifiedPath>
//		| <IdentifiedPath> 'DESCENDING'
//		| <IdentifiedPath> 'ASCENDING'
//		| <IdentifiedPath> 'DESC'
//		| <IdentifiedPath> 'ASC'
orderByExpr
	:	identifiedPath (DESCENDING|DESC) -> ^(ORDERDESC identifiedPath)
	|	identifiedPath (ASCENDING|ASC)? -> ^(ORDERASC identifiedPath);

//<SelectExpr> ::= <IdentifiedPathSeq>
selectExpr
	:	identifiedPathSeq;

//! When multiple paths provided, each IdentifiedPath must represent an object of type DataValue
//<IdentifiedPathSeq> ::= <IdentifiedPath//>
//			| <IdentifiedPath> 'as' Identifier
//			| <IdentifiedPath> ',' <IdentifiedPathSeq>
//			| <IdentifiedPath> 'as' Identifier ',' <IdentifiedPathSeq>
identifiedPathSeq
	:	selectVar (','! selectVar)*;

selectVar
	:	identifiedPath^ asIdentifier?;

asIdentifier
	:	AS IDENTIFIER;

//<From> ::=   'FROM' <FromExpr>		! stop or/and without root class
//	    | 'FROM' <FromEHR> <ContainsExpr>
//           | 'FROM' <FromEHR>
//!          'FROM' <ContainsOr>
from	: FROM fromExpr -> ^(FROM fromExpr)
	| FROM ehrContains -> ^(FROM ehrContains);

//<FromEHR> ::= 'EHR' <StandardPredicate>
//              | 'EHR' Identifier <StandardPredicate>
//              | 'EHR' Identifier
fromEHR	: EHR IDENTIFIER -> ^(EHR IDENTIFIER)
	| EHR IDENTIFIER standardPredicate -> ^(EHR IDENTIFIER standardPredicate)
	| EHR standardPredicate -> ^(EHR standardPredicate);

ehrContains
	: fromEHR (CONTAINS^ contains)?;

//<IdentifiedExpr> ::= <IdentifiedEquality>
//                   | <IdentifiedExprBoolean>
//                   |'(' <IdentifiedExprBoolean> ')'
//identifiedExpr
//	: identifiedExprBoolean;
//        | '('! identifiedExprBoolean ')'!;

//<IdentifiedExprBoolean> ::= <IdentifiedExpr> 'OR' <IdentifiedExpr>
//                              | <IdentifiedExpr> 'AND' <IdentifiedExpr>
//                              | <IdentifiedExpr> 'XOR' <IdentifiedExpr>
//                              | 'NOT''(' <IdentifiedExprBoolean> ')'
//                              | 'NOT' <IdentifiedEquality>
identifiedExpr
 	: identifiedExprAnd ((OR|XOR)^ identifiedExprAnd)*;

identifiedExprAnd
	: identifiedEquality (AND^ identifiedEquality)*;


//<IdentifiedEquality> ::= <IdentifiedOperand> ComparableOperator <IdentifiedOperand>
//			     | <IdentifiedOperand> 'matches' '{' <MatchesOperand> '}'
//                          | <IdentifiedOperand> 'matches' RegExPattern
//                          | 'EXISTS' <IdentifiedPath>
identifiedEquality
 	: identifiedOperand ((MATCHES^ '{'! matchesOperand '}'!)|(COMPARABLEOPERATOR^ identifiedOperand))
        | EXISTS identifiedPath -> ^(EXISTS identifiedPath)
        | '('! identifiedExpr ')'!
        | NOT^ identifiedEquality
 	;
// 	| identifiedOperand 'matches' '{' matchesOperand '}'
//        | identifiedOperand 'matches' REGEXPATTERN
//        | 'EXISTS' identifiedPath;

//<IdentifiedOperand> ::= <Operand> | <IdentifiedPath>
identifiedOperand
 	: operand | identifiedPath;
//!<IdentifiedOperand> ::= <Operand> | <RelativePath>

//<IdentifiedPath>::= Identifier
//                    | Identifier <Predicate>
//                    | Identifier '/' <ObjectPath>
//                    | Identifier <Predicate> '/' <ObjectPath>
identifiedPath
	 : IDENTIFIER predicate? ('/' objectPath)? -> ^(IDENTIFIER predicate? objectPath?);
//!		| Identifer <AbsolutePath>
//!		| Identifer <Predicate> <AbsolutePath>


//<Predicate> ::= <NodePredicate>
predicate
 	: nodePredicate;

//<NodePredicate> ::= '['<NodePredicateOr>']'
nodePredicate
 	: OPENBRACKET nodePredicateOr CLOSEBRACKET;

//<NodePredicateOr> ::= <NodePredicateAnd>
//                 | <NodePredicateOr> 'or' <NodePredicateAnd>
nodePredicateOr
 	: nodePredicateAnd (OR^ nodePredicateAnd)*;

//<NodePredicateAnd> ::= <NodePredicateComparable>
//                 | <NodePredicateAnd> 'and' <NodePredicateComparable>
nodePredicateAnd
 	: nodePredicateComparable (AND^ nodePredicateComparable)*;

//<NodePredicateComparable> ::= <PredicateOperand> ComparableOperator <PredicateOperand>
//                          | NodeId
//                          | NodeId ',' String        ! <NodeId> and name/value = <String> shortcut
//                          | NodeId ',' parameter     ! <NodeId> and name/value = <Parameter> shortcut
//                          | <NodePredicateRegEx>     ! /items[{/at0001.*/}], /items[at0001 and name/value matches {//}]
//                          | ArchetypeId
//                          | ArchetypeId ',' String        ! <NodeId> and name/value = <String> shortcut
//                          | ArchetypeId ',' parameter     ! <NodeId> and name/value = <Parameter> shortcut
nodePredicateComparable
 	: NODEID (COMMA^ (STRING|PARAMETER))?
 	| ARCHETYPEID (COMMA^ (STRING|PARAMETER))?
 	| predicateOperand ((COMPARABLEOPERATOR^ predicateOperand)|(MATCHES^ REGEXPATTERN))
        | REGEXPATTERN     //! /items[{/at0001.*/}], /items[at0001 and name/value matches {//}]
        ;

//<NodePredicateRegEx>    ::= RegExPattern
//                          | <PredicateOperand> 'matches' RegExPattern
nodePredicateRegEx
 	: REGEXPATTERN
 	| predicateOperand MATCHES^ REGEXPATTERN;

//<MatchesOperand> ::= <ValueListItems>
//			| UriValue
matchesOperand
 	: valueListItems | URIVALUE;

//! <ValueList> ::= '{'<ValueListItems>'}'
//<ValueListItems> ::= <Operand>
//                     |<Operand> ',' <ValueListItems>
valueListItems
 	: operand (','! operand)*;

//<URI>     ::= '{' UriValue '}'
uri 	: '{' URIVALUE '}';


//<ArchetypePredicate> ::= '[' ArchetypeId ']'
//			 | '[' Parameter ']'
//                      | '[' RegExPattern ']'
archetypePredicate
 	: OPENBRACKET (archetypeId|PARAMETER|REGEXPATTERN) CLOSEBRACKET;

archetypeId
	:	ARCHETYPEID;

//<VersionPredicate> ::= '[' <VersionPredicateOptions> ']'
versionPredicate
 	: OPENBRACKET versionPredicateOptions CLOSEBRACKET;

//<VersionPredicateOptions> ::= 'latest_version' | 'all_versions'
versionPredicateOptions
 	: 'latest_version' | ALL_VERSIONS;

//<StandardPredicate> ::= '[' <PredicateExpr> ']'
standardPredicate
 	: '['! predicateExpr ']'!;

//<PredicateExpr> ::= <PredicateOr>
predicateExpr
 	: predicateOr;

//<PredicateOr> ::= <PredicateAnd>
//                 | <PredicateOr> 'or' <PredicateAnd>
predicateOr
 	: predicateAnd (OR^ predicateAnd)*;
// 	: (predicateOr 'or')? predicateAnd; !!!

//<PredicateAnd> ::= <PredicateEquality>
//                 | <PredicateAnd> 'and' <PredicateEquality>
predicateAnd
 	: predicateEquality (AND^ predicateEquality)*;
// 	: (predicateAnd 'and')? predicateEquality; !!!

//<PredicateEquality> ::= <PredicateOperand> ComparableOperator <PredicateOperand>
predicateEquality
 	: predicateOperand COMPARABLEOPERATOR^ predicateOperand;

//<PredicateOperand> ::= !Identifier
//			!| Identifier PathItem
//                     | <ObjectPath>
//                     | <Operand>
predicateOperand
 	: objectPath | operand;

//<Operand> ::= String | Integer | Float | Date | Parameter | Boolean
operand: STRING | INTEGER | FLOAT | DATE | PARAMETER | BOOLEAN;


//<ObjectPath> ::=  <PathPart>
//                | <PathPart> '/' <ObjectPath>
objectPath
 	: pathPart ('/' pathPart)*;


//<PathPart> ::= Identifier
//           | Identifier <Predicate>
pathPart
 	: IDENTIFIER predicate?;

//<FromExpr> ::=  <SimpleClassExpr>
//		| <SimpleClassExpr> <ContainsExpr>
fromExpr: containsExpression;

contains:	simpleClassExpr (CONTAINS^ containsExpression)?;

//! Check thislass
//<ContainsExpr>::= 'CONTAINS' <ContainsExpression>
//                  !'CONTAINS' <ContainsOr>
//<ContainsExpression> ::= <ClassExpr>
//                        | <ContainExpressionBoolean>
//                        |'(' <ContainExpressionBoolean> ')'
containsExpression
 	: containExpressionBool (boolOp containsExpression)?
// 	| '(' containExpressionBool ')' -> ^(OPEN containExpressionBool)
        ;

//<ContainExpressionBoolean> ::= <ContainsExpression> 'OR' <ContainsExpression>
//                              | <ContainsExpression> 'AND' <ContainsExpression>
//                              | <ContainsExpression> 'XOR' <ContainsExpression>
containExpressionBool
 	: contains
 	| '(' containsExpression ')' -> ^(OPEN containsExpression CLOSE);

boolOp	:	OR|XOR|AND;

//<ClassExpr>::=   <SimpleClassExpr>
//		   | '(' <SimpleClassExpr> <ContainsExpr> ')'
//		   | <SimpleClassExpr> <ContainsExpr>
//classExpr
// 	: '(' simpleClassExpr ')'
//	| simpleClassExpr
//	;

//<SimpleClassExpr>::= Identifier							! RM_TYPE_NAME
//               | Identifier Identifier					! RM_TYPE_NAME variable
//               | <ArchetypedClassExpr>
//		 | <VersionedClassExpr>
//		 | <VersionClassExpr>
//		 ! | <IdentifiedObjectExpr>                           ! need to be used once VersionedClassExpr is removed
simpleClassExpr
	: IDENTIFIER IDENTIFIER?					//! RM_TYPE_NAME .. RM_TYPE_NAME variable
        | archetypedClassExpr
        | versionedClassExpr
	| versionClassExpr;
//		 ! | <IdentifiedObjectExpr>                           ! need to be used once VersionedClassExpr is removed

//<ArchetypedClassExpr>::= Identifier <ArchetypePredicate>	! RM_TYPE_NAME [archetype_id]
//               | Identifier Identifier <ArchetypePredicate>	! RM_TYPE_NAME variable [archetype_id]
archetypedClassExpr
 	: IDENTIFIER^ IDENTIFIER? archetypePredicate;	//! RM_TYPE_NAME variable? [archetype_id]

//! need to be used once VersionedClassExpr is removed
//!<IdentifiedObjectExpr>::= Identifier <StandardPredicate>	! RM_TYPE_NAME [path operator operand]
//!               | Identifier Identifier <StandardPredicate>	! RM_TYPE_NAME variable [path operator operand]
//<VersionedClassExpr>::= 'VERSIONED_OBJECT'
//               | 'VERSIONED_OBJECT' Identifier
//               | 'VERSIONED_OBJECT' <StandardPredicate>
//               | 'VERSIONED_OBJECT' Identifier <StandardPredicate>
versionedClassExpr
 	: VERSIONED_OBJECT^ IDENTIFIER? standardPredicate?;

//<VersionClassExpr>::= 'VERSION'
//               | 'VERSION' Identifier
//               | 'VERSION' <StandardPredicate>
//               | 'VERSION' Identifier <StandardPredicate>
//		 | 'VERSION' <VersionPredicate>
//               | 'VERSION' Identifier <VersionPredicate>
versionClassExpr
 	: VERSION^ IDENTIFIER? (standardPredicate|versionPredicate)?;

//
// LEXER PATTERNS
//

WS  :   ( ' '
        | '\t'
        | '\r'
        | '\n'
        ) {$channel=HIDDEN;}
    ;

SELECT : ('S'|'s')('E'|'e')('L'|'l')('E'|'e')('C'|'c')('T'|'t') ;
TOP : ('T'|'t')('O'|'o')('P'|'p') ;
FORWARD : ('F'|'f')('O'|'o')('R'|'r')('W'|'w')('A'|'a')('R'|'r')('D'|'d') ;
BACKWARD : ('B'|'b')('A'|'a')('C'|'c')('K'|'k')('W'|'w')('A'|'a')('R'|'r')('D'|'d') ;
AS : ('A'|'a')('S'|'s') ;
CONTAINS : ('C'|'c')('O'|'o')('N'|'n')('T'|'t')('A'|'a')('I'|'i')('N'|'n')('S'|'s') ;
WHERE : ('W'|'w')('H'|'h')('E'|'e')('R'|'r')('E'|'e') ;
ORDERBY : ('O'|'o')('R'|'r')('D'|'d')('E'|'e')('R'|'r')(' ')('B'|'b')('Y'|'y') ;
FROM : ('F'|'f')('R'|'r')('O'|'o')('M'|'m') ;
DESCENDING : ('D'|'d')('E'|'e')('S'|'s')('C'|'c')('E'|'e')('N'|'n')('D'|'d')('I'|'i')('N'|'n')('G'|'g') ;
ASCENDING : ('A'|'a')('S'|'s')('C'|'c')('E'|'e')('N'|'n')('D'|'d')('I'|'i')('N'|'n')('G'|'g') ;
DESC : ('D'|'d')('E'|'e')('S'|'s')('C'|'c') ;
ASC : ('A'|'a')('S'|'s')('C'|'c') ;
EHR : 'EHR';
AND : ('A'|'a')('N'|'n')('D'|'d') ;
OR : ('O'|'o')('R'|'r') ;
XOR : ('X'|'x')('O'|'o')('R'|'r') ;
NOT : ('N'|'n')('O'|'o')('T'|'t') ;
MATCHES : ('M'|'m')('A'|'a')('T'|'t')('C'|'c')('H'|'h')('E'|'e')('S'|'s') ;
EXISTS: ('E'|'e')('X'|'x')('I'|'i')('S'|'s')('T'|'t')('S'|'s') ;
VERSION	:	'VERSION';
VERSIONED_OBJECT	:	'VERSIONED_OBJECT';
ALL_VERSIONS
	:	'all_versions';

fragment
ESC_SEQ
    :   '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\')
    |   UNICODE_ESC
    |   OCTAL_ESC
    ;

fragment
OCTAL_ESC
    :   '\\' ('0'..'3') ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7')
    ;

fragment
UNICODE_ESC
    :   '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
    ;

fragment
HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;

QUOTE	:	'\'';

fragment
DIGIT	:	'0'..'9';

fragment
HEXCHAR	:	 DIGIT|'a'|'A'|'b'|'B'|'c'|'C'|'d'|'D'|'e'|'E'|'f'|'F';

fragment
LETTER
	:	'a'..'z'|'A'..'Z';

fragment
ALPHANUM
	:	LETTER|DIGIT;

fragment
LETTERMINUSA
	:	'b'..'z'|'B'..'Z';

fragment
LETTERMINUST
	:	'a'..'s'|'A'..'S'|'u'..'z'|'U'..'Z';

fragment
IDCHAR	:	ALPHANUM|'_';

fragment
IDCHARMINUST
	:	LETTERMINUST|DIGIT|'_';

fragment
URISTRING
	:	ALPHANUM|'_'|'-'|'/'|':'|'.'|'?'|'&'|'%'|'$'|'#'|'@'|'!'|'+'|'='|'*';

fragment
REGEXCHAR
	:	URISTRING|'('|')'|'\\'|'^'|'{'|'}'|']'|'[';


// Terminal Definitions

//Boolean     = 'true' | 'false'
BOOLEAN	:	'true' | 'false' | 'TRUE' | 'FALSE' ;

//!NodeId      = 'a''t'{Digit}{Digit}{Digit}{Digit}
//! conflict with Identifier
//!NodeId	     = 'at'({Digit}{Digit}{Digit}{Digit}('.0'*('.'{NonZeroDigit}{Digit}*)+|('.'{NonZeroDigit}{Digit}*)*)|'0''.0'*('.'{NonZeroDigit}{Digit}*)+|('.'{NonZeroDigit}{Digit}*)+)
//NodeId	     = 'at'({Digit}+('.'{Digit}+)*)
//NODEID	:	'at' DIGIT+ ('.' DIGIT+)*;
NODEID	:	'at' DIGIT+ ('.' DIGIT+)*; // DIGIT DIGIT DIGIT DIGIT;

//!Identifier  = {Letter}({Alphanumeric}|'_')*   ! Conflicts with UID
//!Identifier  = {Letter}{IdChar}*   ! Conflicts with extended NodeId
//! restricted to allow only letters after the 4th character due to conflict with extended NodeId
//!Identifier  = {Letter}{IdChar}?{IdChar}?{IdChar}?({Letter}|'_')*  !Conficts with NodeId which may have any length of digit, such as at0.9
//Identifier = {LetterMinusA}{IdCharMinusT}?{IdChar}* | 'a''t'?(({letter}|'_')*|{LetterMinusT}{Alphanumeric}*)
// ???
IDENTIFIER
	:	('a'|'A') (ALPHANUM|'_')*
	| 	LETTERMINUSA IDCHAR*
	;

//!PathItem = '/'{Letter}({Alphanumeric}|'_')*

//Integer     = {Digit}+
INTEGER	:	'-'? DIGIT+;

//Float       = {Digit}+'.'{Digit}+
FLOAT	:	'-'? DIGIT+ '.' DIGIT+;

//Date        = ''{Digit}{Digit}{Digit}{Digit}'-'{Digit}{Digit}'-'{Digit}{Digit}''
DATE	:	'\'' DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT 'T' DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT '.' DIGIT DIGIT DIGIT '+' DIGIT DIGIT DIGIT DIGIT '\'';

//!Parameter   = '$'{letter}({Alphanumeric}|'_')*
//Parameter   = '$'{letter}{IdChar}*
PARAMETER
	:	'$' LETTER IDCHAR*;

//! could constrain UID further
//UniqueId    = {digit}+('.'{digit}+)+'.'{digit}+  ! OID
//          | {Hex Char}+('-'{Hex Char}+)+       ! UUID
UNIQUEID:	DIGIT+ ('.' DIGIT+)+ '.' DIGIT+  // OID
            | HEXCHAR+ ('-' HEXCHAR+)+       // UUID
	;

//! could constrain ArchetypeId further
//!ArchetypeId = {Letter}+'-'{Letter}+'-'({Letter}|'_')+'.'({Letter}|'_'|'-')+'.v'{Digit}+('.'{Digit}+)?  ! not allow a number in archetype id concept, such as openEHR-EHR-OBSERVATION.laboratory-hba1c.v1
//ArchetypeId = {Letter}+'-'{Letter}+'-'({Letter}|'_')+'.'({IdChar}|'-')+'.v'{Digit}+('.'{Digit}+)?
ARCHETYPEID
	:	LETTER+ '-' LETTER+ '-' (LETTER|'_')+ '.' (IDCHAR|'-')+ '.v' DIGIT+ ('.' DIGIT+)?
	;

//ComparableOperator = '=' | '!=' | '>' | '>=' | '<' | '<='
COMPARABLEOPERATOR
	:	'=' | '!=' | '>' | '>=' | '<' | '<='
	;

//UriValue   = {Letter}+'://'({UriString}|'['|']'|', '''|'')*
//            |{Letter}+':'({UriString}|'['|']'|'')*
URIVALUE: LETTER+ '://' (URISTRING|'['|']'|', \''|'\'')*
//	| LETTER+ ':' (URISTRING|'['|']'|'\'')*
        ;

//RegExPattern = '{/'{RegExChar}+'/}'
REGEXPATTERN
	:	'{/' REGEXCHAR+ '/}';

//String      = '"'{String Char}*'"'
  //          | ''{String Char}*''
STRING
    	:  '\'' ( ESC_SEQ | ~('\\'|'\'') )* '\''
    	|  '"' ( ESC_SEQ | ~('\\'|'"') )* '"'
    	;

SLASH	:	'/';

COMMA	:	',';

OPENBRACKET
	:	'[';

CLOSEBRACKET
	:	']';

OPEN	:	'(';
CLOSE	:	')';