From 343657289e0894cd531ef227621b326ed28b1235 Mon Sep 17 00:00:00 2001 From: Anton Vasetenkov Date: Wed, 31 Jul 2024 03:46:58 +1200 Subject: [PATCH 1/6] Enable tests for .intersect() (#1389) Un-skip intersect tests --- .../src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java b/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java index 3ab7d205b..fe7a83420 100644 --- a/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java +++ b/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java @@ -179,10 +179,6 @@ public static Object[][] dataMethod() { "r4/tests-fhir-r4/testIif/testIif3", "r4/tests-fhir-r4/testIif/testIif4", "r4/tests-fhir-r4/testIndexer/testIndexer1", - "r4/tests-fhir-r4/testIntersect/testIntersect1", - "r4/tests-fhir-r4/testIntersect/testIntersect2", - "r4/tests-fhir-r4/testIntersect/testIntersect3", - "r4/tests-fhir-r4/testIntersect/testIntersect4", "r4/tests-fhir-r4/testInvariants/extension('http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-scoring').exists() and extension('http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-scoring').value = 'ratio' implies group.population.where(code.coding.where(system = 'http://terminology.hl7.org/CodeSystem/measure-population').code = 'initial-population').count() in (1 | 2)", "r4/tests-fhir-r4/testLessOrEqual/testLessOrEqual26", "r4/tests-fhir-r4/testLessOrEqual/testLessOrEqual27", From f601a422861eb6a4abd194af7b611efe73d330fe Mon Sep 17 00:00:00 2001 From: Anton Vasetenkov Date: Wed, 31 Jul 2024 03:47:44 +1200 Subject: [PATCH 2/6] Enable tests for today() (#1390) Un-skip today tests Co-authored-by: JP --- .../src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java b/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java index fe7a83420..755ffd9b5 100644 --- a/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java +++ b/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java @@ -239,9 +239,6 @@ public static Object[][] dataMethod() { "r4/tests-fhir-r4/testTake/testTake4", "r4/tests-fhir-r4/testTimeOfDay/testTimeOfDay1", "r4/tests-fhir-r4/testToChars/testToChars1", - "r4/tests-fhir-r4/testToday/testToday1", - "r4/tests-fhir-r4/testToday/testToday2", - "r4/tests-fhir-r4/testToday/testToday3", "r4/tests-fhir-r4/testToInteger/testToInteger4", "r4/tests-fhir-r4/testTrace/testTrace2", "r4/tests-fhir-r4/testType/testType1", From 846450e499c6341df4b2b3b61a4c0fbe165291e0 Mon Sep 17 00:00:00 2001 From: Anton Vasetenkov Date: Wed, 31 Jul 2024 03:49:20 +1200 Subject: [PATCH 3/6] Implement ToChars in the engine (#1391) Implement ToChars --- .../cql/cql2elm/SystemFunctionResolver.java | 3 +- .../hl7/fhirpath/cql/CqlTypeOperatorsTest.xml | 17 +++++++++ .../elm/executing/ToCharsEvaluator.java | 35 +++++++++++++++++++ .../engine/execution/EvaluationVisitor.java | 6 ++++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/elm/executing/ToCharsEvaluator.java diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/SystemFunctionResolver.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/SystemFunctionResolver.java index c89eccc66..39376caf5 100644 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/SystemFunctionResolver.java +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/SystemFunctionResolver.java @@ -338,7 +338,8 @@ public Invocation resolveSystemFunction(FunctionRef fun) { case "ToTime": case "ToQuantity": case "ToRatio": - case "ToConcept": { + case "ToConcept": + case "ToChars": { return resolveUnary(fun); } diff --git a/Src/java/engine-fhir/src/test/resources/org/hl7/fhirpath/cql/CqlTypeOperatorsTest.xml b/Src/java/engine-fhir/src/test/resources/org/hl7/fhirpath/cql/CqlTypeOperatorsTest.xml index a6c06ad98..9d18b5b3b 100644 --- a/Src/java/engine-fhir/src/test/resources/org/hl7/fhirpath/cql/CqlTypeOperatorsTest.xml +++ b/Src/java/engine-fhir/src/test/resources/org/hl7/fhirpath/cql/CqlTypeOperatorsTest.xml @@ -57,6 +57,23 @@ false + + + ToChars('abc123') + { 'a', 'b', 'c', '1', '2', '3' } + + + ToChars('') + {} + + + ToChars(null) + null + + + ToChars(123) + + ToConcept(Code { code: '8480-6' }) diff --git a/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/elm/executing/ToCharsEvaluator.java b/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/elm/executing/ToCharsEvaluator.java new file mode 100644 index 000000000..8627a938d --- /dev/null +++ b/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/elm/executing/ToCharsEvaluator.java @@ -0,0 +1,35 @@ +package org.opencds.cqf.cql.engine.elm.executing; + +import org.opencds.cqf.cql.engine.exception.InvalidOperatorArgument; + +import java.util.ArrayList; +import java.util.List; + +/* +ToChars(argument String) List + +The ToChars operator takes a string and returns a list with one string for each character in the input, in the order in which they appear in the string. + +If the argument is null, the result is null. +*/ + +public class ToCharsEvaluator { + + public static List toChars(Object operand) { + if (operand == null) { + return null; + } + + if (operand instanceof String) { + List result = new ArrayList<>(); + for (char c : ((String) operand).toCharArray()) { + result.add(String.valueOf(c)); + } + return result; + } + + throw new InvalidOperatorArgument( + "ToChars(String)", + String.format("ToInteger(%s)", operand.getClass().getName())); + } +} diff --git a/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/execution/EvaluationVisitor.java b/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/execution/EvaluationVisitor.java index e6491ba84..acf25dc0d 100644 --- a/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/execution/EvaluationVisitor.java +++ b/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/execution/EvaluationVisitor.java @@ -420,6 +420,12 @@ public Object visitToConcept(ToConcept elm, State state) { return ToConceptEvaluator.toConcept(operand); } + @Override + public Object visitToChars(ToChars elm, State state) { + Object operand = visitExpression(elm.getOperand(), state); + return ToCharsEvaluator.toChars(operand); + } + @Override public Object visitToDate(ToDate elm, State state) { Object operand = visitExpression(elm.getOperand(), state); From a4d92df01e5a866ca0e71bac383c47114b16cdf8 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Tue, 30 Jul 2024 09:51:59 -0600 Subject: [PATCH 4/6] Fix formatting --- .../opencds/cqf/cql/engine/elm/executing/ToCharsEvaluator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/elm/executing/ToCharsEvaluator.java b/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/elm/executing/ToCharsEvaluator.java index 8627a938d..15e454fdc 100644 --- a/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/elm/executing/ToCharsEvaluator.java +++ b/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/elm/executing/ToCharsEvaluator.java @@ -1,9 +1,8 @@ package org.opencds.cqf.cql.engine.elm.executing; -import org.opencds.cqf.cql.engine.exception.InvalidOperatorArgument; - import java.util.ArrayList; import java.util.List; +import org.opencds.cqf.cql.engine.exception.InvalidOperatorArgument; /* ToChars(argument String) List From 0670c4a8b7664277257e8bc1e5bcf4b134cb0c66 Mon Sep 17 00:00:00 2001 From: Anton Vasetenkov Date: Fri, 2 Aug 2024 07:59:57 +1200 Subject: [PATCH 5/6] Enable tests for Distinct (#1395) --- .../src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java b/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java index 755ffd9b5..0b21ce84f 100644 --- a/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java +++ b/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java @@ -103,8 +103,6 @@ public static Object[][] dataMethod() { "cql/CqlIntervalOperatorsTest/Expand/ExpandPer1", "cql/CqlIntervalOperatorsTest/Expand/ExpandPer2Days", "cql/CqlIntervalOperatorsTest/Expand/ExpandPerMinute", - "cql/CqlListOperatorsTest/Distinct/DistinctANullANull", - "cql/CqlListOperatorsTest/Distinct/DistinctNullNullNull", "cql/CqlListOperatorsTest/Equivalent/Equivalent123AndABC", "cql/CqlListOperatorsTest/Equivalent/Equivalent123AndString123", "cql/CqlListOperatorsTest/Equivalent/EquivalentABCAnd123", From 7e91478216eaf17ffff4a9ac52d9810cd72a64d5 Mon Sep 17 00:00:00 2001 From: Anton Vasetenkov Date: Fri, 2 Aug 2024 08:11:39 +1200 Subject: [PATCH 6/6] Update ProperContains evaluator implementation (#1393) * Update ProperContains evaluator implementation * Align library-based tests with XML-based tests * Unskip test --------- Co-authored-by: JP --- .../org/hl7/fhirpath/CQLOperationsR4Test.java | 3 - .../hl7/fhirpath/cql/CqlListOperatorsTest.xml | 44 ++++++++++- .../executing/ProperContainsEvaluator.java | 74 ++++++++++++++++--- .../engine/execution/ListOperatorsTest.java | 4 +- .../engine/execution/CqlListOperatorsTest.cql | 4 +- 5 files changed, 111 insertions(+), 18 deletions(-) diff --git a/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java b/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java index 0b21ce84f..4c064b222 100644 --- a/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java +++ b/Src/java/engine-fhir/src/test/java/org/hl7/fhirpath/CQLOperationsR4Test.java @@ -109,9 +109,6 @@ public static Object[][] dataMethod() { "cql/CqlListOperatorsTest/NotEqual/NotEqual123AndABC", "cql/CqlListOperatorsTest/NotEqual/NotEqual123AndString123", "cql/CqlListOperatorsTest/NotEqual/NotEqualABCAnd123", - "cql/CqlListOperatorsTest/ProperContains/ProperContainsNullRightFalse", - "cql/CqlListOperatorsTest/ProperContains/ProperContainsTimeNull", - "cql/CqlListOperatorsTest/ProperIn/ProperInTimeNull", "cql/CqlListOperatorsTest/ProperlyIncludedIn/ProperlyIncludedInNulRight", "cql/CqlListOperatorsTest/ProperlyIncludes/ProperlyIncludesNullLeft", "cql/CqlListOperatorsTest/Union/UnionListNullAndListNull", diff --git a/Src/java/engine-fhir/src/test/resources/org/hl7/fhirpath/cql/CqlListOperatorsTest.xml b/Src/java/engine-fhir/src/test/resources/org/hl7/fhirpath/cql/CqlListOperatorsTest.xml index 537bf0467..9b577abf4 100644 --- a/Src/java/engine-fhir/src/test/resources/org/hl7/fhirpath/cql/CqlListOperatorsTest.xml +++ b/Src/java/engine-fhir/src/test/resources/org/hl7/fhirpath/cql/CqlListOperatorsTest.xml @@ -662,12 +662,52 @@ + + null as List<String> properly includes 'a' + false + + + {} properly includes 'a' + false + + + { 'a' } properly includes 'a' + false + + + { null } properly includes null as String + false + - {'s', 'u', 'n'} properly includes null + {'s', 'u', 'n'} properly includes null as String + false + + + { null, null } properly includes null as String false - {'s', 'u', 'n', null} properly includes null + {'s', 'u', 'n', null} properly includes null as String + true + + + { 'a', 'b' } properly includes 'a' + true + + + { 'a', 'a' } properly includes 'a' + false + + + { 'a', 'b' } properly includes 'c' + false + + + { 'a', null } properly includes 'a' + null + + + { 'a', 'b', null } properly includes 'a' true diff --git a/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/elm/executing/ProperContainsEvaluator.java b/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/elm/executing/ProperContainsEvaluator.java index 5f100816f..29563fcb1 100644 --- a/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/elm/executing/ProperContainsEvaluator.java +++ b/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/elm/executing/ProperContainsEvaluator.java @@ -12,17 +12,29 @@ Interval, T : The type of T must be the same as the point type of the interval. For the List, T overload, this operator returns true if the given element is in the list, - and it is not the only element in the list, using equivalence semantics. + and it is not the only element in the list, using equality semantics, with the exception + that null elements are considered equal. + If the first argument is null, the result is false. + If the second argument is null, the result is true if the list contains any null elements + and at least one other element, and false otherwise. For the Interval, T overload, this operator returns true if the given point is greater than the starting point of the interval, and less than the ending point of the interval, as determined by the Start and End operators. - If precision is specified and the point type is a date/time type, comparisons used in the - operation are performed at the specified precision. + If precision is specified and the point type is a Date, DateTime, or Time type, comparisons + used in the operation are performed at the specified precision. + If the first argument is null, the result is false. + If the second argument is null, the result is null. */ public class ProperContainsEvaluator { public static Boolean properContains(Object left, Object right, State state) { + + // If the first argument is null, the result is false. + if (left == null) { + return false; + } + if (left instanceof Interval) { Boolean startProperContains = GreaterEvaluator.greater(right, ((Interval) left).getStart(), state); Boolean endProperContains = LessEvaluator.less(right, ((Interval) left).getEnd(), state); @@ -33,15 +45,59 @@ public static Boolean properContains(Object left, Object right, State state) { } else if (left instanceof Iterable) { List leftList = (List) left; - for (Object element : leftList) { - Boolean isElementInList = EquivalentEvaluator.equivalent(element, right, state); - if (isElementInList == null) { - return null; + // The result cannot be true if the list contains fewer than two elements + if (leftList.size() < 2) { + return false; + } + + if (right == null) { + + // The result is true if the list contains any null elements and at least one other element, + // and false otherwise + + boolean listContainsNullElements = false; + boolean listContainsOtherElements = false; + + for (Object element : leftList) { + if (element == null) { + listContainsNullElements = true; + continue; + } + + listContainsOtherElements = true; } - if (isElementInList && leftList.size() > 1) { - return true; + return listContainsNullElements && listContainsOtherElements; + } + + // Return true if the given element is in the list, and it is not the only element in the list, + // using equality semantics + + boolean listContainsGivenElement = false; + boolean listContainsOtherElements = false; + boolean listContainsElementsOfUnknownEquality = false; + + for (Object element : leftList) { + Boolean equalResult = EqualEvaluator.equal(element, right, state); + if (equalResult == null) { + listContainsElementsOfUnknownEquality = true; + continue; } + if (equalResult) { + listContainsGivenElement = true; + continue; + } + listContainsOtherElements = true; + } + + // The given element is in the list and there are other elements, using equality semantics + if (listContainsGivenElement && listContainsOtherElements) { + return true; + } + + // The above is false, but there are elements of unknown equality + if (listContainsElementsOfUnknownEquality) { + return null; } return false; diff --git a/Src/java/engine/src/test/java/org/opencds/cqf/cql/engine/execution/ListOperatorsTest.java b/Src/java/engine/src/test/java/org/opencds/cqf/cql/engine/execution/ListOperatorsTest.java index 18ed93304..46ebd22be 100644 --- a/Src/java/engine/src/test/java/org/opencds/cqf/cql/engine/execution/ListOperatorsTest.java +++ b/Src/java/engine/src/test/java/org/opencds/cqf/cql/engine/execution/ListOperatorsTest.java @@ -584,7 +584,7 @@ void all_interval_operators() { assertThat(value, is(true)); value = results.forExpression("ProperContainsTimeNull").value(); - assertThat(value, is(false)); + assertThat(value, is(nullValue())); value = results.forExpression("ProperInNullRightFalse").value(); assertThat(value, is(false)); @@ -596,7 +596,7 @@ void all_interval_operators() { assertThat(value, is(true)); value = results.forExpression("ProperInTimeNull").value(); - assertThat(value, is(false)); + assertThat(value, is(nullValue())); value = results.forExpression("ProperIncludedInEmptyAndEmpty").value(); assertThat(value, is(false)); diff --git a/Src/java/engine/src/test/resources/org/opencds/cqf/cql/engine/execution/CqlListOperatorsTest.cql b/Src/java/engine/src/test/resources/org/opencds/cqf/cql/engine/execution/CqlListOperatorsTest.cql index 8131bbc4f..91d782e8b 100644 --- a/Src/java/engine/src/test/resources/org/opencds/cqf/cql/engine/execution/CqlListOperatorsTest.cql +++ b/Src/java/engine/src/test/resources/org/opencds/cqf/cql/engine/execution/CqlListOperatorsTest.cql @@ -209,8 +209,8 @@ define ProperlyIncludesNullLeft: null properly includes {2} define ProperlyIncludes1And111: {1} properly includes {1, 1} //ProperContains -define ProperContainsNullRightFalse: {'s', 'u', 'n'} properly includes null -define ProperContainsNullRightTrue: {'s', 'u', 'n', null} properly includes null +define ProperContainsNullRightFalse: {'s', 'u', 'n'} properly includes null as String +define ProperContainsNullRightTrue: {'s', 'u', 'n', null} properly includes null as String define ProperContainsTimeTrue: { @T15:59:59, @T20:59:59.999, @T20:59:49.999 } properly includes @T15:59:59 define ProperContainsTimeNull: { @T15:59:59.999, @T20:59:59.999, @T20:59:49.999 } properly includes @T15:59:59