From f03363079b0764c037a21d553e0d01f11b12b997 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Mon, 1 Jul 2024 14:56:40 +1000 Subject: [PATCH] Refactor implementation of getResourceKey and getReferenceKey --- .../collection/BooleanCollection.java | 6 +- .../fhirpath/collection/CodingCollection.java | 5 ++ .../fhirpath/collection/Collection.java | 30 +++------- .../fhirpath/collection/DateCollection.java | 3 +- .../collection/DateTimeCollection.java | 2 +- .../collection/DecimalCollection.java | 3 +- .../fhirpath/collection/EmptyCollection.java | 37 ++++++++++++ .../collection/IntegerCollection.java | 2 +- .../collection/QuantityCollection.java | 10 +++- .../collection/ReferenceCollection.java | 56 +++++++++++++++++++ .../collection/ResourceCollection.java | 6 +- .../fhirpath/collection/StringCollection.java | 6 ++ .../fhirpath/collection/TimeCollection.java | 5 ++ .../collection/mixed/MixedCollection.java | 4 +- ...entation.java => EmptyRepresentation.java} | 10 ++-- ...pression.java => CollectionTransform.java} | 9 ++- .../fhirpath/function/ColumnTransform.java | 13 +++++ .../fhirpath/function/JoinKeyFunctions.java | 47 ++++++++-------- .../fhirpath/function/StandardFunctions.java | 13 +++-- .../function/TerminologyFunctions.java | 14 ++--- .../fhirpath/function/WrappedFunction.java | 6 +- .../pathling/fhirpath/path/Literals.java | 5 +- .../pathling/view/UnnestingSelection.java | 4 +- 23 files changed, 207 insertions(+), 89 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/EmptyCollection.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/ReferenceCollection.java rename fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/{NullRepresentation.java => EmptyRepresentation.java} (78%) rename fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/{CollectionExpression.java => CollectionTransform.java} (86%) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnTransform.java diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/BooleanCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/BooleanCollection.java index 971f00ef22..2c7c96a4d1 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/BooleanCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/BooleanCollection.java @@ -23,7 +23,6 @@ import au.csiro.pathling.fhirpath.column.ColumnRepresentation; import au.csiro.pathling.fhirpath.column.DefaultRepresentation; import au.csiro.pathling.fhirpath.definition.NodeDefinition; -import com.google.common.collect.ImmutableSet; import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Row; @@ -38,9 +37,6 @@ public class BooleanCollection extends Collection implements Materializable, Comparable { - private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet - .of(BooleanCollection.class); - protected BooleanCollection(@Nonnull final ColumnRepresentation columnRepresentation, @Nonnull final Optional type, @Nonnull final Optional fhirType, @@ -121,7 +117,7 @@ public Optional getFhirValueFromRow(@Nonnull final Row row, final i @Override public boolean isComparableTo(@Nonnull final Collection path) { - return COMPARABLE_TYPES.contains(path.getClass()); + return path instanceof BooleanCollection || super.isComparableTo(path); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/CodingCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/CodingCollection.java index 82007d29af..60f27b5ae7 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/CodingCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/CodingCollection.java @@ -135,6 +135,11 @@ public Optional getFhirValueFromRow(@Nonnull final Row row, final int co return Optional.of(coding); } + @Override + public boolean isComparableTo(@Nonnull final Collection path) { + return path instanceof CodingCollection || super.isComparableTo(path); + } + @Override @Nonnull public Function getComparison(@Nonnull final ComparisonOperation operation) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/Collection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/Collection.java index 6e98c46f66..886187a2dd 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/Collection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/Collection.java @@ -28,12 +28,12 @@ import au.csiro.pathling.fhirpath.collection.mixed.MixedCollection; import au.csiro.pathling.fhirpath.column.ColumnRepresentation; import au.csiro.pathling.fhirpath.column.DefaultRepresentation; -import au.csiro.pathling.fhirpath.column.NullRepresentation; import au.csiro.pathling.fhirpath.definition.ChildDefinition; import au.csiro.pathling.fhirpath.definition.ChoiceChildDefinition; import au.csiro.pathling.fhirpath.definition.ElementChildDefinition; import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.definition.NodeDefinition; +import au.csiro.pathling.fhirpath.function.ColumnTransform; import com.google.common.collect.ImmutableMap; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -82,6 +82,7 @@ public class Collection implements Comparable, Numeric { .put(FHIRDefinedType.CODING, CodingCollection.class) .put(FHIRDefinedType.QUANTITY, QuantityCollection.class) .put(FHIRDefinedType.SIMPLEQUANTITY, QuantityCollection.class) + .put(FHIRDefinedType.REFERENCE, ReferenceCollection.class) .build(); /** @@ -191,8 +192,8 @@ private static Collection getInstance(@Nonnull final ColumnRepresentation column final FHIRDefinedType resolvedType = fhirType .or(() -> definition.flatMap(ElementDefinition::getFhirType)) .orElseThrow(() -> new IllegalArgumentException("Must have a fhirType or a definition")); - final Class elementPathClass = classForType(resolvedType).orElse( - Collection.class); + final Class elementPathClass = classForType(resolvedType) + .orElse(Collection.class); final Optional fhirPathType = FhirPathType.forFhirType(resolvedType); try { @@ -307,7 +308,7 @@ public Function getComparison(@Nonnull final ComparisonOpera @Override public boolean isComparableTo(@Nonnull final Collection path) { - return path.getClass().equals(this.getClass()) || path.getClass().equals(Collection.class); + return path instanceof EmptyCollection; } @Nonnull @@ -328,19 +329,6 @@ public Optional getNumericContext() { return Optional.empty(); } - - /** - * Creates a null {@link Collection}. - * - * @return the null collection. - */ - @Nonnull - public static Collection nullCollection() { - return new Collection(NullRepresentation.getInstance(), Optional.empty(), - Optional.of(FHIRDefinedType.NULL), - Optional.empty()); - } - /** * Returns a new {@link Collection} with the specified {@link ColumnRepresentation}. * @@ -363,7 +351,7 @@ public Collection copyWith(@Nonnull final ColumnRepresentation newValue) { */ @Nonnull public Collection filter( - @Nonnull final Function lambda) { + @Nonnull final ColumnTransform lambda) { return map( ctx -> ctx.filter(col -> lambda.apply(new DefaultRepresentation(col)).getValue())); } @@ -386,7 +374,7 @@ public Collection asSingular() { */ @Nonnull public Collection map( - @Nonnull final Function mapper) { + @Nonnull final ColumnTransform mapper) { return copyWith(mapper.apply(getColumn())); } @@ -400,7 +388,7 @@ public Collection map( */ @Nonnull public C map( - @Nonnull final Function mapper, + @Nonnull final ColumnTransform mapper, @Nonnull final Function constructor) { return constructor.apply(mapper.apply(getColumn())); } @@ -425,7 +413,7 @@ public Column getColumnValue() { */ @Nonnull public Collection filterByType(@Nonnull final TypeSpecifier type) { - return Collection.nullCollection(); + return EmptyCollection.getInstance(); } // TODO: Remove this after removing usages. diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateCollection.java index 7a353a1c81..7dd27db1cd 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateCollection.java @@ -117,7 +117,8 @@ public Function getComparison(@Nonnull final ComparisonOpera @Override public boolean isComparableTo(@Nonnull final Collection path) { - return DateTimeCollection.getComparableTypes().contains(path.getClass()); + return DateTimeCollection.getComparableTypes().contains(path.getClass()) + || super.isComparableTo(path); } @Nonnull diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateTimeCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateTimeCollection.java index c5ff64fab4..c79e611928 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateTimeCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateTimeCollection.java @@ -159,7 +159,7 @@ public Function getComparison(@Nonnull final ComparisonOpera @Override public boolean isComparableTo(@Nonnull final Collection path) { - return COMPARABLE_TYPES.contains(path.getClass()); + return COMPARABLE_TYPES.contains(path.getClass()) || super.isComparableTo(path); } @Nonnull diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DecimalCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DecimalCollection.java index ad48c7708e..9a83e6b774 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DecimalCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DecimalCollection.java @@ -145,7 +145,8 @@ public static org.apache.spark.sql.types.DecimalType getDecimalType() { @Override public boolean isComparableTo(@Nonnull final Collection path) { - return IntegerCollection.getComparableTypes().contains(path.getClass()); + return IntegerCollection.getComparableTypes().contains(path.getClass()) || + super.isComparableTo(path); } @Nonnull diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/EmptyCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/EmptyCollection.java new file mode 100644 index 0000000000..78e372deca --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/EmptyCollection.java @@ -0,0 +1,37 @@ +package au.csiro.pathling.fhirpath.collection; + +import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.column.ColumnRepresentation; +import au.csiro.pathling.fhirpath.column.EmptyRepresentation; +import au.csiro.pathling.fhirpath.definition.NodeDefinition; +import java.util.Optional; +import javax.annotation.Nonnull; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +/** + * Represents an empty collection. + */ +public class EmptyCollection extends Collection { + + private static final EmptyCollection INSTANCE = new EmptyCollection( + EmptyRepresentation.getInstance(), Optional.empty(), Optional.empty(), Optional.empty()); + + protected EmptyCollection(@Nonnull final ColumnRepresentation column, + @Nonnull final Optional type, @Nonnull final Optional fhirType, + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); + } + + /** + * @return A singleton instance of this class + */ + public static EmptyCollection getInstance() { + return INSTANCE; + } + + @Override + public boolean isComparableTo(@Nonnull final Collection path) { + return true; + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/IntegerCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/IntegerCollection.java index 629ecc0dfd..d3a8cceef4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/IntegerCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/IntegerCollection.java @@ -192,7 +192,7 @@ public static ImmutableSet> getComparableTypes() { @Override public boolean isComparableTo(@Nonnull final Collection path) { - return COMPARABLE_TYPES.contains(path.getClass()); + return COMPARABLE_TYPES.contains(path.getClass()) || super.isComparableTo(path); } @Nonnull diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/QuantityCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/QuantityCollection.java index ab92c6d2c1..e3fd295cbd 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/QuantityCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/QuantityCollection.java @@ -228,6 +228,11 @@ private static QuantityCollection buildLiteralPath(@Nonnull final BigDecimal dec new DefaultRepresentation(QuantityEncoding.encodeLiteral(quantity))); } + @Override + public boolean isComparableTo(@Nonnull final Collection path) { + return path instanceof QuantityCollection || super.isComparableTo(path); + } + @Nonnull @Override public Function getComparison(@Nonnull final ComparisonOperation operation) { @@ -268,8 +273,9 @@ public Function getMathOperation(@Nonnull final MathOperati final Column resultStruct = QuantityEncoding.toStruct( sourceContext.getField("id"), FlexiDecimal.toDecimal(resultColumn), - // NOTE: This (setting value_scale to null) works because we never decode this struct to a Quantity. - // The only Quantities that are decoded are calendar duration quantities parsed from literals. + // NOTE: This (setting value_scale to null) works because we never decode this struct to a + // Quantity. The only Quantities that are decoded are calendar duration quantities parsed + // from literals. lit(null), sourceContext.getField("comparator"), resultCode, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/ReferenceCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/ReferenceCollection.java new file mode 100644 index 0000000000..263ec0bb65 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/ReferenceCollection.java @@ -0,0 +1,56 @@ +package au.csiro.pathling.fhirpath.collection; + +import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.TypeSpecifier; +import au.csiro.pathling.fhirpath.column.ColumnRepresentation; +import au.csiro.pathling.fhirpath.definition.NodeDefinition; +import au.csiro.pathling.fhirpath.function.ColumnTransform; +import java.util.Optional; +import javax.annotation.Nonnull; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +/** + * Represents a collection of Reference elements. + * + * @author John Grimes + * @see Resource References + */ +public class ReferenceCollection extends Collection { + + private static final String REFERENCE_ELEMENT_NAME = "reference"; + + protected ReferenceCollection(@Nonnull final ColumnRepresentation column, + @Nonnull final Optional type, @Nonnull final Optional fhirType, + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); + } + + /** + * @param typeSpecifier The type specifier to filter by + * @return a {@link Collection} containing the keys of the references in this collection, suitable + * for joining with resource keys + */ + @Nonnull + public Collection getKeyCollection(@Nonnull final Optional typeSpecifier) { + return typeSpecifier + // If a type was specified, create a regular expression that matches references of this type. + .map(ts -> ts.toFhirType().toCode() + "/.+") + // Get a ColumnTransform that filters the reference column based on the regular expression. + .map(this::keyFilter) + // Apply the filter to the reference column. + .map(this::filter) + // Return a StringCollection of the reference elements.s + .flatMap(c -> c.traverse(REFERENCE_ELEMENT_NAME)) + // If no type was specified, return the reference column as is. + .or(() -> this.traverse(REFERENCE_ELEMENT_NAME)) + // If the reference column is not present, return an empty collection. + .orElse(EmptyCollection.getInstance()); + } + + @Nonnull + private ColumnTransform keyFilter(@Nonnull final String pattern) { + return col -> col.traverse(REFERENCE_ELEMENT_NAME, Optional.of(FHIRDefinedType.STRING)) + .like(pattern); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/ResourceCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/ResourceCollection.java index 6b26df75d6..0595bb7fe0 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/ResourceCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/ResourceCollection.java @@ -174,8 +174,10 @@ public Collection copyWith(@Nonnull final ColumnRepresentation newValue) { * @return A column that can be used as a key for joining to this resource type */ @Nonnull - public ColumnRepresentation getKeyColumn() { - return getColumn().traverse("id_versioned", Optional.of(FHIRDefinedType.STRING)); + public Collection getKeyCollection() { + return StringCollection.build( + getColumn().traverse("id_versioned", Optional.of(FHIRDefinedType.STRING)) + ); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/StringCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/StringCollection.java index ab3c717aaa..cc2254407b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/StringCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/StringCollection.java @@ -269,4 +269,10 @@ public Optional getFhirValueFromRow(@Nonnull final Row row, public StringCollection asStringPath() { return this; } + + @Override + public boolean isComparableTo(@Nonnull final Collection path) { + return path instanceof StringCollection || super.isComparableTo(path); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/TimeCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/TimeCollection.java index 98cbb8c79f..d7b02c0c82 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/TimeCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/TimeCollection.java @@ -109,4 +109,9 @@ public StringCollection asStringPath() { return map(ColumnRepresentation::asString, StringCollection::build); } + @Override + public boolean isComparableTo(@Nonnull final Collection path) { + return path instanceof TimeCollection || super.isComparableTo(path); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/mixed/MixedCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/mixed/MixedCollection.java index 6f72e0aa1e..03e6fbe2ca 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/mixed/MixedCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/mixed/MixedCollection.java @@ -1,7 +1,7 @@ package au.csiro.pathling.fhirpath.collection.mixed; import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.column.NullRepresentation; +import au.csiro.pathling.fhirpath.column.EmptyRepresentation; import au.csiro.pathling.fhirpath.definition.ChoiceChildDefinition; import java.util.Optional; import javax.annotation.Nonnull; @@ -16,7 +16,7 @@ public abstract class MixedCollection extends Collection { protected MixedCollection() { - super(NullRepresentation.getInstance(), Optional.empty(), Optional.empty(), Optional.empty()); + super(EmptyRepresentation.getInstance(), Optional.empty(), Optional.empty(), Optional.empty()); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/NullRepresentation.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/EmptyRepresentation.java similarity index 78% rename from fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/NullRepresentation.java rename to fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/EmptyRepresentation.java index c1edeeb761..4611795afe 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/NullRepresentation.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/EmptyRepresentation.java @@ -14,13 +14,13 @@ * @author Piotr Szul * @author John Grimes */ -public class NullRepresentation extends ColumnRepresentation { +public class EmptyRepresentation extends ColumnRepresentation { - static final ColumnRepresentation INSTANCE = new NullRepresentation(); + static final ColumnRepresentation INSTANCE = new EmptyRepresentation(); static final Column NULL_LITERAL = lit(null); /** - * @return a singleton instance of this class + * @return A singleton instance of this class */ @Nonnull public static ColumnRepresentation getInstance() { @@ -52,13 +52,13 @@ public ColumnRepresentation flatten() { @Nonnull @Override - public NullRepresentation traverse(@Nonnull final String fieldName) { + public EmptyRepresentation traverse(@Nonnull final String fieldName) { return this; } @Nonnull @Override - public NullRepresentation traverse(@Nonnull final String fieldName, + public EmptyRepresentation traverse(@Nonnull final String fieldName, final Optional fhirType) { return traverse(fieldName); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CollectionExpression.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CollectionTransform.java similarity index 86% rename from fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CollectionExpression.java rename to fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CollectionTransform.java index c5c772bf2a..2d6666f9ec 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CollectionExpression.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CollectionTransform.java @@ -19,7 +19,6 @@ import au.csiro.pathling.fhirpath.collection.BooleanCollection; import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.column.ColumnRepresentation; import java.util.function.Function; import javax.annotation.Nonnull; @@ -29,13 +28,13 @@ * @author Piotr Szul */ @FunctionalInterface -public interface CollectionExpression extends Function { +public interface CollectionTransform extends Function { /** * @return the input collection if it returns a {@link BooleanCollection}, otherwise throws an * exception */ - default CollectionExpression requireBoolean() { + default CollectionTransform requireBoolean() { return input -> { final Collection result = apply(input); if (result instanceof BooleanCollection) { @@ -50,10 +49,10 @@ default CollectionExpression requireBoolean() { * @param input the input collection * @return a function that applies the transformation and returns the column representation */ - default Function toColumnFunction( + default ColumnTransform toColumnTransformation( @Nonnull final Collection input) { // The type of the element Collection needs to be the same as the input. return c -> apply(input.copyWith(c)).getColumn(); } - + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnTransform.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnTransform.java new file mode 100644 index 0000000000..250c31448f --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnTransform.java @@ -0,0 +1,13 @@ +package au.csiro.pathling.fhirpath.function; + +import au.csiro.pathling.fhirpath.column.ColumnRepresentation; +import java.util.function.Function; + +/** + * Represents a transformation from one {@link ColumnRepresentation} to another. + * + * @author John Grimes + */ +public interface ColumnTransform extends Function { + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/JoinKeyFunctions.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/JoinKeyFunctions.java index 51689bd954..a6738b8950 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/JoinKeyFunctions.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/JoinKeyFunctions.java @@ -17,19 +17,14 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.utilities.Preconditions.checkArgument; - import au.csiro.pathling.fhirpath.TypeSpecifier; import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.ReferenceCollection; import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.column.ColumnRepresentation; import au.csiro.pathling.fhirpath.validation.FhirpathFunction; import java.util.Optional; -import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** * FHIRPath functions for generating keys for joining between resources. @@ -39,29 +34,35 @@ @SuppressWarnings("unused") public class JoinKeyFunctions { - public static final String REFERENCE_ELEMENT_NAME = "reference"; + /** + * Returns a {@link Collection} of keys for the input {@link ResourceCollection}. + * + * @param input The input {@link ResourceCollection} + * @return A {@link Collection} of keys + * @see getResourceKey + */ @FhirpathFunction - public static StringCollection getResourceKey(@Nonnull final ResourceCollection input) { - return StringCollection.build(input.getKeyColumn()); + @Nonnull + public static Collection getResourceKey(@Nonnull final ResourceCollection input) { + return input.getKeyCollection(); } + /** + * Returns a {@link Collection} of keys for the input {@link ReferenceCollection}. + * + * @param input The input {@link ReferenceCollection} + * @param typeSpecifier An optional {@link TypeSpecifier} to filter the reference keys by + * @return A {@link Collection} of keys + * @see getReferenceKey + */ @FhirpathFunction - // TODO: This needs to be somehow constrained to the collections of References - public static Collection getReferenceKey(@Nonnull final Collection input, + @Nonnull + public static Collection getReferenceKey(@Nonnull final ReferenceCollection input, @Nullable final TypeSpecifier typeSpecifier) { - checkArgument(input.getFhirType().map(FHIRDefinedType.REFERENCE::equals).orElse(false), - "getReferenceKey can only be applied to a REFERENCE collection"); - // TODO: How to deal with exceptions here? - // TODO: add filtering on 'type' but that requies changes in the Encoder (as 'type' is not encoded) - // TODO: add support for other types of references - return Optional.ofNullable(typeSpecifier) - .map(ts -> ts.toFhirType().toCode() + "/.+") - .>map( - regex -> (c -> c.traverse(REFERENCE_ELEMENT_NAME, Optional.of(FHIRDefinedType.STRING)) - .like(regex))) - .map(input::filter).orElse(input) - .traverse(REFERENCE_ELEMENT_NAME).orElse(Collection.nullCollection()); + return input.getKeyCollection(Optional.ofNullable(typeSpecifier)); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/StandardFunctions.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/StandardFunctions.java index 374b5d7be0..341d7736e6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/StandardFunctions.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/StandardFunctions.java @@ -26,6 +26,7 @@ import au.csiro.pathling.fhirpath.TypeSpecifier; import au.csiro.pathling.fhirpath.collection.BooleanCollection; import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.EmptyCollection; import au.csiro.pathling.fhirpath.collection.IntegerCollection; import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.collection.StringCollection; @@ -59,15 +60,15 @@ public class StandardFunctions { @Nonnull @FhirpathFunction public static Collection where(@Nonnull final Collection input, - @Nonnull final CollectionExpression expression) { - return input.filter(expression.requireBoolean().toColumnFunction(input)); + @Nonnull final CollectionTransform expression) { + return input.filter(expression.requireBoolean().toColumnTransformation(input)); } // Maybe these too can be implemented as colum functions???? @FhirpathFunction public static Collection iif(@Nonnull final Collection input, - @Nonnull CollectionExpression expression, @Nonnull final Collection thenValue, + @Nonnull CollectionTransform expression, @Nonnull final Collection thenValue, @Nonnull final Collection otherwiseValue) { // we need to pre-evaluate both expressions to determine that they have the same type @@ -76,7 +77,7 @@ public static Collection iif(@Nonnull final Collection input, // functions.when(requireBoolean(expression).apply(input).getSingleton(), thenValue.getColumn()) // .otherwise(otherwiseValue.getColumn()); - return Collection.nullCollection(); + return EmptyCollection.getInstance(); } /** @@ -126,7 +127,7 @@ public static Collection extension(@Nonnull final Collection input, urlCollection -> urlCollection.getComparison(EQUALS).apply(url)) .map(col -> BooleanCollection.build(new DefaultRepresentation(col))) .orElse(BooleanCollection.fromValue(false))) - ).orElse(Collection.nullCollection()); + ).orElse(EmptyCollection.getInstance()); } /** @@ -162,7 +163,7 @@ public static StringCollection join(@Nonnull final StringCollection input, */ @FhirpathFunction public static BooleanCollection exists(@Nonnull final Collection input, - @Nullable final CollectionExpression criteria) { + @Nullable final CollectionTransform criteria) { return not(empty(nonNull(criteria) ? where(input, criteria) : input)); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/TerminologyFunctions.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/TerminologyFunctions.java index 608ac4d596..4f715fd9c6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/TerminologyFunctions.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/TerminologyFunctions.java @@ -23,9 +23,9 @@ import au.csiro.pathling.fhirpath.collection.CodingCollection; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.column.DefaultRepresentation; import au.csiro.pathling.fhirpath.column.ColumnRepresentation; -import au.csiro.pathling.fhirpath.column.NullRepresentation; +import au.csiro.pathling.fhirpath.column.DefaultRepresentation; +import au.csiro.pathling.fhirpath.column.EmptyRepresentation; import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.validation.FhirpathFunction; import au.csiro.pathling.sql.udf.PropertyUdf; @@ -54,7 +54,7 @@ public static StringCollection display(@Nonnull final CodingCollection input, .transformWithUdf("display", Optional.ofNullable(language) .map(StringCollection::getColumn) .map(ColumnRepresentation::singular) - .orElse(NullRepresentation.getInstance())) + .orElse(EmptyRepresentation.getInstance())) .removeNulls() ); } @@ -84,7 +84,7 @@ public static Collection property(@Nonnull final CodingCollection input, Optional.ofNullable(language) .map(StringCollection::getColumn) .map(ColumnRepresentation::singular) - .orElse(NullRepresentation.getInstance()) + .orElse(EmptyRepresentation.getInstance()) ).flatten().removeNulls(); return Collection.build(resultCtx, propertyType, @@ -109,11 +109,11 @@ public static StringCollection designation(@Nonnull final CodingCollection input Optional.ofNullable(use) .map(CodingCollection::getColumn) .map(ColumnRepresentation::singular) - .orElse(NullRepresentation.getInstance()), + .orElse(EmptyRepresentation.getInstance()), Optional.ofNullable(language) .map(StringCollection::getColumn) .map(ColumnRepresentation::singular) - .orElse(NullRepresentation.getInstance()) + .orElse(EmptyRepresentation.getInstance()) ) .flatten().removeNulls() ); @@ -205,7 +205,7 @@ public static CodingCollection translate(@Nonnull final CodingCollection input, .transform(c -> functions.split(c, ",")), Optional.ofNullable(target).map(StringCollection::getColumn) .map(ColumnRepresentation::singular) - .orElse(NullRepresentation.getInstance()) + .orElse(EmptyRepresentation.getInstance()) )); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WrappedFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WrappedFunction.java index 7688f74b79..b61984ddc9 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WrappedFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WrappedFunction.java @@ -19,9 +19,9 @@ import static java.util.Objects.isNull; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FunctionInput; -import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.TypeSpecifier; import au.csiro.pathling.fhirpath.collection.CodingCollection; import au.csiro.pathling.fhirpath.collection.Collection; @@ -80,9 +80,9 @@ public Object resolveArgument(@Nonnull final Parameter parameter, } else if (Collection.class.isAssignableFrom(parameter.getType())) { // evaluate collection types return resolveCollection(argument.apply(input, evaluationContext), parameter); - } else if (CollectionExpression.class.isAssignableFrom(parameter.getType())) { + } else if (CollectionTransform.class.isAssignableFrom(parameter.getType())) { // bind with context - return (CollectionExpression) (c -> argument.apply(c, evaluationContext)); + return (CollectionTransform) (c -> argument.apply(c, evaluationContext)); } else if (TypeSpecifier.class.isAssignableFrom(parameter.getType())) { // bind type specifier return ((Paths.TypeSpecifierPath) argument).getTypeSpecifier(); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/Literals.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/Literals.java index 3042138168..a2d03c0022 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/Literals.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/Literals.java @@ -19,11 +19,12 @@ import au.csiro.pathling.encoders.terminology.ucum.Ucum; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.EvaluationContext; +import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.collection.BooleanCollection; import au.csiro.pathling.fhirpath.collection.CodingCollection; import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.EmptyCollection; import au.csiro.pathling.fhirpath.collection.QuantityCollection; import au.csiro.pathling.fhirpath.collection.StringCollection; import javax.annotation.Nonnull; @@ -42,7 +43,7 @@ public static class NullLiteral implements FhirPath { @Override public Collection apply(@Nonnull final Collection input, @Nonnull final EvaluationContext context) { - return Collection.nullCollection(); + return EmptyCollection.getInstance(); } @Nonnull diff --git a/fhirpath/src/main/java/au/csiro/pathling/view/UnnestingSelection.java b/fhirpath/src/main/java/au/csiro/pathling/view/UnnestingSelection.java index c27db24d6e..f0a3bc3089 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/view/UnnestingSelection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/view/UnnestingSelection.java @@ -26,7 +26,7 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.column.DefaultRepresentation; -import au.csiro.pathling.fhirpath.column.NullRepresentation; +import au.csiro.pathling.fhirpath.column.EmptyRepresentation; import java.util.List; import java.util.stream.Collectors; import javax.annotation.Nonnull; @@ -72,7 +72,7 @@ public ProjectionResult evaluate(@Nonnull final ProjectionContext context) { // This is a way to evaluate the expression for the purpose of getting the types of the result. final ProjectionContext stubContext = context.withInputContext( - unnestingCollection.map(__ -> NullRepresentation.getInstance())); + unnestingCollection.map(__ -> EmptyRepresentation.getInstance())); final List stubResults = components.stream() .map(s -> s.evaluate(stubContext)) .collect(toUnmodifiableList());