FHIRPath
Pathling leverages the FHIRPath language in order to abstract away some of the complexity of navigating and interacting with FHIR data structures.
Pathling implements the FHIRPath subset within the Sharable View Definition profile of the SQL on FHIR view definition, plus additional terminology and utility functions.
Supported language features
Path navigation
Pathling supports standard FHIRPath path navigation using dot notation:
Patient.name.given
Observation.code.coding.system
Array indexing is supported using bracket notation:
Patient.name[0].given
The $this special variable is supported for referring to the current context
within expressions.
Literals
Pathling supports the following literal types:
| Type | Syntax | Examples |
|---|---|---|
| Boolean | true, false | active = true |
| String | Single quotes with escapes | 'hello', 'it\'s' |
| Integer | Whole numbers | 123, -45 |
| Decimal | Numbers with decimal point | 3.14, -0.5 |
| Date | @ prefix, ISO 8601 | @2023-01-15 |
| DateTime | @ prefix, ISO 8601 | @2023-01-15T14:30:00 |
| Time | @T prefix | @T14:30:00 |
| Quantity | Number with unit | 10 'mg', 4 days |
Operators
See Operators in the FHIRPath specification for detailed semantics.
Comparison operators
| Operator | Description |
|---|---|
= | Equality |
!= | Inequality |
< | Less than |
<= | Less than or equal to |
> | Greater than |
>= | Greater than or equal to |
A Quantity that carries a
comparator records a bound rather than a known value, so comparing it with a
single value returns an empty result. For example, if Observation.value is
<5 mg, then Observation.value.ofType(Quantity) = 5 'mg' and
Observation.value.ofType(Quantity) < 6 'mg' are both empty. Such a quantity
is never equal to another value, so collections that contain one are not
equal, it is not removed as a duplicate by union or distinct, and it is not
found by in or contains.
Boolean operators
| Operator | Description |
|---|---|
and | Logical AND |
or | Logical OR |
xor | Exclusive OR |
implies | Logical implication |
Arithmetic operators
| Operator | Description |
|---|---|
+ | Addition / string concatenation |
- | Subtraction |
* | Multiplication |
/ | Division |
mod | Modulus |
Unary + and - are also supported for numeric values.
Collection operators
| Operator | Description |
|---|---|
| | Union of two collections (equivalent to the union() function) |
in | Test if element is in collection |
contains | Test if collection contains element |
Type operators
| Operator | Description |
|---|---|
is | Type checking |
as | Type casting |
Standard functions
The following standard FHIRPath functions are implemented. See Functions in the FHIRPath specification for detailed semantics.
Existence functions
| Function | Description |
|---|---|
exists(criteria?) | Returns true if the collection has any elements, optionally filtered by criteria |
empty() | Returns true if the collection is empty |
count() | Returns the integer count of items in the collection (0 if empty) |
all(criteria) | Returns true if criteria evaluates to true for every element (true for an empty collection) |
allTrue() | Returns true if all Boolean items are true (true for an empty collection) |
anyTrue() | Returns true if any Boolean item is true (false for an empty collection) |
allFalse() | Returns true if all Boolean items are false (true for an empty collection) |
anyFalse() | Returns true if any Boolean item is false (false for an empty collection) |
isDistinct() | Returns true if all items in the collection are distinct, using FHIRPath equality (=) |
Filtering and projection functions
| Function | Description |
|---|---|
where(criteria) | Filter collection by criteria expression |
select(projection) | Evaluate projection for each element, flattening results |
ofType(type) | Filter collection by type |
repeat(projection) | Recursively evaluate projection, deduplicating results |
repeatAll(projection) | Recursively evaluate projection without deduplication (see below) |
distinct() | Returns only the unique items, using FHIRPath equality (=); element order is not preserved |
Subsetting functions
| Function | Description |
|---|---|
first() | Returns the first element of the collection |
Combining functions
| Function | Description |
|---|---|
union(other) | Merge two collections, eliminating duplicates via FHIRPath equality (equivalent to |) |
combine(other) | Merge two collections without eliminating duplicates |
Both functions follow the type-reconciliation rules used by the | operator
(for example, Integer is promoted to Decimal when merging with a Decimal
collection), and neither introduces a new iteration context — arguments are
evaluated against the same focus that applies to the function's input, so
expressions like name.select(use.union(given)) resolve given against the
current name element, matching name.select(use | given).
Boolean functions
| Function | Description |
|---|---|
not() | Boolean negation |
String functions
| Function | Description |
|---|---|
join(separator?) | Join strings with optional separator |
startsWith(prefix) | Test whether the string starts with the given prefix |
endsWith(suffix) | Test whether the string ends with the given suffix |
contains(substring) | Test whether the string contains the given substring |
Type functions
| Function | Description |
|---|---|
is(type) | Type checking (equivalent to is operator) |
as(type) | Type casting (equivalent to as operator) |
Conversion functions
| Function | Description |
|---|---|
toBoolean() | Convert to Boolean |
toInteger() | Convert to Integer |
toDecimal() | Convert to Decimal |
toString() | Convert to String |
toDate() | Convert to Date |
toDateTime() | Convert to DateTime |
toTime() | Convert to Time |
toQuantity(unit?) | Convert to Quantity with optional unit conversion using UCUM |
| Function | Description |
|---|---|
convertsToBoolean() | Check if convertible to Boolean |
convertsToInteger() | Check if convertible to Integer |
convertsToDecimal() | Check if convertible to Decimal |
convertsToString() | Check if convertible to String |
convertsToDate() | Check if convertible to Date |
convertsToDateTime() | Check if convertible to DateTime |
convertsToTime() | Check if convertible to Time |
convertsToQuantity(unit?) | Check if convertible to Quantity |
A FHIR Quantity converts to
a FHIRPath Quantity or String only if it
maps to a System.Quantity:
it must have a value and a code, its system must be
http://unitsofmeasure.org (UCUM) or the FHIRPath calendar duration system
(https://hl7.org/fhirpath/N1/calendar-duration), and it must not carry a
comparator. For any other Quantity, toQuantity() and toString() return an
empty result, and convertsToQuantity() and convertsToString() return
false. For example, if Observation.value is 3 tab with no system, or
<5 mg, then Observation.value.ofType(Quantity).toQuantity() is empty.
Utility functions
| Function | Description |
|---|---|
trace(name, projection?) | Log the current value under the given name and return it unchanged, optionally applying a projection |
The name argument must be a string literal.
A column returned by fhirPathToColumn (fhirpath_to_column in Python,
pathling_fhirpath_to_column in R) that contains trace() cannot be passed
directly to a Spark aggregate function such as sum or collect_list. Spark
treats the expression as non-deterministic because of its side effects and
rejects it with AGGREGATE_FUNCTION_WITH_NONDETERMINISTIC_EXPRESSION. This
does not affect FHIRPath functions such as count(), extract, or
ViewDefinitions.
To aggregate over such a column, project it first and aggregate the projected column. The trace still fires for every row.
given_count = pc.fhirpath_to_column("Patient", "name.trace('t').given.count()")
# Fails.
patients.agg(F.sum(given_count))
# Works.
patients.withColumn("given_count", given_count).agg(F.sum("given_count"))
Limitations
The following FHIRPath features are not currently supported:
- Equivalence operators:
~and!~ - Aggregate functions:
sum(),avg(),min(),max() - Special variables:
$index,$total - Subset functions:
subsetOf()andsupersetOf() - Quantity arithmetic: Math operations on Quantity types
- DateTime arithmetic: DateTime math operations
- Full resource resolution: The
resolve()function extracts type information only and does not support field traversal
Recursive traversal depth
Both repeat and repeatAll use static type analysis to determine how to
handle the recursion. When the projection expression produces the same FHIR
type at each level (e.g. navigating item within a Questionnaire), the
traversal is bounded by a configurable maximum depth. The default maximum depth
is 10.
For Extension traversal, depth exhaustion silently stops and returns results collected up to that point. For other types, depth exhaustion raises an error.
The maximum depth can be configured:
- In the Pathling libraries, via the
maxUnboundTraversalDepthparameter in the query configuration. - In the Pathling server, via the
pathling.query.maxUnboundTraversalDepthconfiguration property.
Navigation beyond the encoded depth
Recursive elements, such as item within a Questionnaire, are only encoded to
the maximum nesting level, which defaults to 3. Navigating deeper than this
returns an empty collection, as if the data were absent, and functions behave
accordingly. With the default level, four levels of item are encoded, so
item.item.item.item.item.exists() is false and
item.item.item.item.item.count() is 0, even if the source data has a fifth
level of items. repeat and repeatAll stop at the deepest encoded level.
Selecting a type with ofType() that is not one of the encoded open types, such
as an extension value type that has been left out of the encoding, still raises
an error. That indicates a configuration that omits the data, rather than a
depth limit.
The maximum nesting level can be configured:
- In the Pathling libraries, via the
max_nesting_levelparameter (Python and R) or themaxNestingLevelencoding configuration (Java and Scala). - In the Pathling server, via the
pathling.encoding.maxNestingLevelconfiguration property.
Additional functions
Pathling also supports additional functions beyond the standard FHIRPath specification:
- FHIR-specific functions - Functions defined in the FHIR
specification for use with FHIR data, including
extension,resolve,memberOf,subsumes, andsubsumedBy. - SQL on FHIR functions - Functions defined in the
SQL on FHIR specification for joining views:
getResourceKeyandgetReferenceKey. - Extension functions - Functions unique to Pathling,
including terminology functions like
designation,display,property, andtranslate, plus the Coding literal data type.