From 61f6c76e9d77a6c89bfadaa60f2372464bc25ab6 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sun, 6 Nov 2022 10:28:02 +1000 Subject: [PATCH 1/5] Fix comparison operations for instants --- .../sql/dates/TemporalComparisonFunction.java | 9 +- .../datetime/DateTimeComparisonFunction.java | 17 +- .../dates/time/TimeComparisonFunction.java | 2 +- .../ComparisonOperatorInstantTest.java | 195 ++++++++++++++++++ 4 files changed, 215 insertions(+), 8 deletions(-) create mode 100644 fhir-server/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java diff --git a/fhir-server/src/main/java/au/csiro/pathling/sql/dates/TemporalComparisonFunction.java b/fhir-server/src/main/java/au/csiro/pathling/sql/dates/TemporalComparisonFunction.java index 2e5e4975f5..cc73f7852f 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/sql/dates/TemporalComparisonFunction.java +++ b/fhir-server/src/main/java/au/csiro/pathling/sql/dates/TemporalComparisonFunction.java @@ -29,12 +29,12 @@ * * @author John Grimes */ -public abstract class TemporalComparisonFunction implements - SqlFunction2 { +public abstract class TemporalComparisonFunction implements + SqlFunction2 { private static final long serialVersionUID = 492467651418666881L; - protected abstract Function parseEncodedValue(); + protected abstract Function parseEncodedValue(); protected abstract BiFunction getOperationFunction(); @@ -45,7 +45,8 @@ public DataType getReturnType() { @Nullable @Override - public Boolean call(@Nullable final String left, @Nullable final String right) throws Exception { + public Boolean call(@Nullable final StoredType left, @Nullable final StoredType right) + throws Exception { if (left == null || right == null) { return null; } diff --git a/fhir-server/src/main/java/au/csiro/pathling/sql/dates/datetime/DateTimeComparisonFunction.java b/fhir-server/src/main/java/au/csiro/pathling/sql/dates/datetime/DateTimeComparisonFunction.java index 2e47407193..0441b83fec 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/sql/dates/datetime/DateTimeComparisonFunction.java +++ b/fhir-server/src/main/java/au/csiro/pathling/sql/dates/datetime/DateTimeComparisonFunction.java @@ -18,6 +18,9 @@ package au.csiro.pathling.sql.dates.datetime; import au.csiro.pathling.sql.dates.TemporalComparisonFunction; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.Date; import java.util.function.Function; import org.hl7.fhir.r4.model.DateTimeType; @@ -26,13 +29,21 @@ * * @author John Grimes */ -public abstract class DateTimeComparisonFunction extends TemporalComparisonFunction { +public abstract class DateTimeComparisonFunction extends + TemporalComparisonFunction { private static final long serialVersionUID = -2449192480093120211L; @Override - protected Function parseEncodedValue() { - return DateTimeType::new; + protected Function parseEncodedValue() { + return (object) -> { + if (object instanceof Timestamp) { + final Instant instant = ((Timestamp) object).toInstant(); + return new DateTimeType(Date.from(instant)); + } else { + return new DateTimeType((String) object); + } + }; } } diff --git a/fhir-server/src/main/java/au/csiro/pathling/sql/dates/time/TimeComparisonFunction.java b/fhir-server/src/main/java/au/csiro/pathling/sql/dates/time/TimeComparisonFunction.java index fc71e8c3a9..9286484669 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/sql/dates/time/TimeComparisonFunction.java +++ b/fhir-server/src/main/java/au/csiro/pathling/sql/dates/time/TimeComparisonFunction.java @@ -26,7 +26,7 @@ * * @author John Grimes */ -public abstract class TimeComparisonFunction extends TemporalComparisonFunction { +public abstract class TimeComparisonFunction extends TemporalComparisonFunction { private static final long serialVersionUID = 3661335567427062952L; diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java new file mode 100644 index 0000000000..0c8079b9be --- /dev/null +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java @@ -0,0 +1,195 @@ +/* + * Copyright 2022 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package au.csiro.pathling.fhirpath.operator; + +import static au.csiro.pathling.test.assertions.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.element.ElementDefinition; +import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.test.builders.DatasetBuilder; +import au.csiro.pathling.test.builders.ElementPathBuilder; +import au.csiro.pathling.test.builders.ParserContextBuilder; +import au.csiro.pathling.test.helpers.FhirHelpers; +import ca.uhn.fhir.context.FhirContext; +import java.time.Instant; +import java.util.Collections; +import java.util.Optional; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.RowFactory; +import org.apache.spark.sql.SparkSession; +import org.apache.spark.sql.types.DataTypes; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author John Grimes + */ +@SpringBootTest +@Tag("UnitTest") +class ComparisonOperatorInstantTest { + + @Autowired + SparkSession spark; + + @Autowired + FhirContext fhirContext; + + static final String ID_ALIAS = "_abc123"; + private ElementPath left; + private ElementPath right; + private ParserContext parserContext; + + @BeforeEach + void setUp() { + final Optional optionalLeftDefinition = FhirHelpers + .getChildOfResource(fhirContext, "Observation", "issued"); + assertTrue(optionalLeftDefinition.isPresent()); + final ElementDefinition leftDefinition = optionalLeftDefinition.get(); + assertTrue(leftDefinition.getFhirType().isPresent()); + assertEquals(FHIRDefinedType.INSTANT, leftDefinition.getFhirType().get()); + + final Dataset leftDataset = new DatasetBuilder(spark) + .withIdColumn(ID_ALIAS) + .withColumn(DataTypes.TimestampType) + .withRow("patient-1", Instant.ofEpochMilli(1667690454622L)) // Equal, exact + .withRow("patient-2", Instant.ofEpochMilli(1667690454621L)) // Less than + .withRow("patient-3", Instant.ofEpochMilli(1667690454623L)) // Greater than + .build(); + left = new ElementPathBuilder(spark) + .dataset(leftDataset) + .idAndValueColumns() + .expression("authoredOn") + .singular(true) + .definition(leftDefinition) + .buildDefined(); + + final Optional optionalRightDefinition = FhirHelpers + .getChildOfResource(fhirContext, "Condition", "onsetDateTime"); + assertTrue(optionalRightDefinition.isPresent()); + final ElementDefinition rightDefinition = optionalRightDefinition.get(); + assertTrue(rightDefinition.getFhirType().isPresent()); + assertEquals(FHIRDefinedType.DATETIME, rightDefinition.getFhirType().get()); + + final Dataset rightDataset = new DatasetBuilder(spark) + .withIdColumn(ID_ALIAS) + .withColumn(DataTypes.StringType) + .withRow("patient-1", "2022-11-06T09:20:54.622+10:00") // Equal, exact + .withRow("patient-2", "2022-11-06T09:20:54.622+10:00") // Less than + .withRow("patient-3", "2022-11-06T09:20:54.622+10:00") // Greater than + .build(); + right = new ElementPathBuilder(spark) + .dataset(rightDataset) + .idAndValueColumns() + .expression("reverseResolve(Condition.subject).onsetDateTime") + .singular(true) + .definition(rightDefinition) + .buildDefined(); + + parserContext = new ParserContextBuilder(spark, fhirContext) + .groupingColumns(Collections.singletonList(left.getIdColumn())) + .build(); + } + + @Test + void equals() { + final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); + final Operator lessThan = Operator.getInstance("="); + final FhirPath result = lessThan.invoke(comparisonInput); + + assertThat(result).selectOrderedResult().hasRows( + RowFactory.create("patient-1", true), // Equal, exact + RowFactory.create("patient-2", false), // Less than + RowFactory.create("patient-3", false) // Greater than + ); + } + + @Test + void notEquals() { + final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); + final Operator lessThan = Operator.getInstance("!="); + final FhirPath result = lessThan.invoke(comparisonInput); + + assertThat(result).selectOrderedResult().hasRows( + RowFactory.create("patient-1", false), // Equal, exact + RowFactory.create("patient-2", true), // Less than + RowFactory.create("patient-3", true) // Greater than + ); + } + + @Test + void lessThan() { + final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); + final Operator lessThan = Operator.getInstance("<"); + final FhirPath result = lessThan.invoke(comparisonInput); + + assertThat(result).selectOrderedResult().hasRows( + RowFactory.create("patient-1", false), // Equal, exact + RowFactory.create("patient-2", true), // Less than + RowFactory.create("patient-3", false) // Greater than + ); + } + + @Test + void lessThanOrEqualTo() { + final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); + final Operator lessThan = Operator.getInstance("<="); + final FhirPath result = lessThan.invoke(comparisonInput); + + assertThat(result).selectOrderedResult().hasRows( + RowFactory.create("patient-1", true), // Equal, exact + RowFactory.create("patient-2", true), // Less than + RowFactory.create("patient-3", false) // Greater than + ); + } + + @Test + void greaterThan() { + final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); + final Operator lessThan = Operator.getInstance(">"); + final FhirPath result = lessThan.invoke(comparisonInput); + + assertThat(result).selectOrderedResult().hasRows( + RowFactory.create("patient-1", false), // Equal, exact + RowFactory.create("patient-2", false), // Less than + RowFactory.create("patient-3", true) // Greater than + ); + } + + @Test + void greaterThanOrEqualTo() { + final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); + final Operator lessThan = Operator.getInstance(">="); + final FhirPath result = lessThan.invoke(comparisonInput); + + assertThat(result).selectOrderedResult().hasRows( + RowFactory.create("patient-1", true), // Equal, exact + RowFactory.create("patient-2", false), // Less than + RowFactory.create("patient-3", true) // Greater than + ); + } + +} From 84a521fb9a2a0f1c84928ff8179598a48e467fc5 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sun, 6 Nov 2022 11:35:00 +1000 Subject: [PATCH 2/5] Fix date arithmetic operations for instants --- .../sql/dates/TemporalArithmeticFunction.java | 28 +-- .../dates/date/DateArithmeticFunction.java | 2 +- .../datetime/DateTimeArithmeticFunction.java | 16 +- .../fhirpath/operator/DateArithmeticTest.java | 167 ++++++++++++++++++ site/docs/fhirpath/operators.md | 25 ++- 5 files changed, 215 insertions(+), 23 deletions(-) diff --git a/fhir-server/src/main/java/au/csiro/pathling/sql/dates/TemporalArithmeticFunction.java b/fhir-server/src/main/java/au/csiro/pathling/sql/dates/TemporalArithmeticFunction.java index 0be44ff5a9..aa853dc8eb 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/sql/dates/TemporalArithmeticFunction.java +++ b/fhir-server/src/main/java/au/csiro/pathling/sql/dates/TemporalArithmeticFunction.java @@ -36,41 +36,44 @@ * * @author John Grimes */ -public abstract class TemporalArithmeticFunction implements - SqlFunction2 { +public abstract class TemporalArithmeticFunction implements + SqlFunction2 { private static final long serialVersionUID = -5016153440496309996L; @Nonnull - protected T performAddition(@Nonnull final T temporal, @Nonnull final Quantity calendarDuration) { + protected IntermediateType performAddition(@Nonnull final IntermediateType temporal, + @Nonnull final Quantity calendarDuration) { return performArithmetic(temporal, calendarDuration, false); } @Nonnull - protected T performSubtraction(@Nonnull final T temporal, + protected IntermediateType performSubtraction(@Nonnull final IntermediateType temporal, @Nonnull final Quantity calendarDuration) { return performArithmetic(temporal, calendarDuration, true); } @Nonnull - private T performArithmetic(final @Nonnull T temporal, final @Nonnull Quantity calendarDuration, + private IntermediateType performArithmetic(final @Nonnull IntermediateType temporal, + final @Nonnull Quantity calendarDuration, final boolean subtract) { final int amountToAdd = calendarDuration.getValue().setScale(0, RoundingMode.HALF_UP) .intValue(); final int temporalUnit = CalendarDurationUtils.getTemporalUnit(calendarDuration); - @SuppressWarnings("unchecked") final T result = (T) temporal.copy(); + @SuppressWarnings("unchecked") + final IntermediateType result = (IntermediateType) temporal.copy(); result.add(temporalUnit, subtract ? -amountToAdd : amountToAdd); return result; } - protected abstract Function parseEncodedValue(); + protected abstract Function parseEncodedValue(); - protected abstract BiFunction getOperationFunction(); + protected abstract BiFunction getOperationFunction(); - protected abstract Function encodeResult(); + protected abstract Function encodeResult(); @Override public DataType getReturnType() { @@ -79,14 +82,15 @@ public DataType getReturnType() { @Nullable @Override - public String call(@Nullable final String temporalValue, @Nullable final Row calendarDurationRow) + public String call(@Nullable final StoredType temporalValue, + @Nullable final Row calendarDurationRow) throws Exception { if (temporalValue == null || calendarDurationRow == null) { return null; } - final T temporal = parseEncodedValue().apply(temporalValue); + final IntermediateType temporal = parseEncodedValue().apply(temporalValue); final Quantity calendarDuration = QuantityEncoding.decode(calendarDurationRow); - final T result = getOperationFunction().apply(temporal, calendarDuration); + final IntermediateType result = getOperationFunction().apply(temporal, calendarDuration); return encodeResult().apply(result); } diff --git a/fhir-server/src/main/java/au/csiro/pathling/sql/dates/date/DateArithmeticFunction.java b/fhir-server/src/main/java/au/csiro/pathling/sql/dates/date/DateArithmeticFunction.java index a4435c08d3..9beb138043 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/sql/dates/date/DateArithmeticFunction.java +++ b/fhir-server/src/main/java/au/csiro/pathling/sql/dates/date/DateArithmeticFunction.java @@ -26,7 +26,7 @@ * * @author John Grimes */ -public abstract class DateArithmeticFunction extends TemporalArithmeticFunction { +public abstract class DateArithmeticFunction extends TemporalArithmeticFunction { private static final long serialVersionUID = 6759548804191034570L; diff --git a/fhir-server/src/main/java/au/csiro/pathling/sql/dates/datetime/DateTimeArithmeticFunction.java b/fhir-server/src/main/java/au/csiro/pathling/sql/dates/datetime/DateTimeArithmeticFunction.java index 45fe861464..b526b193e4 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/sql/dates/datetime/DateTimeArithmeticFunction.java +++ b/fhir-server/src/main/java/au/csiro/pathling/sql/dates/datetime/DateTimeArithmeticFunction.java @@ -18,6 +18,9 @@ package au.csiro.pathling.sql.dates.datetime; import au.csiro.pathling.sql.dates.TemporalArithmeticFunction; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.Date; import java.util.function.Function; import org.hl7.fhir.r4.model.DateTimeType; @@ -27,13 +30,20 @@ * @author John Grimes */ public abstract class DateTimeArithmeticFunction extends - TemporalArithmeticFunction { + TemporalArithmeticFunction { private static final long serialVersionUID = -6669722492626320119L; @Override - protected Function parseEncodedValue() { - return DateTimeType::new; + protected Function parseEncodedValue() { + return (object) -> { + if (object instanceof Timestamp) { + final Instant instant = ((Timestamp) object).toInstant(); + return new DateTimeType(Date.from(instant)); + } else { + return new DateTimeType((String) object); + } + }; } @Override diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/operator/DateArithmeticTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/operator/DateArithmeticTest.java index 3b808e2313..cca36d5825 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/operator/DateArithmeticTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/operator/DateArithmeticTest.java @@ -31,6 +31,7 @@ import au.csiro.pathling.test.builders.ParserContextBuilder; import ca.uhn.fhir.context.FhirContext; import java.text.ParseException; +import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -138,6 +139,20 @@ Stream parameters() throws ParseException { .singular(true) .build(); + final Dataset instantDataset = new DatasetBuilder(spark) + .withIdColumn(ID_ALIAS) + .withColumn(DataTypes.TimestampType) + .withRow("patient-1", Instant.ofEpochMilli(1667690454622L)) + .withRow("patient-2", Instant.ofEpochMilli(1667690454622L)) + .withRow("patient-3", Instant.ofEpochMilli(1667690454622L)) + .build(); + final ElementPath instantPath = new ElementPathBuilder(spark) + .fhirType(FHIRDefinedType.DATETIME) + .dataset(instantDataset) + .idAndValueColumns() + .singular(true) + .build(); + final DateTimeLiteralPath dateTimeLiteral = DateTimeLiteralPath.fromString( "@2015-02-07T18:28:17+00:00", dateTimePath); final DateLiteralPath dateLiteral = DateLiteralPath.fromString("@2015-02-07", datePath); @@ -151,6 +166,8 @@ Stream parameters() throws ParseException { parameters.addAll(dateTimeSubtraction(dateTimePath, context)); parameters.addAll(dateAddition(datePath, context)); parameters.addAll(dateSubtraction(datePath, context)); + parameters.addAll(instantAddition(instantPath, context)); + parameters.addAll(instantSubtraction(instantPath, context)); parameters.addAll(dateTimeLiteralAddition(dateTimeLiteral, context)); parameters.addAll(dateTimeLiteralSubtraction(dateTimeLiteral, context)); parameters.addAll(dateLiteralAddition(dateLiteral, context)); @@ -381,6 +398,156 @@ Collection dateSubtraction( return parameters; } + Collection instantAddition( + final FhirPath instantPath, final ParserContext context) { + final List parameters = new ArrayList<>(); + parameters.add(new TestParameters("Instant + 10 years", instantPath, + QuantityLiteralPath.fromCalendarDurationString("10 years", instantPath), context, + Operator.getInstance("+"), + new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + .withRow("patient-1", "2032-11-05T23:20:54+00:00") + .withRow("patient-2", "2032-11-05T23:20:54+00:00") + .withRow("patient-3", "2032-11-05T23:20:54+00:00") + .build()) + ); + + parameters.add(new TestParameters("Instant + 9 months", instantPath, + QuantityLiteralPath.fromCalendarDurationString("9 months", instantPath), context, + Operator.getInstance("+"), + new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + .withRow("patient-1", "2023-08-05T23:20:54+00:00") + .withRow("patient-2", "2023-08-05T23:20:54+00:00") + .withRow("patient-3", "2023-08-05T23:20:54+00:00") + .build()) + ); + + parameters.add(new TestParameters("Instant + 30 days", instantPath, + QuantityLiteralPath.fromCalendarDurationString("30 days", instantPath), context, + Operator.getInstance("+"), + new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + .withRow("patient-1", "2022-12-05T23:20:54+00:00") + .withRow("patient-2", "2022-12-05T23:20:54+00:00") + .withRow("patient-3", "2022-12-05T23:20:54+00:00") + .build()) + ); + + parameters.add(new TestParameters("Instant + 12 hours", instantPath, + QuantityLiteralPath.fromCalendarDurationString("12 hours", instantPath), context, + Operator.getInstance("+"), + new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + .withRow("patient-1", "2022-11-06T11:20:54+00:00") + .withRow("patient-2", "2022-11-06T11:20:54+00:00") + .withRow("patient-3", "2022-11-06T11:20:54+00:00") + .build()) + ); + + parameters.add(new TestParameters("Instant + 30 minutes", instantPath, + QuantityLiteralPath.fromCalendarDurationString("30 minutes", instantPath), context, + Operator.getInstance("+"), + new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + .withRow("patient-1", "2022-11-05T23:50:54+00:00") + .withRow("patient-2", "2022-11-05T23:50:54+00:00") + .withRow("patient-3", "2022-11-05T23:50:54+00:00") + .build()) + ); + + parameters.add(new TestParameters("Instant + 10 seconds", instantPath, + QuantityLiteralPath.fromCalendarDurationString("10 seconds", instantPath), context, + Operator.getInstance("+"), + new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + .withRow("patient-1", "2022-11-05T23:21:04+00:00") + .withRow("patient-2", "2022-11-05T23:21:04+00:00") + .withRow("patient-3", "2022-11-05T23:21:04+00:00") + .build()) + ); + + parameters.add(new TestParameters("Instant + 300 milliseconds", instantPath, + QuantityLiteralPath.fromCalendarDurationString("300 milliseconds", instantPath), context, + Operator.getInstance("+"), + new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + .withRow("patient-1", "2022-11-05T23:20:54+00:00") + .withRow("patient-2", "2022-11-05T23:20:54+00:00") + .withRow("patient-3", "2022-11-05T23:20:54+00:00") + .build()) + ); + return parameters; + } + + Collection instantSubtraction( + final FhirPath instantPath, final ParserContext context) { + final List parameters = new ArrayList<>(); + parameters.add(new TestParameters("Instant - 10 years", instantPath, + QuantityLiteralPath.fromCalendarDurationString("10 years", instantPath), context, + Operator.getInstance("-"), + new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + .withRow("patient-1", "2012-11-05T23:20:54+00:00") + .withRow("patient-2", "2012-11-05T23:20:54+00:00") + .withRow("patient-3", "2012-11-05T23:20:54+00:00") + .build()) + ); + + parameters.add(new TestParameters("Instant - 9 months", instantPath, + QuantityLiteralPath.fromCalendarDurationString("9 months", instantPath), context, + Operator.getInstance("-"), + new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + .withRow("patient-1", "2022-02-05T23:20:54+00:00") + .withRow("patient-2", "2022-02-05T23:20:54+00:00") + .withRow("patient-3", "2022-02-05T23:20:54+00:00") + .build()) + ); + + parameters.add(new TestParameters("Instant - 30 days", instantPath, + QuantityLiteralPath.fromCalendarDurationString("30 days", instantPath), context, + Operator.getInstance("-"), + new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + .withRow("patient-1", "2022-10-06T23:20:54+00:00") + .withRow("patient-2", "2022-10-06T23:20:54+00:00") + .withRow("patient-3", "2022-10-06T23:20:54+00:00") + .build()) + ); + + parameters.add(new TestParameters("Instant - 12 hours", instantPath, + QuantityLiteralPath.fromCalendarDurationString("12 hours", instantPath), context, + Operator.getInstance("-"), + new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + .withRow("patient-1", "2022-11-05T11:20:54+00:00") + .withRow("patient-2", "2022-11-05T11:20:54+00:00") + .withRow("patient-3", "2022-11-05T11:20:54+00:00") + .build()) + ); + + parameters.add(new TestParameters("Instant - 30 minutes", instantPath, + QuantityLiteralPath.fromCalendarDurationString("30 minutes", instantPath), context, + Operator.getInstance("-"), + new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + .withRow("patient-1", "2022-11-05T22:50:54+00:00") + .withRow("patient-2", "2022-11-05T22:50:54+00:00") + .withRow("patient-3", "2022-11-05T22:50:54+00:00") + .build()) + ); + + parameters.add(new TestParameters("Instant - 10 seconds", instantPath, + QuantityLiteralPath.fromCalendarDurationString("10 seconds", instantPath), context, + Operator.getInstance("-"), + new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + .withRow("patient-1", "2022-11-05T23:20:44+00:00") + .withRow("patient-2", "2022-11-05T23:20:44+00:00") + .withRow("patient-3", "2022-11-05T23:20:44+00:00") + .build()) + ); + + parameters.add(new TestParameters("Instant - 300 milliseconds", instantPath, + QuantityLiteralPath.fromCalendarDurationString("300 milliseconds", instantPath), context, + Operator.getInstance("-"), + new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + .withRow("patient-1", "2022-11-05T23:20:53+00:00") + .withRow("patient-2", "2022-11-05T23:20:53+00:00") + .withRow("patient-3", "2022-11-05T23:20:53+00:00") + .build()) + ); + return parameters; + } + Collection dateTimeLiteralAddition( final FhirPath dateTimeLiteralPath, final ParserContext context) { final List parameters = new ArrayList<>(); diff --git a/site/docs/fhirpath/operators.md b/site/docs/fhirpath/operators.md index 364601c95a..27227c7714 100644 --- a/site/docs/fhirpath/operators.md +++ b/site/docs/fhirpath/operators.md @@ -38,11 +38,12 @@ individual characters. All comparison operators return a [Boolean](/docs/fhirpath/data-types#boolean) value. -* Not all Quantity values are comparable, it depends upon the +:::caution +Not all Quantity values are comparable, it depends upon the comparability of the units. See the FHIRPath specification for details on how Quantity values are compared. -

+::: See also: [Comparison](https://hl7.org/fhirpath/#comparison) @@ -59,10 +60,12 @@ be compared using the equality operators. If one or both of the operands is an empty collection, the operator will return an empty collection. +:::caution Not all Quantity, Date and DateTime values can be compared for equality, it -depends upon the comparability of the units within the Quantity values. See the -[FHIRPath specification](https://hl7.org/fhirpath/#quantity-equality) for -details on how equality works with Quantity values. +depends upon the comparability of the units within the Quantity values. See +the FHIRPath specification +for details on how equality works with Quantity values. +::: See also: [Equality](https://hl7.org/fhirpath/#equality) @@ -116,8 +119,16 @@ is not supported with these operators. The `Date` or `DateTime` operand must be singular. If it is an empty collection, the operator will return an empty collection. -The use of arithmetic with the [Time](/docs/fhirpath/data-types#time) type is not -supported. +:::note +The use of arithmetic with the Time +type is not supported. +::: + +:::caution +Arithmetic +involving instant +values is limited to a precision of seconds. +::: See also: [Date/Time Arithmetic](https://hl7.org/fhirpath/#datetime-arithmetic) From d294851f266d5d65218b48d90e33f3e632a9c04a Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sun, 6 Nov 2022 12:14:29 +1000 Subject: [PATCH 3/5] Pin spring-security-core to 5.7.5 (CVE-2022-31692) --- pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pom.xml b/pom.xml index 363dad863f..8025cbc31c 100644 --- a/pom.xml +++ b/pom.xml @@ -433,6 +433,13 @@ snakeyaml 1.31 + + + + org.springframework.security + spring-security-core + 5.7.5 + From 6c08adfe2d013f93653b22e44bd2f9741c31eb4b Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sun, 6 Nov 2022 12:14:51 +1000 Subject: [PATCH 4/5] Update Quantity comparability notes in docs --- site/docs/fhirpath/operators.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/site/docs/fhirpath/operators.md b/site/docs/fhirpath/operators.md index 27227c7714..221a1c54c6 100644 --- a/site/docs/fhirpath/operators.md +++ b/site/docs/fhirpath/operators.md @@ -39,10 +39,10 @@ individual characters. All comparison operators return a [Boolean](/docs/fhirpath/data-types#boolean) value. :::caution -Not all Quantity values are comparable, it depends upon the -comparability of the units. See -the FHIRPath specification for -details on how Quantity values are compared. +The comparability of units within Quantities is defined +within UCUM. +You can use the NLM Converter Tool to +check whether your units are comparable to each other. ::: See also: [Comparison](https://hl7.org/fhirpath/#comparison) @@ -60,11 +60,14 @@ be compared using the equality operators. If one or both of the operands is an empty collection, the operator will return an empty collection. +If the operands are Quantity values and are not comparable, an empty collection +will be returned. + :::caution -Not all Quantity, Date and DateTime values can be compared for equality, it -depends upon the comparability of the units within the Quantity values. See -the FHIRPath specification -for details on how equality works with Quantity values. +The comparability of units within Quantities is defined +within UCUM. +You can use the NLM Converter Tool to +check whether your units are comparable to each other. ::: See also: [Equality](https://hl7.org/fhirpath/#equality) From 37dad933168650e6ed9fdcd074df44d62c06e913 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sun, 6 Nov 2022 12:22:59 +1000 Subject: [PATCH 5/5] Update version to 6.0.3 --- encoders/pom.xml | 2 +- fhir-server/pom.xml | 2 +- lib/import/pom.xml | 2 +- lib/js/pom.xml | 2 +- lib/python/pom.xml | 2 +- library-api/pom.xml | 2 +- pom.xml | 2 +- site/pom.xml | 2 +- terminology/pom.xml | 2 +- utilities/pom.xml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/encoders/pom.xml b/encoders/pom.xml index 0ee418820c..a1e331352d 100644 --- a/encoders/pom.xml +++ b/encoders/pom.xml @@ -30,7 +30,7 @@ au.csiro.pathling pathling - 6.0.2 + 6.0.3 encoders jar diff --git a/fhir-server/pom.xml b/fhir-server/pom.xml index 210ec6523d..4c6289d897 100644 --- a/fhir-server/pom.xml +++ b/fhir-server/pom.xml @@ -24,7 +24,7 @@ au.csiro.pathling pathling - 6.0.2 + 6.0.3 fhir-server jar diff --git a/lib/import/pom.xml b/lib/import/pom.xml index 329993ae93..c900e2f98a 100644 --- a/lib/import/pom.xml +++ b/lib/import/pom.xml @@ -24,7 +24,7 @@ au.csiro.pathling pathling - 6.0.2 + 6.0.3 ../../pom.xml import diff --git a/lib/js/pom.xml b/lib/js/pom.xml index f8f195fe1f..7392d754ff 100644 --- a/lib/js/pom.xml +++ b/lib/js/pom.xml @@ -24,7 +24,7 @@ au.csiro.pathling pathling - 6.0.2 + 6.0.3 ../../pom.xml js diff --git a/lib/python/pom.xml b/lib/python/pom.xml index 96e0a0ad4d..1e8a0e47d4 100644 --- a/lib/python/pom.xml +++ b/lib/python/pom.xml @@ -24,7 +24,7 @@ au.csiro.pathling pathling - 6.0.2 + 6.0.3 ../../pom.xml python diff --git a/library-api/pom.xml b/library-api/pom.xml index 8dd6964a34..10f5104ab1 100644 --- a/library-api/pom.xml +++ b/library-api/pom.xml @@ -25,7 +25,7 @@ pathling au.csiro.pathling - 6.0.2 + 6.0.3 library-api jar diff --git a/pom.xml b/pom.xml index 8025cbc31c..95cba48dab 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ au.csiro.pathling pathling - 6.0.2 + 6.0.3 pom Pathling diff --git a/site/pom.xml b/site/pom.xml index c833faaab9..de58e7da3d 100644 --- a/site/pom.xml +++ b/site/pom.xml @@ -13,7 +13,7 @@ au.csiro.pathling pathling - 6.0.2 + 6.0.3 ../pom.xml site diff --git a/terminology/pom.xml b/terminology/pom.xml index 8c3cbf92ed..d365f03393 100644 --- a/terminology/pom.xml +++ b/terminology/pom.xml @@ -25,7 +25,7 @@ pathling au.csiro.pathling - 6.0.2 + 6.0.3 terminology jar diff --git a/utilities/pom.xml b/utilities/pom.xml index e710e876ba..cd86b26b2c 100644 --- a/utilities/pom.xml +++ b/utilities/pom.xml @@ -8,7 +8,7 @@ pathling au.csiro.pathling - 6.0.2 + 6.0.3 utilities jar