Skip to content

Commit

Permalink
Convert cql-execution to async (#271)
Browse files Browse the repository at this point in the history
This commit contains all work done to convert the source code to async. Individual commits below show the progress of first converting over the source code and then fixing all of the unit tests as well as other issues that surfaced due to the underlying source code changes.

* WIP: initial async conversion in source code

* Asyncified elm date-tests

* Asyncified elm datetime-tests

* Fixed rejection test for Today()

* asyncified list-tests. fixed missing Promise.all and added some types mainly to the builder and base expression class while hunting down the issue

* interval tests converted

* asyncified elm parameters-test

* asyncified elm external-test

* fixed should issues in elm external-test

* asyncified elm library-test (shows errors caused by sort issues)

* Asyncified elm instance-test

* Asyncified elm structured-test

* Asyncified elm nullological-test

* asyncified elm message-test

* asyncify elm executor-test

* more efficient use of Promise.all in List expression

* added async/await for clinical tests

* removed unnecessary async/awaits

* changed should() syntax

* Asyncified elm string-test

* Asyncified elm logical-test

* Asyncified elm type-test

* async-ify convert-test and fix convert bug

* Asyncified elm query-test

* added missing await for query relationship resolution in query expression processing

* asyncify aggregate test

* asyncify literal and reusable

* remove erroneous asyncs

* Update comparison-test.ts

* add parens

* add async sorting functionality for queries

* remove verified checked TODOs

* regen cql4browsers

* Update browser and node examples to async

* update docs to async

* Use new should syntax in example unit test documentation

* Switch asyncMergeSort to use generics and add more tests

* Add detection of promises from patient source and await them

* Add V2_to_V3_MIGRATION document and update README example

* Update terminology to allow for async providers

* Remove uses of isPromise and always use await

* Use better function typing in multi-source query forEach

* Add documentation about terminology provider async to migration guide

* Force synchronous iteration where it might matter

In testing, I discovered that some queries do not operate correctly when each iteration of the query is being executed asynchronously. It seems related to how aliases and contexts are handled. Instead of allowing a query to run over the list asynchronously, force synchronous iteration to avoid these issues.

* Preserve original order for equivalent items in async merge sort

When two items compare as equal, the sort should preserve the original order of the items relative to each other.  In other words, if item A and item B are deemed equivalent by the sort comparator, and item A appeared before item B in the original unsorted list, then item A should continue to appear before item B in the sorted list.  This makes for a more predictable stable ordering.

* Separate out construction of expression and execution in ELM type conversion

Co-authored-by: Chris Hossenlopp <hossenlopp@mitre.org>
Co-authored-by: sarahmcdougall <smcdougall@mitre.org>
Co-authored-by: natjoe4 <njjones@mitre.org>
Co-authored-by: mriley <mriley@mitre.org>
Co-authored-by: Chris Moesel <cmoesel@mitre.org>
  • Loading branch information
6 people committed Mar 28, 2023
1 parent a63a9ea commit e11d0b7
Show file tree
Hide file tree
Showing 63 changed files with 6,160 additions and 5,703 deletions.
32 changes: 22 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,14 @@ const psource = new cql.PatientSource([
}
]);

const result = executor.exec(psource);
console.log(JSON.stringify(result, undefined, 2));
executor
.exec(psource)
.then(result => {
console.log(JSON.stringify(result, undefined, 2));
})
.catch(err => {
console.error(err);
});
```

In the above file, we've assumed the JSON ELM JSON file for the measure is called
Expand Down Expand Up @@ -187,8 +193,14 @@ const psource = new cql.PatientSource([ {
'birthDate' : '2007-08-02T11:47'
} ]);

const result = executor.exec(psource);
console.log(JSON.stringify(result, undefined, 2));
executor
.exec(psource)
.then(result => {
console.log(JSON.stringify(result, undefined, 2));
})
.catch(err => {
console.error(err);
});

```

Expand Down Expand Up @@ -308,16 +320,16 @@ describe('And', () => {
setup(this, data);
});

it('should execute allTrue as true', function () {
this.allTrue.exec(this.ctx).should.be.true();
it('should execute allTrue as true', async function () {
should(await this.allTrue.exec(this.ctx)).be.true();
});

it('should execute someTrue as false', function () {
this.someTrue.exec(this.ctx).should.be.false();
it('should execute someTrue as false', async function () {
should(await this.someTrue.exec(this.ctx)).be.false();
});

it('should execute allFalse as false', function () {
this.allFalse.exec(this.ctx).should.be.false();
it('should execute allFalse as false', async function () {
should(await this.allFalse.exec(this.ctx)).be.false();
});
});
```
Expand Down
28 changes: 28 additions & 0 deletions V2_to_V3_MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
This document outlines the breaking changes contributed to `cql-execution` that will be included in a `v3.0.0` release of the library. This document will be updated as new breaking changes are contributed to the repository before finalizing a `v3.0.0` release.

## Async Conversion

[This pull request](https://github.com/cqframework/cql-execution/pull/271) converted the core execution code of `cql-execution` to be asynchronous. This enables a wider variety of [DataProviders](https://github.com/cqframework/cql-execution/blob/7ecb00b236903fc0816966e4ca8368d50d6afbc4/src/types/cql-patient.interfaces.ts#L8)
to integrate with `cql-execution`, as data can now be retrieved using asynchronous operations (e.g. HTTP requests, database lookups, etc.). This conversion requires a change in how an [Executor](https://github.com/cqframework/cql-execution/blob/master/src/runtime/executor.ts) is used in practice:

``` TypeScript
// v2.x.x usage
const result = executor.exec(patientSource);
// Do something with result

// v3.x.x usage
executor.exec(patientSource).then((result) => {
// Do something with result
})

// or

const result = await executor.exec(patientSource);
```

The above pattern applies to the `exec_expression` and `exec_patient_context` methods of the `Executor` class as well.

In addition, the above pull request also adds support for a [TerminologyProvider](https://github.com/cqframework/cql-execution/blob/9fd81cb6eec615048513fdc8927725f853e2c085/src/types/cql-code-service.interfaces.ts#L29) to use asynchronous implementations of the `findValueSet*` functions
No changes are needed to how one configures an `Executor` to enable this, as the underlying code will now safely handle functions that return a `Promise` or not.

**NOTE:** This asynchronous approach is designed to be backwards-compatible with existing synchronous patient sources (e.g. [cql-exec-fhir](https://github.com/cqframework/cql-exec-fhir)), the only difference being that `exec` needs to be called using the above pattern instead of synchronously.
4 changes: 2 additions & 2 deletions examples/browser/cql4browsers.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
<head>
<script src="cql4browsers.js"></script>
<script>
function exec() {
async function exec() {
f = document.getElementById('form');
eval('elm = ' + f.elm.value)
if (f.parameters.value.trim()) {
eval('params = ' + f.parameters.value)
} else {
params = {}
}
f.results.value = JSON.stringify(executeSimpleELM(elm, new cql.PatientSource([]), [], 'Math', '1', null, params), null, 2)
f.results.value = JSON.stringify(await executeSimpleELM(elm, new cql.PatientSource([]), [], 'Math', '1', null, params), null, 2)
}
</script>
</head>
Expand Down
Loading

0 comments on commit e11d0b7

Please # to comment.