Liking cljdoc? Tell your friends :D

ECL Implementation Status

This document tracks which Expression Constraint Language features Hermes supports. For usage examples with the HTTP API, see search-and-ecl.md.

Hermes parses the full ECL v2.2 grammar. The parser accepts all valid ECL expressions; limitations are in the evaluator, which throws an exception for unsupported clauses. This means adding support for a feature never requires changing the grammar — only adding evaluation logic.

ECL Core and Extended profiles

ECL v2.3 (March 2026) introduced two conformance profiles, defined by separate ABNF grammars in the IHTSDO ECL repository:

  • ECL Core — a minimal subset for terminology servers. Covers hierarchy operators, refinements (expression, numeric, string and boolean comparison), compound expressions, attribute groups (without cardinality), dot notation, memberOf, concept active filter, history supplements, and comments. No cardinality, no reverse flag, no description or member filters.
  • ECL Extended — everything in Core plus cardinality, reverse flag, description/concept/member filters, dialect filters, alternate identifiers, and reference set field selection.

Hermes implements most of ECL Core. The remaining Core gaps — all planned — are: string and boolean concrete refinements, > * and >! * wildcards, disjunction (OR) and parenthesized conditions within attribute groups, and two v2.3 additions (^R refsetContainingAny and active = *). Hermes also implements most of ECL Extended. See the summary table below for complete status.

Summary

