Skip to main content

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:

TypeSyntaxExamples
Booleantrue, falseactive = true
StringSingle quotes with escapes'hello', 'it\'s'
IntegerWhole numbers123, -45
DecimalNumbers with decimal point3.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
QuantityNumber with unit10 'mg', 4 days

Operators​

See Operators in the FHIRPath specification for detailed semantics.

Comparison operators​

OperatorDescription
=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​

OperatorDescription
andLogical AND
orLogical OR
xorExclusive OR
impliesLogical implication

Arithmetic operators​

OperatorDescription
+Addition / string concatenation
-Subtraction
*Multiplication
/Division
modModulus

Unary + and - are also supported for numeric values.

Collection operators​

OperatorDescription
|Union of two collections (equivalent to the union() function)
inTest if element is in collection
containsTest if collection contains element

Type operators​

OperatorDescription
isType checking
asType casting

Standard functions​

The following standard FHIRPath functions are implemented. See Functions in the FHIRPath specification for detailed semantics.

Existence functions​

FunctionDescription
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​

FunctionDescription
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​

FunctionDescription
first()Returns the first element of the collection

Combining functions​

FunctionDescription
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​

FunctionDescription
not()Boolean negation

String functions​

FunctionDescription
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​

FunctionDescription
is(type)Type checking (equivalent to is operator)
as(type)Type casting (equivalent to as operator)

Conversion functions​

FunctionDescription
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
FunctionDescription
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​

FunctionDescription
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.

info

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() and supersetOf()
  • 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 maxUnboundTraversalDepth parameter in the query configuration.
  • In the Pathling server, via the pathling.query.maxUnboundTraversalDepth configuration property.

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_level parameter (Python and R) or the maxNestingLevel encoding configuration (Java and Scala).
  • In the Pathling server, via the pathling.encoding.maxNestingLevel configuration 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, and subsumedBy.
  • SQL on FHIR functions - Functions defined in the SQL on FHIR specification for joining views: getResourceKey and getReferenceKey.
  • Extension functions - Functions unique to Pathling, including terminology functions like designation, display, property, and translate, plus the Coding literal data type.