-
Notifications
You must be signed in to change notification settings - Fork 124
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* WIP aggregation * WIP * WIP * WIP * Initial passing aggregate tests * Remove some todos * More tests * Multi source aggregate * Formatting * Clean up the logic a bit * Fix a counter mismatch * Whitespace fixes * More whitespace fixes * Fixed off by one * Fixed missing null check
- Loading branch information
Showing
7 changed files
with
196 additions
and
29 deletions.
There are no files selected for viewing
69 changes: 69 additions & 0 deletions
69
...gine/src/main/java/org/opencds/cqf/cql/engine/elm/executing/AggregateClauseEvaluator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package org.opencds.cqf.cql.engine.elm.executing; | ||
|
||
import org.cqframework.cql.elm.visiting.ElmLibraryVisitor; | ||
import org.hl7.elm.r1.AggregateClause; | ||
import org.opencds.cqf.cql.engine.exception.CqlException; | ||
import org.opencds.cqf.cql.engine.execution.State; | ||
import org.opencds.cqf.cql.engine.execution.Variable; | ||
import org.opencds.cqf.cql.engine.runtime.Tuple; | ||
|
||
import java.util.List; | ||
import java.util.Objects; | ||
|
||
/* | ||
CQL provides support for a limited class of recursive problems | ||
using the aggregate clause of the query construct. | ||
This clause is similar in function to the JavaScript .reduce() function, | ||
in that it allows an expression to be repeatedly evaluated for each element of a list, | ||
and that expression can access the current value of the aggregation. | ||
https://cql.hl7.org/03-developersguide.html#aggregate-queries | ||
*/ | ||
|
||
public class AggregateClauseEvaluator { | ||
|
||
public static Object aggregate(AggregateClause elm, State state, ElmLibraryVisitor<Object, State> visitor, List<Object> elements) { | ||
Objects.requireNonNull(elm, "elm can not be null"); | ||
Objects.requireNonNull(visitor, "visitor can not be null"); | ||
Objects.requireNonNull(elements, "elements can not be null"); | ||
Objects.requireNonNull(state, "state can not be null"); | ||
|
||
if (elm.isDistinct()) { | ||
elements = DistinctEvaluator.distinct(elements, state); | ||
} | ||
|
||
Object aggregatedValue = null; | ||
if (elm.getStarting() != null) { | ||
aggregatedValue = visitor.visitExpression(elm.getStarting(), state); | ||
} | ||
|
||
for(var e : elements) { | ||
if (!(e instanceof Tuple)) { | ||
throw new CqlException("expected aggregation source to be a Tuple"); | ||
} | ||
var tuple = (Tuple)e; | ||
|
||
int pushes = 0; | ||
|
||
try { | ||
state.push(new Variable().withName(elm.getIdentifier()).withValue(aggregatedValue)); | ||
pushes++; | ||
|
||
for (var p : tuple.getElements().entrySet()) { | ||
state.push(new Variable().withName(p.getKey()).withValue(p.getValue())); | ||
pushes++; | ||
} | ||
|
||
aggregatedValue = visitor.visitExpression(elm.getExpression(), state); | ||
} | ||
finally { | ||
while(pushes > 0) { | ||
state.pop(); | ||
pushes--; | ||
} | ||
} | ||
} | ||
|
||
return aggregatedValue; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
...java/engine/src/test/java/org/opencds/cqf/cql/engine/execution/CqlAggregateQueryTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package org.opencds.cqf.cql.engine.execution; | ||
|
||
import org.testng.annotations.Test; | ||
|
||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.is; | ||
|
||
public class CqlAggregateQueryTest extends CqlTestBase { | ||
@Test | ||
void test_all_aggregate_clause_tests() { | ||
var evaluationResult = engine.evaluate(toElmIdentifier("CqlAggregateQueryTest")); | ||
var result = evaluationResult.forExpression("AggregateSumWithStart").value(); | ||
assertThat(result, is(16)); | ||
|
||
result = evaluationResult.forExpression("AggregateSumWithNull").value(); | ||
assertThat(result, is(15)); | ||
|
||
result = evaluationResult.forExpression("AggregateSumAll").value(); | ||
assertThat(result, is(24)); | ||
|
||
result = evaluationResult.forExpression("AggregateSumDistinct").value(); | ||
assertThat(result, is(15)); | ||
|
||
result = evaluationResult.forExpression("Multi").value(); | ||
assertThat(result, is(6)); | ||
|
||
result = evaluationResult.forExpression("MegaMulti").value(); | ||
assertThat(result, is(36)); | ||
|
||
result = evaluationResult.forExpression("MegaMultiDistinct").value(); | ||
assertThat(result, is(37)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
.../engine/src/test/resources/org/opencds/cqf/cql/engine/execution/CqlAggregateQueryTest.cql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
library CqlAggregateQueryTest | ||
|
||
//Aggregate clause | ||
define AggregateSumWithStart: | ||
({ 1, 2, 3, 4, 5 }) Num | ||
aggregate Result starting 1: Result + Num // 15 + 1 (the initial value) | ||
|
||
define AggregateSumWithNull: | ||
({ 1, 2, 3, 4, 5 }) Num | ||
aggregate Result: Coalesce(Result, 0) + Num // 15 + 0 (the initial value from null) | ||
|
||
define AggregateSumAll: | ||
({ 1, 1, 2, 2, 2, 3, 4, 4, 5 }) Num | ||
aggregate all Result: Coalesce(Result, 0) + Num // 24 + 0 | ||
|
||
define AggregateSumDistinct: | ||
({ 1, 1, 2, 2, 2, 3, 4, 4, 5 }) Num | ||
aggregate distinct Result: Coalesce(Result, 0) + Num // 15 + 0 (the initial value) | ||
|
||
|
||
define First: {1} | ||
define Second: {2} | ||
define Third: {3} | ||
|
||
define Multi: | ||
from First X, Second Y, Third Z | ||
aggregate Agg: Coalesce(Agg, 0) + X + Y + Z // 6 | ||
|
||
define "A": {1, 2} | ||
define "B": {1, 2} | ||
define "C": {1, 2} | ||
|
||
define MegaMulti: | ||
from "A" X, "B" Y, "C" Z | ||
aggregate Agg starting 0: Agg + X + Y + Z // 36 -- (1+1+1)+(1+1+2)+(1+2+1)+(1+2+2)+(2+1+1)+(2+1+2)+(2+2+1)+(2+2+2) | ||
|
||
|
||
define "1": {1, 2, 2, 1} | ||
define "2": {1, 2, 1, 2} | ||
define "3": {2, 1, 2, 1} | ||
|
||
define MegaMultiDistinct: | ||
from "1" X, "2" Y, "3" Z | ||
aggregate distinct Agg starting 1: Agg + X + Y + Z // 37 -- 1 + (1+1+1)+(1+1+2)+(1+2+1)+(1+2+2)+(2+1+1)+(2+1+2)+(2+2+1)+(2+2+2) | ||
|