Skip to content

Commit

Permalink
Fixed some performance issues
Browse files Browse the repository at this point in the history
LF-3005
  • Loading branch information
yuriy-sedinkin committed May 8, 2024
1 parent 4efd8b9 commit 35ac9c9
Show file tree
Hide file tree
Showing 11 changed files with 109 additions and 79 deletions.
8 changes: 3 additions & 5 deletions fhir-context/dstu2/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const updateWithGeneratedData = require('../general-additions');
/**
* Exports the FHIR model data for DSTU2. This is an internal structure that
* will likely evolve as more FHIR specific processing is added.
Expand All @@ -24,10 +25,7 @@ const modelInfo = {
path2Type: require('./path2Type.json')
};

// Generate a set of available data types
modelInfo.availableTypes = new Set();
// IE11 probably doesn't support `new Set(iterable)`
Object.keys(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
Object.values(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
// Update with generated data
updateWithGeneratedData(modelInfo)

module.exports = modelInfo;
17 changes: 17 additions & 0 deletions fhir-context/general-additions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Update the model with generated data
module.exports = (modelInfo) => {
// Generate a set of available data types
modelInfo.availableTypes = new Set();
// IE11 probably doesn't support `new Set(iterable)`
Object.keys(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
Object.values(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));

// Generate a hash map to map paths to data types excluding "BackboneElement" and "Element".
modelInfo.path2TypeWithoutElements = {};
for(let i in modelInfo.path2Type) {
if (modelInfo.path2Type[i] === 'Element' || modelInfo.path2Type[i] === 'BackboneElement') {
continue;
}
modelInfo.path2TypeWithoutElements[i] = modelInfo.path2Type[i];
}
};
9 changes: 4 additions & 5 deletions fhir-context/r4/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const updateWithGeneratedData = require('../general-additions');

/**
* Exports the FHIR model data for R4. This is an internal structure that
* will likely evolve as more FHIR specific processing is added.
Expand All @@ -24,10 +26,7 @@ const modelInfo = {
path2Type: require('./path2Type.json')
};

// Generate a set of available data types
modelInfo.availableTypes = new Set();
// IE11 probably doesn't support `new Set(iterable)`
Object.keys(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
Object.values(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
// Update with generated data
updateWithGeneratedData(modelInfo)

module.exports = modelInfo;
8 changes: 3 additions & 5 deletions fhir-context/r5/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const updateWithGeneratedData = require('../general-additions');
/**
* Exports the FHIR model data for R5. This is an internal structure that
* will likely evolve as more FHIR specific processing is added.
Expand All @@ -24,10 +25,7 @@ const modelInfo = {
path2Type: require('./path2Type.json')
};

// Generate a set of available data types
modelInfo.availableTypes = new Set();
// IE11 probably doesn't support `new Set(iterable)`
Object.keys(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
Object.values(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
// Update with generated data
updateWithGeneratedData(modelInfo)

module.exports = modelInfo;
8 changes: 3 additions & 5 deletions fhir-context/stu3/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const updateWithGeneratedData = require('../general-additions');
/**
* Exports the FHIR model data for STU3. This is an internal structure that
* will likely evolve as more FHIR specific processing is added.
Expand All @@ -24,11 +25,8 @@ const modelInfo = {
path2Type: require('./path2Type.json')
};

// Generate a set of available data types
modelInfo.availableTypes = new Set();
// IE11 probably doesn't support `new Set(iterable)`
Object.keys(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
Object.values(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
// Update with generated data
updateWithGeneratedData(modelInfo)

module.exports = modelInfo;

7 changes: 5 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@babel/eslint-parser": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"babel-loader": "^8.2.3",
"benny": "github:caderek/benny#pull/40",
"benny": "github:caderek/benny#0ad058d3c7ef0b488a8fe9ae3519159fc7f36bb6",
"bestzip": "^2.2.0",
"copy-webpack-plugin": "^6.0.3",
"cypress": "^13.7.2",
Expand Down
54 changes: 31 additions & 23 deletions src/fhirpath.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,36 @@ engine.ExternalConstantTerm = function(ctx, parentData, node) {
var identifier = extConstant.children[0];
var varName = engine.Identifier(ctx, parentData, identifier)[0];

var value = ctx.vars[varName];
if (!(varName in ctx.vars)) {
var value;
// Check the user-defined environment variables first as the user can override
// the "context" variable like we do in unit tests. In this case, the user
// environment variable can replace the system environment variable in "processedVars".
if (varName in ctx.vars) {
// Restore the ResourceNodes for the top-level objects of the environment
// variables. The nested objects will be converted to ResourceNodes
// in the MemberInvocation method.
value = ctx.vars[varName];
if (Array.isArray(value)) {
value = value.map(
i => i?.__path__
? makeResNode(i, i.__path__.path || null, null,
i.__path__.fhirNodeDataType || null)
: i?.resourceType
? makeResNode(i, null, null)
: i );
} else {
value = value?.__path__
? makeResNode(value, value.__path__.path || null, null,
value.__path__.fhirNodeDataType || null)
: value?.resourceType
? makeResNode(value, null, null)
: value;
}
ctx.processedVars[varName] = value;
delete ctx.vars[varName];
} else if (varName in ctx.processedVars) {
value = ctx.processedVars[varName];
} else {
if (ctx.definedVars && varName in ctx.definedVars)
value = ctx.definedVars[varName];
else
Expand Down Expand Up @@ -656,27 +684,7 @@ function applyParsedPath(resource, parsedPath, context, model, options) {
// Set up default standard variables, and allow override from the variables.
// However, we'll keep our own copy of dataRoot for internal processing.
let vars = {context: dataRoot, ucum: 'http://unitsofmeasure.org'};
// Restore the ResourceNodes for the top-level objects of the context
// variables. The nested objects will be converted to ResourceNodes
// in the MemberInvocation method.
if (context) {
context = Object.keys(context).reduce((restoredContext, key) => {
if (Array.isArray(context[key])) {
restoredContext[key] = context[key].map(
i => i?.__path__
? makeResNode(i, i.__path__.path || null, null,
i.__path__.fhirNodeDataType || null)
: makeResNode(i, null, null) );
} else {
restoredContext[key] = context[key]?.__path__
? makeResNode(context[key], context[key].__path__.path || null, null,
context[key].__path__.fhirNodeDataType || null)
: makeResNode(context[key], null, null);
}
return restoredContext;
}, {});
}
let ctx = {dataRoot, vars: Object.assign(vars, context), model};
let ctx = {dataRoot, processedVars: vars, vars: context || {}, model};
if (options.traceFn) {
ctx.customTraceFn = options.traceFn;
}
Expand Down
2 changes: 1 addition & 1 deletion src/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ engine.defineVariable = function (x, label, expr) {
// Just in time initialization of definedVars
if (!this.definedVars) this.definedVars = {};

if (Object.keys(this.vars).includes(label)) {
if (label in this.vars || label in this.processedVars) {
throw new Error("Environment Variable %" + label + " already defined");
}

Expand Down
66 changes: 36 additions & 30 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -1315,23 +1315,26 @@ class ResourceNode {
* @return {TypeInfo}
*/
getTypeInfo() {
let result;

if (TypeInfo.model) {
if (/^System\.(.*)$/.test(this.fhirNodeDataType)) {
result = new TypeInfo({namespace: TypeInfo.System, name: RegExp.$1});
} else if (this.fhirNodeDataType) {
result = new TypeInfo({
namespace: TypeInfo.FHIR,
name: this.fhirNodeDataType
});
if (!this.typeInfo) {
let typeInfo;

if (TypeInfo.model) {
if (/^System\.(.*)$/.test(this.fhirNodeDataType)) {
typeInfo = new TypeInfo({namespace: TypeInfo.System, name: RegExp.$1});
} else if (this.fhirNodeDataType) {
typeInfo = new TypeInfo({
namespace: TypeInfo.FHIR,
name: this.fhirNodeDataType
});
}
}
}

return result
// Resource object properties that are not defined in the model now have
// System.* data types:
|| TypeInfo.createByValueInSystemNamespace(this.data);
this.typeInfo = typeInfo
// Resource object properties that are not defined in the model now have
// System.* data types:
|| TypeInfo.createByValueInSystemNamespace(this.data);
}
return this.typeInfo;
}

toJSON() {
Expand All @@ -1350,24 +1353,27 @@ class ResourceNode {
* @return {FP_Type|any}
*/
convertData() {
var data = this.data;
const cls = TypeInfo.typeToClassWithCheckString[this.path];
if (cls) {
data = cls.checkString(data) || data;
} else if (TypeInfo.isType(this.path, 'Quantity')) {
if (data?.system === ucumSystemUrl) {
if (typeof data.value === 'number' && typeof data.code === 'string') {
if (data.comparator !== undefined)
throw new Error('Cannot convert a FHIR.Quantity that has a comparator');
data = new FP_Quantity(
data.value,
FP_Quantity.mapUCUMCodeToTimeUnits[data.code] || '\'' + data.code + '\''
);
if (!this.convertedData) {
var data = this.data;
const cls = TypeInfo.typeToClassWithCheckString[this.path];
if (cls) {
data = cls.checkString(data) || data;
} else if (TypeInfo.isType(this.path, 'Quantity')) {
if (data?.system === ucumSystemUrl) {
if (typeof data.value === 'number' && typeof data.code === 'string') {
if (data.comparator !== undefined)
throw new Error('Cannot convert a FHIR.Quantity that has a comparator');
data = new FP_Quantity(
data.value,
FP_Quantity.mapUCUMCodeToTimeUnits[data.code] || '\'' + data.code + '\''
);
}
}
}
}

return data;
this.convertedData = data;
}
return this.convertedData;
}

}
Expand Down
7 changes: 5 additions & 2 deletions src/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,11 @@ util.makeChildResNodes = function(parentResNode, childProperty, model) {
}
}

const fhirNodeDataType = model && model.path2Type[childPath] || null;
childPath = fhirNodeDataType === 'BackboneElement' || fhirNodeDataType === 'Element' ? childPath : fhirNodeDataType || childPath;
let fhirNodeDataType = null;
if (model) {
fhirNodeDataType = model.path2Type[childPath] || null;
childPath = model.path2TypeWithoutElements[childPath] || childPath;
}

let result;
if (util.isSome(toAdd) || util.isSome(_toAdd)) {
Expand Down

0 comments on commit 35ac9c9

Please sign in to comment.