FeatureProfileStatus
Descendant of (<)CoreYes
Descendant or self of (<<)CoreYes
Child of (<!)CoreYes
Child or self of (<<!)CoreYes
Ancestor of (>)CoreYes
Ancestor or self of (>>)CoreYes
Parent of (>!)CoreYes
Parent or self of (>>!)CoreYes
Top of set (!!>)CoreYes
Bottom of set (!!<)CoreYes
Wildcard (*)CoreYes
Wildcard > *, >! *CorePlanned
Concept references with pipe termsCoreYes
Conjunction (AND, ,)CoreYes
Disjunction (OR)CoreYes
Exclusion (MINUS)CoreYes
Nested expressions ( )CoreYes
Attribute refinement (:)CoreYes
Expression comparison (=, !=)CoreYes
Numeric concrete (#value)CoreYes
String concrete ("value")CorePlanned
Boolean concrete (true/false)CorePlanned
Attribute groups { }CoreYes
Dot notation (.)CoreYes
MemberOf (^)CoreYes
MemberOf with wildcard (^ *)CoreYes
RefsetContainingAny (^R)CorePlanned
Concept active filterCoreYes
Active filter wildcard (active = *)CorePlanned
History supplementsCoreYes
Comments (/* */)CoreYes
Attribute cardinality ([min..max])ExtendedYes
Attribute group cardinalityExtendedPlanned
Cardinality + reverse flagExtendedPlanned
[0..0] with !=ExtendedPlanned
Reverse flag (R)ExtendedYes
Refset field selection (^ [field])ExtendedNo
Description term filterExtendedYes
Description type filterExtendedYes
Description dialect filter (= only)ExtendedYes
Description active filterExtendedYes
Description module filterExtendedPlanned
Description effectiveTime filterExtendedNo
Description ID filterExtendedPlanned
Description language filterExtendedNo
Concept definition status filterExtendedNo
Concept module filterExtendedNo
Concept effectiveTime filterExtendedNo
Member active filterExtendedYes
Member module filterExtendedYes
Member effectiveTime filterExtendedYes
Member field filters (no time)ExtendedYes
Alternate identifiersExtendedNo

Supported features

Hierarchy operators

All ten hierarchy operators are supported: <, <<, <!, <<!, >, >>, >!, >>!, !!>, and !!<. Wildcards work with << *, >> *, < *, <! *, <<! *, and >>! *. Wildcards with > * and >! * are planned.

Compound expressions

Conjunction (AND and ,), disjunction (OR), and exclusion (MINUS) are all supported, including nested parenthesized expressions.

Refinements

Attribute refinements with expression comparison operators (=, !=) and numeric concrete values (#250, #62.5) with all comparison operators (=, !=, <, <=, >, >=) are supported.

String concrete refinements (e.g., 3460481009 = "PANADOL") and boolean concrete refinements (e.g., * : * = true) are not yet supported.

Attribute groups

Attribute groups with same-group semantics are supported. Multiple attributes within { } are conjunctive (AND). Disjunction (OR) within groups and parenthesized group conditions are not yet supported.

Cardinality

Cardinality constraints on individual attributes are supported: [0..0], [1..1], [0..1], [2..*], etc. Group cardinality ([1..3] { ... }), combined cardinality with reverse flag ([3..3] R 127489000 = *), and [0..0] with != (universal quantification) are not yet supported.

Dot notation

Single and chained dot notation for attribute value navigation is supported: . 363698007 and . 363698007 . 116680003.

MemberOf

MemberOf (^) is supported with concept IDs (^ 723264001), wildcards (^ * for all installed reference sets), and nested expressions (^ (<< 723264001)). Member filter constraints can be applied to refine results.

Reference set field selection (^ [targetComponentId] ...) is not supported.

Reverse flag

The reverse flag (R) is supported on simple attributes. It is not yet supported in combination with cardinality constraints.

Description filters

Supported description filters:

  • Term filter: match (term = "heart attack") and wildcard (term = wild:"*sclero*") with locale-aware case folding
  • Type filter: by token (type = syn, type = fsn, type = def) and by ID (typeId = 900000000000013009)
  • Dialect filter: by alias (dialect = en-gb) and by ID (dialectId = 999001261000000100), including dialect sets (dialect = (en-gb en-us)). Only the = operator is supported; != is not yet implemented, nor are per-dialect acceptability annotations (e.g. dialect = (en-gb preferred))
  • Active filter: active = true / active = 1

Concept filters

Only the active filter is supported ({{ C active = true }}). Definition status, module, and effectiveTime filters are not planned — see below.

Member filters

All member filter types are supported:

  • Active filter: {{ M active = true }}
  • Module filter: {{ M moduleId = ... }} with = and !=
  • EffectiveTime filter: with all comparison operators
  • Field filters: numeric, boolean, string (typed search), and expression comparison on arbitrary reference set member fields (e.g., {{ M mapTarget = "J45.9" }}). Time comparison on arbitrary fields is not yet supported (only the dedicated effectiveTime filter handles dates)

History supplements

All history profiles are supported: {{ +HISTORY }}, {{ +HISTORY-MIN }}, {{ +HISTORY-MOD }}, {{ +HISTORY-MAX }}, and custom history subsets ({{ +HISTORY (subexpression) }}).

Comments

Block comments (/* ... */) are supported.

Planned

Features that will be implemented:

FeatureNotes
> * and >! *Return all non-leaf concepts. Trivial to add.
Per-dialect acceptability annotationsdialect = (en-gb preferred) syntax parses but the evaluator does not yet treat the acceptability token as distinct from a dialect alias.
String concrete refinementse.g., 3460481009 = "PANADOL". Concrete string values in attribute refinements.
Boolean concrete refinementse.g., * : * = true.
OR within attribute groupsDisjunction inside { } syntax.
Parenthesized group conditions{ (attr = val, attr2 = val) } syntax.
Attribute group cardinality[1..3] { 127489000 = < 105590001 }. Cardinality on groups, not individual attributes.
Cardinality + reverse flag[3..3] R 127489000 = *. Combined constraint.
[0..0] with !=Universal quantification. Requires care to implement correctly.
Dialect filter !=Dialect filters currently only support the = operator.
Description module filterThe index already stores module-id; only evaluator wiring is needed.
Description ID filterThe index already stores description-id with query helpers in place.

ECL v2.3 features

These require upgrading the ABNF grammar from v2.2 to v2.3:

FeatureNotes
^R (refsetContainingAny)Inverse of memberOf — concepts that contain matching reference set members.
Active filter wildcard (active = *)Match both active and inactive. Trivial addition.
Simplified concrete string matchingconcreteString replaces typedSearchTerm in attribute value matching. Simplifies both grammar and evaluator.

Not currently supported

These features are parsed by the grammar but aren't currently implemented. Most would require expanding the search index — increasing database size for all users — to serve use cases that are primarily relevant to authoring tools rather than clinical or analytic use. Others, such as the language filter, are omitted because they are deprecated in the ECL specification and superseded by better-supported constructs. If you have a use case for one of these features, please open an issue and explain the clinical, analytic, or interoperability need — none of these are closed doors.

FeatureRationale
Concept definition status filterRequires indexing definition status. Primarily useful in authoring (distinguishing primitive from fully defined concepts).
Concept module filterRequires indexing module ID on concepts. Authoring and release management use case.
Concept effectiveTime filterRequires indexing effective time on concepts. Authoring and release management use case.
Description effectiveTime filterRequires indexing effective time on descriptions. Authoring and release management use case.
Alternate identifiersRequires external identifier mapping infrastructure (e.g., LOINC scheme aliases). Cross-terminology mapping is handled via reference set member filters instead.
Refset field selection^ [targetComponentId] ... — returns specific fields from reference set members rather than concepts. Complex to implement and a niche use case.
Language filterDeprecated in the ECL specification and semantically weaker than dialect filters. language = en only matches the coarse two-letter description language code; it cannot express language reference set membership or acceptability (for example en-GB preferred vs acceptable). Hermes intentionally supports dialect filters instead, because they match how SNOMED CT language selection is performed in practice.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close