This page is part of the FHIR Specification (v1.4.0: STU 3 Ballot 3). The current version which supercedes this version is 5.0.0. For a full list of available versions, see the Directory of published versions
Implementable Technology Specifications Work Group | Maturity Level: 0 | Ballot Status: Draft |
Note: this content is a joint development between the FHIR project and the CQL project. It is intended that this will be published as its own standard. As a temporary measure, it is included in FHIR until publishing questions are resolved.
FluentPath is a path based navigation and extraction language, somewhat like XPath. Operations are expressed in terms of the logical content of hierarchical data models, and support traversal, selection and projection of data. Its design was influenced by the needs for path navigation, selection and formulation of invariants in both HL7 Fast Healthcare Interoperability Resources (FHIR) and HL7 Clinical Quality Language (CQL).
In both FHIR and CQL, this means that expressions can be written that deal with the contents of the resources and data types as described in the Logical views, or the UML diagrams, rather than against the physical representation of those resources. JSON and XML specific features are not visible to the FluentPath language (such as comments and the split representation of primitives).
The expressions can in theory be converted to equivalent expressions in XPath, OCL, or another similarly expressive language.
FluentPath navigates and selects nodes from a tree that abstracts away and is independent of the actual underlying implementation of the source against which the FluentPath query is run. This way, FluentPath can be used on in-memory Java POJOs, Xml data or any other physical representation, so long as that representation can be viewed as classes that have properties. In somewhat more formal terms, FluentPath operates on a directed acyclic graph of classes as defined by a MOF-equivalent type system.
Data is represented as a tree of labelled nodes, where each node may optionally carry a primitive value and have child nodes. Nodes need not have a unique label, and leaf nodes must carry a primitive value. For example, a (partial) representation of a FHIR Patient resource in this model looks like this:
The diagram shows a tree with a repeating identifier
node, which represents
repeating members of the FHIR object model. Leaf nodes such as use
and
family
carry a (string) value. It is also possible for internal nodes
to carry a value, as is the case for the node labelled active
: this
allows the tree to represent FHIR "primitives", which may still have child extension data.
FluentPath allows navigation in the tree by composing a path of concatenated labels, e.g.
name.given
This would result in a set of nodes, one with the value "Wouter" and one with the value
"Gert". In fact, each step in such a path results in a collection of nodes by selecting nodes
with the given label from the step before it. The focus at the beginning of the evaluation
contained all elements from Patient, and the path name
selected just those
named name
. Since the name
element repeats, the next step
given
along the path, will contain all nodes labeled given
from
all nodes name
in the preceding step.
The underlying type of the entry point (here: Patient) can be represented, but is optional.
To illustrate this point, the path name.given
above can be evaluated as an
expression on a set of data of any type. However, for natural human use, expressions may
be prefixed with the name of the type:
Patient.name.given
The two expressions have the same outcome, but when evaluating the second, the evaluation
will only produce results when used on data of type Patient
.
Syntactically, FluentPath defines identifiers as any sequence of characters consisting only of letters, digits, and underscores, beginning with a letter or underscore. Paths may use double quotes to include characters in path parts that would otherwise be interpreted as operators, e.g.:
Message."PID-1"
In the underlying representation of data, nodes may be typed and represent polymorphic items. Paths may either ignore the type of a node, and continue along the path or may be explicit about the expected node and filter the set of nodes by type before navigating down child nodes:
Observation.value.unit - all kinds of value
Observation.value.as(Quantity).unit - only values that are of type Quantity
The is
function can be used to determine whether or not a given value is of a given type:
Observation.value.is(Quantity) - returns true if the value is of type Quantity
The list of available types that can be passed as a parameter to the
as
and is
functions is determined by the
underlying data model.
It is sometimes useful to refer to the current item under evaluation when writing an expression,
especially within where()
when the value of the current item needs to be passed as
a function parameter. This can be done using the special path $this
:
Patient.name.given.where(substring($this.length()-3)) = 'out'
Collections of nodes are inherently ordered, and implementations must
retain the original order of a collection. There are two special cases:
the outcome of operations like children()
and descendents()
cannot be assumed to be in any meaningful order, and first()
,
last()
, tail()
, skip()
and take()
should not be used on collections derived from these paths. Note that some
implementations may follow the standard order, and some may not, and some may
be different depending on the underlying source.
In addition to paths, FluentPath expressions may contain literals and function invocations. FluentPath supports the following types of literals:
boolean: true, false
string: 'test string', 'urn:oid:3.4.5.6.7.8'
integer: 0, 45
decimal: 0.0, 3.141592653587793236
datetime: @2015-02-04T14:34:28Z - @ followed by ISO8601 compliant date/time
time: @T14:34:28Z @ followed by ISO8601 compliant time (beginning with T)
Unicode is supported in both string literals and quoted identifiers. String literals are surrounded
by single quotes and may use \
-escapes to escape quotes and represent Unicode characters:
\u
followed by four hex digits.\\
(backslash), \/
(slash), \f
(form feed - \u000c),\n
(newline - \u000a), \r
(carriage return - \u000d), \t
(tab - \u0009)Numbers can be integers and may optionally have decimal parts. Numbers cannot use exponential notation.
Expressions can also contain operators, like those for mathematical operations and boolean logic:
Appointment.minutesDuration / 60 > 5
MedicationAdministration.wasNotGiven.exists() implies MedicationAdministration.reasonNotGiven.exists()
name.given | name.last
'sir ' + name.given
Finally, FluentPath supports the notion of functions, which all take a collection of values as input and produce another collection as output. For example:
(name.given | name.last).distinct()
identifier.where(use = 'official')
Since all functions work on collections, constants will first be converted to a collection when functions are invoked on constants:
(4+5).count()
will return 1
, since this is implicitly a collection with one constant number 9
.
Note: There is no concept of
null
in FluentPath. This means that when, in an underlying instance a member is null, there will simply be no corresponding node for that member in the tree. This means that for examplePatient.children()
will contain only non-null members andPatient.name
will return an empty collection if there are no name elements in the instance.
Collections can be evaluated as booleans in logical tests in criteria. When a collection is implicitly converted to a boolean then:
true
This same principle applies when using the path statement in invariants.
Note: Because the path language is side effect free, it does not matter whether implementations use short circuit boolean evaluation or not. However with regard to performance, implementations are encouraged to use short circuit evaluation, and authors of path statements should pay attention to short circuit evaluation when designing statements for optimal performance.
FluentPath functions and operators both propagate empty results. This means in general that if any input to a function or operator is empty, then the result will be empty as well. More specifically:
When functions behave differently (for example the count()
and empty()
functions), this is clearly documented in the next sections.
Functions are distinguished from path navigation names by the fact that they are followed by a ()
with zero or more parameters. Functions always take a collection as input and produce another collection as output, even though these may be collections of just a single item. Correspondingly, arguments to the functions can be any FluentPath expression, though some functions require these expressions to evaluate to a collection containing a single item of a specific type.
The following list contains all functions supported in FluentPath, detailing the expected kind of parameters and kind of collection returned by the function:
item(index: integer)
and it is passed an argument that evaluates to a collection with multiple items or a collection with an item that is not of the required type, the evaluation of the expression will end and an error will be signaled to the calling environment.If the function takes an expression
as a parameter, the function will evaluate this parameter with respect to each of the items in the input collection. These expressions may refer to the special $this
element, which represents the item from the input collection currently under evaluation. For example, in:
name.given.where($this > 'ba' and $this < 'bc')
the where()
function will iterate over each item in the input collection (elements named given
) and $this
will be set to each item when the expression passed to where()
is evaluated.
collection
, e.g. all(...) : boolean
empty() : boolean
Returns true
if the input collection is empty ({ }
) and false
otherwise.
not() : boolean
Returns true
if the input collection evaluates to false
, and false
if it evaluates to true
. Otherwise, the result is empty ({ }
):
not | |
---|---|
true |
false |
false |
true |
empty ({ } ) |
empty ({ } ) |
exists() : boolean
Returns the opposite of empty()
, and as such is a shorthand for empty().not()
all() : boolean
Returns true
if every element in the input collection evaluates to true
, and false
if any element evaluates to false
. Otherwise, the result is empty ({ }
).
subsetOf(other : collection) : boolean
Returns true
if all items in the input collection are members of the collection passed as the other
argument. Membership is determined using the equals (=
) operation (see below).
supersetOf(other : collection) : boolean
Returns true
if all items in the collection passed as the other
argument are members of the input collection. Membership is determined using the equals (=
) operation (see below).
isDistinct() : boolean
Returns true
if all the items in the input collection are distinct. To determine whether two items are distinct, the equals (=
) operator is used, as defined below.
distinct() : collection
Returns a collection containing only the unique items in the input collection. To determine whether two items are the same, the equals (=
) operator is used, as defined below.
count() : integer
Returns a collection with a single value which is the integer count of the number of items in the input collection. Returns 0 when the input collection is empty.
where(criteria : expression) : collection
Filter the input collection to only those elements for which the stated criteria expression evaluates to true.
select(projection: expression) : collection
Evaluates the given expression for each item in the input collection. The result of each evaluation is added to the output collection. If the evaluation results in a collection with multiple items, all items are added to the output collection (collections resulting from evaluation of projection
are flattened).
repeat(projection: expression) : collection
A version of select
that will repeat the projection and add it to the output collection, as long as the projection yields new items (as determined by the Equals operator).
This operation can be used to traverse a tree and selecting only specific children:
ValueSet.expansion.repeat(contains)
Will repeat finding children called contains
, until no new nodes are found.
Questionnaire.repeat(group | question).question
Will repeat finding children called group
or question
, until no new nodes are found.
Note that this is slightly different from
Questionnaire.descendants().select(group | question)
which would find any descendants called group
or question
, not just the ones nested inside other group
or question
elements.
is(type : identifier) : boolean
Returns true
if the collection contains a single element of the given type or a subclass thereof.
as(type : identifier) : collection
Returns a collection that contains all items in the input collection that are of the given type or a subclass thereof.
name[ index : integer ] : collection
This indexer operation returns a collection with only the index
-th item (0-based index). If the index lies outside the boundaries of the input collection, an empty collection is returned.
Example:
Patient.name[0]
single() : collection
Will return the single item in the input if there is just one item. If there are multiple items, an error is signaled to the evaluation environment.
first() : collection
Returns a collection containing just the first item in the list. Equivalent to item(0)
, so it will return an empty collection if the input collection has no items.
last() : collection
Returns a collection containing the last item in the list. Will return an empty collection if the input collection has no items.
tail() : collection
Returns a collection containing all but the first item in the list. Will return an empty collection if the input collection has no or just one item.
skip(num : integer) : collection
Returns a collection containing all but the first num
items in the list. Will return an empty collection if there are no items remaining after the indicated number of items have been skipped.
take(num : integer) : collection
Returns a collection containing the first num
items in the list, or less if there are less then num
items. Will return an empty collection if the input collection is empty.
The functions in this section operate on collections with a single item. If there is more than one item, or an incompatible item, the evaluation of the expression will end and signal an error to the calling environment.
To use these functions over a collection with multiple items, one may use filters like where()
and select()
:
Patient.name.given.select(substring(1))
iif(criterium: expression, true-expression [, otherwise-expression]) : collection
If the input collection contains a single item, this function evaluates the criterium
expression. If this expression evaluates to true, the function evaluates the true-expression
on the input and returns that as a result.
If the evaluation resulted in false
or an empty collection, the otherwise-expression
is evaluated on the input and returned, unless the optional otherwise-expression
is not given, in which case the function returns an empty collection.
If the input has multiple items or is empty, the function will return an empty collection.
toInteger() : integer
If the input collection contains a single item, this function will return a single integer if:
true
results in a 1 and false
results in a 0.In all other cases, the function will return an empty collection.
toDecimal() : decimal
If the input collection contains a single item, this function will return a single decimal if:
true
results in a 1 and false
results in a 0.In all other cases, the function will return an empty collection.
toString() : string
If the input collection contains a single item, this function will return a single string if:
true
results in "true" and false
in "false".In all other cases, the function will return an empty collection.
The functions in this section operate on collections with a single item. If there is more than one item, or an incompatible item, the evaluation of the expression will end and signal an error to the calling environment.
substring(start : integer [, length : integer]) : collection
If the input collection contains a single item of type string, it returns a collection with the part of the string starting at position start
(zero-based). If length
is given, will return at most length
number of characters from the input string.
If start
lies outside the length of the string, the function returns an empty collection. If there are less remaining characters in the string than indicated by length
, the function returns just the remaining characters.
startsWith(string : string) : boolean
If the input collection contains a single item of type string, the function will return true
when the value starts with the specified content.
endsWith(string : string) : boolean
If the input collection contains a single item of type string, the function will return true
when the value ends with the specified content.
matches(regex : string) : boolean
If the input collection contains a single item of type string, the function will return true when the value matches the given regular expression. Regular expressions are supposed to work culture invariant, case-sensitive and in 'single line' mode and allow Unicode characters.
replaceMatches(regex : string, substitution: string) : string
If the input collection contains a single item of type string, the function will match the input using the regular expression in regex
and replace each match with the substitution
string. The substitution may refer to identified match groups in the regular expression.
This example of replaceMatches()
will convert a string with a date formatted as MM/dd/yy to dd-MM-yy:
'11/30/1972'.replaceMatches('\\b(?<month>\\d{1,2})/(?<day>\\d{1,2})/(?<year>\\d{2,4})\\b',
'${day}-${month}-${year}')
Note: All platforms will use their native regular expression implementations, which will commonly be close to the regular expressions in Perl 5, however there are always small differences. I don't think we can prescribe any "common" dialect for FluentPath.
contains(string : string) : boolean
A simpler variation of matches()
that returns a boolean when the given string
is a substring of the single string in the input collection.
replace(pattern : string, substitution : string) : string
A simpler variation of replaceMatches
that returns the input string with all instances of pattern
replaced with substitution
.
length() : integer
If the input collection contains a single item of type string, the function will return the length of the string.
children() : collection
Returns a collection with all immediate child nodes of all items in the input collection.
descendants() : collection
Returns a collection with all descendant nodes of all items in the input collection. The result does not include the nodes in the input collection themselves. Is a shorthand for repeat(children())
.
Note: Many of these functions will result in a set of nodes of different underlying types. It may be necessary to use
as()
as described in the previous section to maintain type safety. See section 8 for more information about type safe use of FluentPath expressions.
memberOf(valueset : string) : boolean
If the input collection contains a single string item, it is taken to be a code and valueset membership is tested against the valueset passed as the argument. The valueset
argument is a uri used to resolve to a valueset.
trace(name : string) : collection
Add a string representation of the input collection to the log, using the parameter name
as the name in the log. This log should be made available to the user in some appropriate fashion. Does not change the input, so returns the input collection as output.
today() : datetime
Returns a datetime containing the current date.
now() : datetime
Returns a datetime containing the current date and time, including timezone.
Operators are allowed to be used between any kind of path expressions (e.g. expr op expr). Like functions, operators will generally propagate an empty collection in any of their operands. This is true even when comparing two empty collections using the equality operators.
= (Equals)
Returns true
if the left collection is equal to the right collection:
If both operands are collections with a single item:
If both operands are collections with at least one item:
Otherwise, equals returns false
.
Note that this implies that if both collections have a different number of items to compare, the result will be false
.
Typically, this operator is used with single fixed values as operands. This means that Patient.telecom.system = 'phone' will return false
if there is more than one telecom with a use
element. Typically, you'd want Patient.telecom.where(system = 'phone')
If one or both of the operands is the empty collection, this operation returns an empty collection.
~ (Equivalent)
Returns true
if the collections are the same.
If both operands are collections with a single item:
If both operands are collections with multiple items:
Note that this implies that if both collections have a different number of items to compare, the result will be false
.
If one or both of the operands is the empty collection, this operation returns an empty collection.
!= (Not Equals)
The inverse of the equals operator.
!~ (Not Equivalent)
The inverse of the equivalent operator.
> (Greater Than)
< (Less Than)
<= (Less or Equal)
>= (Greater or Equal)
is
If the left operand is a collection with a single item and the second operand is an identifier, this operator returns true
if the type of the left operand is the type specified in the second operand, or a subclass thereof. In all other cases this function returns the empty collection.
Patient.contained.all($this is Patient implies age > 10)
as
If the left operand is a collection with a single item and the second operand is an identifier, this function returns the value of the left operand, or a subclass thereof. Otherwise, this operator returns the empty collection.
| (union collections)
Merge the two collections into a single collection, eliminating any duplicate values (using equals (=
)) to determine equality).
in (membership)
If the left operand is a collection with a single item, this operator returns true if the item is in the right operand using equality semantics. This is the inverse operation of contains.
contains (containership)
If the right operand is a collection with a single item, this operator returns true if the item is in the left operand using equality semantics. This is the inverse operation of in.
For all boolean operators, the collections passed as operands are first evaluated as booleans (as described in 4.1). The operators then use three-valued logic to propagate empty operands.
and
Returns true
if both operands evaluate to true
, false
if either operand evaluates to false
, and empty collection ({ }
) otherwise:
true |
false |
empty ({ } ) |
|
---|---|---|---|
true |
true |
false |
empty ({ } ) |
false |
false |
false |
false |
empty ({ } ) |
empty ({ } ) |
false |
empty ({ } ) |
or
Returns false
if both operands evaluate to false
, true
if either operand evaluates to true
, and empty ({ }
) otherwise:
true |
false |
empty ({ } ) |
|
---|---|---|---|
true |
true |
true |
true |
false |
true |
false |
empty ({ } ) |
empty ({ } ) |
true |
empty ({ } ) |
empty ({ } ) |
xor
Returns true
if exactly one of the operands evaluates to true
, false
if either both operands evaluate to true
or both operands evaluate to false
, and the empty collection ({ }
) otherwise:
true |
false |
empty ({ } ) |
|
---|---|---|---|
true |
false |
true |
empty ({ } ) |
false |
true |
false |
empty ({ } ) |
empty ({ } ) |
empty ({ } ) |
empty ({ } ) |
empty ({ } ) |
implies
If the left operand evaluates to true
, this operator returns the boolean evaluation of the right operand. If the left operand evaluates to false
, this operator returns true
. Otherwise, this operator returns true
if the right operand evaluates to true
, and the empty collection ({ }
) otherwise.
true |
false |
empty ({ } ) |
|
---|---|---|---|
true |
true |
false |
empty ({ } ) |
false |
true |
true |
true |
empty ({ } ) |
true |
empty ({ } ) |
empty ({ } ) |
The math operators require each operand to be a single element. Both operands must be of the same type, each operator below specifies which types are supported.
If there is more than one item, or an incompatible item, the evaluation of the expression will end and signal an error to the calling environment.
As with the other operators, the math operators will return an empty collection if one or both of the operands are empty.
* (multiplication)
Multiplies both arguments (numbers only)
/ (division)
Divides the left operand by the right operand (numbers only).
+ (addition)
For numbers, add the numbers. For strings, concatenates the right operand to the left operand.
- (subtraction)
Subtracts the right operand from the left operand (numbers only).
div
Performs truncated division of the left operand by the right operand (numbers only).
mod
Computes the remainder of the truncated division of its arguments (numbers only).
Precedence of operations, in order from high to low:
#01 . (path/function invocation)
#02 [] (indexer)
#03 unary + and -
#04: *, /, div, mod
#05: +, -,
#06: |
#07: >, <, >=, <=
#08: is, as
#09: =, ~, !=, !~
#10: in, contains
#11: and
#12: xor, or
#13: implies
As customary, expressions may be grouped by parenthesis (()
).
A token introduced by a % refers to a value that is passed into the evaluation engine by the calling environment. Using environment variables, authors can avoid repetition of fixed values and can pass in external values and data.
The following environmental values are set for all contexts:
%sct - (string) url for snomed ct
%loinc - (string) url for loinc
%ucum - (string) url for ucum
%"vs-[name]" - (string) full url for the provided HL7 value set with id [name]
%"ext-[name]" - (string) full url for the provided HL7 extension with id [name]
%context - The original node that was passed to the evaluation engine before starting evaluation
Note how the names of the `vs-` and `ext-` constants are escaped (just like paths) to allow "-" in the name.
Implementers should note that using additional environment variables is a formal extension point for the language. Implementation Guides are allowed to define their own externals, and implementers should provide some appropriate configuration framework to allow these constants to be provided to the evaluation engine at run time. E.g.:
%us-zip = '[0-9]{5}(-[0-9]{4}){0,1}'
Authors of Implementation Guides should be aware that adding specific environment variables restricts the use of the FluentPath to their particular context.
Note that these tokens are not restricted to simple types, and they may not have defined fixed values that are known before evaluation at run-time, though there is no way to define these kind of values in implementation guides.
Strongly typed languages are intended to help authors avoid mistakes by ensuring that expressions written describe valid operations. For example, a strongly typed language would typically disallow the expression:
1 + 'John'
because it performs an invalid operation, namely adding numbers and strings. However, there are cases where the author knows that a particular invocation may be safe, but the compiler is not aware of, or cannot infer, the reason. In these cases, type-safety errors can become an unwelcome burden, especially for experienced developers.
As a result, FluentPath defines a strict option that allows an execution environment to determine how much type safety should be applied. With strict enabled, FluentPath behaves as a traditional strongly-typed language, whereas without strict, it behaves as a traditional dynamically-typed language.
For example, since some functions and most operators will only accept a single item as input, and throw an exception otherwise:
Patient.name.given + ' ' + Patient.name.family
will work perfectly fine, as long as the patient has a single name, but will fail otherwise. It is in fact "safer" to formulate such statements as either:
Patient.name.select(given + ' ' + family)
which would return a collection of concatenated first and last names, one for each name of a patient. Of course, if the patient turns out to have multiple given names, even this statement will fail and the author would need to choose the first name in each collection explicitly:
Patient.name.first().select(given.first() + ' ' + family.first())
It is clear that, although more robust, the last expression is also much more elaborate, certainly in situations where, because of external constraints, the author is sure names will not repeat, even if the unconstrained data model allows repetition.
Apart from throwing exceptions, unexpected outcomes may result because of the way the equality operators are defined. The expression
Patient.name.given = 'Wouter'
will return false as soon as a patient has multiple names, even though one of those may well be 'Wouter'. Again, this can be corrected:
Patient.name.where(given = 'Wouter').exists()
but is still less concise than would be possible if constraints were well known in advance.
The strict option provides a mode in which the author of the FluentPath statement is protected against such cases by employing strict typing. Based on the definition of the operators and functions and given the type of input, a compiler can trace the statement and determine whether "unsafe" situations can occur.
Unsafe uses are:
There are a few constructs in the FluentPath language where the compiler cannot trace the type, and should issue a warning to the user when doing "strict" evaluation:
children()
and descendants()
functionsresolve()
functionAuthors can use the as()
function directly after such constructs to inform the compiler of the expected type, so that strict type-checking can continue.
In strict mode, when the compiler finds places where a collection of multiple items can be present while just a single item is expected, the author will need to make explicit how repetitions are dealt with. Depending on the situation one may:
first()
, last()
or indexer ([ ]
) to select a single itemselect()
and where()
to turn the expression into one that evaluates each of the repeating items individually (as in the examples above)single()
to return either the single item or else an empty collection. This is especially useful when using FluentPath to formulate invariants: in cases where single items are considered the "positive" or "true" situation, single()
will return an empty collection, so the invariant will evaluate to the empty collection (or false) in any other circumstance.FluentPath is used in five places in the FHIR specifications:
As stated in the introduction, FluentPath uses a tree model that abstracts away the actual underlying datamodel of the data being queries. For FHIR, this means that the contents of the resources and data types as described in the Logical views (or the UML diagrams) are used as the model, rather than the JSON and XML formats, so specific xml or json features are not visible to the FluentPath language (such as comments and the split representation of primitives).
More specifically:
contained
element node does not have the name of the Resource as its first and only child (instead it directly contains the contained resource's children)FHIR has the notion of choice elements, where elements can be one of multiple types, e.g. Patient.deceased[x]
. In actual instances these will be present as either Patient.deceasedBoolean
or Patient.deceasedDateTime
. In FluentPath choice elements are labeled according to the name without the '[x]' suffix, and children can be explicitly filtered using the asType
operation:
Observation.value.as(Quantity).unit
The evaluation engine will automatically convert the value of FHIR types representing primitives to FluentPath types when they are used in expression in the following fashion:
FHIR primitive type | FluentPath type |
---|---|
boolean | boolean |
string, uri, code, oid, id, uuid, sid, markdown, base64Binary | string |
integer, unsignedInt, positiveInt | integer |
decimal | decimal |
date, dateTime, instant | datetime |
time | time |
Note that FHIR primitives may contain extensions, so that the following expressions are not mutually exclusive:
Patient.name.given = 'Ewout' // value of Patient.name.given as a string
Patient.name.given.extension.first().value = true // extension of the primitive value
FHIR adds (backwards compatible) functionality to the common set of functions:
extension(string : string) : collection
Return any extension with the given URL. This is a syntactical shortcut for .extension.where(url = string)
, but is simpler to write.
trace(name : string) : collection
When FluentPath statements are used in an invariant, the log contents should be added to the error message constructed when the invariant is violated. For example:
"SHALL have a local reference if the resource is provided inline (url: height; ids: length,weight)"
from
"reference.startsWith('#').not()
or ($context.reference.substring(1).log('url') in $resource.contained.id.log('ids'))"
resolve() : collection
For each item in the collection, if it is a string, locate the target of the reference, and add it to the resulting collection. If the item is not a string, the item is ignored and nothing is added to the output collection.
The items in the collection may also represent a Reference, in which case the Reference.reference
is resolved.
If fetching the resource fails, the failure message is added to the output collection.
as(type : identifier) : collection
In FHIR, only concrete core types are allowed as an argument. All primitives are considered to be independent types (so markdown
is not a subclass of string
). Profiled types are not allowed, so to select SimpleQuantity
one would pass Quantity
as an argument.
~ (Equivalence)
Equivalence works in exactly the same manner, but with the addition that for complex types, equality requires all child properties to be equal, except for "id" elements.
The FHIR specification specified one additional variable:
%resource - The original resource current context is part of.
When evaluating a datatype, this would be the resource the element is part of. Do not go past a root resource into a bundle, if it is contained in a bundle
Clinical Quality Language is being extended to use FluentPath as its core expression language, in much the same way that XQuery uses XPath to represent expressions within queries. In particular, the following extensions to CQL are proposed:
When a path expression involves an element with multiple cardinality, the expression is considered short-hand for an equivalent query invocation. For example:
Patient.name
is allowed, and is considered a short-hand for the following query expression:
Patient.name X where X.name is not null return X.name
Note that the restriction is required as it ensures that the resulting list will not contain any null elements.
FluentPath has the ability to reference contexts (using the $
prefix) and environment-defined variables (using the %
prefix). Within CQL, these contexts and environment-defined variables are added to the appropriate scope (global for environment-variables, local for contexts) with the prefix included. This allows them to be referenced like any other variable within CQL, but preserves the prefix as a namespace differentiator.
The following additional operators are being added to CQL:
~
, !~
- Equivalent operators (formerly matches
in CQL)!=
- As a synonym for <>
implies
- Logical implication|
- As a synonym for union
One of the primary syntactic features of FluentPath is the ability to “invoke” a function on a collection. For example:
Patient.name.given.substring(3)
The CQL syntax is being extended to support this style of invocation, but as a short-hand for an equivalent CQL statement for each operator. For example:
stringValue.substring(3, 5)
is allowed, and is considered a short-hand for the following CQL expression:
Substring(stringValue, 3, 5)
For most functions, this short-hand is a simple rewrite, but for contextual functions such as where()
and select()
, this rewrite must preserve the context semantics:
Patient.name.where(given = 'John')
is short-hand for:
Patient.name N where N.given = 'John'
Because CQL is a type-safe language, embedded FluentPath expressions should be compiled in strict mode. However, to enable the use of FluentPath in loose mode, an implicit conversion from a list of elements to an element is added. This implicit conversion is implemented as an invocation of singleton from
, ensuring that if the list has multiple elements at run-time an error will be thrown.
In addition, the underlying Expression Logical Model (ELM) is being extended to allow for dynamic invocation. A Dynamic
type is introduced with appropriate operators to support run-time invocation where necessary. However, these operators are introduced as an additional layer on top of core ELM, and CQL compiled with the strict option will never produce expressions containing these elements. This avoids placing additional implementation burden on systems that do not need dynamic capabilities.