You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
There are several places where the cql-engine relies upon runtime reflection to reduce the amount of boilerplate code that must be maintained. The use of reflection means that some scenarios require caching to get reasonable performance. There are also some platforms for which reflection is particularly slow, like Andriod, that could benefit.
Two areas in particular are:
Resolving runtime types
Resolving context relationships
Resolving runtime types
CQL allows the use of arbitrary clinical data models. One or more data models are selected with the using statement. For example:
using FHIR version '4.0.0'
or
using QDM version '5.6'
At runtime the cql-engine maps the logical data model to Java classes. This is the job of the ModelResolver. If one is using the engine.fhir package, it maps the FHIR model to the HAPI FHIR data structures. For example, the following CQL:
using FHIR version '4.0.0'
define "Encounter":
[Encounter]
results in the creation of 0 or more org.hl7.fhir.r4.model.Encounter instances in the engine. The resolveType function in the ModelResolver does this: resolveType
Snippet:
@OverridepublicClass<?> resolveType(StringtypeName) {
// dataTypesBaseRuntimeElementDefinition<?> definition = this.fhirContext.getElementDefinition(typeName);
if (definition != null) {
returndefinition.getImplementingClass();
}
try {
// Resourcesreturnthis.fhirContext.getResourceDefinition(typeName).getImplementingClass();
} catch (Exceptione) {
}
// Sniptry {
returnClass.forName(typeName);
} catch (ClassNotFoundExceptione) {
thrownewUnknownType(String.format("Could not resolve type %s. Primary package(s) for this resolver are %s",
typeName, String.join(",", this.packageNames)));
}
It'd be possible to enumerate all the types in FHIR and manually generate a switch statement that looks something like:
Given the number of resources in FHIR and the various versions of FHIR, this would be a lot of manually generated code that'd need to be maintained.
Alternatively, we could code-gen those switch statements as part of the build. That should be straightforward and be easy to extend as new versions of FHIR are released.
Resolving context relationships
CQL supports various "contexts" that imply an automatic filter on the data being returned. For example:
using FHIR version '4.0.0'
context Patient
define "Encounter":
[Encounter]
implies that all the Encounters being returned are filtered to the particular patient in context. In pseudo-SQL, it's something like:
select*from Encounter E whereE.patientId="123"
The cql-engine works out what attribute to use to filter Encounter with using reflection: getContextPath
The logic is approximately "if we have a set of Encounters that we're filtering to a specific patient, find the attribute on Encounter that represents the foreign key to Patient".
This too could be done at compile time to generate a switch that looks approximately like:
switch(contextType) {
case"Encounter":
switch(targetType) {
case"Diagnosis": return"encounterId"; // select * from Diagnosis D where encounterId = "XYZ" - All the diagnosis for a given encounter
}
case"Patient":
switch(targetType) {
case"Encounter": return"patientId"; // All the Encounters for a given Patientcase"Observation": return"subjectId"; // All the Observations for a given Patient
}
}
The context paths are in terms of the logical model and not the physical model, so a second resolution may be required to map that path to a given Java class.
NOTE: All those switch statements might be able to be further optimized by hashing string a switching on integers, so that's all meant to be pseudo-code.
Code Generation
The cql-engine already uses some JAXB based code-gen to create classes representative of the ELM model. These are derived from the XML schema for ELM. However, this may not be a good fit for the code generation needed for this use case since it requires inspecting Java classes (or at least meta data about Java classes). We could write the code to generate the functions described above as a one-off, but ideally, it'd be fully integrated into the build to minimize the amount of manual maintenance required. There are a couple of build plugins for Maven that allow code generation. We need to research those and determine which would be most appropriate for this use case. If there is no existing plugin that's applicable we should consider creating one.
The text was updated successfully, but these errors were encountered:
There are several places where the cql-engine relies upon runtime reflection to reduce the amount of boilerplate code that must be maintained. The use of reflection means that some scenarios require caching to get reasonable performance. There are also some platforms for which reflection is particularly slow, like Andriod, that could benefit.
Two areas in particular are:
Resolving runtime types
CQL allows the use of arbitrary clinical data models. One or more data models are selected with the
using
statement. For example:using FHIR version '4.0.0'
or
At runtime the cql-engine maps the logical data model to Java classes. This is the job of the
ModelResolver
. If one is using theengine.fhir
package, it maps the FHIR model to the HAPI FHIR data structures. For example, the following CQL:results in the creation of 0 or more
org.hl7.fhir.r4.model.Encounter
instances in the engine. TheresolveType
function in theModelResolver
does this: resolveTypeSnippet:
It'd be possible to enumerate all the types in FHIR and manually generate a switch statement that looks something like:
Given the number of resources in FHIR and the various versions of FHIR, this would be a lot of manually generated code that'd need to be maintained.
Alternatively, we could code-gen those switch statements as part of the build. That should be straightforward and be easy to extend as new versions of FHIR are released.
Resolving context relationships
CQL supports various "contexts" that imply an automatic filter on the data being returned. For example:
implies that all the Encounters being returned are filtered to the particular patient in context. In pseudo-SQL, it's something like:
The cql-engine works out what attribute to use to filter Encounter with using reflection: getContextPath
The logic is approximately "if we have a set of Encounters that we're filtering to a specific patient, find the attribute on Encounter that represents the foreign key to Patient".
This too could be done at compile time to generate a switch that looks approximately like:
The context paths are in terms of the logical model and not the physical model, so a second resolution may be required to map that path to a given Java class.
NOTE: All those switch statements might be able to be further optimized by hashing string a switching on integers, so that's all meant to be pseudo-code.
Code Generation
The cql-engine already uses some JAXB based code-gen to create classes representative of the ELM model. These are derived from the XML schema for ELM. However, this may not be a good fit for the code generation needed for this use case since it requires inspecting Java classes (or at least meta data about Java classes). We could write the code to generate the functions described above as a one-off, but ideally, it'd be fully integrated into the build to minimize the amount of manual maintenance required. There are a couple of build plugins for Maven that allow code generation. We need to research those and determine which would be most appropriate for this use case. If there is no existing plugin that's applicable we should consider creating one.
The text was updated successfully, but these errors were encountered: