From e59d06271a28b4144bdb4fbe7ef12e0b8d2de708 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Tue, 9 May 2023 10:57:44 -0500 Subject: [PATCH 01/95] Implement initial part of query, and test stub --- .../java/au/csiro/pathling/QueryExecutor.java | 19 ++-- .../fhirpath/function/NamedFunction.java | 48 +++++++++- .../operator/PathTraversalOperator.java | 3 +- .../fhirpath/parser/ExecutionContext.java | 6 ++ .../fhirpath/parser/InvocationVisitor.java | 3 +- .../fhirpath/parser/ParserContext.java | 27 ++++++ .../au/csiro/pathling/views/FhirView.java | 23 +++++ .../pathling/views/FhirViewExecutor.java | 71 +++++++++++++++ .../csiro/pathling/views/NamedExpression.java | 21 +++++ lib/python/pathling/datasource.py | 33 ++++++- lib/python/pathling/query.py | 81 +++++++++++++++++ lib/python/tests/test_query.py | 28 ++++++ .../library/io/source/AbstractSource.java | 19 +++- .../io/source/QueryableDataSource.java | 15 ++++ .../library/query/FhirViewBuilder.java | 87 +++++++++++++++++++ .../library/query/QueryDispatcher.java | 20 ++++- 16 files changed, 491 insertions(+), 13 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ExecutionContext.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/NamedExpression.java create mode 100644 library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index 5ba0d3a9f6..c4cd2e2388 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -135,21 +135,28 @@ protected Dataset joinExpressionsAndFilters(final FhirPath inputContext, @Nonnull final Collection expressions, @Nonnull final Collection filters, @Nonnull final Column idColumn) { + final Dataset combinedGroupings = joinExpressionsWithoutCorrelation( + inputContext, expressions, idColumn); + + return filters.stream() + .map(FhirPath::getDataset) + .reduce(combinedGroupings, + ((result, element) -> join(element, idColumn, result, idColumn, JoinType.RIGHT_OUTER))); + } + + @Nonnull + protected static Dataset joinExpressionsWithoutCorrelation(final FhirPath inputContext, + final @Nonnull Collection expressions, final @Nonnull Column idColumn) { // We need to remove any trailing null values from non-empty collections, so that aggregations do // not count non-empty collections in the empty collection grouping. // We start from the inputContext's dataset and then outer join subsequent expressions datasets // where the value is not null. - final Dataset combinedGroupings = expressions.stream() + return expressions.stream() .map(expr -> expr.getDataset().filter(expr.getValueColumn().isNotNull())) // the use of RIGHT_OUTER join seems to be necessary to preserve the original // id column in the result .reduce(inputContext.getDataset(), ((result, element) -> join(element, idColumn, result, idColumn, JoinType.RIGHT_OUTER))); - - return filters.stream() - .map(FhirPath::getDataset) - .reduce(combinedGroupings, - ((result, element) -> join(element, idColumn, result, idColumn, JoinType.RIGHT_OUTER))); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java index 708ab13271..7606346625 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java @@ -29,6 +29,7 @@ import au.csiro.pathling.fhirpath.function.terminology.PropertyFunction; import au.csiro.pathling.fhirpath.function.terminology.SubsumesFunction; import au.csiro.pathling.fhirpath.function.terminology.TranslateFunction; +import au.csiro.pathling.fhirpath.parser.ExecutionContext; import com.google.common.collect.ImmutableMap; import java.util.Map; import java.util.stream.Collectors; @@ -71,6 +72,34 @@ public interface NamedFunction { .put("designation", new DesignationFunction()) .build(); + /** + * Mapping of function names to the instances of those functions. + */ + Map NAME_TO_INSTANCE_VIEW_CONTEXT = new ImmutableMap.Builder() + .put("count", new CountFunction()) + .put("ofType", new OfTypeFunction()) + .put("memberOf", new MemberOfFunction()) + .put("where", new WhereFunction()) + .put("subsumes", new SubsumesFunction()) + .put("subsumedBy", new SubsumesFunction(true)) + .put("empty", new EmptyFunction()) + .put("first", new FirstFunction()) + .put("not", new NotFunction()) + .put("iif", new IifFunction()) + .put("translate", new TranslateFunction()) + .put("sum", new SumFunction()) + .put("anyTrue", new BooleansTestFunction(ANY_TRUE)) + .put("anyFalse", new BooleansTestFunction(ANY_FALSE)) + .put("allTrue", new BooleansTestFunction(ALL_TRUE)) + .put("allFalse", new BooleansTestFunction(ALL_FALSE)) + .put("extension", new ExtensionFunction()) + .put("until", new UntilFunction()) + .put("exists", new ExistsFunction()) + .put("display", new DisplayFunction()) + .put("property", new PropertyFunction()) + .put("designation", new DesignationFunction()) + .build(); + /** * The FHIRPath expression for the $this keyword, used to access the current item in the * collection in functions such as {@code where}. @@ -89,14 +118,29 @@ public interface NamedFunction { FhirPath invoke(@Nonnull NamedFunctionInput input); /** - * Retrieves an instance of the function with the specified name. + * Retrieves an instance of the function with the specified name in the specified context. * * @param name The name of the function * @return An instance of a NamedFunction */ @Nonnull static NamedFunction getInstance(@Nonnull final String name) { - final NamedFunction function = NAME_TO_INSTANCE.get(name); + return getInstance(name, ExecutionContext.DEFAULT); + } + + /** + * Retrieves an instance of the function with the specified name. + * + * @param name The name of the function + * @param context The execution context + * @return An instance of a NamedFunction + */ + @Nonnull + static NamedFunction getInstance(@Nonnull final String name, + @Nonnull final ExecutionContext context) { + final NamedFunction function = context == ExecutionContext.VIEW + ? NAME_TO_INSTANCE_VIEW_CONTEXT.get(name) + : NAME_TO_INSTANCE.get(name); checkUserInput(function != null, "Unsupported function: " + name); return function; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java index 4b69468523..785c9a1371 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java @@ -29,6 +29,7 @@ import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.parser.ExecutionContext; import java.util.Optional; import javax.annotation.Nonnull; import org.apache.commons.lang3.tuple.MutablePair; @@ -91,7 +92,7 @@ public ElementPath invoke(@Nonnull final PathTraversalInput input) { final Optional eidColumnCandidate; final Dataset resultDataset; - if (maxCardinalityOfOne) { + if (maxCardinalityOfOne || input.getContext().getExecutionContext() == ExecutionContext.VIEW) { valueColumn = field; eidColumnCandidate = left.getEidColumn(); resultDataset = leftDataset; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ExecutionContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ExecutionContext.java new file mode 100644 index 0000000000..bcb2357839 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ExecutionContext.java @@ -0,0 +1,6 @@ +package au.csiro.pathling.fhirpath.parser; + +public enum ExecutionContext { + DEFAULT, + VIEW +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index 046042b86d..8de1a1f883 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -162,7 +162,8 @@ public FhirPath visitFunctionInvocation(@Nullable final FunctionInvocationContex @Nullable final String functionIdentifier = requireNonNull(ctx).function().identifier() .getText(); requireNonNull(functionIdentifier); - final NamedFunction function = NamedFunction.getInstance(functionIdentifier); + final NamedFunction function = NamedFunction.getInstance(functionIdentifier, + context.getExecutionContext()); // If there is no invoker, we use either the input context or the this context, depending on // whether we are in the context of function arguments. diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index 8becab00aa..0f8e900a5d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -101,6 +101,9 @@ public class ParserContext { @Nonnull private final Map nodeIdColumns; + @Nonnull + private final ExecutionContext executionContext; + /** * @param inputContext the input context from which the FHIRPath is to be evaluated * @param fhirContext a {@link FhirContext} that can be used to do FHIR stuff @@ -118,6 +121,29 @@ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirCo @Nonnull final Optional terminologyServiceFactory, @Nonnull final List groupingColumns, @Nonnull final Map nodeIdColumns) { + this(inputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, + groupingColumns, nodeIdColumns, ExecutionContext.DEFAULT); + } + + /** + * @param inputContext the input context from which the FHIRPath is to be evaluated + * @param fhirContext a {@link FhirContext} that can be used to do FHIR stuff + * @param sparkSession a {@link SparkSession} that can be used to resolve Spark queries required + * for this expression + * @param dataSource for retrieving data relating to resource references + * @param terminologyServiceFactory a factory for {@link TerminologyService} objects, used for + * parallel processing + * @param groupingColumns the list of columns to group on when aggregating + * @param nodeIdColumns columns relating to the identity of resources and elements for different + * paths parsed within this context + * @param executionContext the execution context to use + */ + public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirContext fhirContext, + @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataSource, + @Nonnull final Optional terminologyServiceFactory, + @Nonnull final List groupingColumns, + @Nonnull final Map nodeIdColumns, + @Nonnull final ExecutionContext executionContext) { this.inputContext = inputContext; this.fhirContext = fhirContext; this.sparkSession = sparkSession; @@ -125,6 +151,7 @@ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirCo this.terminologyServiceFactory = terminologyServiceFactory; this.groupingColumns = groupingColumns; this.nodeIdColumns = nodeIdColumns; + this.executionContext = executionContext; } public void setThisContext(@Nonnull final FhirPath thisContext) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java new file mode 100644 index 0000000000..20a1c13c78 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java @@ -0,0 +1,23 @@ +package au.csiro.pathling.views; + +import java.util.List; +import javax.annotation.Nonnull; +import lombok.Data; +import org.hl7.fhir.r4.model.Enumerations.ResourceType; + +@Data +public class FhirView { + + @Nonnull + ResourceType resource; + + @Nonnull + List columns; + + @Nonnull + List variables; + + @Nonnull + List filters; + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java new file mode 100644 index 0000000000..e2f069bfc3 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -0,0 +1,71 @@ +package au.csiro.pathling.views; + +import static java.util.stream.Collectors.toList; + +import au.csiro.pathling.QueryExecutor; +import au.csiro.pathling.config.QueryConfiguration; +import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.io.source.DataSource; +import au.csiro.pathling.terminology.TerminologyServiceFactory; +import ca.uhn.fhir.context.FhirContext; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.SparkSession; + +public class FhirViewExecutor extends QueryExecutor { + + + public FhirViewExecutor( + @Nonnull final QueryConfiguration configuration, + @Nonnull final FhirContext fhirContext, + @Nonnull final SparkSession sparkSession, + @Nonnull final DataSource dataSource, + @Nonnull final Optional terminologyServiceFactory) { + super(configuration, fhirContext, sparkSession, dataSource, terminologyServiceFactory); + } + + @Nonnull + public Dataset buildQuery(@Nonnull final FhirView view) { + // Build a new expression parser, and parse all the column expressions within the query. + final ResourcePath inputContext = ResourcePath + .build(getFhirContext(), getDataSource(), view.getResource(), + view.getResource().toCode(), true); + + // The context of evaluation is a single resource. + final ParserContext parserContext = buildParserContext(inputContext, + Collections.singletonList(inputContext.getIdColumn())); + final List columnExpressions = view.getColumns().stream() + .map(NamedExpression::getExpression) + .collect(toList()); + final List columnParseResult = + parseMaterializableExpressions(parserContext, columnExpressions, "Column"); + final List columnPaths = columnParseResult.stream() + .map(FhirPathAndContext::getFhirPath) + .collect(Collectors.toUnmodifiableList()); + final Dataset unfilteredDataset = joinExpressionsWithoutCorrelation(inputContext, + columnPaths, + inputContext.getIdColumn()); + final Dataset filteredDataset = filterDataset(inputContext, view.getFilters(), + unfilteredDataset, Column::and); + + // Select the column values. + final Column idColumn = inputContext.getIdColumn(); + final Column[] columnValues = labelColumns( + columnPaths.stream().map(path -> ((Materializable) path).getExtractableColumn()), + view.getColumns().stream().map(NamedExpression::getName).map(Optional::of) + ).toArray(Column[]::new); + return filteredDataset.select(columnValues) + .filter(idColumn.isNotNull()); + } + + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/NamedExpression.java b/fhirpath/src/main/java/au/csiro/pathling/views/NamedExpression.java new file mode 100644 index 0000000000..dfbb8acd41 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/NamedExpression.java @@ -0,0 +1,21 @@ +package au.csiro.pathling.views; + +import javax.annotation.Nonnull; +import lombok.Getter; + +@Getter +public class NamedExpression { + + @Nonnull + private final String expression; + + @Nonnull + private final String name; + + + public NamedExpression(@Nonnull final String expression, @Nonnull final String name) { + this.expression = expression; + this.name = name; + } + +} diff --git a/lib/python/pathling/datasource.py b/lib/python/pathling/datasource.py index b19c9b063a..c516b6e081 100644 --- a/lib/python/pathling/datasource.py +++ b/lib/python/pathling/datasource.py @@ -21,7 +21,12 @@ from pyspark.sql import DataFrame from pathling import PathlingContext -from pathling.core import ExpOrStr, StringToStringSetMapper, SparkConversionsMixin +from pathling.core import ( + ExpOrStr, + Expression, + StringToStringSetMapper, + SparkConversionsMixin, +) from pathling.fhir import MimeType @@ -111,6 +116,32 @@ def aggregate( self ) + def view( + self, + resource_type: str, + columns: Sequence[Expression], + variables: Sequence[Expression], + filters: Optional[Sequence[Expression]] = None, + ) -> DataFrame: + """ + Runs a view query for the given resource type, using the specified columns, variables and + filters. + + :param resource_type: A string representing the type of FHIR resource to base the view upon. + :param columns: A sequence of FHIRPath expressions that define the columns to include in the + view. + :param variables: A sequence of FHIRPath expressions that define variables that can be used + in the view. + :param filters: An optional sequence of FHIRPath expressions that can be evaluated against + each resource in the data set to determine whether it is included within the result. + The expression must evaluate to a Boolean value. Multiple filters are combined using + AND logic. + :return: A Spark DataFrame object that represents the view query. + """ + from pathling.query import FhirView + + return FhirView(resource_type, columns, variables, filters).execute(self) + class DataSources(SparkConversionsMixin): """ diff --git a/lib/python/pathling/query.py b/lib/python/pathling/query.py index 68bc7482b0..69996f53b8 100644 --- a/lib/python/pathling/query.py +++ b/lib/python/pathling/query.py @@ -161,6 +161,87 @@ def _create_jquery(self, data_source: DataSource) -> JavaObject: return jquery +class FhirView(QueryWithFilters): + """ + Represents a FHIR view that describes a way of flattening the data from a specified resource + type into a tabular format. + + :param subject_resource: A string representing the type of FHIR resource to base the view on. + :param columns: A sequence of FHIRPath expressions that define the columns to include in the + view. + :param variables: A sequence of FHIRPath expressions that define the variables that can be used + in the view. + :param filters: An optional sequence of FHIRPath expressions that can be evaluated against each + resource in the data set to determine whether it is included within the result. The + expression must evaluate to a Boolean value. Multiple filters are combined using AND + logic. + :param data_source: An optional DataSource instance to use for executing the query. + """ + + def __init__( + self, + subject_resource: str, + columns: Sequence[Expression], + variables: Sequence[Expression], + filters: Optional[Sequence[str]], + data_source: DataSource = None, + ): + """ + Initializes a new instance of the FhirView class. + + :param subject_resource: A string representing the type of FHIR resource to base the view + on. + :param columns: A sequence of FHIRPath expressions that define the columns to include in + the view. + :param variables: A sequence of FHIRPath expressions that define the variables that can be + used in the view. + :param filters: An optional sequence of FHIRPath expressions that can be evaluated against + each resource in the data set to determine whether it is included within the + result. The expression must evaluate to a Boolean value. Multiple filters are + combined using AND logic. + :param data_source: An optional DataSource instance to use for executing the query. + """ + super().__init__(subject_resource, filters, data_source) + self._columns = columns + self._variables = variables + + @property + def columns(self) -> Sequence[Expression]: + """ + Gets the columns to include in the view. + + :return: A sequence of Expression objects representing the columns to include in the view. + """ + return self._columns + + @property + def variables(self) -> Sequence[Expression]: + """ + Gets the variables that can be used in the view. + + :return: A sequence of Expression objects representing the variables that can be used in + the view. + """ + return self._variables + + def _create_jquery(self, data_source: DataSource) -> JavaObject: + """ + Creates a new instance of a Java-based view object. + + :param data_source: The DataSource instance to use for executing the query. + :return: A new instance of a Java-based view object. + """ + jquery = data_source._jds.view(self._subject_resource) + for column in self._columns: + jquery.column(column.expression, column.label) + for variable in self._variables: + jquery.variable(variable.expression, variable.label) + if self._filters: + for f in self._filters: + jquery.filter(f) + return jquery + + class AggregateQuery(QueryWithFilters): """ Represents an aggregate query for FHIR data. The query calculates summary values based diff --git a/lib/python/tests/test_query.py b/lib/python/tests/test_query.py index 41ee72066c..01c24d9db3 100644 --- a/lib/python/tests/test_query.py +++ b/lib/python/tests/test_query.py @@ -152,6 +152,34 @@ def test_many_aggregate_no_grouping(test_data_source): assert_result([ResultRow(9, 9)], agg_result.collect()) +def test_view(test_data_source): + result = test_data_source.view( + "Patient", + columns=[ + fpe("id", "patient_id"), + fpe("name.given", "given_name"), + fpe("name.family", "family_name"), + ], + variables=[fpe("name", "name")], + filters=["gender = 'female'"], + ) + + # noinspection PyPep8Naming + ViewRow = Row("patient_id", "given_name", "family_name") + assert result.columns == list(ViewRow) + + result.show(truncate=False) + assert_result( + [ + ViewRow("121503c8-9564-4b48-9086-a22df717948e", "Lorilee", "Bartolomeazzi"), + ViewRow("a7eb2ce7-1075-426c-addd-957b861b0e55", "Lorilee", "Bartolomeazzi"), + ViewRow("a7eb2ce7-1075-426c-addd-957b861b0e55", "Lorilee", "Bartolomeazzi"), + ViewRow("a7eb2ce7-1075-426c-addd-957b861b0e55", "Lorilee", "Bartolomeazzi"), + ], + result.limit(4).collect(), + ) + + def assert_result(expected: Sequence[Row], actual: Sequence[Row]): assert len(expected) == len(actual) assert set(expected).issubset(set(actual)) diff --git a/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java b/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java index a14f4f4e19..d725a2234f 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java +++ b/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java @@ -28,7 +28,9 @@ import au.csiro.pathling.library.io.sink.DataSinkBuilder; import au.csiro.pathling.library.query.AggregateQuery; import au.csiro.pathling.library.query.ExtractQuery; +import au.csiro.pathling.library.query.FhirViewBuilder; import au.csiro.pathling.library.query.QueryDispatcher; +import au.csiro.pathling.views.FhirViewExecutor; import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -66,9 +68,12 @@ private QueryDispatcher buildDispatcher(final @Nonnull PathlingContext context, final ExtractQueryExecutor extractExecutor = new ExtractQueryExecutor(queryConfiguration, context.getFhirContext(), context.getSpark(), dataSource, Optional.of(context.getTerminologyServiceFactory())); + final FhirViewExecutor viewExecutor = new FhirViewExecutor(queryConfiguration, + context.getFhirContext(), context.getSpark(), dataSource, + Optional.of(context.getTerminologyServiceFactory())); // Build the dispatcher using the executors. - return new QueryDispatcher(aggregateExecutor, extractExecutor); + return new QueryDispatcher(aggregateExecutor, extractExecutor, viewExecutor); } @Nonnull @@ -101,4 +106,16 @@ public ExtractQuery extract(@Nullable final String subjectResource) { return extract(getResourceType(subjectResource)); } + @Nonnull + @Override + public FhirViewBuilder view(@Nullable final ResourceType subjectResource) { + return new FhirViewBuilder(dispatcher, requireNonNull(subjectResource)); + } + + @Nonnull + @Override + public FhirViewBuilder view(@Nullable final String subjectResource) { + return view(getResourceType(subjectResource)); + } + } diff --git a/library-api/src/main/java/au/csiro/pathling/library/io/source/QueryableDataSource.java b/library-api/src/main/java/au/csiro/pathling/library/io/source/QueryableDataSource.java index 8c16107178..1441aed363 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/io/source/QueryableDataSource.java +++ b/library-api/src/main/java/au/csiro/pathling/library/io/source/QueryableDataSource.java @@ -20,6 +20,7 @@ import au.csiro.pathling.library.io.sink.DataSinkBuilder; import au.csiro.pathling.library.query.AggregateQuery; import au.csiro.pathling.library.query.ExtractQuery; +import au.csiro.pathling.library.query.FhirViewBuilder; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -53,6 +54,13 @@ public interface QueryableDataSource extends DataSource { @Nonnull ExtractQuery extract(@Nullable ResourceType subjectResource); + /** + * @param subjectResource the subject resource type + * @return a query builder for the view operation + */ + @Nonnull + FhirViewBuilder view(@Nullable ResourceType subjectResource); + /** * @param subjectResource the subject resource code * @return a query builder for the aggregate operation @@ -67,4 +75,11 @@ public interface QueryableDataSource extends DataSource { @Nonnull ExtractQuery extract(@Nullable String subjectResource); + /** + * @param subjectResource the subject resource code + * @return a query builder for the view operation + */ + @Nonnull + FhirViewBuilder view(@Nullable String subjectResource); + } diff --git a/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java b/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java new file mode 100644 index 0000000000..feac82dccf --- /dev/null +++ b/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java @@ -0,0 +1,87 @@ +/* + * Copyright 2023 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.library.query; + +import static au.csiro.pathling.utilities.Preconditions.requireNonBlank; + +import au.csiro.pathling.views.FhirView; +import au.csiro.pathling.views.NamedExpression; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; +import org.hl7.fhir.r4.model.Enumerations.ResourceType; + +/** + * Represents a FHIR view. + * + * @author John Grimes + */ +public class FhirViewBuilder extends QueryBuilder { + + @Nonnull + private final List columns = new ArrayList<>(); + + @Nonnull + private final List variables = new ArrayList<>(); + + public FhirViewBuilder(@Nonnull final QueryDispatcher dispatcher, + @Nonnull final ResourceType subjectResource) { + super(dispatcher, subjectResource); + } + + /** + * Adds an expression that represents a column to be included in the view. + * + * @param expression the column expression + * @param name the name of the column + * @return this query + */ + @Nonnull + public FhirViewBuilder column(@Nullable final String expression, @Nullable final String name) { + columns.add( + new NamedExpression(requireNonBlank(expression, "Column expression cannot be blank"), + requireNonBlank(name, "Column name cannot be blank"))); + return this; + } + + /** + * Adds an expression that represents a variable to be defined within the view. + * + * @param expression the variable expression + * @param name the name of the variable + * @return this query + */ + @Nonnull + public FhirViewBuilder variable(@Nullable final String expression, @Nullable final String name) { + variables.add( + new NamedExpression(requireNonBlank(expression, "Variable expression cannot be blank"), + requireNonBlank(name, "Variable name cannot be blank"))); + return this; + } + + @Nonnull + @Override + public Dataset execute() { + final FhirView view = new FhirView(subjectResource, columns, variables, filters); + return dispatcher.dispatch(view); + } + +} diff --git a/library-api/src/main/java/au/csiro/pathling/library/query/QueryDispatcher.java b/library-api/src/main/java/au/csiro/pathling/library/query/QueryDispatcher.java index 7e8b1c9374..7776c3dfa9 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/query/QueryDispatcher.java +++ b/library-api/src/main/java/au/csiro/pathling/library/query/QueryDispatcher.java @@ -21,6 +21,8 @@ import au.csiro.pathling.aggregate.AggregateRequest; import au.csiro.pathling.extract.ExtractQueryExecutor; import au.csiro.pathling.extract.ExtractRequest; +import au.csiro.pathling.views.FhirView; +import au.csiro.pathling.views.FhirViewExecutor; import javax.annotation.Nonnull; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; @@ -39,10 +41,15 @@ public class QueryDispatcher { @Nonnull private final ExtractQueryExecutor extractExecutor; + @Nonnull + private final FhirViewExecutor viewExecutor; + public QueryDispatcher(@Nonnull final AggregateQueryExecutor aggregateExecutor, - @Nonnull final ExtractQueryExecutor extractExecutor) { + @Nonnull final ExtractQueryExecutor extractExecutor, + @Nonnull final FhirViewExecutor viewExecutor) { this.aggregateExecutor = aggregateExecutor; this.extractExecutor = extractExecutor; + this.viewExecutor = viewExecutor; } /** @@ -67,4 +74,15 @@ public Dataset dispatch(@Nonnull final AggregateRequest aggregateRequest) { return aggregateExecutor.buildQuery(aggregateRequest).getDataset(); } + /** + * Dispatches the given view request to the relevant executor and returns the result. + * + * @param view the request to execute + * @return the result of the execution + */ + @Nonnull + public Dataset dispatch(@Nonnull final FhirView view) { + return viewExecutor.buildQuery(view); + } + } From 6ecad1bc05f6a4a1f9932eb386d6836098457a4a Mon Sep 17 00:00:00 2001 From: John Grimes Date: Wed, 17 May 2023 11:33:46 +1000 Subject: [PATCH 02/95] Get test #2 working --- .../java/au/csiro/pathling/QueryExecutor.java | 30 ++-- .../aggregate/AggregateQueryExecutor.java | 5 +- .../extract/ExtractQueryExecutor.java | 17 +- .../pathling/fhirpath/FhirPathAndContext.java | 16 ++ .../fhirpath/FhirPathContextAndResult.java | 21 +++ .../fhirpath/function/NamedFunction.java | 8 +- .../operator/PathTraversalOperator.java | 13 +- .../fhirpath/parser/ExecutionContext.java | 6 - .../fhirpath/parser/InvocationVisitor.java | 2 +- .../fhirpath/parser/ParserContext.java | 22 ++- .../pathling/fhirpath/parser/TermVisitor.java | 32 ++-- .../fhirpath/parser/UnnestBehaviour.java | 6 + .../au/csiro/pathling/views/FhirView.java | 2 +- .../pathling/views/FhirViewExecutor.java | 86 +++++++++- .../pathling/views/VariableExpression.java | 17 ++ .../au/csiro/pathling/views/WhenMany.java | 31 ++++ .../test/assertions/DatasetAssert.java | 5 + .../au/csiro/pathling/views/FhirViewTest.java | 147 ++++++++++++++++++ ...-8e8e-3f959160df7e-c000.snappy.parquet.crc | Bin 0 -> 356 bytes .../_delta_log/.00000000000000000000.json.crc | Bin 0 -> 160 bytes .../_delta_log/00000000000000000000.json | 4 + ...4d4f-8e8e-3f959160df7e-c000.snappy.parquet | Bin 0 -> 44411 bytes lib/python/pathling/__init__.py | 3 +- lib/python/pathling/core.py | 30 ++++ lib/python/pathling/datasource.py | 2 +- lib/python/pathling/query.py | 13 +- lib/python/tests/test_query.py | 28 ---- .../library/query/FhirViewBuilder.java | 11 +- 28 files changed, 441 insertions(+), 116 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathContextAndResult.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ExecutionContext.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/UnnestBehaviour.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/VariableExpression.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/WhenMany.java create mode 100644 fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java create mode 100644 fhirpath/src/test/resources/test-data/views/Patient.parquet/.part-00000-ddd590a4-cb85-4d4f-8e8e-3f959160df7e-c000.snappy.parquet.crc create mode 100644 fhirpath/src/test/resources/test-data/views/Patient.parquet/_delta_log/.00000000000000000000.json.crc create mode 100644 fhirpath/src/test/resources/test-data/views/Patient.parquet/_delta_log/00000000000000000000.json create mode 100644 fhirpath/src/test/resources/test-data/views/Patient.parquet/part-00000-ddd590a4-cb85-4d4f-8e8e-3f959160df7e-c000.snappy.parquet diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index c4cd2e2388..dc2faedc3e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -28,6 +28,7 @@ import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.FhirPathAndContext; import au.csiro.pathling.fhirpath.Materializable; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.element.BooleanPath; @@ -49,7 +50,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.Getter; -import lombok.Value; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; @@ -96,21 +96,24 @@ protected ParserContext buildParserContext(@Nonnull final FhirPath inputContext, } @Nonnull - protected List parseMaterializableExpressions( + protected List parseExpressions( @Nonnull final ParserContext parserContext, @Nonnull final Collection expressions, - @Nonnull final String display) { + @Nonnull final String display, final boolean checkMaterializable) { return expressions.stream() .map(expression -> { final ParserContext currentContext = new ParserContext(parserContext.getInputContext(), parserContext.getFhirContext(), parserContext.getSparkSession(), parserContext.getDataSource(), parserContext.getTerminologyServiceFactory(), - parserContext.getGroupingColumns(), new HashMap<>()); + parserContext.getGroupingColumns(), new HashMap<>(), + parserContext.getUnnestBehaviour(), parserContext.getVariables()); final Parser parser = new Parser(currentContext); final FhirPath result = parser.parse(expression); - // Each expression must evaluate to a Materializable path, or a user error will be thrown. - // There is no requirement for it to be singular. - checkUserInput(result instanceof Materializable, - display + " expression is not of a supported type: " + expression); + if (checkMaterializable) { + // Each expression must evaluate to a Materializable path, or a user error will be thrown. + // There is no requirement for it to be singular. + checkUserInput(result instanceof Materializable, + display + " expression is not of a supported type: " + expression); + } return new FhirPathAndContext(result, parser.getContext()); }).collect(Collectors.toList()); } @@ -259,15 +262,4 @@ private DatasetWithColumn getFilteredIds(@Nonnull final Iterable filters return new DatasetWithColumn(dataset.filter(filterColumn), col(filterIdAlias)); } - @Value - protected static class FhirPathAndContext { - - @Nonnull - FhirPath fhirPath; - - @Nonnull - ParserContext context; - - } - } diff --git a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java index 87a242148b..cbbcc4cf96 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java @@ -25,6 +25,7 @@ import au.csiro.pathling.QueryHelpers; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.FhirPathAndContext; import au.csiro.pathling.fhirpath.Materializable; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.parser.Parser; @@ -87,8 +88,8 @@ public ResultWithExpressions buildQuery(@Nonnull final AggregateRequest query) { Collections.singletonList(inputContext.getIdColumn())); final Parser parser = new Parser(groupingAndFilterContext); final List filters = parseFilters(parser, query.getFilters()); - final List groupingParseResult = parseMaterializableExpressions( - groupingAndFilterContext, query.getGroupings(), "Grouping"); + final List groupingParseResult = parseExpressions( + groupingAndFilterContext, query.getGroupings(), "Grouping", true); final List groupings = groupingParseResult.stream() .map(FhirPathAndContext::getFhirPath) .collect(Collectors.toList()); diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java index 9105f096f3..a9e69b8663 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java @@ -9,6 +9,8 @@ import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.FhirPathAndContext; +import au.csiro.pathling.fhirpath.FhirPathContextAndResult; import au.csiro.pathling.fhirpath.Materializable; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.parser.ParserContext; @@ -27,7 +29,6 @@ import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nonnull; -import lombok.Value; import lombok.extern.slf4j.Slf4j; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; @@ -61,7 +62,7 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query) { final ParserContext parserContext = buildParserContext(inputContext, Collections.singletonList(inputContext.getIdColumn())); final List columnParseResult = - parseMaterializableExpressions(parserContext, query.getColumns(), "Column"); + parseExpressions(parserContext, query.getColumns(), "Column", true); final List columnPaths = columnParseResult.stream() .map(FhirPathAndContext::getFhirPath) .collect(Collectors.toUnmodifiableList()); @@ -189,16 +190,4 @@ private Dataset trimTrailingNulls(final @Nonnull Column idColumn, } } - @Value - private static class FhirPathContextAndResult { - - @Nonnull - FhirPath fhirPath; - - @Nonnull - ParserContext context; - - @Nonnull - Dataset result; - } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java new file mode 100644 index 0000000000..88301b2bf9 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java @@ -0,0 +1,16 @@ +package au.csiro.pathling.fhirpath; + +import au.csiro.pathling.fhirpath.parser.ParserContext; +import javax.annotation.Nonnull; +import lombok.Value; + +@Value +public class FhirPathAndContext { + + @Nonnull + FhirPath fhirPath; + + @Nonnull + ParserContext context; + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathContextAndResult.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathContextAndResult.java new file mode 100644 index 0000000000..79d172507f --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathContextAndResult.java @@ -0,0 +1,21 @@ +package au.csiro.pathling.fhirpath; + +import au.csiro.pathling.fhirpath.parser.ParserContext; +import javax.annotation.Nonnull; +import lombok.Value; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; + +@Value +public class FhirPathContextAndResult { + + @Nonnull + FhirPath fhirPath; + + @Nonnull + ParserContext context; + + @Nonnull + Dataset result; + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java index 7606346625..60a81163ae 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java @@ -29,7 +29,7 @@ import au.csiro.pathling.fhirpath.function.terminology.PropertyFunction; import au.csiro.pathling.fhirpath.function.terminology.SubsumesFunction; import au.csiro.pathling.fhirpath.function.terminology.TranslateFunction; -import au.csiro.pathling.fhirpath.parser.ExecutionContext; +import au.csiro.pathling.fhirpath.parser.UnnestBehaviour; import com.google.common.collect.ImmutableMap; import java.util.Map; import java.util.stream.Collectors; @@ -125,7 +125,7 @@ public interface NamedFunction { */ @Nonnull static NamedFunction getInstance(@Nonnull final String name) { - return getInstance(name, ExecutionContext.DEFAULT); + return getInstance(name, UnnestBehaviour.UNNEST); } /** @@ -137,8 +137,8 @@ static NamedFunction getInstance(@Nonnull final String name) { */ @Nonnull static NamedFunction getInstance(@Nonnull final String name, - @Nonnull final ExecutionContext context) { - final NamedFunction function = context == ExecutionContext.VIEW + @Nonnull final UnnestBehaviour context) { + final NamedFunction function = context == UnnestBehaviour.NOOP ? NAME_TO_INSTANCE_VIEW_CONTEXT.get(name) : NAME_TO_INSTANCE.get(name); checkUserInput(function != null, "Unsupported function: " + name); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java index 785c9a1371..0080514436 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java @@ -29,7 +29,7 @@ import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.parser.ExecutionContext; +import au.csiro.pathling.fhirpath.parser.UnnestBehaviour; import java.util.Optional; import javax.annotation.Nonnull; import org.apache.commons.lang3.tuple.MutablePair; @@ -91,12 +91,13 @@ public ElementPath invoke(@Nonnull final PathTraversalInput input) { final Column valueColumn; final Optional eidColumnCandidate; final Dataset resultDataset; + final UnnestBehaviour unnestBehaviour = input.getContext().getUnnestBehaviour(); - if (maxCardinalityOfOne || input.getContext().getExecutionContext() == ExecutionContext.VIEW) { + if (maxCardinalityOfOne || unnestBehaviour == UnnestBehaviour.NOOP) { valueColumn = field; eidColumnCandidate = left.getEidColumn(); resultDataset = leftDataset; - } else { + } else if (unnestBehaviour == UnnestBehaviour.UNNEST) { final MutablePair valueAndEidColumns = new MutablePair<>(); final Dataset explodedDataset = left.explodeArray(leftDataset, field, valueAndEidColumns); @@ -106,6 +107,8 @@ public ElementPath invoke(@Nonnull final PathTraversalInput input) { valueColumn = datasetWithColumnMap.getColumn(valueAndEidColumns.getLeft()); eidColumnCandidate = Optional.of( datasetWithColumnMap.getColumn(valueAndEidColumns.getRight())); + } else { + throw new UnsupportedOperationException("Unsupported unnest behaviour: " + unnestBehaviour); } final Optional eidColumn = resultSingular @@ -114,7 +117,9 @@ public ElementPath invoke(@Nonnull final PathTraversalInput input) { // If there is an element ID column, we need to add it to the parser context so that it can // be used within joins in certain situations, e.g. extract. - eidColumn.ifPresent(c -> input.getContext().getNodeIdColumns().putIfAbsent(expression, c)); + if (unnestBehaviour == UnnestBehaviour.UNNEST && !maxCardinalityOfOne) { + eidColumn.ifPresent(c -> input.getContext().getNodeIdColumns().putIfAbsent(expression, c)); + } return ElementPath .build(expression, resultDataset, left.getIdColumn(), eidColumn, valueColumn, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ExecutionContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ExecutionContext.java deleted file mode 100644 index bcb2357839..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ExecutionContext.java +++ /dev/null @@ -1,6 +0,0 @@ -package au.csiro.pathling.fhirpath.parser; - -public enum ExecutionContext { - DEFAULT, - VIEW -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index 8de1a1f883..11e0569673 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -163,7 +163,7 @@ public FhirPath visitFunctionInvocation(@Nullable final FunctionInvocationContex .getText(); requireNonNull(functionIdentifier); final NamedFunction function = NamedFunction.getInstance(functionIdentifier, - context.getExecutionContext()); + context.getUnnestBehaviour()); // If there is no invoker, we use either the input context or the this context, depending on // whether we are in the context of function arguments. diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index 0f8e900a5d..aa56d95a47 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -18,10 +18,12 @@ package au.csiro.pathling.fhirpath.parser; import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.FhirPathAndContext; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyService; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -102,7 +104,10 @@ public class ParserContext { private final Map nodeIdColumns; @Nonnull - private final ExecutionContext executionContext; + private final UnnestBehaviour unnestBehaviour; + + @Nonnull + private final Map variables; /** * @param inputContext the input context from which the FHIRPath is to be evaluated @@ -122,7 +127,7 @@ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirCo @Nonnull final List groupingColumns, @Nonnull final Map nodeIdColumns) { this(inputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, - groupingColumns, nodeIdColumns, ExecutionContext.DEFAULT); + groupingColumns, nodeIdColumns, UnnestBehaviour.UNNEST, new HashMap<>()); } /** @@ -136,14 +141,15 @@ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirCo * @param groupingColumns the list of columns to group on when aggregating * @param nodeIdColumns columns relating to the identity of resources and elements for different * paths parsed within this context - * @param executionContext the execution context to use + * @param unnestBehaviour the execution context to use */ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataSource, @Nonnull final Optional terminologyServiceFactory, @Nonnull final List groupingColumns, @Nonnull final Map nodeIdColumns, - @Nonnull final ExecutionContext executionContext) { + @Nonnull final UnnestBehaviour unnestBehaviour, + @Nonnull final Map variables) { this.inputContext = inputContext; this.fhirContext = fhirContext; this.sparkSession = sparkSession; @@ -151,11 +157,17 @@ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirCo this.terminologyServiceFactory = terminologyServiceFactory; this.groupingColumns = groupingColumns; this.nodeIdColumns = nodeIdColumns; - this.executionContext = executionContext; + this.unnestBehaviour = unnestBehaviour; + this.variables = variables; } public void setThisContext(@Nonnull final FhirPath thisContext) { this.thisContext = Optional.of(thisContext); } + public ParserContext withUnnestBehaviour(@Nonnull final UnnestBehaviour unnestBehaviour) { + return new ParserContext(inputContext, fhirContext, sparkSession, dataSource, + terminologyServiceFactory, groupingColumns, nodeIdColumns, unnestBehaviour, variables); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java index 1d4c1c3701..6f1ab188b9 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java @@ -18,17 +18,16 @@ package au.csiro.pathling.fhirpath.parser; import static au.csiro.pathling.utilities.Preconditions.check; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static java.util.Objects.requireNonNull; import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.FhirPathAndContext; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ExternalConstantTermContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.InvocationTermContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.LiteralTermContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ParenthesizedTermContext; -import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -63,18 +62,27 @@ public FhirPath visitLiteralTerm(@Nullable final LiteralTermContext ctx) { public FhirPath visitExternalConstantTerm(@Nullable final ExternalConstantTermContext ctx) { @Nullable final String term = requireNonNull(ctx).getText(); requireNonNull(term); - checkUserInput(term.equals("%resource") || term.equals("%context"), - "Unsupported environment variable: " + term); - check(context.getInputContext() instanceof NonLiteralPath); - // The %resource and %context elements both return the input context. - final NonLiteralPath inputContext = (NonLiteralPath) context.getInputContext(); + if (term.equals("%resource") || term.equals("%context")) { + check(context.getInputContext() instanceof NonLiteralPath); - // In the case of %resource and %context, the new expression will be the input context with the - // expression updated to match the external constant term. - return inputContext.copy(term, inputContext.getDataset(), inputContext.getIdColumn(), - inputContext.getEidColumn(), inputContext.getValueColumn(), inputContext.isSingular(), - inputContext.getThisColumn()); + // The %resource and %context elements both return the input context. + final NonLiteralPath inputContext = (NonLiteralPath) context.getInputContext(); + + // In the case of %resource and %context, the new expression will be the input context with the + // expression updated to match the external constant term. + return inputContext.copy(term, inputContext.getDataset(), inputContext.getIdColumn(), + inputContext.getEidColumn(), inputContext.getValueColumn(), inputContext.isSingular(), + inputContext.getThisColumn()); + } else { + final String variableName = term.replaceFirst("%", ""); + final FhirPathAndContext variable = context.getVariables().get(variableName); + if (variable == null) { + throw new IllegalArgumentException("Unknown variable: " + variableName); + } + context.getNodeIdColumns().putAll(variable.getContext().getNodeIdColumns()); + return variable.getFhirPath(); + } } @Override diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/UnnestBehaviour.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/UnnestBehaviour.java new file mode 100644 index 0000000000..1fc9f36b90 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/UnnestBehaviour.java @@ -0,0 +1,6 @@ +package au.csiro.pathling.fhirpath.parser; + +public enum UnnestBehaviour { + UNNEST, + NOOP +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java index 20a1c13c78..616018c48c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java @@ -15,7 +15,7 @@ public class FhirView { List columns; @Nonnull - List variables; + List variables; @Nonnull List filters; diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index e2f069bfc3..b97e3f2ed5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -1,20 +1,30 @@ package au.csiro.pathling.views; +import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toUnmodifiableList; import au.csiro.pathling.QueryExecutor; +import au.csiro.pathling.QueryHelpers; +import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.FhirPathAndContext; import au.csiro.pathling.fhirpath.Materializable; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.parser.Parser; import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.parser.UnnestBehaviour; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; +import java.util.Set; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; @@ -40,20 +50,35 @@ public Dataset buildQuery(@Nonnull final FhirView view) { .build(getFhirContext(), getDataSource(), view.getResource(), view.getResource().toCode(), true); - // The context of evaluation is a single resource. - final ParserContext parserContext = buildParserContext(inputContext, + // Parse the variable expressions. + ParserContext variableContext = buildParserContext(inputContext, Collections.singletonList(inputContext.getIdColumn())); + for (final VariableExpression variable : view.getVariables()) { + final UnnestBehaviour unnestBehaviour = variable.getWhenMany() == WhenMany.UNNEST + ? UnnestBehaviour.UNNEST + : UnnestBehaviour.NOOP; + variableContext = variableContext.withUnnestBehaviour(unnestBehaviour); + final Parser parser = new Parser(variableContext); + final FhirPath result = parser.parse(variable.getExpression()); + variableContext.getVariables().put(variable.getName(), new FhirPathAndContext(result, + variableContext)); + } + + // Parse the column expressions. + final ParserContext columnContext = variableContext.withUnnestBehaviour(UnnestBehaviour.NOOP); final List columnExpressions = view.getColumns().stream() .map(NamedExpression::getExpression) .collect(toList()); final List columnParseResult = - parseMaterializableExpressions(parserContext, columnExpressions, "Column"); + parseExpressions(columnContext, columnExpressions, "Column", true); final List columnPaths = columnParseResult.stream() .map(FhirPathAndContext::getFhirPath) - .collect(Collectors.toUnmodifiableList()); - final Dataset unfilteredDataset = joinExpressionsWithoutCorrelation(inputContext, - columnPaths, - inputContext.getIdColumn()); + .collect(toUnmodifiableList()); + + // Join the column expressions together. + final Dataset unfilteredDataset = joinAllColumns(columnParseResult); + + // Filter the dataset. final Dataset filteredDataset = filterDataset(inputContext, view.getFilters(), unfilteredDataset, Column::and); @@ -67,5 +92,50 @@ public Dataset buildQuery(@Nonnull final FhirView view) { .filter(idColumn.isNotNull()); } + @Nonnull + private Dataset joinAllColumns( + @Nonnull final Collection columnsAndContexts) { + if (columnsAndContexts.isEmpty()) { + throw new IllegalArgumentException("No columns to join"); + + } else if (columnsAndContexts.size() == 1) { + final FhirPathAndContext fhirPathAndContext = columnsAndContexts.iterator().next(); + return fhirPathAndContext.getFhirPath().getDataset(); + } + + final List sorted = columnsAndContexts.stream() + .sorted((a, b) -> { + final String sortStringA = a.getContext().getNodeIdColumns().values().stream() + .map(Column::toString) + .collect(joining("")); + final String sortStringB = b.getContext().getNodeIdColumns().values().stream() + .map(Column::toString) + .collect(joining("")); + return sortStringA.compareTo(sortStringB) * -1; + }) + .collect(toList()); + + FhirPathAndContext left = sorted.get(0); + Dataset result = sorted.get(0).getFhirPath().getDataset(); + + for (final FhirPathAndContext right : sorted) { + final Set leftJoinColumns = new HashSet<>(); + final Set rightJoinColumns = new HashSet<>(); + leftJoinColumns.add(left.getFhirPath().getIdColumn()); + rightJoinColumns.add(right.getFhirPath().getIdColumn()); + + final Set commonNodeIds = new HashSet<>(left.getContext().getNodeIdColumns() + .values()); + commonNodeIds.retainAll(right.getContext().getNodeIdColumns().values()); + leftJoinColumns.addAll(commonNodeIds); + rightJoinColumns.addAll(commonNodeIds); + + result = QueryHelpers.join(result, new ArrayList<>(leftJoinColumns), + right.getFhirPath().getDataset(), new ArrayList<>(rightJoinColumns), JoinType.LEFT_OUTER); + left = right; + } + + return result; + } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/VariableExpression.java b/fhirpath/src/main/java/au/csiro/pathling/views/VariableExpression.java new file mode 100644 index 0000000000..fa0ec8f0fc --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/VariableExpression.java @@ -0,0 +1,17 @@ +package au.csiro.pathling.views; + +import javax.annotation.Nonnull; +import lombok.Getter; + +@Getter +public class VariableExpression extends NamedExpression { + + private final WhenMany whenMany; + + public VariableExpression(@Nonnull final String expression, @Nonnull final String name, + final WhenMany whenMany) { + super(expression, name); + this.whenMany = whenMany; + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/WhenMany.java b/fhirpath/src/main/java/au/csiro/pathling/views/WhenMany.java new file mode 100644 index 0000000000..c0ae03743a --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/WhenMany.java @@ -0,0 +1,31 @@ +package au.csiro.pathling.views; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.Getter; + +public enum WhenMany { + + ERROR("error"), + ARRAY("array"), + UNNEST("unnest"), + CROSS("cross"); + + @Nonnull + @Getter + private final String code; + + WhenMany(@Nonnull final String code) { + this.code = code; + } + + public static WhenMany fromCode(@Nullable final String code) { + for (final WhenMany whenMany : values()) { + if (whenMany.code.equals(code)) { + return whenMany; + } + } + throw new IllegalArgumentException("Unknown code: " + code); + } + +} diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java index 4dcf76cf76..046c89957d 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java @@ -90,6 +90,11 @@ public DatasetAssert hasRows(@Nonnull final SparkSession spark, return this; } + @Nonnull + public DatasetAssert hasRowsUnordered(@Nonnull final Row... expected) { + return hasRowsUnordered(Arrays.asList(expected)); + } + @Nonnull private DatasetAssert hasRowsUnordered(@Nonnull final Collection expected) { final List actualRows = dataset.collectAsList(); diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java new file mode 100644 index 0000000000..d532cf8d83 --- /dev/null +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -0,0 +1,147 @@ +package au.csiro.pathling.views; + +import au.csiro.pathling.config.QueryConfiguration; +import au.csiro.pathling.encoders.FhirEncoders; +import au.csiro.pathling.io.Database; +import au.csiro.pathling.io.source.DataSource; +import au.csiro.pathling.terminology.TerminologyServiceFactory; +import au.csiro.pathling.test.SpringBootUnitTest; +import au.csiro.pathling.test.assertions.DatasetAssert; +import ca.uhn.fhir.context.FhirContext; +import java.nio.file.Path; +import java.util.List; +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.hl7.fhir.r4.model.Enumerations.ResourceType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + +@SpringBootUnitTest +class FhirViewTest { + + @Autowired + SparkSession spark; + + @Autowired + FhirContext fhirContext; + + @Autowired + FhirEncoders fhirEncoders; + + @MockBean + TerminologyServiceFactory terminologyServiceFactory; + + FhirViewExecutor executor; + + static final Path TEST_DATA_PATH = Path.of( + "src/test/resources/test-data/views").toAbsolutePath().normalize(); + + @BeforeEach + void setUp() { + final QueryConfiguration queryConfiguration = QueryConfiguration.builder().build(); + final DataSource dataSource = Database.forFileSystem(spark, fhirEncoders, + TEST_DATA_PATH.toUri().toString(), true); + executor = new FhirViewExecutor(queryConfiguration, fhirContext, spark, dataSource, + Optional.of(terminologyServiceFactory)); + } + + // Test 1: + // Select ID, gender and birth date for each patient. + // { + // "resource": "Patient", + // "columns": [ + // { + // "name": "id", + // "expr": "id" + // }, + // { + // "name": "gender", + // "expr": "gender" + // }, + // { + // "name": "birth_date", + // "expr": "birthDate" + // } + // ] + // } + @Test + void test1() { + final FhirView view = new FhirView(ResourceType.PATIENT, + List.of( + new NamedExpression("id", "id"), + new NamedExpression("gender", "gender"), + new NamedExpression("birthDate", "birth_date") + ), List.of(), List.of()); + final Dataset result = executor.buildQuery(view); + + // Expected result: + // | id | gender | birth_date | + // |----|--------|------------| + // | 1 | female | 1959-09-27 | + // | 2 | male | 1983-09-06 | + DatasetAssert.of(result).hasRows( + RowFactory.create("1", "female", "1959-09-27"), + RowFactory.create("2", "male", "1983-09-06") + ); + } + + // Test 2: + // Select ID, name prefix and family name for each patient. + // { + // "resource": "Patient", + // "vars": [ + // { + // "name": "name", + // "expr": "name", + // "whenMany": "unnest" + // } + // ], + // "columns": [ + // { + // "name": "id", + // "expr": "id" + // }, + // { + // "name": "name_use", + // "expr": "%name.use" + // }, + // { + // "name": "family_name", + // "expr": "%name.family" + // } + // ] + // } + @Test + void test2() { + final FhirView view = new FhirView(ResourceType.PATIENT, + List.of( + new NamedExpression("id", "id"), + new NamedExpression("%name.use", "name_use"), + new NamedExpression("%name.family", "family_name") + ), + List.of( + new VariableExpression("name", "name", WhenMany.UNNEST) + ), + List.of()); + final Dataset result = executor.buildQuery(view); + + // Expected result: + // | id | name_use | family_name | + // |----|----------|-------------| + // | 1 | maiden | Wuckert | + // | 1 | official | Oberbrunner | + // | 2 | nickname | Cleveland | + // | 2 | official | Towne | + DatasetAssert.of(result).hasRowsUnordered( + RowFactory.create("1", "maiden", "Wuckert"), + RowFactory.create("1", "official", "Oberbrunner"), + RowFactory.create("2", "nickname", "Cleveland"), + RowFactory.create("2", "official", "Towne") + ); + } +} diff --git a/fhirpath/src/test/resources/test-data/views/Patient.parquet/.part-00000-ddd590a4-cb85-4d4f-8e8e-3f959160df7e-c000.snappy.parquet.crc b/fhirpath/src/test/resources/test-data/views/Patient.parquet/.part-00000-ddd590a4-cb85-4d4f-8e8e-3f959160df7e-c000.snappy.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..dacc426a64082331f62a3ee837797eec0225f7a6 GIT binary patch literal 356 zcmV-q0h|6~a$^7h00IEzB?~DGGdU-X>u*LyPScp>V9y^qRZUr=$KLrlh$Nr~gF7qGT0k_UDUo<`7t7k{;U0}yu81(&j_cj z;>{AIadS-WY{yP;mIC^pSjBs$POC;FT%Vh2msk46TpjYECWZQrF9}|KCFCE=D2ccb z=A3>ZkNnzO@17IMTF>p99L!0^@)XdGZRG$NC>30s7iB2{EDv-zc-eaT#7H{UEt|2Vrv34_pU)^vN~aXU0o_1%nQoNOxK4VGxKl_RojQs zgNMpud=w(+$9O>zt=?g*)66cS%#KLV)x*tsaGy&k-85#rF20@~ZOV8HoL#e*c30z5=J3SB}nDi5TG(LSDMiY^cOpqXf@dBxgMcXe4#$py$ OQVi{>{h7P@XwjYaV@w4A literal 0 HcmV?d00001 diff --git a/fhirpath/src/test/resources/test-data/views/Patient.parquet/_delta_log/00000000000000000000.json b/fhirpath/src/test/resources/test-data/views/Patient.parquet/_delta_log/00000000000000000000.json new file mode 100644 index 0000000000..1bf4ab166c --- /dev/null +++ b/fhirpath/src/test/resources/test-data/views/Patient.parquet/_delta_log/00000000000000000000.json @@ -0,0 +1,4 @@ +{"commitInfo":{"timestamp":1683834324003,"operation":"WRITE","operationParameters":{"mode":"Overwrite","partitionBy":"[]"},"isolationLevel":"Serializable","isBlindAppend":false,"operationMetrics":{"numFiles":"1","numOutputRows":"2","numOutputBytes":"44411"},"engineInfo":"Apache-Spark/3.3.2 Delta-Lake/2.2.0","txnId":"8b858a77-e68a-4b7c-90ec-c92486d23860"}} +{"protocol":{"minReaderVersion":1,"minWriterVersion":2}} +{"metaData":{"id":"19044b9f-61df-4780-a133-fa6b426228c8","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"id_versioned\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"meta\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"versionId\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"versionId_versioned\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"lastUpdated\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}},{\"name\":\"source\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"profile\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"security\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"tag\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"implicitRules\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"language\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"status\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"div\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"identifier\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"assigner\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"active\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}},{\"name\":\"name\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"family\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"given\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"prefix\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"suffix\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"telecom\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"rank\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"gender\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"birthDate\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"deceasedBoolean\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}},{\"name\":\"deceasedDateTime\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"address\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"line\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"city\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"district\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"state\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"postalCode\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"country\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"maritalStatus\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"multipleBirthBoolean\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}},{\"name\":\"multipleBirthInteger\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"photo\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"contentType\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"language\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"data\",\"type\":\"binary\",\"nullable\":true,\"metadata\":{}},{\"name\":\"url\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"size\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"hash\",\"type\":\"binary\",\"nullable\":true,\"metadata\":{}},{\"name\":\"title\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"creation\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"contact\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"relationship\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"name\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"family\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"given\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"prefix\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"suffix\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"telecom\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"rank\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"address\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"line\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"city\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"district\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"state\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"postalCode\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"country\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"gender\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"organization\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"communication\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"language\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"preferred\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"generalPractitioner\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"managingOrganization\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"link\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"other\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}}]}","partitionColumns":[],"configuration":{},"createdTime":1683834321904}} +{"add":{"path":"part-00000-ddd590a4-cb85-4d4f-8e8e-3f959160df7e-c000.snappy.parquet","partitionValues":{},"size":44411,"modificationTime":1683834323360,"dataChange":true,"stats":"{\"numRecords\":2,\"minValues\":{\"id\":\"1\",\"id_versioned\":\"Patient/1\",\"meta\":{},\"text\":{},\"gender\":\"female\",\"birthDate\":\"1959-09-27\",\"maritalStatus\":{\"text\":\"Married\"}},\"maxValues\":{\"id\":\"2\",\"id_versioned\":\"Patient/2\",\"meta\":{},\"text\":{},\"gender\":\"male\",\"birthDate\":\"1983-09-06\",\"maritalStatus\":{\"text\":\"Married\"}},\"nullCount\":{\"id\":0,\"id_versioned\":0,\"meta\":{\"id\":2,\"versionId\":2,\"versionId_versioned\":2,\"lastUpdated\":2,\"source\":2,\"profile\":0,\"security\":0,\"tag\":0},\"implicitRules\":2,\"language\":2,\"text\":{\"id\":2,\"status\":2,\"div\":2},\"identifier\":0,\"active\":2,\"name\":0,\"telecom\":0,\"gender\":0,\"birthDate\":0,\"deceasedBoolean\":2,\"deceasedDateTime\":2,\"address\":0,\"maritalStatus\":{\"id\":2,\"coding\":0,\"text\":1},\"multipleBirthBoolean\":2,\"multipleBirthInteger\":2,\"photo\":0,\"contact\":0,\"communication\":0}}"}} diff --git a/fhirpath/src/test/resources/test-data/views/Patient.parquet/part-00000-ddd590a4-cb85-4d4f-8e8e-3f959160df7e-c000.snappy.parquet b/fhirpath/src/test/resources/test-data/views/Patient.parquet/part-00000-ddd590a4-cb85-4d4f-8e8e-3f959160df7e-c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..bb7a236469c6cb5f2b56e4bef266a781d4b6dce7 GIT binary patch literal 44411 zcmeI54{%h+dBE@V=MWfdjCq%nLmi^nGIl_KWd26M|A;NyU=#e0`2R_If(xgUe0L(o zF|DG=loCayib@o!D1;EAYE%tTHI!i(jfSXH)o2)vONpzfjEWhBQVl~WQ8gNkru%*S z-nZ}VJ>6fhfZR;#uJ+#kzHh(%_S=2?c9%_0ZeFg~l!eNJN?XIFpWd}VNn?LHlnk3~ zZhnf*W=l=Av;QuK|Kze?o>0~+)xZ7T4>Al7F8qi;IFmiI$sP2lzTnd3#y_Co-O4=W z>JP^{XB!I6aHWETblWZP`vPWz{@)yHvg`UeY__`}!;0C`K+AbHP&4@$GsPWdklD&y zW%PghKAWXCJ#&7?eBSh|$s_!HvmF~e?%E$(f0C+eCp#wVX_{l+xW^zK^!)zU|4s3L z37wmdz13z*hwgAM`wtU)M?E+#roID&M(80>!^y4-XITHrU@qfOga^;U(E2-vjSV~N zl>+5Ium7=URz7=#_ug>(tNu8$Eo?Cr_J_o z161h+x8LJ)L&tiyVkclWy35OaWdS65yoDA1t*nmO%A?AubN~0VISZ6odL8d8!0xy{ z1xaAzGB%jc!$bY)+xWN{j7_f*v!NT-8aI|)8bZ1q4eIy<5RS8?2ek?M_rj{t#d28RkU@u(a%kbO>T50ri&M~ zhu-)Hr={JZ6dG-FM2Sm2CXEJ3gT!Z$8{Z3W2jZ>lEs@?IBK>~L)-ChiluVa>Is3m{ zB_-2ovthW+!RfST zXVS80rfY__Y^p!ad_0w{gu)Nn>ZkDabX={>bfxoUO6>=UAMc-^YyV)ibnxZ2OjjDN zwuxHK63c3tF7^kls)g3~bJf{k*y)n%;J)V6mTaWumrU2n_||g3_fpK^QInLRFTSv- zyHS4(!th4splm$|S6HSjtv?a=+3B)nQf_iMOim8&;JMKPXNIzwO%rXI3!HB#el68! zSDw(4H7S(r6UmxXkt>ol?E9rwY(ONN(^AHHqE2tRgpY( zALZ81C+-E2JgQLcq)7HTDfhq^D0xXFkGUwfI+JRADwA?2?^j;a>~QvrlzV7_(rVYT zPd}h+(UMJHQugTCM;0oZwCvG^RIvNYl&s659s>o+NPTqJuo6Sv`IdSF;2J6@sO zs#hu5FOp5KQSOLHo_L*d_iUl$Igvd4Ey}$nl3j05Zta_t92Cixw7vCdTyPJ}gg_Jxal8r^8KZ#^xF+F=(B#*0YIHjqPMmma$z352d(LJjD;$HY{M{ApSYqQ~d4@)gSPb`BeRf6)Ef`Obxn~ zxt2v{YR^BXKaZgMC^v+sIGEhKyzW5o)$(F@P%Y+IN1&|2U!-c)Qb+iH=b zI5y-n4OgVO(!8EPP)Xq>W~g4ZlOICa(x;$YiGm-CJ)pwXmt?cuLE?Y!g{< zbA?w8=yd?+$Y6W3wpO^e>Xijg2&x|h*$y=;=Wzt^+rN>JTI|`O{mlx`g8gYhPl-qM zN3z7-mJ(8#Q3Wn7xU*ah)rARiuom+8ZcTHe1&ubc@VfLJZf}L5rjBye?~I$aJv&tJ-!$A% zXwethZ`7}>LxiE(P*SJ~O5CL$uik$cFnueN>a%hZu4+inca*a_c|M4xM4*E0os6kO zLai_(2*IGN)JjF838M~8CMQ9&810Y``EAy&uANRMpdM-Tvil zeP$GuRrrE_otRkDu+no%jk~Tw;{yn|U1A~l&-~H~Z_rcjRi6UyA}0c8HvjaTFQ{%+ z{ovf`<=e`FWsw|co}~lbRaC}6zThitBr&#=Jo0g0V6zoB$D~&Hz54Um(*m9kRsK`j zHg{kfPo@VwL9dxx0?8<^y@welaNK`Q12FV_wgK1e^L&WC zKc;yd^!kJ*GIQ7@rnG_`G2yZ!%m}Tb4n^17f54gv^G?<8=kIv;v1U^JZto^P%$p%F zM}E_ZWlql8ZhooT=ibU*ZeB{j0bm}qJ)ZF*y)3vb*7pc`V&+WCT#z~Ob9**BBs)8E zMF#tr2+I&kiZjjmqH}Y0M*d3MB6}{|%)!>?*#>R)N#162w*R$VneRMXqGZ{!-w*{& z3l@m*IsMsh07C5d5L-V?$kT`%Em5AbW%sdzt(r26aJ56%DBt-6`!Rnd`yeQk8DJ%y z&f{B^2W;6jY`eLsq*xF^eE?9CA_1-<;2Z+_WPsJ$38DIA)b4$t%XO{`Z2RXX<+U&_ zvk;;tRnn#r5X=Uww5cZAk_;8>lMA+^?7D4ehcqbt5Npi>G@!nGgG)9v_&_5+?|d=4 zCQZ_z2T?~6H9)8>h@xd36H9L)zzxW_^N2fxxMsFzpKBHlr@e+_WdYfoE@^ljkt2v4 zjS8tR9uad?X72qPXuQ21FkrVM8Z~k8iGbY>Ny&o}D~GeCY4MA|Lo&Dz!50zShS31rhl63cY|7?tV==f2z)iAW^5r6*+C z7}5?Rt(P5;F*;9(ULlR#J5y4wwhkJ(6LAgfOiNgtxqTaENvy-j8bsD`Bvwe%4$HU` zh#N;-2RkBaY}$~TTDNM)U6P9Z$ZXgSZBfO}f$1^cyv~STvDUecJtCDK#`4Foe3zlT zm^DBUfudb<(FrVi7K=`bqKk0BNskv0kSFDmbw7eOx`rh?*?CQhgRtJIEo`F7P`Y!D zq;fNs-sgoXPs*iX4U~#b%0)Y|Xd4#oWJd|L5NcQ|ow65n-Yuzd7Fm7Bs<|g33*MTj zbJpA=!7d~0GQtK3wq+{CaOp)iF@|IrSXmDy-ct%jYM!fWwbp1o+L*!zNb0*+s?sTxbO9wb&5K`>h%6wcX`UqJ zQxsF_gXX!KfEZKI0ZCV7Nmo!(6G}RDpB{8#cZAS*1maHJCsnNaZ^7?QqPY6`y8XgC zGXKn>Pgd&ZOUMI=96;m+LKaNr%0_2`u5m%;9Yx+4@{Y1&;}L=YU-_l3dsLCsJB_&g zWf+bKH)V~r*Fj_Fos^M-i0nXQpHtT`5|V$JAVvF}lA_m;`Uz4Gd?8vYyc`qrz!xOu zp8e3=SCM%sGLt@zh>s$~z9h4okX>62hCSwrs4uZieF8k-m`l>S1IevOu7)=a?Zrs@ z0D~&QkXM~473{@=y;$%RE2y~+u9jJZ%?zAZAv#S`=;9D)+UIDLgqbOwe`^s8d z_Sr9nQC07KT-mc*|JYRpc`Xbs4@6gDwBPftXa+;yR+ZUie{hrGq8=mR~xr(TiE3U)~C_0 z7+2Gui zePp538kbP}$^f*+C}FpRTFVq>c+-x`ut|h9A*}n$x{g!BD8O)D_m?H*>KicjBd;zi zGEWOFoK}}5(OQr;fV3f^73g2~Oa?PX(;)^jy{>d2XbeGZU(waF2FZg*+gBvmIfU&G zLIX@hfJr0Agv=U2RtK_r9}KUUY5LgvprlpRPr&k@AgSS@1#AxrE)7`P3cpiQ)NOc3 zLNy@jDx!uNN_&5@M46n7FEtFyyf)<3R)E1evZLaWpK)YIwxnV=B3ltz^>9>3(tD@pV-6UIJ&6)~48AhfhE0=+m!e7!}I z-?@YR3?##e3OHS%-(X^7?mhr5dtit144cP4B8DpY+b@z~%xZo_s$3%yhIT*$3^RhR zRmgW@LJZ4@c0^1f;_#!o!3*>+2~&2(!;eb3^dPA5JD|(82q2TkUz17ak#qt{U0)5a z65qO_&o8^aDk*gxQJ*5Jc5x&W?8wnkwTmUx-oJy^8b#C~p|)8XidoHP9`2C~VSiBO z9z<^a2Vl3>B@tAFkLaXg>k>)DV~A=;)OZ9GeSRn0xQy#VTtDJ^a>8rIKim_mCr484 z0$?aOrawpccwF{{&d1SoER58LkoTGN<6?=9| zUjE9IMRA_ja!qjn%&2SKPjQO-p>oV_%WhZ^gR+M83T}sn6_OoV7~AQr{|FjrI2N?N zK;dwDojz6+Y+&E$qElwZ3Ur~6_K(;;*6gz@W6@vRtI0Z*!})Eemz4%N`e8J7cFic} z9E!Q{k&V@0Z(pi$a(!MoQ*CP;t3H1Dd>Hr!yqfsy;-YN_WmAm$S4duR>J>iB%ZjL|md z5a!St3Dbs{-G2_%9%2~nrIqjGnR4tNk}=(gIgFULwc#+veLfUU+tx~I4Iu9n@+QLa z%xlr`Mn56Lt|Dv*VZC1qr(7EBDNyKT$mpF~cY5WW9 zmyDy$$SsVX2D_OEG%SPK5p)7U9bXTp5blK{P{-FLjd~FDDT1ojg#+=OR79#;Cy~x0 zX%tEQVM!VK4lIK8%dqPRtFN?!rJ5cmz4V=0Q#xqVhdcgN{^S!<-HxH$D_m~vlQ&YX7!bMa+9xI1eJH!; zd)PulH&nJ^eB;80WZ@T3cncTa{?v`ExIO}M$?Z=`)gD91JzR42(>GGGX@wG&(W{@9 zgxCHYe0i7)KgWdgvG7LtnLJqX>YtM}IE)%pS7EC+uh**|Yi|;xDfFl2^-?`gpp-*g z${0!6V%eW?l}n#%!Z0u<xM!TTCb$0IiZD)J}YV5jQHam zUs(_Zeu{-t&aW(x_?^f<$NBwH@vT=(Ilo`#pGE$4&Oi8^uK%{FJ4Ghb%AfaW0REbYfLNk{z9*+bHCcqz; z`B#vCn)7=%>T|*v+CVJbf`HzQQU$6HgYR7A0uH>Wn`n=vt6Mg*oFC(uZa-1VuzZsp9=5^23Z%S(J`6akZ!(U=Ujz`TjE`W~9 z%qCTAfpJcz z@B0;aMCD$z@ZoRix<)}az2Si7hrcChegxS~oP8|<+rk~L$-t8cJjsDwxN9Ak53LQ$ z4`oPh(8ad0n?48|&0qVbM7)H=F-{yLVuAUbhvk9Ls;97?RM!G^k$xJ2{n#T8lH2#Rav;=0&Bz`|F3 zyIaz@8?lEuwz5#yxWMW>lPM5bgzU;f2|j@EQyhLK z2Dqt#A=I3a(N_^Y#L*Xn9IXQWifA7D>wPZUL8>9M3aWV8{ z25T%P%fbY6yUCQs(AQWjsnw2{6C87yFk3<~{DhWNRWROVnc0KPPdW3rs;f37(^Od? z9#B1l6-cQV-ai18!$pc80UBWInsBZL!P6PCn+i z%iZi6n;g8+CP%xX#g(3ylb4fgb3GFBMBsgI4!iv3{R%ah<1hP84qRsgA6;C7{_keN zEh1TKv)HNrilSiF;w*MOiMKeAwf4>LX2JGH_LHZWWsAqy;XC%Xufpqf7kb(6tf0C$ zOJ`=S{qC+^iw$MP1vv3j08XWEw#syM4@m^$#8hiS+=$8>t_l(w1=%$c?1qKXa+Vck zQ}7Fh1R~Z-Y4;HkfZBoGbn4#8vb9@D;4%U3P~2vCz~AEwyvRI;d&{oqb=EF4ic%=4 zbd$W9Uyww~#OoLF1K{eV_)BZw{^JK2FLe@>LVno^%Lwazv3sr**PkG(|r;zA0u%wSC^gAi?whp zZ9+Gnkq|P-v~*z>$w-r!ZH03oeM&_2xJki=-(N(^lDKIxTXFytw;EEDWPM%(pfR3A z>9jHQ(S+L;F6|RWiFta7cl=R#^7EV2t?<%7k`|>k#UR7Z;Kf=vhNjSOEF=-pSc7gg zq*5{0D$bcf#G1haiA??YHH@NOQFb`dT=*s%60xTxgeErf#1R6775FV+!i=$YQM{Fs zijz-Tno;;(A`Q>4wftN~^1Updt4RLoBUI#{PUuTCKxmhXhMLgQH_3BvmVxmeWl~0T zg(%-(@cP^MizgIe;LcPb+QaXeWEoMao^<6zU725XCz>GEAWaw3#*fhNtJTg!2vE%9 zu1?Z#z7?ogtEKHsAprH^Y3bn!burWHW6Z(9w5kwuX*l8-r40o;kr5ouK9WFzj53_C z2`Sz-96tfCCXTz+Yh5;l+De~If}xuPNd*)qbMD7W5V_BqIj&D%+!d@5vI=t5R~LOOAAWvrs&*Cy91X>($+xG^tDM)h~{DfEbsP>$g$tI7_bI zM%a>fGd^9#Fjq}3)f(Q35(*~CrP}Zl=7>(2=(scGn&d4x^uV@E{S#=+uRQS#>tf(a zt~$oHe>l177_);;^Qe~SR~zHMXGFb}-s!@`lUsP#hv@&w1?)tJ8q+^I73v_J;QCUTf0xyrE1Fn3I@r^j|EDdKd{5U=!q)r@*tw2SD8_H&DWL5*~v?iF`$pWeYD zk7>i)++AiP9)aizjPO>%k6IAc=lez*Qej!Wph2Row{67>R+gx z30$~5XL)W;?!rZd6&`Q#!d0u(yk$kJm#xXmTfI^(T9I3nS5)F&S(Nu!;VQKxFE4j_ hUT$vDn#Uf2e==}^&6fP%ooxYITb0eW>B-H@{~wr&*9!mu literal 0 HcmV?d00001 diff --git a/lib/python/pathling/__init__.py b/lib/python/pathling/__init__.py index bd7d75ce3e..fbdd669aae 100644 --- a/lib/python/pathling/__init__.py +++ b/lib/python/pathling/__init__.py @@ -15,7 +15,7 @@ from .coding import Coding from .context import PathlingContext, StorageType -from .core import Expression +from .core import Expression, VariableExpression from .datasource import DataSources, DataSource from .etc import find_jar from .fhir import MimeType, Version @@ -53,6 +53,7 @@ "to_ecl_value_set", "find_jar", "Expression", + "VariableExpression", "ExtractQuery", "AggregateQuery", "DataSources", diff --git a/lib/python/pathling/core.py b/lib/python/pathling/core.py index f6e6abc0d3..dfdf8215a1 100644 --- a/lib/python/pathling/core.py +++ b/lib/python/pathling/core.py @@ -168,6 +168,36 @@ def as_expression_sequence( ) +class VariableExpression(Expression): + """ + Represents a FHIRPath variable expression that has a `when many` attribute, and may have an + optional name/alias. + """ + + def __init__( + self, expression: str, label: Optional[str], when_many: Optional[str] = "array" + ): + """ + Initializes a new instance of the VariableExpression class. + + :param expression: The FHIRPath expression. + :param label: The optional label/alias for the expression. + :param when_many: The optional `when many` attribute for the expression, can be "error", + "array", "unnest" or "cross". + """ + super().__init__(expression, label) + self._when_many = when_many + + @property + def when_many(self) -> Optional[str]: + """ + Gets the optional `when many` attribute for the expression. + + :return: The optional `when many` attribute for the expression. + """ + return self._when_many + + class StringMapper: """ A wrapper for a Python lambda that can be passed as a Java lambda for mapping a string value to diff --git a/lib/python/pathling/datasource.py b/lib/python/pathling/datasource.py index c516b6e081..af6f58762c 100644 --- a/lib/python/pathling/datasource.py +++ b/lib/python/pathling/datasource.py @@ -120,7 +120,7 @@ def view( self, resource_type: str, columns: Sequence[Expression], - variables: Sequence[Expression], + variables: Optional[Sequence[Expression]] = None, filters: Optional[Sequence[Expression]] = None, ) -> DataFrame: """ diff --git a/lib/python/pathling/query.py b/lib/python/pathling/query.py index 69996f53b8..1d9bb3e8da 100644 --- a/lib/python/pathling/query.py +++ b/lib/python/pathling/query.py @@ -17,7 +17,7 @@ from py4j.java_gateway import JavaObject -from pathling.core import ExpOrStr, Expression +from pathling.core import ExpOrStr, Expression, VariableExpression from pathling.datasource import DataSource @@ -182,8 +182,8 @@ def __init__( self, subject_resource: str, columns: Sequence[Expression], - variables: Sequence[Expression], - filters: Optional[Sequence[str]], + variables: Optional[Sequence[VariableExpression]] = None, + filters: Optional[Sequence[str]] = None, data_source: DataSource = None, ): """ @@ -215,7 +215,7 @@ def columns(self) -> Sequence[Expression]: return self._columns @property - def variables(self) -> Sequence[Expression]: + def variables(self) -> Sequence[VariableExpression]: """ Gets the variables that can be used in the view. @@ -234,8 +234,9 @@ def _create_jquery(self, data_source: DataSource) -> JavaObject: jquery = data_source._jds.view(self._subject_resource) for column in self._columns: jquery.column(column.expression, column.label) - for variable in self._variables: - jquery.variable(variable.expression, variable.label) + if self._variables: + for variable in self._variables: + jquery.variable(variable.expression, variable.label, variable.when_many) if self._filters: for f in self._filters: jquery.filter(f) diff --git a/lib/python/tests/test_query.py b/lib/python/tests/test_query.py index 01c24d9db3..41ee72066c 100644 --- a/lib/python/tests/test_query.py +++ b/lib/python/tests/test_query.py @@ -152,34 +152,6 @@ def test_many_aggregate_no_grouping(test_data_source): assert_result([ResultRow(9, 9)], agg_result.collect()) -def test_view(test_data_source): - result = test_data_source.view( - "Patient", - columns=[ - fpe("id", "patient_id"), - fpe("name.given", "given_name"), - fpe("name.family", "family_name"), - ], - variables=[fpe("name", "name")], - filters=["gender = 'female'"], - ) - - # noinspection PyPep8Naming - ViewRow = Row("patient_id", "given_name", "family_name") - assert result.columns == list(ViewRow) - - result.show(truncate=False) - assert_result( - [ - ViewRow("121503c8-9564-4b48-9086-a22df717948e", "Lorilee", "Bartolomeazzi"), - ViewRow("a7eb2ce7-1075-426c-addd-957b861b0e55", "Lorilee", "Bartolomeazzi"), - ViewRow("a7eb2ce7-1075-426c-addd-957b861b0e55", "Lorilee", "Bartolomeazzi"), - ViewRow("a7eb2ce7-1075-426c-addd-957b861b0e55", "Lorilee", "Bartolomeazzi"), - ], - result.limit(4).collect(), - ) - - def assert_result(expected: Sequence[Row], actual: Sequence[Row]): assert len(expected) == len(actual) assert set(expected).issubset(set(actual)) diff --git a/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java b/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java index feac82dccf..dcece81d17 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java +++ b/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java @@ -21,6 +21,8 @@ import au.csiro.pathling.views.FhirView; import au.csiro.pathling.views.NamedExpression; +import au.csiro.pathling.views.VariableExpression; +import au.csiro.pathling.views.WhenMany; import java.util.ArrayList; import java.util.List; import javax.annotation.Nonnull; @@ -40,7 +42,7 @@ public class FhirViewBuilder extends QueryBuilder { private final List columns = new ArrayList<>(); @Nonnull - private final List variables = new ArrayList<>(); + private final List variables = new ArrayList<>(); public FhirViewBuilder(@Nonnull final QueryDispatcher dispatcher, @Nonnull final ResourceType subjectResource) { @@ -70,10 +72,11 @@ public FhirViewBuilder column(@Nullable final String expression, @Nullable final * @return this query */ @Nonnull - public FhirViewBuilder variable(@Nullable final String expression, @Nullable final String name) { + public FhirViewBuilder variable(@Nullable final String expression, @Nullable final String name, + @Nullable final String whenMany) { variables.add( - new NamedExpression(requireNonBlank(expression, "Variable expression cannot be blank"), - requireNonBlank(name, "Variable name cannot be blank"))); + new VariableExpression(requireNonBlank(expression, "Variable expression cannot be blank"), + requireNonBlank(name, "Variable name cannot be blank"), WhenMany.fromCode(whenMany))); return this; } From f0bf7cefc004855ee9b08eda00a236f7a78187ec Mon Sep 17 00:00:00 2001 From: John Grimes Date: Thu, 18 May 2023 13:12:04 +1000 Subject: [PATCH 03/95] Get test #3 working --- .../java/au/csiro/pathling/QueryExecutor.java | 8 +- .../fhirpath/parser/ParserContext.java | 16 ++++ .../pathling/views/FhirViewExecutor.java | 78 +++++++++++++------ .../au/csiro/pathling/views/FhirViewTest.java | 66 +++++++++++++++- 4 files changed, 138 insertions(+), 30 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index dc2faedc3e..c7aa4e2c4d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -101,11 +101,9 @@ protected List parseExpressions( @Nonnull final String display, final boolean checkMaterializable) { return expressions.stream() .map(expression -> { - final ParserContext currentContext = new ParserContext(parserContext.getInputContext(), - parserContext.getFhirContext(), parserContext.getSparkSession(), - parserContext.getDataSource(), parserContext.getTerminologyServiceFactory(), - parserContext.getGroupingColumns(), new HashMap<>(), - parserContext.getUnnestBehaviour(), parserContext.getVariables()); + // Create a new copy of the parser context from the previous context, except for node IDs + // which need to be reset each time. + final ParserContext currentContext = parserContext.unsetNodeIds(); final Parser parser = new Parser(currentContext); final FhirPath result = parser.parse(expression); if (checkMaterializable) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index aa56d95a47..2d496589b0 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -165,9 +165,25 @@ public void setThisContext(@Nonnull final FhirPath thisContext) { this.thisContext = Optional.of(thisContext); } + /** + * Creates a copy of the current parser context, but with the specified unnest behaviour. + * + * @param unnestBehaviour the {@link UnnestBehaviour} to use + * @return a new #{link ParserContext} + */ public ParserContext withUnnestBehaviour(@Nonnull final UnnestBehaviour unnestBehaviour) { return new ParserContext(inputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, groupingColumns, nodeIdColumns, unnestBehaviour, variables); } + /** + * Creates a copy of the current parser context, but with the node IDs emptied out. + * + * @return a new {@link ParserContext} + */ + public ParserContext unsetNodeIds() { + return new ParserContext(inputContext, fhirContext, sparkSession, dataSource, + terminologyServiceFactory, groupingColumns, new HashMap<>(), unnestBehaviour, variables); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index b97e3f2ed5..cc609f96df 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -1,6 +1,6 @@ package au.csiro.pathling.views; -import static java.util.stream.Collectors.joining; +import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toUnmodifiableList; @@ -20,7 +20,6 @@ import ca.uhn.fhir.context.FhirContext; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -49,23 +48,34 @@ public Dataset buildQuery(@Nonnull final FhirView view) { final ResourcePath inputContext = ResourcePath .build(getFhirContext(), getDataSource(), view.getResource(), view.getResource().toCode(), true); + final ParserContext parserContext = buildParserContext(inputContext, + singletonList(inputContext.getIdColumn())); // Parse the variable expressions. - ParserContext variableContext = buildParserContext(inputContext, - Collections.singletonList(inputContext.getIdColumn())); for (final VariableExpression variable : view.getVariables()) { final UnnestBehaviour unnestBehaviour = variable.getWhenMany() == WhenMany.UNNEST ? UnnestBehaviour.UNNEST : UnnestBehaviour.NOOP; - variableContext = variableContext.withUnnestBehaviour(unnestBehaviour); + + // Create a copy of the parser context that uses the specified unnest behaviour. + final ParserContext variableContext = parserContext + .withUnnestBehaviour(unnestBehaviour) + .unsetNodeIds(); + final Parser parser = new Parser(variableContext); - final FhirPath result = parser.parse(variable.getExpression()); - variableContext.getVariables().put(variable.getName(), new FhirPathAndContext(result, + final FhirPath result = parser.parse(variable.getExpression()) + .withExpression("%" + variable.getName()); + + // Add the variable path and its context to the parser context. This ensures that previously + // declared variables can be referenced in later ones. + parserContext.getVariables().put(variable.getName(), new FhirPathAndContext(result, variableContext)); } // Parse the column expressions. - final ParserContext columnContext = variableContext.withUnnestBehaviour(UnnestBehaviour.NOOP); + final ParserContext columnContext = buildParserContext(inputContext, + singletonList(inputContext.getIdColumn())).withUnnestBehaviour(UnnestBehaviour.NOOP); + columnContext.getVariables().putAll(parserContext.getVariables()); final List columnExpressions = view.getColumns().stream() .map(NamedExpression::getExpression) .collect(toList()); @@ -96,42 +106,62 @@ public Dataset buildQuery(@Nonnull final FhirView view) { private Dataset joinAllColumns( @Nonnull final Collection columnsAndContexts) { if (columnsAndContexts.isEmpty()) { + // If there are no columns, throw an error. throw new IllegalArgumentException("No columns to join"); } else if (columnsAndContexts.size() == 1) { + // If there is only one column, skip joining and return its dataset. final FhirPathAndContext fhirPathAndContext = columnsAndContexts.iterator().next(); return fhirPathAndContext.getFhirPath().getDataset(); } + // Sort the columns by the nodes encountered while parsing. This ensures that we join them + // together in order from the general to the specific. final List sorted = columnsAndContexts.stream() .sorted((a, b) -> { - final String sortStringA = a.getContext().getNodeIdColumns().values().stream() - .map(Column::toString) - .collect(joining("")); - final String sortStringB = b.getContext().getNodeIdColumns().values().stream() - .map(Column::toString) - .collect(joining("")); - return sortStringA.compareTo(sortStringB) * -1; + final List nodesA = a.getContext().getNodeIdColumns().keySet().stream() + .sorted() + .collect(toList()); + final List nodesB = b.getContext().getNodeIdColumns().keySet().stream() + .sorted() + .collect(toList()); + final String sortStringA = String.join("|", nodesA); + final String sortStringB = String.join("|", nodesB); + return sortStringA.compareTo(sortStringB); }) .collect(toList()); + // Start with the first column and its unjoined dataset. FhirPathAndContext left = sorted.get(0); - Dataset result = sorted.get(0).getFhirPath().getDataset(); + Dataset result = left.getFhirPath().getDataset(); - for (final FhirPathAndContext right : sorted) { + // Move through the list of columns, joining each one to the result of the previous join. + for (final FhirPathAndContext right : sorted.subList(1, sorted.size())) { final Set leftJoinColumns = new HashSet<>(); final Set rightJoinColumns = new HashSet<>(); + + // The join column always includes the resource ID. leftJoinColumns.add(left.getFhirPath().getIdColumn()); rightJoinColumns.add(right.getFhirPath().getIdColumn()); - final Set commonNodeIds = new HashSet<>(left.getContext().getNodeIdColumns() - .values()); - commonNodeIds.retainAll(right.getContext().getNodeIdColumns().values()); - leftJoinColumns.addAll(commonNodeIds); - rightJoinColumns.addAll(commonNodeIds); - + // Add the intersection of the nodes present in both the left and right column contexts. + final Set commonNodes = left.getContext().getNodeIdColumns().keySet(); + commonNodes.retainAll(right.getContext().getNodeIdColumns().keySet()); + final FhirPathAndContext finalLeft = left; + leftJoinColumns.addAll(commonNodes.stream() + .map(key -> finalLeft.getContext().getNodeIdColumns().get(key)) + .collect(toList())); + rightJoinColumns.addAll(commonNodes.stream() + .map(key -> right.getContext().getNodeIdColumns().get(key)) + .collect(toList())); + + // Use a left outer join, so that we don't lose rows that don't have a value for the right + // column. result = QueryHelpers.join(result, new ArrayList<>(leftJoinColumns), - right.getFhirPath().getDataset(), new ArrayList<>(rightJoinColumns), JoinType.LEFT_OUTER); + right.getFhirPath().getDataset(), new ArrayList<>(rightJoinColumns), + JoinType.LEFT_OUTER); + + // The result of the join becomes the left side of the next join. left = right; } diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java index d532cf8d83..c60fc97a0e 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -91,7 +91,7 @@ void test1() { } // Test 2: - // Select ID, name prefix and family name for each patient. + // Select ID, name use and family name for each patient. // { // "resource": "Patient", // "vars": [ @@ -144,4 +144,68 @@ void test2() { RowFactory.create("2", "official", "Towne") ); } + + // Test 3: + // Select ID, family name and given name for each patient. + // { + // "resource": "Patient", + // "vars": [ + // { + // "name": "name", + // "expr": "name", + // "whenMany": "unnest" + // }, + // { + // "name": "givenName", + // "expr": "name.given", + // "whenMany": "unnest" + // } + // ], + // "columns": [ + // { + // "name": "id", + // "expr": "id" + // }, + // { + // "name": "family_name", + // "expr": "%name.family" + // }, + // { + // "name": "given_name", + // "expr": "%givenName" + // } + // ] + // } + @Test + void test3() { + final FhirView view = new FhirView(ResourceType.PATIENT, + List.of( + new NamedExpression("id", "id"), + new NamedExpression("%name.family", "family_name"), + new NamedExpression("%givenName", "given_name") + ), + List.of( + new VariableExpression("name", "name", WhenMany.UNNEST), + new VariableExpression("name.given", "givenName", WhenMany.UNNEST) + ), + List.of()); + final Dataset result = executor.buildQuery(view); + + // Expected result: + // | id | family_name | given_name | + // |----|-------------|------------| + // | 1 | Wuckert | Karina | + // | 1 | Oberbrunner | Karina | + // | 2 | Towne | Guy | + // | 2 | Cleveland | Maponos | + // | 2 | Cleveland | Wilburg | + DatasetAssert.of(result).hasRowsUnordered( + RowFactory.create("1", "Wuckert", "Karina"), + RowFactory.create("1", "Oberbrunner", "Karina"), + RowFactory.create("2", "Towne", "Guy"), + RowFactory.create("2", "Cleveland", "Maponos"), + RowFactory.create("2", "Cleveland", "Wilburg") + ); + } + } From 74d0822dea20c35cbf60658c085157f871ba432d Mon Sep 17 00:00:00 2001 From: John Grimes Date: Thu, 18 May 2023 13:26:29 +1000 Subject: [PATCH 04/95] Add test #4 --- .../au/csiro/pathling/views/FhirViewTest.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java index c60fc97a0e..81b077d7b4 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -208,4 +208,88 @@ void test3() { ); } + // Test 4: + // Select ID, name prefix, family name, marital status system and marital status code for each + // patient. + // { + // "resource": "Patient", + // "vars": [ + // { + // "name": "name", + // "expr": "name", + // "whenMany": "unnest" + // }, + // { + // "name": "namePrefix", + // "expr": "name.prefix", + // "whenMany": "unnest" + // }, + // { + // "name": "maritalStatus", + // "expr": "maritalStatus.coding", + // "whenMany": "unnest" + // } + // ], + // "columns": [ + // { + // "name": "id", + // "expr": "id" + // }, + // { + // "name": "name_prefix", + // "expr": "%namePrefix" + // }, + // { + // "name": "family_name", + // "expr": "%name.family" + // }, + // { + // "name": "marital_status_system", + // "expr": "%maritalStatus.system" + // }, + // { + // "name": "marital_status_code", + // "expr": "%maritalStatus.code" + // } + // ] + // } + @Test + void test4() { + final FhirView view = new FhirView(ResourceType.PATIENT, + List.of( + new NamedExpression("id", "id"), + new NamedExpression("%namePrefix", "name_prefix"), + new NamedExpression("%name.family", "family_name"), + new NamedExpression("%maritalStatus.system", "marital_status_system"), + new NamedExpression("%maritalStatus.code", "marital_status_code") + ), + List.of( + new VariableExpression("name", "name", WhenMany.UNNEST), + new VariableExpression("name.prefix", "namePrefix", WhenMany.UNNEST), + new VariableExpression("maritalStatus.coding", "maritalStatus", WhenMany.UNNEST) + ), + List.of()); + final Dataset result = executor.buildQuery(view); + + // Expected result: + // | id | name_prefix | family_name | marital_status_system | marital_status_code | + // |----|-------------|-------------|--------------------------------------------------------|---------------------| + // | 1 | Miss. | Wuckert | http://terminology.hl7.org/CodeSystem/v3-MaritalStatus | M | + // | 1 | Miss. | Wuckert | http://snomed.info/sct | 87915002 | + // | 1 | Mrs. | Oberbrunner | http://terminology.hl7.org/CodeSystem/v3-MaritalStatus | M | + // | 1 | Mrs. | Oberbrunner | http://snomed.info/sct | 87915002 | + // | 2 | Mr. | Towne | NULL | NULL | + // | 2 | Prof. | Cleveland | NULL | NULL | + DatasetAssert.of(result).hasRowsUnordered( + RowFactory.create("1", "Miss.", "Wuckert", + "http://terminology.hl7.org/CodeSystem/v3-MaritalStatus", "M"), + RowFactory.create("1", "Miss.", "Wuckert", "http://snomed.info/sct", "87915002"), + RowFactory.create("1", "Mrs.", "Oberbrunner", + "http://terminology.hl7.org/CodeSystem/v3-MaritalStatus", "M"), + RowFactory.create("1", "Mrs.", "Oberbrunner", "http://snomed.info/sct", "87915002"), + RowFactory.create("2", "Mr.", "Towne", null, null), + RowFactory.create("2", "Prof.", "Cleveland", null, null) + ); + } + } From 14b022916bf0fd3480a6a825b6afd456d0f18fd8 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Thu, 18 May 2023 15:28:40 +1000 Subject: [PATCH 05/95] Ensure determinism in ordering of join columns --- .../au/csiro/pathling/views/FhirViewExecutor.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index cc609f96df..1b908ff413 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -20,10 +20,8 @@ import ca.uhn.fhir.context.FhirContext; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Optional; -import java.util.Set; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; @@ -137,15 +135,16 @@ private Dataset joinAllColumns( // Move through the list of columns, joining each one to the result of the previous join. for (final FhirPathAndContext right : sorted.subList(1, sorted.size())) { - final Set leftJoinColumns = new HashSet<>(); - final Set rightJoinColumns = new HashSet<>(); + final List leftJoinColumns = new ArrayList<>(); + final List rightJoinColumns = new ArrayList<>(); // The join column always includes the resource ID. leftJoinColumns.add(left.getFhirPath().getIdColumn()); rightJoinColumns.add(right.getFhirPath().getIdColumn()); // Add the intersection of the nodes present in both the left and right column contexts. - final Set commonNodes = left.getContext().getNodeIdColumns().keySet(); + final List commonNodes = new ArrayList<>( + left.getContext().getNodeIdColumns().keySet()); commonNodes.retainAll(right.getContext().getNodeIdColumns().keySet()); final FhirPathAndContext finalLeft = left; leftJoinColumns.addAll(commonNodes.stream() @@ -157,9 +156,8 @@ private Dataset joinAllColumns( // Use a left outer join, so that we don't lose rows that don't have a value for the right // column. - result = QueryHelpers.join(result, new ArrayList<>(leftJoinColumns), - right.getFhirPath().getDataset(), new ArrayList<>(rightJoinColumns), - JoinType.LEFT_OUTER); + result = QueryHelpers.join(result, leftJoinColumns, right.getFhirPath().getDataset(), + rightJoinColumns, JoinType.LEFT_OUTER); // The result of the join becomes the left side of the next join. left = right; From c0c059d138762764dffd8b7bcc3faa1558ea163d Mon Sep 17 00:00:00 2001 From: John Grimes Date: Thu, 18 May 2023 15:34:51 +1000 Subject: [PATCH 06/95] Add test purpose comments --- .../src/test/java/au/csiro/pathling/views/FhirViewTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java index 81b077d7b4..a60bf1d1da 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -52,6 +52,7 @@ void setUp() { // Test 1: // Select ID, gender and birth date for each patient. + // Tests the use of singular elements with no unnesting. // { // "resource": "Patient", // "columns": [ @@ -92,6 +93,7 @@ void test1() { // Test 2: // Select ID, name use and family name for each patient. + // Tests the unnesting of two elements that are singular relative to their common parent. // { // "resource": "Patient", // "vars": [ @@ -147,6 +149,7 @@ void test2() { // Test 3: // Select ID, family name and given name for each patient. + // Tests multiple levels of unnesting that share the same lineage. // { // "resource": "Patient", // "vars": [ @@ -211,6 +214,8 @@ void test3() { // Test 4: // Select ID, name prefix, family name, marital status system and marital status code for each // patient. + // Tests two sets of multi-level nesting that have the root resource as their nearest common + // ancestor. // { // "resource": "Patient", // "vars": [ From 5b33f5301bba8074152cdfe987490f05153b5706 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Fri, 19 May 2023 08:03:26 +1000 Subject: [PATCH 07/95] Add tests #5-7 --- .../operator/PathTraversalOperator.java | 3 + .../fhirpath/parser/UnnestBehaviour.java | 12 ++ .../pathling/views/FhirViewExecutor.java | 39 +++- .../au/csiro/pathling/views/WhenMany.java | 21 ++- .../au/csiro/pathling/views/FhirViewTest.java | 174 +++++++++++++++++- 5 files changed, 239 insertions(+), 10 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java index 0080514436..3e13966411 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java @@ -24,6 +24,7 @@ import au.csiro.pathling.QueryHelpers.DatasetWithColumnMap; import au.csiro.pathling.encoders.ExtensionSupport; +import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.ResourcePath; @@ -97,6 +98,8 @@ public ElementPath invoke(@Nonnull final PathTraversalInput input) { valueColumn = field; eidColumnCandidate = left.getEidColumn(); resultDataset = leftDataset; + } else if (unnestBehaviour == UnnestBehaviour.ERROR) { + throw new InvalidUserInputError("Encountered a repeating element: " + expression); } else if (unnestBehaviour == UnnestBehaviour.UNNEST) { final MutablePair valueAndEidColumns = new MutablePair<>(); final Dataset explodedDataset = left.explodeArray(leftDataset, field, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/UnnestBehaviour.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/UnnestBehaviour.java index 1fc9f36b90..76de796eae 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/UnnestBehaviour.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/UnnestBehaviour.java @@ -1,6 +1,18 @@ package au.csiro.pathling.fhirpath.parser; public enum UnnestBehaviour { + /** + * When a repeating element is encountered, it will be unnested. + */ UNNEST, + + /** + * When a repeating element is encountered, an error will be thrown. + */ + ERROR, + + /** + * When a repeating element is encountered, it will be left as an array. + */ NOOP } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index 1b908ff413..bda8175a55 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -3,6 +3,7 @@ import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toUnmodifiableList; +import static org.apache.spark.sql.functions.flatten; import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.QueryHelpers; @@ -11,6 +12,7 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirPathAndContext; import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.parser.Parser; import au.csiro.pathling.fhirpath.parser.ParserContext; @@ -20,7 +22,9 @@ import ca.uhn.fhir.context.FhirContext; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -28,8 +32,21 @@ import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; +/** + * Executes a FHIR view query. + * + * @author John Grimes + */ public class FhirViewExecutor extends QueryExecutor { + private static Map WHEN_MANY_UNNEST_MAP = new HashMap<>(); + + static { + WHEN_MANY_UNNEST_MAP.put(WhenMany.UNNEST, UnnestBehaviour.UNNEST); + WHEN_MANY_UNNEST_MAP.put(WhenMany.ERROR, UnnestBehaviour.ERROR); + WHEN_MANY_UNNEST_MAP.put(WhenMany.ARRAY, UnnestBehaviour.NOOP); + } + public FhirViewExecutor( @Nonnull final QueryConfiguration configuration, @@ -51,9 +68,7 @@ public Dataset buildQuery(@Nonnull final FhirView view) { // Parse the variable expressions. for (final VariableExpression variable : view.getVariables()) { - final UnnestBehaviour unnestBehaviour = variable.getWhenMany() == WhenMany.UNNEST - ? UnnestBehaviour.UNNEST - : UnnestBehaviour.NOOP; + final UnnestBehaviour unnestBehaviour = WHEN_MANY_UNNEST_MAP.get(variable.getWhenMany()); // Create a copy of the parser context that uses the specified unnest behaviour. final ParserContext variableContext = parserContext @@ -61,13 +76,25 @@ public Dataset buildQuery(@Nonnull final FhirView view) { .unsetNodeIds(); final Parser parser = new Parser(variableContext); - final FhirPath result = parser.parse(variable.getExpression()) + FhirPath result = parser.parse(variable.getExpression()) .withExpression("%" + variable.getName()); + // If the variable has a `whenMany` value of `array`, we need to flatten the result to remove + // accumulated array nesting from the underlying structure. + if (variable.getWhenMany() == WhenMany.ARRAY && result instanceof NonLiteralPath + && result instanceof Materializable) { + final NonLiteralPath nonLiteralResult = (NonLiteralPath) result; + final Column flattenedValue = flatten(nonLiteralResult.getValueColumn()); + result = nonLiteralResult.copy(nonLiteralResult.getExpression(), + nonLiteralResult.getDataset(), nonLiteralResult.getIdColumn(), + nonLiteralResult.getEidColumn(), flattenedValue, nonLiteralResult.isSingular(), + nonLiteralResult.getThisColumn()); + } + // Add the variable path and its context to the parser context. This ensures that previously // declared variables can be referenced in later ones. - parserContext.getVariables().put(variable.getName(), new FhirPathAndContext(result, - variableContext)); + parserContext.getVariables().put(variable.getName(), + new FhirPathAndContext(result, variableContext)); } // Parse the column expressions. diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/WhenMany.java b/fhirpath/src/main/java/au/csiro/pathling/views/WhenMany.java index c0ae03743a..490ed662e8 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/WhenMany.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/WhenMany.java @@ -6,10 +6,21 @@ public enum WhenMany { + /** + * Indicates that a variable should not have repeated values. + */ ERROR("error"), + + /** + * Indicates that a variable should create an array for repeated values. + */ ARRAY("array"), - UNNEST("unnest"), - CROSS("cross"); + + /** + * Indicates that repeated values should be unnested into separate rows. Does not allow for + * multiple levels of nesting that have the root resource as their nearest common ancestor. + */ + UNNEST("unnest"); @Nonnull @Getter @@ -19,6 +30,12 @@ public enum WhenMany { this.code = code; } + /** + * Returns the {@link WhenMany} value corresponding to the given code. + * + * @param code the code to look up + * @return the corresponding {@link WhenMany} value + */ public static WhenMany fromCode(@Nullable final String code) { for (final WhenMany whenMany : values()) { if (whenMany.code.equals(code)) { diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java index a60bf1d1da..a9f3300f9b 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; +import scala.collection.mutable.WrappedArray; @SpringBootUnitTest class FhirViewTest { @@ -93,7 +94,8 @@ void test1() { // Test 2: // Select ID, name use and family name for each patient. - // Tests the unnesting of two elements that are singular relative to their common parent. + // Tests the unnesting of two elements that are singular relative to their common repeating + // parent. // { // "resource": "Patient", // "vars": [ @@ -270,7 +272,8 @@ void test4() { ), List.of( new VariableExpression("name", "name", WhenMany.UNNEST), - new VariableExpression("name.prefix", "namePrefix", WhenMany.UNNEST), + new VariableExpression("name.prefix", "namePrefix", + WhenMany.UNNEST), new VariableExpression("maritalStatus.coding", "maritalStatus", WhenMany.UNNEST) ), List.of()); @@ -297,4 +300,171 @@ void test4() { ); } + // Test case 5: + // Select ID and given name for each patient, but leave the given names in an array. + // Tests the ability to return columns as arrays, where the expressions that define those columns + // return a collection. + // { + // "resource": "Patient", + // "vars": [ + // { + // "name": "givenName", + // "expr": "%name.given", + // "whenMany": "array" + // } + // ], + // "columns": [ + // { + // "name": "id", + // "expr": "id" + // }, + // { + // "name": "given_name", + // "expr": "%givenName" + // } + // ] + // } + @Test + void test5() { + final FhirView view = new FhirView(ResourceType.PATIENT, + List.of( + new NamedExpression("id", "id"), + new NamedExpression("%givenName", "given_name") + ), + List.of( + new VariableExpression("name.given", "givenName", WhenMany.ARRAY) + ), + List.of()); + final Dataset result = executor.buildQuery(view); + + // Expected result: + // | id | given_name | + // |----|-------------------------| + // | 1 | [Karina, Karina] | + // | 2 | [Guy, Maponos, Wilburg] | + DatasetAssert.of(result).hasRowsUnordered( + RowFactory.create("1", WrappedArray.make(List.of("Karina", "Karina").toArray())), + RowFactory.create("2", WrappedArray.make(List.of("Guy", "Maponos", "Wilburg").toArray())) + ); + } + + // Test case 6: + // Select ID and given name for each patient, with a row for each name and the given names for + // each name within an array. + // Tests the ability to use the unnest and array directives together. + // { + // "resource": "Patient", + // "vars": [ + // { + // "name": "name", + // "expr": "name", + // "whenMany": "unnest" + // }, + // { + // "name": "givenName", + // "expr": "%name.given", + // "whenMany": "array" + // } + // ], + // "columns": [ + // { + // "name": "id", + // "expr": "id" + // }, + // { + // "name": "given_name", + // "expr": "%givenName" + // } + // ] + // } + @Test + void test6() { + final FhirView view = new FhirView(ResourceType.PATIENT, + List.of( + new NamedExpression("id", "id"), + new NamedExpression("%name.given", "given_name") + ), + List.of( + new VariableExpression("name", "name", WhenMany.UNNEST), + new VariableExpression("name.given", "givenName", WhenMany.ARRAY) + ), + List.of()); + final Dataset result = executor.buildQuery(view); + + // Expected result: + // | id | given_name | + // |----|--------------------| + // | 1 | [Karina] | + // | 1 | [Karina] | + // | 2 | [Guy] | + // | 2 | [Maponos, Wilburg] | + DatasetAssert.of(result).hasRowsUnordered( + RowFactory.create("1", WrappedArray.make(List.of("Karina").toArray())), + RowFactory.create("1", WrappedArray.make(List.of("Karina").toArray())), + RowFactory.create("2", WrappedArray.make(List.of("Guy").toArray())), + RowFactory.create("2", WrappedArray.make(List.of("Maponos", "Wilburg").toArray())) + ); + } + + // Test case 7: + // Select ID and given name for each patient, with the given names for each name represented + // within a nested array. + // Tests the ability to use the multiple array directives together. + // { + // "resource": "Patient", + // "vars": [ + // { + // + // "name": "name", + // "expr": "name", + // "whenMany": "unnest" + // }, + // { + // "name": "givenName", + // "expr": "%name.given", + // "whenMany": "array" + // } + // ], + // "columns": [ + // { + // "name": "id", + // "expr": "id" + // }, + // { + // "name": "given_name", + // "expr": "%givenName" + // } + // ] + // } + @Test + void test7() { + final FhirView view = new FhirView(ResourceType.PATIENT, + List.of( + new NamedExpression("id", "id"), + new NamedExpression("%name.given", "given_name") + ), + List.of( + new VariableExpression("name", "name", WhenMany.ARRAY), + new VariableExpression("name.given", "givenName", WhenMany.ARRAY) + ), + List.of()); + final Dataset result = executor.buildQuery(view); + + // Expected result: + // | id | given_name | + // |----|-----------------------------| + // | 1 | [[Karina], [Karina]] | + // | 2 | [[Guy], [Maponos, Wilburg]] | + DatasetAssert.of(result).hasRowsUnordered( + RowFactory.create("1", WrappedArray.make(List.of( + WrappedArray.make(List.of("Karina").toArray()), + WrappedArray.make(List.of("Karina").toArray()) + ).toArray())), + RowFactory.create("2", WrappedArray.make(List.of( + WrappedArray.make(List.of("Guy").toArray()), + WrappedArray.make(List.of("Maponos", "Wilburg").toArray()) + ).toArray())) + ); + } + } From fd25b92d52b302fdd429fcb87796ce1c3aa1de40 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Fri, 19 May 2023 08:24:43 +1000 Subject: [PATCH 08/95] Add test 8 --- .../au/csiro/pathling/views/FhirViewTest.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java index a9f3300f9b..9f6a0bbc41 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -1,7 +1,10 @@ package au.csiro.pathling.views; +import static org.junit.jupiter.api.Assertions.assertThrows; + import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; +import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.io.Database; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -467,4 +470,46 @@ void test7() { ); } + // Test case 8: + // Select ID and family name for each patient, but force an error through the use of the `error` + // value for `whenMany`. + // This tests the behaviour of the `error` value for `whenMany`. + // { + // "resource": "Patient", + // "vars": [ + // { + // + // "name": "name", + // "expr": "name", + // "whenMany": "error" + // } + // ], + // "columns": [ + // { + // "name": "id", + // "expr": "id" + // }, + // { + // "name": "family_name", + // "expr": "%name.family" + // } + // ] + // } + @Test + void test8() { + final FhirView view = new FhirView(ResourceType.PATIENT, + List.of( + new NamedExpression("id", "id"), + new NamedExpression("%name.family", "family_name") + ), + List.of( + new VariableExpression("name", "name", WhenMany.ERROR) + ), + List.of()); + + assertThrows(InvalidUserInputError.class, () -> { + executor.buildQuery(view); + }); + } + } From 9c3cd021752609fbece07bd07277e1649fe2f52b Mon Sep 17 00:00:00 2001 From: John Grimes Date: Wed, 24 May 2023 16:49:38 +1000 Subject: [PATCH 09/95] Update version to 6.3.0-SNAPSHOT --- encoders/pom.xml | 2 +- fhir-server/pom.xml | 2 +- fhirpath/pom.xml | 2 +- lib/import/pom.xml | 2 +- lib/js/pom.xml | 2 +- lib/python/Dockerfile | 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 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/encoders/pom.xml b/encoders/pom.xml index dee07695ae..65c15620ac 100644 --- a/encoders/pom.xml +++ b/encoders/pom.xml @@ -30,7 +30,7 @@ au.csiro.pathling pathling - 6.2.1 + 6.3.0-SNAPSHOT encoders jar diff --git a/fhir-server/pom.xml b/fhir-server/pom.xml index d32b4063e6..e39896f381 100644 --- a/fhir-server/pom.xml +++ b/fhir-server/pom.xml @@ -24,7 +24,7 @@ au.csiro.pathling pathling - 6.2.1 + 6.3.0-SNAPSHOT fhir-server jar diff --git a/fhirpath/pom.xml b/fhirpath/pom.xml index b6d2a50147..409c2a837f 100644 --- a/fhirpath/pom.xml +++ b/fhirpath/pom.xml @@ -24,7 +24,7 @@ au.csiro.pathling pathling - 6.2.1 + 6.3.0-SNAPSHOT fhirpath jar diff --git a/lib/import/pom.xml b/lib/import/pom.xml index 827ac62472..c026f93278 100644 --- a/lib/import/pom.xml +++ b/lib/import/pom.xml @@ -24,7 +24,7 @@ au.csiro.pathling pathling - 6.2.1 + 6.3.0-SNAPSHOT ../../pom.xml import diff --git a/lib/js/pom.xml b/lib/js/pom.xml index 3fb6db1274..a425c677f6 100644 --- a/lib/js/pom.xml +++ b/lib/js/pom.xml @@ -24,7 +24,7 @@ au.csiro.pathling pathling - 6.2.1 + 6.3.0-SNAPSHOT ../../pom.xml js diff --git a/lib/python/Dockerfile b/lib/python/Dockerfile index a1004592d4..4d1a23f173 100644 --- a/lib/python/Dockerfile +++ b/lib/python/Dockerfile @@ -1,7 +1,7 @@ FROM jupyter/all-spark-notebook USER root -RUN echo "spark.jars.packages au.csiro.pathling:library-api:6.2.1" >> /usr/local/spark/conf/spark-defaults.conf +RUN echo "spark.jars.packages au.csiro.pathling:library-api:6.3.0-SNAPSHOT" >> /usr/local/spark/conf/spark-defaults.conf USER ${NB_UID} diff --git a/lib/python/pom.xml b/lib/python/pom.xml index 3e326144b4..ce3a2b45ed 100644 --- a/lib/python/pom.xml +++ b/lib/python/pom.xml @@ -24,7 +24,7 @@ au.csiro.pathling pathling - 6.2.1 + 6.3.0-SNAPSHOT ../../pom.xml python diff --git a/library-api/pom.xml b/library-api/pom.xml index e68cb7a81c..421f8db050 100644 --- a/library-api/pom.xml +++ b/library-api/pom.xml @@ -25,7 +25,7 @@ pathling au.csiro.pathling - 6.2.1 + 6.3.0-SNAPSHOT library-api jar diff --git a/pom.xml b/pom.xml index b264ac2052..0fbbbab6b9 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ au.csiro.pathling pathling - 6.2.1 + 6.3.0-SNAPSHOT pom Pathling diff --git a/site/pom.xml b/site/pom.xml index 9e9150d93d..2e39b86177 100644 --- a/site/pom.xml +++ b/site/pom.xml @@ -13,7 +13,7 @@ au.csiro.pathling pathling - 6.2.1 + 6.3.0-SNAPSHOT ../pom.xml site diff --git a/terminology/pom.xml b/terminology/pom.xml index 35b18ee8f5..14fa5aaf62 100644 --- a/terminology/pom.xml +++ b/terminology/pom.xml @@ -25,7 +25,7 @@ pathling au.csiro.pathling - 6.2.1 + 6.3.0-SNAPSHOT terminology jar diff --git a/utilities/pom.xml b/utilities/pom.xml index 1fe1b3b9f5..c2d201c39c 100644 --- a/utilities/pom.xml +++ b/utilities/pom.xml @@ -8,7 +8,7 @@ pathling au.csiro.pathling - 6.2.1 + 6.3.0-SNAPSHOT utilities jar From 37c01fd196c87cb11375d5a641a06b3b69ee9c7f Mon Sep 17 00:00:00 2001 From: John Grimes Date: Wed, 24 May 2023 17:35:01 +1000 Subject: [PATCH 10/95] Replace extract column joining logic with view executor method --- .../extract/ExtractQueryExecutor.java | 130 +++++++++--------- 1 file changed, 63 insertions(+), 67 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java index a9e69b8663..ba256396b4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java @@ -2,15 +2,15 @@ import static au.csiro.pathling.QueryHelpers.join; import static au.csiro.pathling.query.ExpressionWithLabel.labelsAsStream; -import static au.csiro.pathling.utilities.Preconditions.check; import static au.csiro.pathling.utilities.Preconditions.checkArgument; +import static java.util.stream.Collectors.toList; import au.csiro.pathling.QueryExecutor; +import au.csiro.pathling.QueryHelpers; import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirPathAndContext; -import au.csiro.pathling.fhirpath.FhirPathContextAndResult; import au.csiro.pathling.fhirpath.Materializable; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.parser.ParserContext; @@ -21,12 +21,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; @@ -68,8 +64,7 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query) { .collect(Collectors.toUnmodifiableList()); // Join all the column expressions together. - final FhirPathContextAndResult columnJoinResult = joinColumns(columnParseResult); - final Dataset columnJoinResultDataset = columnJoinResult.getResult(); + final Dataset columnJoinResultDataset = joinAllColumns(columnParseResult); final Dataset trimmedDataset = trimTrailingNulls(inputContext.getIdColumn(), columnPaths, columnJoinResultDataset); @@ -94,67 +89,68 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query) { } @Nonnull - private FhirPathContextAndResult joinColumns( + private Dataset joinAllColumns( @Nonnull final Collection columnsAndContexts) { - // Sort the columns in descending order of expression length. - final List sortedColumnsAndContexts = columnsAndContexts.stream() - .sorted(Comparator.comparingInt(p -> - p.getFhirPath().getExpression().length()).reversed()) - .collect(Collectors.toList()); - - FhirPathContextAndResult result = null; - check(sortedColumnsAndContexts.size() > 0); - for (final FhirPathAndContext current : sortedColumnsAndContexts) { - if (result != null) { - // Get the set of unique prefixes from the two parser contexts, and sort them in descending - // order of prefix length. - final Set prefixes = new HashSet<>(); - final Map resultNodeIds = result.getContext().getNodeIdColumns(); - final Map currentNodeIds = current.getContext().getNodeIdColumns(); - prefixes.addAll(resultNodeIds.keySet()); - prefixes.addAll(currentNodeIds.keySet()); - final List sortedCommonPrefixes = new ArrayList<>(prefixes).stream() - .sorted(Comparator.comparingInt(String::length).reversed()) - .collect(Collectors.toList()); - final FhirPathContextAndResult finalResult = result; - - // Find the longest prefix that is common to the two expressions. - final Optional commonPrefix = sortedCommonPrefixes.stream() - .filter(p -> finalResult.getFhirPath().getExpression().startsWith(p) && - current.getFhirPath().getExpression().startsWith(p)) - .findFirst(); - - if (commonPrefix.isPresent() && - resultNodeIds.containsKey(commonPrefix.get()) && - currentNodeIds.containsKey(commonPrefix.get())) { - // If there is a common prefix, we add the corresponding node identifier column to the - // join condition. - final Column previousNodeId = resultNodeIds - .get(commonPrefix.get()); - final List previousJoinColumns = Arrays.asList(result.getFhirPath().getIdColumn(), - previousNodeId); - final Column currentNodeId = currentNodeIds - .get(commonPrefix.get()); - final List currentJoinColumns = Arrays.asList(current.getFhirPath().getIdColumn(), - currentNodeId); - final Dataset dataset = join(result.getResult(), previousJoinColumns, - current.getFhirPath().getDataset(), - currentJoinColumns, JoinType.LEFT_OUTER); - result = new FhirPathContextAndResult(current.getFhirPath(), current.getContext(), - dataset); - } else { - // If there is no common prefix, we join using only the resource ID. - final Dataset dataset = join(result.getResult(), result.getFhirPath().getIdColumn(), - current.getFhirPath().getDataset(), current.getFhirPath().getIdColumn(), - JoinType.LEFT_OUTER); - result = new FhirPathContextAndResult(current.getFhirPath(), current.getContext(), - dataset); - } - } else { - result = new FhirPathContextAndResult(current.getFhirPath(), current.getContext(), - current.getFhirPath().getDataset()); - } + if (columnsAndContexts.isEmpty()) { + // If there are no columns, throw an error. + throw new IllegalArgumentException("No columns to join"); + + } else if (columnsAndContexts.size() == 1) { + // If there is only one column, skip joining and return its dataset. + final FhirPathAndContext fhirPathAndContext = columnsAndContexts.iterator().next(); + return fhirPathAndContext.getFhirPath().getDataset(); } + + // Sort the columns by the nodes encountered while parsing. This ensures that we join them + // together in order from the general to the specific. + final List sorted = columnsAndContexts.stream() + .sorted((a, b) -> { + final List nodesA = a.getContext().getNodeIdColumns().keySet().stream() + .sorted() + .collect(toList()); + final List nodesB = b.getContext().getNodeIdColumns().keySet().stream() + .sorted() + .collect(toList()); + final String sortStringA = String.join("|", nodesA); + final String sortStringB = String.join("|", nodesB); + return sortStringA.compareTo(sortStringB); + }) + .collect(toList()); + + // Start with the first column and its unjoined dataset. + FhirPathAndContext left = sorted.get(0); + Dataset result = left.getFhirPath().getDataset(); + + // Move through the list of columns, joining each one to the result of the previous join. + for (final FhirPathAndContext right : sorted.subList(1, sorted.size())) { + final List leftJoinColumns = new ArrayList<>(); + final List rightJoinColumns = new ArrayList<>(); + + // The join column always includes the resource ID. + leftJoinColumns.add(left.getFhirPath().getIdColumn()); + rightJoinColumns.add(right.getFhirPath().getIdColumn()); + + // Add the intersection of the nodes present in both the left and right column contexts. + final List commonNodes = new ArrayList<>( + left.getContext().getNodeIdColumns().keySet()); + commonNodes.retainAll(right.getContext().getNodeIdColumns().keySet()); + final FhirPathAndContext finalLeft = left; + leftJoinColumns.addAll(commonNodes.stream() + .map(key -> finalLeft.getContext().getNodeIdColumns().get(key)) + .collect(toList())); + rightJoinColumns.addAll(commonNodes.stream() + .map(key -> right.getContext().getNodeIdColumns().get(key)) + .collect(toList())); + + // Use a left outer join, so that we don't lose rows that don't have a value for the right + // column. + result = QueryHelpers.join(result, leftJoinColumns, right.getFhirPath().getDataset(), + rightJoinColumns, JoinType.LEFT_OUTER); + + // The result of the join becomes the left side of the next join. + left = right; + } + return result; } From b4f2db076728244290adccf14a55cffc50856485 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Wed, 24 May 2023 18:03:12 +1000 Subject: [PATCH 11/95] Add ExtractQueryTest#multipleIndependentUnnestings --- .../pathling/extract/ExtractQueryTest.java | 18 ++++++++++++++++++ .../multipleIndependentUnnestings.csv | 12 ++++++++++++ .../resources/test-data/fhir/Patient.ndjson | 2 +- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv diff --git a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java index af2e70b8a2..9c91dce123 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java @@ -315,6 +315,24 @@ void multipleNonSingularColumnsWithDifferentTypes() { "responses/ExtractQueryTest/multipleNonSingularColumnsWithDifferentTypes.csv"); } + @Test + void multipleIndependentUnnestings() { + subjectResource = ResourceType.PATIENT; + mockResource(subjectResource); + + final ExtractRequest request = new ExtractRequestBuilder(subjectResource) + .withColumn("id") + .withColumn("name.given") + .withColumn("name.family") + .withColumn("maritalStatus.coding.system") + .withColumn("maritalStatus.coding.code") + .build(); + + final Dataset result = executor.buildQuery(request); + assertThat(result) + .hasRows(spark, "responses/ExtractQueryTest/multipleIndependentUnnestings.csv"); + } + @Test void emptyColumn() { subjectResource = ResourceType.PATIENT; diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv new file mode 100644 index 0000000000..bcb613d176 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv @@ -0,0 +1,12 @@ +121503c8-9564-4b48-9086-a22df717948e,Ophelia894,Gleichner915,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S +2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316,Ulibarri312,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S +7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901,Heller342,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882,Krajcik437,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M +9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Oberbrunner298,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M +9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Wuckert783,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M +9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Oberbrunner298,http://snomed.info/sct,87915002 +9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Wuckert783,http://snomed.info/sct,87915002 +a7eb2ce7-1075-426c-addd-957b861b0e55,Shirley182,Botsford977,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Gilberto712,MacGyver246,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S +beff242e-580b-47c0-9844-c1a68c36c5bf,Guy979,Towne435,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Su690,Ebert178,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S diff --git a/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson b/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson index b4786f24a4..ed940b44e4 100644 --- a/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson +++ b/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson @@ -5,5 +5,5 @@ {"resourceType":"Patient","id":"bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -8938394749892435555 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Amiee221 Dach178"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Everett","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":0.0},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":9.0}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"e993ce4e-4cb9-4e7c-b082-a9144e9efb03"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"e993ce4e-4cb9-4e7c-b082-a9144e9efb03"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-38-1164"}],"name":[{"use":"official","family":"MacGyver246","given":["Gilberto712"]}],"telecom":[{"system":"phone","value":"555-159-1897","use":"home"}],"gender":"male","birthDate":"1957-06-06","deceasedDateTime":"1967-06-08T08:27:40+00:00","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.441729264152904},{"url":"longitude","valueDecimal":-71.0046866999325}]}],"line":["1096 Kulas Rest"],"city":"Malden","state":"Massachusetts","postalCode":"02148","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"S","display":"Never Married"}],"text":"Never Married"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} {"resourceType":"Patient","id":"a7eb2ce7-1075-426c-addd-957b861b0e55","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -8922649844579538715 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Leo278 Dare640"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Medway","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":1.4546293031743411},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":59.54537069682566}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"3d67a22e-4e26-4d7a-a586-ea79114faa50"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"3d67a22e-4e26-4d7a-a586-ea79114faa50"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-36-4411"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99932632"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X69119058X"}],"name":[{"use":"official","family":"Botsford977","given":["Shirley182"],"prefix":["Mr."]}],"telecom":[{"system":"phone","value":"555-845-4560","use":"home"}],"gender":"male","birthDate":"1957-06-06","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.500088671800995},{"url":"longitude","valueDecimal":-71.0172687720252}]}],"line":["329 Prosacco Highlands Suite 8"],"city":"Malden","state":"Massachusetts","postalCode":"02148","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"S","display":"S"}],"text":"S"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} {"resourceType":"Patient","id":"121503c8-9564-4b48-9086-a22df717948e","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: 2253619407104150534 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Waneta858 Bailey598"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"F"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Ashland","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":6.4204402402865135},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":51.57955975971349}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"f34e77c9-df31-49c4-92e2-e871fa76026e"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"f34e77c9-df31-49c4-92e2-e871fa76026e"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-77-4115"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99962421"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X20365162X"}],"name":[{"use":"official","family":"Gleichner915","given":["Ophelia894"],"prefix":["Ms."]}],"telecom":[{"system":"phone","value":"555-201-6080","use":"home"}],"gender":"female","birthDate":"1959-09-27","deceasedDateTime":"2018-02-17T09:11:55+00:00","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.40926977447224},{"url":"longitude","valueDecimal":-70.96339944589374}]}],"line":["256 Rohan Frontage road Suite 89"],"city":"Boston","state":"Massachusetts","postalCode":"02108","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"S","display":"S"}],"text":"S"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} -{"resourceType":"Patient","id":"9360820c-8602-4335-8b50-c88d627a0c20","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: 7586416063432214179 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Rosann381 Dietrich576"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"F"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Saugus","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":3.476675883341075},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":47.52332411665893}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"eb4458b4-cfda-463e-ba15-eaad8d291d1d"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"eb4458b4-cfda-463e-ba15-eaad8d291d1d"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-23-9134"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99938323"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X48845477X"}],"name":[{"use":"official","family":"Oberbrunner298","given":["Karina848"],"prefix":["Mrs."]},{"use":"maiden","family":"Wuckert783","given":["Karina848"],"prefix":["Mrs."]}],"telecom":[{"system":"phone","value":"555-441-4475","use":"home"}],"gender":"female","birthDate":"1959-09-27","deceasedDateTime":"2011-09-17T09:11:55+00:00","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.34744381371551},{"url":"longitude","valueDecimal":-70.97583342874475}]}],"line":["796 Wiza Rue Unit 94"],"city":"Boston","state":"Massachusetts","postalCode":"02108","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} +{"resourceType":"Patient","id":"9360820c-8602-4335-8b50-c88d627a0c20","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: 7586416063432214179 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Rosann381 Dietrich576"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"F"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Saugus","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":3.476675883341075},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":47.52332411665893}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"eb4458b4-cfda-463e-ba15-eaad8d291d1d"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"eb4458b4-cfda-463e-ba15-eaad8d291d1d"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-23-9134"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99938323"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X48845477X"}],"name":[{"use":"official","family":"Oberbrunner298","given":["Karina848"],"prefix":["Mrs."]},{"use":"maiden","family":"Wuckert783","given":["Karina848"],"prefix":["Mrs."]}],"telecom":[{"system":"phone","value":"555-441-4475","use":"home"}],"gender":"female","birthDate":"1959-09-27","deceasedDateTime":"2011-09-17T09:11:55+00:00","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.34744381371551},{"url":"longitude","valueDecimal":-70.97583342874475}]}],"line":["796 Wiza Rue Unit 94"],"city":"Boston","state":"Massachusetts","postalCode":"02108","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"},{"system":"http://snomed.info/sct","code":"87915002","display":"Married"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} {"resourceType":"Patient","id":"7001ad9c-34d2-4eb5-8165-5fdc2147f469","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -9085673012511978392 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Claudia969 Schaden604"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"F"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Kingston","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":0.44204305296442803},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":58.55795694703557}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"11fd8dbd-dd20-479f-b82b-558fed60bae7"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"11fd8dbd-dd20-479f-b82b-558fed60bae7"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-14-8633"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99975586"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X19252951X"}],"name":[{"use":"official","family":"Heller342","given":["Cherryl901"],"prefix":["Ms."]}],"telecom":[{"system":"phone","value":"555-330-3298","use":"home"}],"gender":"female","birthDate":"1959-09-27","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.364178041557025},{"url":"longitude","valueDecimal":-70.92644299024927}]}],"line":["128 Langworth Hollow Unit 50"],"city":"Boston","state":"Massachusetts","postalCode":"02108","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"S","display":"S"}],"text":"S"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} From b321ead859c5e5753fb33496ffaa825d1ddb3452 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Thu, 25 May 2023 14:37:03 +1000 Subject: [PATCH 12/95] Add ExtractQueryTest#toleranceOfColumnOrdering --- .../pathling/extract/ExtractQueryTest.java | 26 +++++++++++++++++++ .../toleranceOfColumnOrdering1.csv | 10 +++++++ .../toleranceOfColumnOrdering2.csv | 10 +++++++ 3 files changed, 46 insertions(+) create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv diff --git a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java index 9c91dce123..7d56835282 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java @@ -333,6 +333,32 @@ void multipleIndependentUnnestings() { .hasRows(spark, "responses/ExtractQueryTest/multipleIndependentUnnestings.csv"); } + @Test + void toleranceOfColumnOrdering() { + subjectResource = ResourceType.PATIENT; + mockResource(subjectResource); + + final ExtractRequest request = new ExtractRequestBuilder(subjectResource) + .withColumn("id") + .withColumn("name.family") + .withColumn("name.given") + .build(); + + final Dataset result = executor.buildQuery(request); + assertThat(result) + .hasRows(spark, "responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv"); + + final ExtractRequest request2 = new ExtractRequestBuilder(subjectResource) + .withColumn("name.given") + .withColumn("name.family") + .withColumn("id") + .build(); + + final Dataset result2 = executor.buildQuery(request2); + assertThat(result2) + .hasRows(spark, "responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv"); + } + @Test void emptyColumn() { subjectResource = ResourceType.PATIENT; diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv new file mode 100644 index 0000000000..4c014986b1 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv @@ -0,0 +1,10 @@ +121503c8-9564-4b48-9086-a22df717948e,Gleichner915,Ophelia894 +2b36c1e2-bbe1-45ae-8124-4adad2677702,Ulibarri312,Pedro316 +7001ad9c-34d2-4eb5-8165-5fdc2147f469,Heller342,Cherryl901 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437,Seymour882 +9360820c-8602-4335-8b50-c88d627a0c20,Oberbrunner298,Karina848 +9360820c-8602-4335-8b50-c88d627a0c20,Wuckert783,Karina848 +a7eb2ce7-1075-426c-addd-957b861b0e55,Botsford977,Shirley182 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,MacGyver246,Gilberto712 +beff242e-580b-47c0-9844-c1a68c36c5bf,Towne435,Guy979 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Ebert178,Su690 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv new file mode 100644 index 0000000000..d81ffd6b67 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv @@ -0,0 +1,10 @@ +Ophelia894,Gleichner915,121503c8-9564-4b48-9086-a22df717948e +Pedro316,Ulibarri312,2b36c1e2-bbe1-45ae-8124-4adad2677702 +Cherryl901,Heller342,7001ad9c-34d2-4eb5-8165-5fdc2147f469 +Seymour882,Krajcik437,8ee183e2-b3c0-4151-be94-b945d6aa8c6d +Karina848,Oberbrunner298,9360820c-8602-4335-8b50-c88d627a0c20 +Karina848,Wuckert783,9360820c-8602-4335-8b50-c88d627a0c20 +Shirley182,Botsford977,a7eb2ce7-1075-426c-addd-957b861b0e55 +Gilberto712,MacGyver246,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +Guy979,Towne435,beff242e-580b-47c0-9844-c1a68c36c5bf +Su690,Ebert178,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 From 414f4be7e6465036fa9220143c977e9caaa5afc9 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Mon, 29 May 2023 16:31:53 +1000 Subject: [PATCH 13/95] Enable structured extract results within library --- .../pathling/aggregate/AggregateExecutor.java | 10 ++--- .../pathling/extract/ExtractExecutor.java | 2 +- .../pathling/extract/ExtractQueryTest.java | 22 ++++++++++ .../extract/ExtractRequestBuilder.java | 14 +++++-- .../ExtractQueryTest/structuredResult.csv | 10 +++++ .../java/au/csiro/pathling/QueryExecutor.java | 10 +---- .../aggregate/AggregateQueryExecutor.java | 20 ++++++--- .../extract/ExtractQueryExecutor.java | 41 +++++++++++++++++-- .../pathling/extract/ExtractRequest.java | 6 +-- .../pathling/extract/ExtractResultType.java | 22 ++++++++++ .../csiro/pathling/fhirpath/AbstractPath.java | 11 +++++ .../{Materializable.java => FhirValue.java} | 22 +++------- .../java/au/csiro/pathling/fhirpath/Flat.java | 20 +++++++++ .../pathling/fhirpath/NonLiteralPath.java | 6 --- .../fhirpath/UntypedResourcePath.java | 2 +- .../fhirpath/element/BooleanPath.java | 6 +-- .../pathling/fhirpath/element/CodingPath.java | 14 ++----- .../pathling/fhirpath/element/DatePath.java | 6 +-- .../fhirpath/element/DateTimePath.java | 6 +-- .../fhirpath/element/DecimalPath.java | 6 +-- .../fhirpath/element/IntegerPath.java | 11 ++--- .../pathling/fhirpath/element/StringPath.java | 7 ++-- .../pathling/fhirpath/element/TimePath.java | 6 +-- .../fhirpath/literal/BooleanLiteralPath.java | 6 +-- .../fhirpath/literal/CodingLiteralPath.java | 12 ++---- .../fhirpath/literal/DateLiteralPath.java | 6 +-- .../fhirpath/literal/DateTimeLiteralPath.java | 6 +-- .../fhirpath/literal/DecimalLiteralPath.java | 6 +-- .../fhirpath/literal/IntegerLiteralPath.java | 7 ++-- .../fhirpath/literal/LiteralPath.java | 5 --- .../fhirpath/literal/StringLiteralPath.java | 8 ++-- .../fhirpath/literal/TimeLiteralPath.java | 6 +-- .../pathling/views/FhirViewExecutor.java | 8 ++-- .../library/query/QueryDispatcher.java | 3 +- 34 files changed, 226 insertions(+), 127 deletions(-) create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv create mode 100644 fhirpath/src/main/java/au/csiro/pathling/extract/ExtractResultType.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/AbstractPath.java rename fhirpath/src/main/java/au/csiro/pathling/fhirpath/{Materializable.java => FhirValue.java} (60%) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/Flat.java diff --git a/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java index cabd5a73cc..63230d791c 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java @@ -19,7 +19,7 @@ import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.io.Database; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -110,19 +110,19 @@ private Function mapRowToGrouping( final List> results = new ArrayList<>(); for (int i = 0; i < groupings.size(); i++) { - final Materializable grouping = (Materializable) groupings.get(i); + final FhirValue grouping = (FhirValue) groupings.get(i); // Delegate to the `getValueFromRow` method within each Materializable path class to extract // the Type value from the Row in the appropriate way. - final Optional label = grouping.getValueFromRow(row, i); + final Optional label = grouping.getFhirValueFromRow(row, i); labels.add(label); } for (int i = 0; i < aggregations.size(); i++) { //noinspection rawtypes - final Materializable aggregation = (Materializable) aggregations.get(i); + final FhirValue aggregation = (FhirValue) aggregations.get(i); // Delegate to the `getValueFromRow` method within each Materializable path class to extract // the Type value from the Row in the appropriate way. - final Optional result = aggregation.getValueFromRow(row, i + groupings.size()); + final Optional result = aggregation.getFhirValueFromRow(row, i + groupings.size()); results.add(result); } diff --git a/fhir-server/src/main/java/au/csiro/pathling/extract/ExtractExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/extract/ExtractExecutor.java index c87ac39e5b..d8aee6c58d 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/extract/ExtractExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/extract/ExtractExecutor.java @@ -86,7 +86,7 @@ public ExtractExecutor(@Nonnull final QueryConfiguration configuration, public ExtractResponse execute(@Nonnull final ExtractRequest query, @Nonnull final String serverBase, @Nonnull final String requestId) { log.info("Executing request: {}", query); - final Dataset result = buildQuery(query); + final Dataset result = buildQuery(query, ExtractResultType.FLAT); // Write the result and get the URL. final String resultUrl = resultWriter.write(result, requestId); diff --git a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java index 7d56835282..8b46bf25ec 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java @@ -18,6 +18,8 @@ package au.csiro.pathling.extract; import static au.csiro.pathling.test.assertions.Assertions.assertThat; +import static org.apache.spark.sql.functions.col; +import static org.apache.spark.sql.functions.explode_outer; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -416,6 +418,26 @@ void nonPositiveLimit() { } } + @Test + void structuredResult() { + subjectResource = ResourceType.PATIENT; + mockResource(ResourceType.PATIENT); + + final ExtractRequest request = new ExtractRequestBuilder(subjectResource) + .withColumn("id", "id") + .withColumn("name", "name") + .build(); + + Dataset result = executor.buildQuery(request, ExtractResultType.UNCONSTRAINED); + result = result.select( + col("id"), + explode_outer(col("name").getField("given")).as("given") + ); + + assertThat(result) + .hasRows(spark, "responses/ExtractQueryTest/structuredResult.csv"); + } + void mockResource(final ResourceType... resourceTypes) { TestHelpers.mockResource(dataSource, spark, resourceTypes); } diff --git a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractRequestBuilder.java b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractRequestBuilder.java index 2d86c88d30..bf6a24111f 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractRequestBuilder.java +++ b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractRequestBuilder.java @@ -17,6 +17,7 @@ package au.csiro.pathling.extract; +import au.csiro.pathling.query.ExpressionWithLabel; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -33,7 +34,7 @@ public class ExtractRequestBuilder { private final ResourceType subjectResource; @Nonnull - private final List columns; + private final List columns; @Nonnull private final List filters; @@ -49,7 +50,13 @@ public ExtractRequestBuilder(@Nonnull final ResourceType subjectResource) { } public ExtractRequestBuilder withColumn(@Nonnull final String expression) { - columns.add(expression); + columns.add(ExpressionWithLabel.withNoLabel(expression)); + return this; + } + + public ExtractRequestBuilder withColumn(@Nonnull final String expression, + @Nonnull final String label) { + columns.add(ExpressionWithLabel.of(expression, label)); return this; } @@ -64,7 +71,6 @@ public ExtractRequestBuilder withLimit(@Nonnull final Integer limit) { } public ExtractRequest build() { - return ExtractRequest.fromUserInput(subjectResource, Optional.of(columns), Optional.of(filters), - Optional.ofNullable(limit)); + return new ExtractRequest(subjectResource, columns, filters, Optional.ofNullable(limit)); } } diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv new file mode 100644 index 0000000000..3d205484f4 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv @@ -0,0 +1,10 @@ +121503c8-9564-4b48-9086-a22df717948e,Ophelia894 +2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316 +7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882 +9360820c-8602-4335-8b50-c88d627a0c20,Karina848 +9360820c-8602-4335-8b50-c88d627a0c20,Karina848 +a7eb2ce7-1075-426c-addd-957b861b0e55,Shirley182 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Gilberto712 +beff242e-580b-47c0-9844-c1a68c36c5bf,Guy979 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Su690 diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index c7aa4e2c4d..ecf6ea0ae6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -29,7 +29,6 @@ import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirPathAndContext; -import au.csiro.pathling.fhirpath.Materializable; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.element.BooleanPath; import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; @@ -97,8 +96,7 @@ protected ParserContext buildParserContext(@Nonnull final FhirPath inputContext, @Nonnull protected List parseExpressions( - @Nonnull final ParserContext parserContext, @Nonnull final Collection expressions, - @Nonnull final String display, final boolean checkMaterializable) { + @Nonnull final ParserContext parserContext, @Nonnull final Collection expressions) { return expressions.stream() .map(expression -> { // Create a new copy of the parser context from the previous context, except for node IDs @@ -106,12 +104,6 @@ protected List parseExpressions( final ParserContext currentContext = parserContext.unsetNodeIds(); final Parser parser = new Parser(currentContext); final FhirPath result = parser.parse(expression); - if (checkMaterializable) { - // Each expression must evaluate to a Materializable path, or a user error will be thrown. - // There is no requirement for it to be singular. - checkUserInput(result instanceof Materializable, - display + " expression is not of a supported type: " + expression); - } return new FhirPathAndContext(result, parser.getContext()); }).collect(Collectors.toList()); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java index cbbcc4cf96..85cf4823c1 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java @@ -26,7 +26,7 @@ import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirPathAndContext; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.parser.Parser; import au.csiro.pathling.fhirpath.parser.ParserContext; @@ -89,7 +89,8 @@ public ResultWithExpressions buildQuery(@Nonnull final AggregateRequest query) { final Parser parser = new Parser(groupingAndFilterContext); final List filters = parseFilters(parser, query.getFilters()); final List groupingParseResult = parseExpressions( - groupingAndFilterContext, query.getGroupings(), "Grouping", true); + groupingAndFilterContext, query.getGroupings()); + validateGroupings(groupingParseResult); final List groupings = groupingParseResult.stream() .map(FhirPathAndContext::getFhirPath) .collect(Collectors.toList()); @@ -160,16 +161,25 @@ private List parseAggregations(@Nonnull final Parser parser, @Nonnull final Collection aggregations) { return aggregations.stream().map(aggregation -> { final FhirPath result = parser.parse(aggregation); - // Aggregation expressions must evaluate to a singular, Materializable path, or a user error - // will be returned. - checkUserInput(result instanceof Materializable, + // An aggregation expression must be able to be extracted into a FHIR value. + checkUserInput(result instanceof FhirValue, "Aggregation expression is not of a supported type: " + aggregation); + // An aggregation expression must be singular, relative to its input context. checkUserInput(result.isSingular(), "Aggregation expression does not evaluate to a singular value: " + aggregation); return result; }).collect(Collectors.toList()); } + private void validateGroupings(final List groupingParseResult) { + for (final FhirPathAndContext fhirPathAndContext : groupingParseResult) { + final FhirPath fhirPath = fhirPathAndContext.getFhirPath(); + // A grouping expression must be able to be extracted into a FHIR value. + checkUserInput(fhirPath instanceof FhirValue, + "Grouping expression is not of a supported type: " + fhirPath); + } + } + @Value public static class ResultWithExpressions { diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java index ba256396b4..0258dd9561 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java @@ -9,9 +9,10 @@ import au.csiro.pathling.QueryHelpers; import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.config.QueryConfiguration; +import au.csiro.pathling.fhirpath.AbstractPath; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirPathAndContext; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.Flat; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; @@ -50,6 +51,20 @@ public ExtractQueryExecutor(@Nonnull final QueryConfiguration configuration, @SuppressWarnings("WeakerAccess") @Nonnull public Dataset buildQuery(@Nonnull final ExtractRequest query) { + return buildQuery(query, ExtractResultType.UNCONSTRAINED); + } + + /** + * Builds up the query for an extract request. + * + * @param query an {@link ExtractRequest} + * @param resultType the {@link ExtractResultType} that will be required + * @return an uncollected {@link Dataset} + */ + @SuppressWarnings("WeakerAccess") + @Nonnull + public Dataset buildQuery(@Nonnull final ExtractRequest query, + @Nonnull final ExtractResultType resultType) { // Build a new expression parser, and parse all the column expressions within the query. final ResourcePath inputContext = ResourcePath .build(getFhirContext(), getDataSource(), query.getSubjectResource(), @@ -58,7 +73,8 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query) { final ParserContext parserContext = buildParserContext(inputContext, Collections.singletonList(inputContext.getIdColumn())); final List columnParseResult = - parseExpressions(parserContext, query.getColumns(), "Column", true); + parseExpressions(parserContext, query.getColumnsAsStrings()); + validateColumns(columnParseResult, resultType); final List columnPaths = columnParseResult.stream() .map(FhirPathAndContext::getFhirPath) .collect(Collectors.toUnmodifiableList()); @@ -76,8 +92,8 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query) { // Select the column values. final Column idColumn = inputContext.getIdColumn(); final Column[] columnValues = labelColumns( - columnPaths.stream().map(path -> ((Materializable) path).getExtractableColumn()), - labelsAsStream(query.getColumnsWithLabels()) + columnPaths.stream().map(FhirPath::getValueColumn), + labelsAsStream(query.getColumns()) ).toArray(Column[]::new); final Dataset selectedDataset = filteredDataset.select(columnValues) .filter(idColumn.isNotNull()); @@ -88,6 +104,23 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query) { : selectedDataset; } + private void validateColumns(@Nonnull final List columnParseResult, + @Nonnull final ExtractResultType resultType) { + for (final FhirPathAndContext fhirPathAndContext : columnParseResult) { + final FhirPath column = fhirPathAndContext.getFhirPath(); + final boolean condition; + if (resultType == ExtractResultType.FLAT) { + // In flat mode, only flat columns are allowed. + condition = column instanceof Flat; + } else { + // Otherwise, a column can be of any type, as long as it has not been specifically flagged + // as being abstract, e.g. an UntypedResourcePath. + condition = !(column instanceof AbstractPath); + } + checkArgument(condition, "Column name is not of a supported type: " + column); + } + } + @Nonnull private Dataset joinAllColumns( @Nonnull final Collection columnsAndContexts) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractRequest.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractRequest.java index e2f27b80a1..2916a19692 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractRequest.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractRequest.java @@ -40,7 +40,7 @@ public class ExtractRequest { ResourceType subjectResource; @Nonnull - List columnsWithLabels; + List columns; @Nonnull List filters; @@ -52,8 +52,8 @@ public class ExtractRequest { * @return The list of column expressions */ @Nonnull - public List getColumns() { - return ExpressionWithLabel.expressionsAsList(columnsWithLabels); + public List getColumnsAsStrings() { + return ExpressionWithLabel.expressionsAsList(columns); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractResultType.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractResultType.java new file mode 100644 index 0000000000..c76de383b4 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractResultType.java @@ -0,0 +1,22 @@ +package au.csiro.pathling.extract; + +/** + * Describes the type of result that is intended to be returned from the extract query. This + * influences the way that the expressions are validated during parsing - some data types may not be + * supported by the result type. + * + * @author John Grimes + */ +public enum ExtractResultType { + /** + * Indicates that the result will be returned in a form that supports the representation of + * complex types. + */ + UNCONSTRAINED, + + /** + * Indicates that the result will be returned in a form that will be constrained to a flat, + * unstructured representation. + */ + FLAT +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/AbstractPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/AbstractPath.java new file mode 100644 index 0000000000..0ee6ab214f --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/AbstractPath.java @@ -0,0 +1,11 @@ +package au.csiro.pathling.fhirpath; + +/** + * Designates a path as being abstract or intermediate in nature. This means that this type of path + * is not intended to be returned as part of a query result, and required further processing. + * + * @author John Grimes + */ +public interface AbstractPath { + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Materializable.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirValue.java similarity index 60% rename from fhirpath/src/main/java/au/csiro/pathling/fhirpath/Materializable.java rename to fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirValue.java index 66fea8712b..2a3a3fa297 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Materializable.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirValue.java @@ -19,21 +19,20 @@ import java.util.Optional; import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; import org.apache.spark.sql.Row; import org.hl7.fhir.r4.model.Type; /** - * Designates an expression that is capable of being extracted from a Spark dataset and materialized - * back into a value featured within a result, e.g. for use in a grouping label or as part of an - * extract. + * Designates an expression that is capable of being extracted into a FHIR value. * + * @param the {@link Type} of FHIR value that can be extracted * @author John Grimes + * @see Data Types */ -public interface Materializable { +public interface FhirValue { /** - * Extracts a value from a {@link Row} within a {@link org.apache.spark.sql.Dataset}. + * Extracts a FHIR value from a {@link Row} within a {@link org.apache.spark.sql.Dataset}. *

* Implementations of this method should use {@link Row#isNullAt} to check for a null value. * @@ -42,15 +41,6 @@ public interface Materializable { * @return the value, which may be missing */ @Nonnull - Optional getValueFromRow(@Nonnull Row row, int columnNumber); - - /** - * Provides a {@link Column} that can be used to materialize a value in the extract operation, - * which uses a CSV representation that is not compatible with struct values. - * - * @return a column which can be written out as part of an extract result - */ - @Nonnull - Column getExtractableColumn(); + Optional getFhirValueFromRow(@Nonnull Row row, int columnNumber); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Flat.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Flat.java new file mode 100644 index 0000000000..fad2f88ccc --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Flat.java @@ -0,0 +1,20 @@ +package au.csiro.pathling.fhirpath; + +import javax.annotation.Nonnull; +import org.apache.spark.sql.Column; + +/** + * Designates an expression that is capable of being rendered into a flat, unstructured + * representation such as CSV. + * + * @author John Grimes + */ +public interface Flat { + + /** + * @return a {@link Column} within the dataset containing the values of the nodes + */ + @Nonnull + Column getValueColumn(); + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java index f0925d664c..2603906c78 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java @@ -152,12 +152,6 @@ public Column getOrderingColumn() { return eidColumn.orElse(ORDERING_NULL_VALUE); } - @Nonnull - public Column getExtractableColumn() { - return getValueColumn(); - } - - /** * Returns the column with the extension container (the _fid to extension values map). * diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java index 437041ca58..c7947fa81d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java @@ -35,7 +35,7 @@ * * @author John Grimes */ -public class UntypedResourcePath extends ReferencePath { +public class UntypedResourcePath extends ReferencePath implements AbstractPath { public UntypedResourcePath(@Nonnull final String expression, @Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/BooleanPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/BooleanPath.java index cffd7cb610..19aac1a2f4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/BooleanPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/BooleanPath.java @@ -19,7 +19,7 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; import au.csiro.pathling.fhirpath.literal.NullLiteralPath; @@ -38,7 +38,7 @@ * * @author John Grimes */ -public class BooleanPath extends ElementPath implements Materializable, Comparable { +public class BooleanPath extends ElementPath implements FhirValue, Comparable { private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet .of(BooleanPath.class, BooleanLiteralPath.class, NullLiteralPath.class); @@ -54,7 +54,7 @@ protected BooleanPath(@Nonnull final String expression, @Nonnull final Dataset getValueFromRow(@Nonnull final Row row, final int columnNumber) { + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { return valueFromRow(row, columnNumber); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/CodingPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/CodingPath.java index c55cc130df..509e8fa290 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/CodingPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/CodingPath.java @@ -21,7 +21,7 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.comparison.CodingSqlComparator; import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; @@ -42,7 +42,7 @@ * * @author John Grimes */ -public class CodingPath extends ElementPath implements Materializable, Comparable { +public class CodingPath extends ElementPath implements FhirValue, Comparable { private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet .of(CodingPath.class, CodingLiteralPath.class, NullLiteralPath.class); @@ -58,7 +58,7 @@ protected CodingPath(@Nonnull final String expression, @Nonnull final Dataset getValueFromRow(@Nonnull final Row row, final int columnNumber) { + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { return valueFromRow(row, columnNumber); } @@ -98,7 +98,7 @@ public static Optional valueFromRow(@Nonnull final Row row, final int co public static ImmutableSet> getComparableTypes() { return COMPARABLE_TYPES; } - + @Override @Nonnull public Function getComparison(@Nonnull final ComparisonOperation operation) { @@ -115,10 +115,4 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { return super.canBeCombinedWith(target) || target instanceof CodingLiteralPath; } - @Nonnull - @Override - public Column getExtractableColumn() { - return callUDF(CodingToLiteral.FUNCTION_NAME, getValueColumn()); - } - } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java index a026a885a6..1099f38e88 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java @@ -22,7 +22,7 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.Numeric.MathOperation; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.Temporal; @@ -48,7 +48,7 @@ * @author John Grimes */ @Slf4j -public class DatePath extends ElementPath implements Materializable, Comparable, +public class DatePath extends ElementPath implements FhirValue, Comparable, Temporal { protected DatePath(@Nonnull final String expression, @Nonnull final Dataset dataset, @@ -78,7 +78,7 @@ public static Function buildComparison(@Nonnull final Compar @Nonnull @Override - public Optional getValueFromRow(@Nonnull final Row row, final int columnNumber) { + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { return valueFromRow(row, columnNumber); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java index 8b42db521a..3814953edc 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java @@ -21,7 +21,7 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.Numeric.MathOperation; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.Temporal; @@ -49,7 +49,7 @@ * * @author John Grimes */ -public class DateTimePath extends ElementPath implements Materializable, +public class DateTimePath extends ElementPath implements FhirValue, Comparable, Temporal { private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet @@ -67,7 +67,7 @@ protected DateTimePath(@Nonnull final String expression, @Nonnull final Dataset< @Nonnull @Override - public Optional getValueFromRow(@Nonnull final Row row, + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { return valueFromRow(row, columnNumber, getFhirType()); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java index 00fa11d3a3..92fdfe4707 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java @@ -21,7 +21,7 @@ import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.Numeric; import au.csiro.pathling.fhirpath.ResourcePath; @@ -44,7 +44,7 @@ * * @author John Grimes */ -public class DecimalPath extends ElementPath implements Materializable, Comparable, +public class DecimalPath extends ElementPath implements FhirValue, Comparable, Numeric { private static final org.apache.spark.sql.types.DecimalType DECIMAL_TYPE = DataTypes @@ -61,7 +61,7 @@ protected DecimalPath(@Nonnull final String expression, @Nonnull final Dataset getValueFromRow(@Nonnull final Row row, final int columnNumber) { + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { return valueFromRow(row, columnNumber); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java index e6bdd83df2..91701bc970 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java @@ -20,7 +20,7 @@ import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.Numeric; import au.csiro.pathling.fhirpath.ResourcePath; @@ -47,7 +47,7 @@ * * @author John Grimes */ -public class IntegerPath extends ElementPath implements Materializable, Comparable, +public class IntegerPath extends ElementPath implements FhirValue, Comparable, Numeric { private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet @@ -65,7 +65,8 @@ protected IntegerPath(@Nonnull final String expression, @Nonnull final Dataset getValueFromRow(@Nonnull final Row row, final int columnNumber) { + public Optional getFhirValueFromRow(@Nonnull final Row row, + final int columnNumber) { return valueFromRow(row, columnNumber, getFhirType()); } @@ -151,8 +152,8 @@ public Column getNumericContextColumn() { * @param operation The type of {@link au.csiro.pathling.fhirpath.Numeric.MathOperation} * @param expression The FHIRPath expression to use in the result * @param dataset The {@link Dataset} to use in the result - * @return A {@link Function} that takes a {@link Numeric} as a parameter, and returns a {@link - * NonLiteralPath} + * @return A {@link Function} that takes a {@link Numeric} as a parameter, and returns a + * {@link NonLiteralPath} */ @Nonnull public static Function buildMathOperation(@Nonnull final Numeric source, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/StringPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/StringPath.java index 0f936d7bc7..49b21a9d41 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/StringPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/StringPath.java @@ -19,7 +19,8 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; +import au.csiro.pathling.fhirpath.Flat; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.literal.NullLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; @@ -46,7 +47,7 @@ * * @author John Grimes */ -public class StringPath extends ElementPath implements Materializable, Comparable { +public class StringPath extends ElementPath implements FhirValue, Flat, Comparable { private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet .of(StringPath.class, StringLiteralPath.class, NullLiteralPath.class); @@ -62,7 +63,7 @@ protected StringPath(@Nonnull final String expression, @Nonnull final Dataset getValueFromRow(@Nonnull final Row row, + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { return valueFromRow(row, columnNumber, getFhirType()); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java index 78c82e7a8a..8b9d29aacd 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java @@ -19,7 +19,7 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.literal.NullLiteralPath; import au.csiro.pathling.fhirpath.literal.TimeLiteralPath; @@ -38,7 +38,7 @@ * * @author John Grimes */ -public class TimePath extends ElementPath implements Materializable, Comparable { +public class TimePath extends ElementPath implements FhirValue, Comparable { private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet .of(TimePath.class, TimeLiteralPath.class, NullLiteralPath.class); @@ -54,7 +54,7 @@ protected TimePath(@Nonnull final String expression, @Nonnull final Dataset @Nonnull @Override - public Optional getValueFromRow(@Nonnull final Row row, final int columnNumber) { + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { return valueFromRow(row, columnNumber); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/BooleanLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/BooleanLiteralPath.java index 7d93a01e24..5b14788df2 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/BooleanLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/BooleanLiteralPath.java @@ -21,7 +21,7 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.element.BooleanPath; import java.util.Optional; import java.util.function.Function; @@ -37,7 +37,7 @@ * @author John Grimes */ public class BooleanLiteralPath extends LiteralPath implements - Materializable, Comparable { + FhirValue, Comparable { @SuppressWarnings("WeakerAccess") protected BooleanLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @@ -86,7 +86,7 @@ public boolean isComparableTo(@Nonnull final Class type) { @Nonnull @Override - public Optional getValueFromRow(@Nonnull final Row row, final int columnNumber) { + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { return BooleanPath.valueFromRow(row, columnNumber); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java index 166d8d4859..36c3fd697b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java @@ -22,7 +22,7 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.comparison.CodingSqlComparator; import au.csiro.pathling.fhirpath.element.CodingPath; import java.util.Optional; @@ -40,7 +40,7 @@ * @author John Grimes */ @Getter -public class CodingLiteralPath extends LiteralPath implements Materializable, +public class CodingLiteralPath extends LiteralPath implements FhirValue, Comparable { protected CodingLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @@ -105,7 +105,7 @@ public boolean isComparableTo(@Nonnull final Class type) { @Nonnull @Override - public Optional getValueFromRow(@Nonnull final Row row, final int columnNumber) { + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { return CodingPath.valueFromRow(row, columnNumber); } @@ -114,10 +114,4 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { return super.canBeCombinedWith(target) || target instanceof CodingPath; } - @Nonnull - @Override - public Column getExtractableColumn() { - return lit(CodingLiteral.toLiteral(getValue())); - } - } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java index 2b6f4e75eb..e5f8eadb93 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java @@ -22,7 +22,7 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.Numeric.MathOperation; import au.csiro.pathling.fhirpath.Temporal; import au.csiro.pathling.fhirpath.comparison.DateTimeSqlComparator; @@ -44,7 +44,7 @@ * * @author John Grimes */ -public class DateLiteralPath extends LiteralPath implements Materializable, +public class DateLiteralPath extends LiteralPath implements FhirValue, Comparable, Temporal { protected DateLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @@ -98,7 +98,7 @@ public boolean isComparableTo(@Nonnull final Class type) { @Nonnull @Override - public Optional getValueFromRow(@Nonnull final Row row, final int columnNumber) { + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { return DatePath.valueFromRow(row, columnNumber); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java index 9849d0f63b..ceb8fbf36e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java @@ -22,7 +22,7 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.Numeric.MathOperation; import au.csiro.pathling.fhirpath.Temporal; import au.csiro.pathling.fhirpath.comparison.DateTimeSqlComparator; @@ -46,7 +46,7 @@ * @author John Grimes */ public class DateTimeLiteralPath extends LiteralPath implements - Materializable, Comparable, Temporal { + FhirValue, Comparable, Temporal { protected DateTimeLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final BaseDateTimeType literalValue) { @@ -101,7 +101,7 @@ public boolean isComparableTo(@Nonnull final Class type) { @Nonnull @Override - public Optional getValueFromRow(@Nonnull final Row row, + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { return DateTimePath.valueFromRow(row, columnNumber, FHIRDefinedType.DATETIME); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java index 1973c8e240..b50830dd2c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java @@ -22,7 +22,7 @@ import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.Numeric; import au.csiro.pathling.fhirpath.element.DecimalPath; @@ -43,7 +43,7 @@ * @author John Grimes */ public class DecimalLiteralPath extends LiteralPath implements - Materializable, Comparable, Numeric { + FhirValue, Comparable, Numeric { protected DecimalLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final DecimalType literalValue) { @@ -129,7 +129,7 @@ public FHIRDefinedType getFhirType() { @Nonnull @Override - public Optional getValueFromRow(@Nonnull final Row row, final int columnNumber) { + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { return DecimalPath.valueFromRow(row, columnNumber); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java index 5a0965b303..f4bfdc7d02 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java @@ -21,7 +21,7 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.Numeric; import au.csiro.pathling.fhirpath.element.IntegerPath; @@ -42,7 +42,7 @@ * @author John Grimes */ public class IntegerLiteralPath extends LiteralPath implements - Materializable, Comparable, Numeric { + FhirValue, Comparable, Numeric { @SuppressWarnings("WeakerAccess") protected IntegerLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @@ -117,7 +117,8 @@ public FHIRDefinedType getFhirType() { @Nonnull @Override - public Optional getValueFromRow(@Nonnull final Row row, final int columnNumber) { + public Optional getFhirValueFromRow(@Nonnull final Row row, + final int columnNumber) { return IntegerPath.valueFromRow(row, columnNumber, FHIRDefinedType.INTEGER); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java index 62d3c6819c..dac56b966b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java @@ -179,11 +179,6 @@ public Column getOrderingColumn() { return ORDERING_NULL_VALUE; } - @Nonnull - public Column getExtractableColumn() { - return getValueColumn(); - } - /** * @return A column representing the value for this literal. */ diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java index 93de9682c3..596a7cc105 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java @@ -23,7 +23,8 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; +import au.csiro.pathling.fhirpath.Flat; import au.csiro.pathling.fhirpath.element.StringPath; import java.util.Optional; import java.util.function.Function; @@ -43,7 +44,7 @@ */ @Getter public class StringLiteralPath extends LiteralPath implements - Materializable, Comparable { + FhirValue, Flat, Comparable { protected StringLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final PrimitiveType literalValue) { @@ -114,7 +115,8 @@ public boolean isComparableTo(@Nonnull final Class type) { @Nonnull @Override - public Optional getValueFromRow(@Nonnull final Row row, final int columnNumber) { + public Optional getFhirValueFromRow(@Nonnull final Row row, + final int columnNumber) { return StringPath.valueFromRow(row, columnNumber, FHIRDefinedType.STRING); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java index de436e2d09..801c1a14a3 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java @@ -21,7 +21,7 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.element.TimePath; import java.util.Optional; import java.util.function.Function; @@ -36,7 +36,7 @@ * * @author John Grimes */ -public class TimeLiteralPath extends LiteralPath implements Materializable, +public class TimeLiteralPath extends LiteralPath implements FhirValue, Comparable { protected TimeLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @@ -90,7 +90,7 @@ public boolean isComparableTo(@Nonnull final Class type) { @Nonnull @Override - public Optional getValueFromRow(@Nonnull final Row row, final int columnNumber) { + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { return TimePath.valueFromRow(row, columnNumber); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index bda8175a55..d0c1383015 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -11,7 +11,7 @@ import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirPathAndContext; -import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.parser.Parser; @@ -82,7 +82,7 @@ public Dataset buildQuery(@Nonnull final FhirView view) { // If the variable has a `whenMany` value of `array`, we need to flatten the result to remove // accumulated array nesting from the underlying structure. if (variable.getWhenMany() == WhenMany.ARRAY && result instanceof NonLiteralPath - && result instanceof Materializable) { + && result instanceof FhirValue) { final NonLiteralPath nonLiteralResult = (NonLiteralPath) result; final Column flattenedValue = flatten(nonLiteralResult.getValueColumn()); result = nonLiteralResult.copy(nonLiteralResult.getExpression(), @@ -105,7 +105,7 @@ public Dataset buildQuery(@Nonnull final FhirView view) { .map(NamedExpression::getExpression) .collect(toList()); final List columnParseResult = - parseExpressions(columnContext, columnExpressions, "Column", true); + parseExpressions(columnContext, columnExpressions); final List columnPaths = columnParseResult.stream() .map(FhirPathAndContext::getFhirPath) .collect(toUnmodifiableList()); @@ -120,7 +120,7 @@ public Dataset buildQuery(@Nonnull final FhirView view) { // Select the column values. final Column idColumn = inputContext.getIdColumn(); final Column[] columnValues = labelColumns( - columnPaths.stream().map(path -> ((Materializable) path).getExtractableColumn()), + columnPaths.stream().map(FhirPath::getValueColumn), view.getColumns().stream().map(NamedExpression::getName).map(Optional::of) ).toArray(Column[]::new); return filteredDataset.select(columnValues) diff --git a/library-api/src/main/java/au/csiro/pathling/library/query/QueryDispatcher.java b/library-api/src/main/java/au/csiro/pathling/library/query/QueryDispatcher.java index 7776c3dfa9..2b7d7e5b4a 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/query/QueryDispatcher.java +++ b/library-api/src/main/java/au/csiro/pathling/library/query/QueryDispatcher.java @@ -21,6 +21,7 @@ import au.csiro.pathling.aggregate.AggregateRequest; import au.csiro.pathling.extract.ExtractQueryExecutor; import au.csiro.pathling.extract.ExtractRequest; +import au.csiro.pathling.extract.ExtractResultType; import au.csiro.pathling.views.FhirView; import au.csiro.pathling.views.FhirViewExecutor; import javax.annotation.Nonnull; @@ -60,7 +61,7 @@ public QueryDispatcher(@Nonnull final AggregateQueryExecutor aggregateExecutor, */ @Nonnull public Dataset dispatch(@Nonnull final ExtractRequest extractRequest) { - return extractExecutor.buildQuery(extractRequest); + return extractExecutor.buildQuery(extractRequest, ExtractResultType.UNCONSTRAINED); } /** From 5493c67516cfce4cf7c58f8661adc71d668d454d Mon Sep 17 00:00:00 2001 From: John Grimes Date: Tue, 30 May 2023 10:17:12 +1000 Subject: [PATCH 14/95] Add string coercion --- .../pathling/extract/ExtractQueryTest.java | 32 +++++++-------- .../extract/ExtractQueryExecutor.java | 32 ++++++++++++--- .../pathling/extract/ExtractRequest.java | 28 +++++++++---- .../pathling/fhirpath/StringCoercible.java | 20 ++++++++++ .../pathling/fhirpath/element/CodingPath.java | 12 +++++- .../pathling/fhirpath/element/DatePath.java | 11 +++++- .../fhirpath/element/DateTimePath.java | 19 ++++++++- .../fhirpath/element/DecimalPath.java | 11 +++++- .../fhirpath/element/IntegerPath.java | 11 +++++- .../pathling/fhirpath/element/StringPath.java | 10 ++++- .../pathling/fhirpath/element/TimePath.java | 11 +++++- .../fhirpath/function/NamedFunction.java | 1 + .../fhirpath/function/ToStringFunction.java | 39 +++++++++++++++++++ .../fhirpath/literal/CodingLiteralPath.java | 10 ++++- .../fhirpath/literal/DateLiteralPath.java | 10 ++++- .../fhirpath/literal/DateTimeLiteralPath.java | 10 ++++- .../fhirpath/literal/DecimalLiteralPath.java | 10 ++++- .../fhirpath/literal/IntegerLiteralPath.java | 10 ++++- .../fhirpath/literal/StringLiteralPath.java | 13 +++++-- .../fhirpath/literal/TimeLiteralPath.java | 10 ++++- .../fhirpath/literal/StringLiteral.java | 5 +++ 21 files changed, 271 insertions(+), 44 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/StringCoercible.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ToStringFunction.java diff --git a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java index 8b46bf25ec..f72594ba2b 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java @@ -105,12 +105,12 @@ void simpleQueryWithAliases() { ExpressionWithLabel.withExpressionAsLabel("id"), ExpressionWithLabel.withExpressionAsLabel("gender"), ExpressionWithLabel.of("name.given.first()", "given_name"), - ExpressionWithLabel.of("reverseResolve(Condition.subject).count()", "patient_count") + ExpressionWithLabel.of("reverseResolve(Condition.subject).count().toString()", "patient_count") ), List.of("gender = 'female'"), Optional.empty() ); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertArrayEquals(new String[]{"id", "gender", "given_name", "patient_count"}, result.columns()); assertThat(result) @@ -129,7 +129,7 @@ void multipleResolves() { .withColumn("serviceProvider.resolve().address.postalCode") .build(); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertTrue(Stream.of(result.columns()).allMatch(Strings::looksLikeAlias)); assertThat(result) @@ -148,7 +148,7 @@ void multipleReverseResolves() { .withColumn("reverseResolve(Condition.subject).code.coding.code") .build(); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) .hasRows(spark, "responses/ExtractQueryTest/multipleReverseResolves.csv"); } @@ -166,7 +166,7 @@ void multiplePolymorphicResolves() { .withColumn("subject.resolve().ofType(Patient).name.family") .build(); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) .hasRows(spark, "responses/ExtractQueryTest/multiplePolymorphicResolves.csv"); } @@ -181,7 +181,7 @@ void literalColumn() { .withColumn("19") .build(); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) .hasRows(spark, "responses/ExtractQueryTest/literalColumn.csv"); } @@ -196,7 +196,7 @@ void codingColumn() { .withColumn("code.coding") .build(); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) .hasRows(spark, "responses/ExtractQueryTest/codingColumn.csv"); } @@ -212,7 +212,7 @@ void codingLiteralColumn() { "http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231") .build(); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) .hasRows(spark, "responses/ExtractQueryTest/codingLiteralColumn.csv"); } @@ -231,7 +231,7 @@ void multipleFilters() { .withFilter("reverseResolve(Condition.subject).count() >= 10") .build(); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) .hasRows(spark, "responses/ExtractQueryTest/multipleFilters.csv"); } @@ -248,7 +248,7 @@ void limit() { .withLimit(3) .build(); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) .hasRows(spark, "responses/ExtractQueryTest/limit.csv"); } @@ -263,7 +263,7 @@ void eliminatesTrailingNulls() { .withColumn("reverseResolve(Condition.subject).code.coding.code.where($this = '72892002')") .build(); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) .hasRows(spark, "responses/ExtractQueryTest/eliminatesTrailingNulls.csv"); } @@ -279,7 +279,7 @@ void combineResultInSecondFilter() { .withFilter("(name.given combine name.family).empty().not()") .build(); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) .hasRows(spark, "responses/ExtractQueryTest/combineResultInSecondFilter.csv"); } @@ -295,7 +295,7 @@ void whereInMultipleColumns() { .withColumn("identifier.where(system = 'http://hl7.org/fhir/sid/us-ssn').value") .build(); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) .hasRows(spark, "responses/ExtractQueryTest/whereInMultipleColumns.csv"); } @@ -311,7 +311,7 @@ void multipleNonSingularColumnsWithDifferentTypes() { .withColumn("type.coding") .build(); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) .hasRows(spark, "responses/ExtractQueryTest/multipleNonSingularColumnsWithDifferentTypes.csv"); @@ -330,7 +330,7 @@ void multipleIndependentUnnestings() { .withColumn("maritalStatus.coding.code") .build(); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) .hasRows(spark, "responses/ExtractQueryTest/multipleIndependentUnnestings.csv"); } @@ -346,7 +346,7 @@ void toleranceOfColumnOrdering() { .withColumn("name.given") .build(); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) .hasRows(spark, "responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv"); diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java index 0258dd9561..d43f28e94d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java @@ -14,6 +14,7 @@ import au.csiro.pathling.fhirpath.FhirPathAndContext; import au.csiro.pathling.fhirpath.Flat; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -74,8 +75,9 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query, Collections.singletonList(inputContext.getIdColumn())); final List columnParseResult = parseExpressions(parserContext, query.getColumnsAsStrings()); - validateColumns(columnParseResult, resultType); - final List columnPaths = columnParseResult.stream() + final List validatedColumns = + validateAndCoerceColumns(columnParseResult, resultType); + final List columnPaths = validatedColumns.stream() .map(FhirPathAndContext::getFhirPath) .collect(Collectors.toUnmodifiableList()); @@ -104,9 +106,27 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query, : selectedDataset; } - private void validateColumns(@Nonnull final List columnParseResult, + private List validateAndCoerceColumns( + @Nonnull final List columnParseResult, @Nonnull final ExtractResultType resultType) { - for (final FhirPathAndContext fhirPathAndContext : columnParseResult) { + + // Perform any necessary String coercion. + final List coerced = columnParseResult.stream() + .map(column -> { + final FhirPath fhirPath = column.getFhirPath(); + if (resultType == ExtractResultType.FLAT && !(fhirPath instanceof Flat) + && fhirPath instanceof StringCoercible) { + // If the result type is flat and the path is string-coercible, we can coerce it. + final StringCoercible stringCoercible = (StringCoercible) fhirPath; + final FhirPath stringPath = stringCoercible.asStringPath(fhirPath.getExpression()); + return new FhirPathAndContext(stringPath, column.getContext()); + } else { + return column; + } + }).collect(toList()); + + // Validate the final set of paths. + for (final FhirPathAndContext fhirPathAndContext : coerced) { final FhirPath column = fhirPathAndContext.getFhirPath(); final boolean condition; if (resultType == ExtractResultType.FLAT) { @@ -117,8 +137,10 @@ private void validateColumns(@Nonnull final List columnParse // as being abstract, e.g. an UntypedResourcePath. condition = !(column instanceof AbstractPath); } - checkArgument(condition, "Column name is not of a supported type: " + column); + checkArgument(condition, "Column is not of a supported type: " + column.getExpression()); } + + return coerced; } @Nonnull diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractRequest.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractRequest.java index 2916a19692..5713dae666 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractRequest.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractRequest.java @@ -48,6 +48,16 @@ public class ExtractRequest { @Nonnull Optional limit; + public ExtractRequest(@Nonnull final ResourceType subjectResource, + @Nonnull final List columns, @Nonnull final List filters, + @Nonnull final Optional limit) { + this.subjectResource = subjectResource; + this.columns = columns; + this.filters = filters; + this.limit = limit; + validate(columns, filters, limit); + } + /** * @return The list of column expressions */ @@ -68,15 +78,19 @@ public List getColumnsAsStrings() { public static ExtractRequest fromUserInput(@Nonnull final ResourceType subjectResource, @Nonnull final Optional> columns, @Nonnull final Optional> filters, @Nonnull final Optional limit) { - checkUserInput(columns.isPresent() && columns.get().size() > 0, - "Query must have at least one column expression"); - checkUserInput(columns.get().stream().noneMatch(String::isBlank), - "Column expression cannot be blank"); - filters.ifPresent(f -> checkUserInput(f.stream().noneMatch(String::isBlank), - "Filter expression cannot be blank")); - limit.ifPresent(l -> checkUserInput(l > 0, "Limit must be greater than zero")); return new ExtractRequest(subjectResource, ExpressionWithLabel.fromUnlabelledExpressions(checkPresent(columns)), normalizeEmpty(filters), limit); } + + private static void validate(@Nonnull final List columns, + @Nonnull final List filters, @Nonnull final Optional limit) { + checkUserInput(columns.size() > 0, "Query must have at least one column expression"); + checkUserInput( + columns.stream().map(ExpressionWithLabel::getExpression).noneMatch(String::isBlank), + "Column expression cannot be blank"); + checkUserInput(filters.stream().noneMatch(String::isBlank), + "Filter expression cannot be blank"); + limit.ifPresent(l -> checkUserInput(l > 0, "Limit must be greater than zero")); + } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/StringCoercible.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/StringCoercible.java new file mode 100644 index 0000000000..39c32181e1 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/StringCoercible.java @@ -0,0 +1,20 @@ +package au.csiro.pathling.fhirpath; + +import javax.annotation.Nonnull; + +/** + * Describes a path that can be coerced to a String representation. + * + * @author John Grimes + * @see toString + */ +public interface StringCoercible { + + /** + * @param expression the expression for the new path + * @return a new {@link FhirPath} representing the String representation of this path + */ + @Nonnull + FhirPath asStringPath(@Nonnull String expression); + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/CodingPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/CodingPath.java index 509e8fa290..f144d5c19d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/CodingPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/CodingPath.java @@ -23,6 +23,7 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.comparison.CodingSqlComparator; import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; import au.csiro.pathling.fhirpath.literal.NullLiteralPath; @@ -42,7 +43,8 @@ * * @author John Grimes */ -public class CodingPath extends ElementPath implements FhirValue, Comparable { +public class CodingPath extends ElementPath implements FhirValue, Comparable, + StringCoercible { private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet .of(CodingPath.class, CodingLiteralPath.class, NullLiteralPath.class); @@ -115,4 +117,12 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { return super.canBeCombinedWith(target) || target instanceof CodingLiteralPath; } + @Nonnull + @Override + public FhirPath asStringPath(@Nonnull final String expression) { + final Column valueColumn = callUDF(CodingToLiteral.FUNCTION_NAME, this.valueColumn); + return ElementPath.build(expression, getDataset(), getIdColumn(), getEidColumn(), valueColumn, + isSingular(), getCurrentResource(), getThisColumn(), FHIRDefinedType.STRING); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java index 1099f38e88..8e186efc6e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java @@ -25,6 +25,7 @@ import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.Numeric.MathOperation; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.Temporal; import au.csiro.pathling.fhirpath.comparison.DateTimeSqlComparator; import au.csiro.pathling.fhirpath.literal.DateLiteralPath; @@ -49,7 +50,7 @@ */ @Slf4j public class DatePath extends ElementPath implements FhirValue, Comparable, - Temporal { + Temporal, StringCoercible { protected DatePath(@Nonnull final String expression, @Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, @@ -123,4 +124,12 @@ public Function getDateArithmeticOperation( DateAddDurationFunction.FUNCTION_NAME, DateSubtractDurationFunction.FUNCTION_NAME); } + @Nonnull + @Override + public FhirPath asStringPath(@Nonnull final String expression) { + return ElementPath.build(expression, getDataset(), getIdColumn(), getEidColumn(), + getValueColumn(), isSingular(), getCurrentResource(), getThisColumn(), + FHIRDefinedType.STRING); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java index 3814953edc..41f5178e85 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java @@ -18,12 +18,14 @@ package au.csiro.pathling.fhirpath.element; import static au.csiro.pathling.fhirpath.Temporal.buildDateArithmeticOperation; +import static org.apache.spark.sql.functions.date_format; import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.Numeric.MathOperation; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.Temporal; import au.csiro.pathling.fhirpath.comparison.DateTimeSqlComparator; import au.csiro.pathling.fhirpath.literal.DateLiteralPath; @@ -50,12 +52,14 @@ * @author John Grimes */ public class DateTimePath extends ElementPath implements FhirValue, - Comparable, Temporal { + Comparable, Temporal, StringCoercible { private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet .of(DatePath.class, DateTimePath.class, DateLiteralPath.class, DateTimeLiteralPath.class, NullLiteralPath.class); + public static final String SPARK_FHIRPATH_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; + protected DateTimePath(@Nonnull final String expression, @Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, @Nonnull final Column valueColumn, final boolean singular, @@ -125,4 +129,17 @@ public Function getDateArithmeticOperation( DateTimeAddDurationFunction.FUNCTION_NAME, DateTimeSubtractDurationFunction.FUNCTION_NAME); } + @Nonnull + @Override + public FhirPath asStringPath(@Nonnull final String expression) { + final Column valueColumn; + if (getFhirType() == FHIRDefinedType.INSTANT) { + valueColumn = date_format(getValueColumn(), SPARK_FHIRPATH_DATETIME_FORMAT); + } else { + valueColumn = getValueColumn(); + } + return ElementPath.build(expression, getDataset(), getIdColumn(), getEidColumn(), valueColumn, + isSingular(), getCurrentResource(), getThisColumn(), FHIRDefinedType.STRING); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java index 92fdfe4707..c4fae63e42 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java @@ -25,6 +25,7 @@ import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.Numeric; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.literal.DecimalLiteralPath; import java.math.BigDecimal; import java.math.RoundingMode; @@ -45,7 +46,7 @@ * @author John Grimes */ public class DecimalPath extends ElementPath implements FhirValue, Comparable, - Numeric { + Numeric, StringCoercible { private static final org.apache.spark.sql.types.DecimalType DECIMAL_TYPE = DataTypes .createDecimalType(DecimalCustomCoder.precision(), DecimalCustomCoder.scale()); @@ -179,4 +180,12 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { return super.canBeCombinedWith(target) || target instanceof DecimalLiteralPath; } + @Override + @Nonnull + public FhirPath asStringPath(@Nonnull final String expression) { + final Column valueColumn = getValueColumn().cast(DataTypes.StringType); + return ElementPath.build(expression, getDataset(), getIdColumn(), getEidColumn(), valueColumn, + isSingular(), getCurrentResource(), getThisColumn(), FHIRDefinedType.STRING); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java index 91701bc970..4c56afc70f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java @@ -24,6 +24,7 @@ import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.Numeric; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.literal.DecimalLiteralPath; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; import au.csiro.pathling.fhirpath.literal.NullLiteralPath; @@ -48,7 +49,7 @@ * @author John Grimes */ public class IntegerPath extends ElementPath implements FhirValue, Comparable, - Numeric { + Numeric, StringCoercible { private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet .of(IntegerPath.class, IntegerLiteralPath.class, DecimalPath.class, DecimalLiteralPath.class, @@ -195,4 +196,12 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { return super.canBeCombinedWith(target) || target instanceof IntegerLiteralPath; } + @Override + @Nonnull + public FhirPath asStringPath(@Nonnull final String expression) { + final Column valueColumn = getValueColumn().cast(DataTypes.StringType); + return ElementPath.build(expression, getDataset(), getIdColumn(), getEidColumn(), valueColumn, + isSingular(), getCurrentResource(), getThisColumn(), FHIRDefinedType.STRING); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/StringPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/StringPath.java index 49b21a9d41..c632d3c19b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/StringPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/StringPath.java @@ -22,6 +22,7 @@ import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.Flat; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.literal.NullLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import com.google.common.collect.ImmutableSet; @@ -47,7 +48,8 @@ * * @author John Grimes */ -public class StringPath extends ElementPath implements FhirValue, Flat, Comparable { +public class StringPath extends ElementPath implements FhirValue, Flat, Comparable, + StringCoercible { private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet .of(StringPath.class, StringLiteralPath.class, NullLiteralPath.class); @@ -123,4 +125,10 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { return super.canBeCombinedWith(target) || target instanceof StringLiteralPath; } + @Nonnull + @Override + public FhirPath asStringPath(@Nonnull final String expression) { + return this; + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java index 8b9d29aacd..ea3275d68d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java @@ -21,6 +21,7 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.literal.NullLiteralPath; import au.csiro.pathling.fhirpath.literal.TimeLiteralPath; import com.google.common.collect.ImmutableSet; @@ -38,7 +39,8 @@ * * @author John Grimes */ -public class TimePath extends ElementPath implements FhirValue, Comparable { +public class TimePath extends ElementPath implements FhirValue, Comparable, + StringCoercible { private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet .of(TimePath.class, TimeLiteralPath.class, NullLiteralPath.class); @@ -94,4 +96,11 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { return super.canBeCombinedWith(target) || target instanceof TimeLiteralPath; } + @Nonnull + @Override + public FhirPath asStringPath(@Nonnull final String expression) { + return ElementPath.build(expression, getDataset(), getIdColumn(), getEidColumn(), + getValueColumn(), isSingular(), getCurrentResource(), getThisColumn(), + FHIRDefinedType.STRING); + } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java index 60a81163ae..4722a55afb 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java @@ -70,6 +70,7 @@ public interface NamedFunction { .put("display", new DisplayFunction()) .put("property", new PropertyFunction()) .put("designation", new DesignationFunction()) + .put("toString", new ToStringFunction()) .build(); /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ToStringFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ToStringFunction.java new file mode 100644 index 0000000000..fed8a00b84 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ToStringFunction.java @@ -0,0 +1,39 @@ +package au.csiro.pathling.fhirpath.function; + +import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; +import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; +import static au.csiro.pathling.utilities.Preconditions.checkUserInput; + +import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.StringCoercible; +import javax.annotation.Nonnull; + +/** + * A function that converts a path to a String, if the operation is supported. + * + * @author John Grimes + */ +public class ToStringFunction implements NamedFunction { + + private static final String NAME = "toString"; + + @Nonnull + @Override + public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + // Check that the function has no arguments. + checkNoArguments(NAME, input); + final FhirPath inputPath = input.getInput(); + + // Check that the input is coercible to a String. + checkUserInput(inputPath instanceof StringCoercible, + "Cannot coerce path to a String type: " + inputPath.getExpression()); + final StringCoercible stringCoercible = (StringCoercible) inputPath; + + // Create an expression for the new path. + final String expression = expressionFromInput(input, NAME); + + // Coerce the input to a String. + return stringCoercible.asStringPath(expression); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java index 36c3fd697b..5d000e1cd0 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java @@ -23,6 +23,7 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirValue; +import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.comparison.CodingSqlComparator; import au.csiro.pathling.fhirpath.element.CodingPath; import java.util.Optional; @@ -41,7 +42,7 @@ */ @Getter public class CodingLiteralPath extends LiteralPath implements FhirValue, - Comparable { + Comparable, StringCoercible { protected CodingLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final Coding literalValue) { @@ -114,4 +115,11 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { return super.canBeCombinedWith(target) || target instanceof CodingPath; } + @Nonnull + @Override + public FhirPath asStringPath(@Nonnull final String expression) { + final String fhirPath = StringLiteral.stringToFhirPath(CodingLiteral.toLiteral(getValue())); + return StringLiteralPath.fromString(fhirPath, this); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java index e5f8eadb93..13ffb63b0e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java @@ -24,6 +24,7 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.Numeric.MathOperation; +import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.Temporal; import au.csiro.pathling.fhirpath.comparison.DateTimeSqlComparator; import au.csiro.pathling.fhirpath.element.DatePath; @@ -45,7 +46,7 @@ * @author John Grimes */ public class DateLiteralPath extends LiteralPath implements FhirValue, - Comparable, Temporal { + Comparable, Temporal, StringCoercible { protected DateLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final DateType literalValue) { @@ -116,4 +117,11 @@ public Function getDateArithmeticOperation( DateAddDurationFunction.FUNCTION_NAME, DateSubtractDurationFunction.FUNCTION_NAME); } + @Nonnull + @Override + public FhirPath asStringPath(@Nonnull final String expression) { + final String fhirPath = StringLiteral.stringToFhirPath(getValue().asStringValue()); + return StringLiteralPath.fromString(fhirPath, this); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java index ceb8fbf36e..8b877dfe25 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java @@ -24,6 +24,7 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.Numeric.MathOperation; +import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.Temporal; import au.csiro.pathling.fhirpath.comparison.DateTimeSqlComparator; import au.csiro.pathling.fhirpath.element.DateTimePath; @@ -46,7 +47,7 @@ * @author John Grimes */ public class DateTimeLiteralPath extends LiteralPath implements - FhirValue, Comparable, Temporal { + FhirValue, Comparable, Temporal, StringCoercible { protected DateTimeLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final BaseDateTimeType literalValue) { @@ -120,4 +121,11 @@ public Function getDateArithmeticOperation( DateTimeAddDurationFunction.FUNCTION_NAME, DateTimeSubtractDurationFunction.FUNCTION_NAME); } + @Nonnull + @Override + public FhirPath asStringPath(@Nonnull final String expression) { + final String fhirPath = StringLiteral.stringToFhirPath(getValue().asStringValue()); + return StringLiteralPath.fromString(fhirPath, this); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java index b50830dd2c..ed70d02574 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java @@ -25,6 +25,7 @@ import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.Numeric; +import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.element.DecimalPath; import au.csiro.pathling.fhirpath.element.IntegerPath; import java.math.BigDecimal; @@ -43,7 +44,7 @@ * @author John Grimes */ public class DecimalLiteralPath extends LiteralPath implements - FhirValue, Comparable, Numeric { + FhirValue, Comparable, Numeric, StringCoercible { protected DecimalLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final DecimalType literalValue) { @@ -138,4 +139,11 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { return super.canBeCombinedWith(target) || target instanceof DecimalPath; } + @Override + @Nonnull + public FhirPath asStringPath(@Nonnull final String expression) { + final String fhirPath = StringLiteral.stringToFhirPath(getValue().asStringValue()); + return StringLiteralPath.fromString(fhirPath, this); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java index f4bfdc7d02..ba9cc0351b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java @@ -24,6 +24,7 @@ import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.Numeric; +import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.element.IntegerPath; import java.util.Optional; import java.util.function.Function; @@ -42,7 +43,7 @@ * @author John Grimes */ public class IntegerLiteralPath extends LiteralPath implements - FhirValue, Comparable, Numeric { + FhirValue, Comparable, Numeric, StringCoercible { @SuppressWarnings("WeakerAccess") protected IntegerLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @@ -127,4 +128,11 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { return super.canBeCombinedWith(target) || target instanceof IntegerPath; } + @Override + @Nonnull + public FhirPath asStringPath(@Nonnull final String expression) { + final String fhirPath = StringLiteral.stringToFhirPath(getValue().asStringValue()); + return StringLiteralPath.fromString(fhirPath, this); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java index 596a7cc105..7b88e84f1f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java @@ -17,7 +17,7 @@ package au.csiro.pathling.fhirpath.literal; -import static au.csiro.pathling.fhirpath.literal.StringLiteral.escapeFhirPathString; +import static au.csiro.pathling.fhirpath.literal.StringLiteral.stringToFhirPath; import static au.csiro.pathling.utilities.Strings.unSingleQuote; import static org.apache.spark.sql.functions.lit; @@ -25,6 +25,7 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.Flat; +import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.element.StringPath; import java.util.Optional; import java.util.function.Function; @@ -44,7 +45,7 @@ */ @Getter public class StringLiteralPath extends LiteralPath implements - FhirValue, Flat, Comparable { + FhirValue, Flat, Comparable, StringCoercible { protected StringLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final PrimitiveType literalValue) { @@ -74,7 +75,7 @@ public static StringLiteralPath fromString(@Nonnull final String fhirPath, @Nonnull @Override public String getExpression() { - return "'" + escapeFhirPathString(getValue().getValueAsString()) + "'"; + return stringToFhirPath(getValue().getValueAsString()); } @Nonnull @@ -125,4 +126,10 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { return super.canBeCombinedWith(target) || target instanceof StringPath; } + @Nonnull + @Override + public FhirPath asStringPath(@Nonnull final String expression) { + return this; + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java index 801c1a14a3..f19d47f2af 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java @@ -22,6 +22,7 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirValue; +import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.element.TimePath; import java.util.Optional; import java.util.function.Function; @@ -37,7 +38,7 @@ * @author John Grimes */ public class TimeLiteralPath extends LiteralPath implements FhirValue, - Comparable { + Comparable, StringCoercible { protected TimeLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final TimeType literalValue) { @@ -99,4 +100,11 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { return super.canBeCombinedWith(target) || target instanceof TimePath; } + @Nonnull + @Override + public FhirPath asStringPath(@Nonnull final String expression) { + final String fhirPath = StringLiteral.stringToFhirPath(getValue().asStringValue()); + return StringLiteralPath.fromString(fhirPath, this); + } + } diff --git a/utilities/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteral.java b/utilities/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteral.java index bd4eca14d5..95dfb346b3 100644 --- a/utilities/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteral.java +++ b/utilities/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteral.java @@ -4,6 +4,11 @@ public abstract class StringLiteral { + @Nonnull + public static String stringToFhirPath(@Nonnull final String value) { + return "'" + escapeFhirPathString(value) + "'"; + } + /** * On the way back out, we only do the minimal escaping to guarantee syntactical correctness. * From 69d7c3bc1b0dd3cac7c53d28919ac3f509b402c2 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Wed, 7 Jun 2023 14:15:09 +1000 Subject: [PATCH 15/95] Implement joining with topological sorting --- .../pathling/extract/ExtractQueryTest.java | 9 ++- .../toleranceOfColumnOrdering2.csv | 20 ++--- .../java/au/csiro/pathling/QueryExecutor.java | 29 ++++--- .../aggregate/AggregateQueryExecutor.java | 11 ++- .../extract/ExtractQueryExecutor.java | 79 +++++++++---------- .../au/csiro/pathling/fhirpath/FhirPath.java | 9 +++ .../pathling/fhirpath/NonLiteralPath.java | 6 ++ .../fhirpath/TopologicalExpressionSorter.java | 20 +++++ .../fhirpath/element/ElementDefinition.java | 18 +++++ .../fhirpath/function/ResolveFunction.java | 5 +- .../function/ReverseResolveFunction.java | 5 +- .../fhirpath/literal/BooleanLiteralPath.java | 6 ++ .../fhirpath/literal/CodingLiteralPath.java | 16 ++-- .../fhirpath/literal/DateLiteralPath.java | 17 ++-- .../fhirpath/literal/DateTimeLiteralPath.java | 16 ++-- .../fhirpath/literal/DecimalLiteralPath.java | 8 +- .../fhirpath/literal/IntegerLiteralPath.java | 6 ++ .../fhirpath/literal/LiteralPath.java | 13 --- .../fhirpath/literal/NullLiteralPath.java | 6 ++ .../fhirpath/literal/QuantityLiteralPath.java | 22 +++--- .../fhirpath/literal/StringLiteralPath.java | 6 ++ .../fhirpath/literal/TimeLiteralPath.java | 17 ++-- .../operator/PathTraversalOperator.java | 2 +- .../fhirpath/parser/ParserContext.java | 22 ++++-- .../pathling/views/FhirViewExecutor.java | 56 +++++++------ .../test/builders/ParserContextBuilder.java | 3 +- 26 files changed, 258 insertions(+), 169 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/TopologicalExpressionSorter.java diff --git a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java index f72594ba2b..81a75e1d44 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java @@ -105,7 +105,8 @@ void simpleQueryWithAliases() { ExpressionWithLabel.withExpressionAsLabel("id"), ExpressionWithLabel.withExpressionAsLabel("gender"), ExpressionWithLabel.of("name.given.first()", "given_name"), - ExpressionWithLabel.of("reverseResolve(Condition.subject).count().toString()", "patient_count") + ExpressionWithLabel.of("reverseResolve(Condition.subject).count().toString()", + "patient_count") ), List.of("gender = 'female'"), Optional.empty() @@ -297,7 +298,8 @@ void whereInMultipleColumns() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/whereInMultipleColumns.csv"); + .debugAllRows(); + // .hasRows(spark, "responses/ExtractQueryTest/whereInMultipleColumns.csv"); } @Test @@ -348,12 +350,13 @@ void toleranceOfColumnOrdering() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) + .debugAllRows() .hasRows(spark, "responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv"); final ExtractRequest request2 = new ExtractRequestBuilder(subjectResource) .withColumn("name.given") - .withColumn("name.family") .withColumn("id") + .withColumn("name.family") .build(); final Dataset result2 = executor.buildQuery(request2); diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv index d81ffd6b67..3e00c8c171 100644 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv @@ -1,10 +1,10 @@ -Ophelia894,Gleichner915,121503c8-9564-4b48-9086-a22df717948e -Pedro316,Ulibarri312,2b36c1e2-bbe1-45ae-8124-4adad2677702 -Cherryl901,Heller342,7001ad9c-34d2-4eb5-8165-5fdc2147f469 -Seymour882,Krajcik437,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -Karina848,Oberbrunner298,9360820c-8602-4335-8b50-c88d627a0c20 -Karina848,Wuckert783,9360820c-8602-4335-8b50-c88d627a0c20 -Shirley182,Botsford977,a7eb2ce7-1075-426c-addd-957b861b0e55 -Gilberto712,MacGyver246,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 -Guy979,Towne435,beff242e-580b-47c0-9844-c1a68c36c5bf -Su690,Ebert178,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +Ophelia894,121503c8-9564-4b48-9086-a22df717948e,Gleichner915 +Pedro316,2b36c1e2-bbe1-45ae-8124-4adad2677702,Ulibarri312 +Cherryl901,7001ad9c-34d2-4eb5-8165-5fdc2147f469,Heller342 +Seymour882,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437 +Karina848,9360820c-8602-4335-8b50-c88d627a0c20,Oberbrunner298 +Karina848,9360820c-8602-4335-8b50-c88d627a0c20,Wuckert783 +Shirley182,a7eb2ce7-1075-426c-addd-957b861b0e55,Botsford977 +Gilberto712,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,MacGyver246 +Guy979,beff242e-580b-47c0-9844-c1a68c36c5bf,Towne435 +Su690,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Ebert178 diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index ecf6ea0ae6..ca98d7ff63 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -22,6 +22,7 @@ import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static au.csiro.pathling.utilities.Strings.randomAlias; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toList; import static org.apache.spark.sql.functions.col; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; @@ -38,13 +39,13 @@ import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import com.google.common.collect.Streams; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Optional; import java.util.function.BinaryOperator; -import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -97,15 +98,21 @@ protected ParserContext buildParserContext(@Nonnull final FhirPath inputContext, @Nonnull protected List parseExpressions( @Nonnull final ParserContext parserContext, @Nonnull final Collection expressions) { - return expressions.stream() - .map(expression -> { - // Create a new copy of the parser context from the previous context, except for node IDs - // which need to be reset each time. - final ParserContext currentContext = parserContext.unsetNodeIds(); - final Parser parser = new Parser(currentContext); - final FhirPath result = parser.parse(expression); - return new FhirPathAndContext(result, parser.getContext()); - }).collect(Collectors.toList()); + final List parsed = new ArrayList<>(); + ParserContext currentContext = parserContext; + for (final String expression : expressions) { + if (parsed.size() > 0) { + final FhirPathAndContext lastParsed = parsed.get(parsed.size() - 1); + // Create a new copy of the original parser context, except use the dataset from the last + // column parse and reset the node IDs. + currentContext = parserContext.withContextDataset( + lastParsed.getFhirPath().getDataset()); + } + final Parser parser = new Parser(currentContext); + // Add the parse result to the list of parsed expressions. + parsed.add(new FhirPathAndContext(parser.parse(expression), currentContext)); + } + return parsed; } @Nonnull @@ -120,7 +127,7 @@ protected List parseFilters(@Nonnull final Parser parser, checkUserInput(result.isSingular(), "Filter expression must represent a singular value: " + expression); return result; - }).collect(Collectors.toList()); + }).collect(toList()); } @Nonnull diff --git a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java index 85cf4823c1..b41559a50f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java @@ -88,12 +88,11 @@ public ResultWithExpressions buildQuery(@Nonnull final AggregateRequest query) { Collections.singletonList(inputContext.getIdColumn())); final Parser parser = new Parser(groupingAndFilterContext); final List filters = parseFilters(parser, query.getFilters()); - final List groupingParseResult = parseExpressions( - groupingAndFilterContext, query.getGroupings()); - validateGroupings(groupingParseResult); - final List groupings = groupingParseResult.stream() - .map(FhirPathAndContext::getFhirPath) - .collect(Collectors.toList()); + // final List groupingParseResult = parseExpressions( + // groupingAndFilterContext, query.getGroupings()); + final List groupingParseResult = new ArrayList<>(); + // validateGroupings(groupingParseResult); + final List groupings = groupingParseResult; // Join all filter and grouping expressions together. final Column idColumn = inputContext.getIdColumn(); diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java index d43f28e94d..1248507905 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java @@ -4,6 +4,7 @@ import static au.csiro.pathling.query.ExpressionWithLabel.labelsAsStream; import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.QueryHelpers; @@ -15,6 +16,7 @@ import au.csiro.pathling.fhirpath.Flat; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.StringCoercible; +import au.csiro.pathling.fhirpath.TopologicalExpressionSorter; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -66,25 +68,23 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query) { @Nonnull public Dataset buildQuery(@Nonnull final ExtractRequest query, @Nonnull final ExtractResultType resultType) { - // Build a new expression parser, and parse all the column expressions within the query. + + // The context of evaluation is a single resource. final ResourcePath inputContext = ResourcePath .build(getFhirContext(), getDataSource(), query.getSubjectResource(), query.getSubjectResource().toCode(), true); - // The context of evaluation is a single resource. final ParserContext parserContext = buildParserContext(inputContext, Collections.singletonList(inputContext.getIdColumn())); - final List columnParseResult = + final List parsedColumns = parseExpressions(parserContext, query.getColumnsAsStrings()); - final List validatedColumns = - validateAndCoerceColumns(columnParseResult, resultType); - final List columnPaths = validatedColumns.stream() - .map(FhirPathAndContext::getFhirPath) - .collect(Collectors.toUnmodifiableList()); + final List coercedColumns = + validateAndCoerceColumns(parsedColumns, resultType); + final Dataset queryDataset = joinAllColumns(coercedColumns); - // Join all the column expressions together. - final Dataset columnJoinResultDataset = joinAllColumns(columnParseResult); - final Dataset trimmedDataset = trimTrailingNulls(inputContext.getIdColumn(), - columnPaths, columnJoinResultDataset); + // Trim trailing nulls. + // final Dataset trimmedDataset = trimTrailingNulls( + // parserContext.getInputContext().getIdColumn(), coercedColumns, queryDataset); + final Dataset trimmedDataset = queryDataset; // Apply the filters. final List filters = query.getFilters(); @@ -94,7 +94,9 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query, // Select the column values. final Column idColumn = inputContext.getIdColumn(); final Column[] columnValues = labelColumns( - columnPaths.stream().map(FhirPath::getValueColumn), + coercedColumns.stream() + .map(FhirPathAndContext::getFhirPath) + .map(FhirPath::getValueColumn), labelsAsStream(query.getColumns()) ).toArray(Column[]::new); final Dataset selectedDataset = filteredDataset.select(columnValues) @@ -107,27 +109,27 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query, } private List validateAndCoerceColumns( - @Nonnull final List columnParseResult, + @Nonnull final List parsedColumns, @Nonnull final ExtractResultType resultType) { // Perform any necessary String coercion. - final List coerced = columnParseResult.stream() - .map(column -> { - final FhirPath fhirPath = column.getFhirPath(); - if (resultType == ExtractResultType.FLAT && !(fhirPath instanceof Flat) - && fhirPath instanceof StringCoercible) { + final List coercedColumns = parsedColumns.stream() + .map(columnAndContext -> { + final FhirPath column = columnAndContext.getFhirPath(); + if (resultType == ExtractResultType.FLAT && !(column instanceof Flat) + && column instanceof StringCoercible) { // If the result type is flat and the path is string-coercible, we can coerce it. - final StringCoercible stringCoercible = (StringCoercible) fhirPath; - final FhirPath stringPath = stringCoercible.asStringPath(fhirPath.getExpression()); - return new FhirPathAndContext(stringPath, column.getContext()); + final StringCoercible stringCoercible = (StringCoercible) column; + return new FhirPathAndContext(stringCoercible.asStringPath(column.getExpression()), + columnAndContext.getContext()); } else { - return column; + return new FhirPathAndContext(column, columnAndContext.getContext()); } }).collect(toList()); // Validate the final set of paths. - for (final FhirPathAndContext fhirPathAndContext : coerced) { - final FhirPath column = fhirPathAndContext.getFhirPath(); + for (final FhirPathAndContext columnAndContext : coercedColumns) { + final FhirPath column = columnAndContext.getFhirPath(); final boolean condition; if (resultType == ExtractResultType.FLAT) { // In flat mode, only flat columns are allowed. @@ -140,7 +142,7 @@ private List validateAndCoerceColumns( checkArgument(condition, "Column is not of a supported type: " + column.getExpression()); } - return coerced; + return coercedColumns; } @Nonnull @@ -159,17 +161,7 @@ private Dataset joinAllColumns( // Sort the columns by the nodes encountered while parsing. This ensures that we join them // together in order from the general to the specific. final List sorted = columnsAndContexts.stream() - .sorted((a, b) -> { - final List nodesA = a.getContext().getNodeIdColumns().keySet().stream() - .sorted() - .collect(toList()); - final List nodesB = b.getContext().getNodeIdColumns().keySet().stream() - .sorted() - .collect(toList()); - final String sortStringA = String.join("|", nodesA); - final String sortStringB = String.join("|", nodesB); - return sortStringA.compareTo(sortStringB); - }) + .sorted(new TopologicalExpressionSorter()) .collect(toList()); // Start with the first column and its unjoined dataset. @@ -186,16 +178,16 @@ private Dataset joinAllColumns( rightJoinColumns.add(right.getFhirPath().getIdColumn()); // Add the intersection of the nodes present in both the left and right column contexts. - final List commonNodes = new ArrayList<>( + final List commonNodes = new ArrayList<>( left.getContext().getNodeIdColumns().keySet()); commonNodes.retainAll(right.getContext().getNodeIdColumns().keySet()); final FhirPathAndContext finalLeft = left; leftJoinColumns.addAll(commonNodes.stream() - .map(key -> finalLeft.getContext().getNodeIdColumns().get(key)) - .collect(toList())); + .flatMap(key -> finalLeft.getContext().getNodeIdColumns().get(key).stream()) + .collect(toSet())); rightJoinColumns.addAll(commonNodes.stream() - .map(key -> right.getContext().getNodeIdColumns().get(key)) - .collect(toList())); + .flatMap(key -> right.getContext().getNodeIdColumns().get(key).stream()) + .collect(toSet())); // Use a left outer join, so that we don't lose rows that don't have a value for the right // column. @@ -209,6 +201,9 @@ private Dataset joinAllColumns( return result; } + // @Nonnull + // private Dataset filterInvalidRowCombinations(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, ) + @Nonnull private Dataset trimTrailingNulls(final @Nonnull Column idColumn, @Nonnull final List expressions, @Nonnull final Dataset dataset) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java index 1720e9a8fa..be63db6cd5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java @@ -75,6 +75,15 @@ public interface FhirPath extends Orderable { @Nonnull FhirPath withExpression(@Nonnull String expression); + /** + * Creates a copy of the path with a different dataset. + * + * @param dataset the new dataset + * @return the new FhirPath + */ + @Nonnull + FhirPath withDataset(@Nonnull Dataset dataset); + /** * Trims the columns to those common with the target and sorts them, ready for a union operation. * diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java index 2603906c78..363b3c26bb 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java @@ -214,6 +214,12 @@ public FhirPath withExpression(@Nonnull final String expression) { return copy(expression, dataset, idColumn, eidColumn, valueColumn, singular, thisColumn); } + @Nonnull + @Override + public FhirPath withDataset(@Nonnull final Dataset dataset) { + return copy(expression, dataset, idColumn, eidColumn, valueColumn, singular, thisColumn); + } + /** * Construct a $this path based upon this path. * diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TopologicalExpressionSorter.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TopologicalExpressionSorter.java new file mode 100644 index 0000000000..6f56aecce9 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TopologicalExpressionSorter.java @@ -0,0 +1,20 @@ +package au.csiro.pathling.fhirpath; + +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; + +public class TopologicalExpressionSorter implements Comparator { + + @Override + public int compare(final FhirPathAndContext fpc1, final FhirPathAndContext fpc2) { + final Set nodes1 = fpc1.getContext().getNodeIdColumns().keySet(); + final Set nodes2 = fpc2.getContext().getNodeIdColumns().keySet(); + + final Set intersection = new HashSet<>(nodes1); + intersection.retainAll(nodes2); + + return nodes1.size() - intersection.size() - (nodes2.size() - intersection.size()); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementDefinition.java index 805b1419b1..6e5f9c1b8a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementDefinition.java @@ -25,6 +25,7 @@ import com.google.common.collect.ImmutableMap; import java.util.Collections; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import javax.annotation.Nonnull; @@ -178,4 +179,21 @@ private static Optional getFhirTypeFromObject(final IBase hapiO } } + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ElementDefinition that = (ElementDefinition) o; + return Objects.equals(childDefinition, that.childDefinition); + } + + @Override + public int hashCode() { + return Objects.hash(childDefinition); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java index 8f7a23d530..495856db9a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java @@ -31,6 +31,7 @@ import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; import ca.uhn.fhir.context.FhirContext; +import java.util.List; import java.util.Optional; import java.util.Set; import javax.annotation.Nonnull; @@ -106,8 +107,8 @@ public static FhirPath resolveMonomorphicReference(@Nonnull final ReferencePath // We need to add the resource ID column to the parser context so that it can be used within // joins in certain situations, e.g. extract. - context.getNodeIdColumns() - .putIfAbsent(expression, resourcePath.getElementColumn("id")); + final Object nodeKey = List.of(referencePath.getDefinition(), resourcePath.getDefinition()); + context.addNodeId(nodeKey, resourcePath.getElementColumn("id")); final ResourcePath result = resourcePath.copy(expression, dataset, inputId, inputEid, resourcePath.getValueColumn(), referencePath.isSingular(), referencePath.getThisColumn()); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java index 4c88cfb086..9881990af5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java @@ -30,6 +30,7 @@ import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.element.ReferencePath; +import java.util.List; import java.util.Optional; import java.util.Set; import javax.annotation.Nonnull; @@ -104,7 +105,9 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { // joins in certain situations, e.g. extract. final Column syntheticEid = inputPath.expandEid(currentResourceIndex); final DatasetWithColumn datasetWithEid = QueryHelpers.createColumn(dataset, syntheticEid); - input.getContext().getNodeIdColumns().putIfAbsent(expression, datasetWithEid.getColumn()); + final List nodeKey = List.of(referencePath.getDefinition(), + currentResource.getDefinition()); + input.getContext().addNodeId(nodeKey, datasetWithEid.getColumn()); final ResourcePath result = currentResource .copy(expression, datasetWithEid.getDataset(), inputPath.getIdColumn(), diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/BooleanLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/BooleanLiteralPath.java index 5b14788df2..660d4e1709 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/BooleanLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/BooleanLiteralPath.java @@ -95,4 +95,10 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { return super.canBeCombinedWith(target) || target instanceof BooleanPath; } + @Nonnull + @Override + public FhirPath withDataset(@Nonnull final Dataset dataset) { + return new BooleanLiteralPath(dataset, getIdColumn(), getValue()); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java index 5d000e1cd0..d40cca85ad 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java @@ -49,11 +49,6 @@ protected CodingLiteralPath(@Nonnull final Dataset dataset, @Nonnull final super(dataset, idColumn, literalValue); } - protected CodingLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final Coding literalValue, @Nonnull final String expression) { - super(dataset, idColumn, literalValue, expression); - } - /** * Returns a new instance, parsed from a FHIRPath literal. * @@ -67,14 +62,13 @@ protected CodingLiteralPath(@Nonnull final Dataset dataset, @Nonnull final public static CodingLiteralPath fromString(@Nonnull final String fhirPath, @Nonnull final FhirPath context) throws IllegalArgumentException { return new CodingLiteralPath(context.getDataset(), context.getIdColumn(), - CodingLiteral.fromString(fhirPath), fhirPath); + CodingLiteral.fromString(fhirPath)); } @Nonnull @Override public String getExpression() { - return expression.orElse(CodingLiteral.toLiteral(getValue())); - + return CodingLiteral.toLiteral(getValue()); } @Nonnull @@ -121,5 +115,11 @@ public FhirPath asStringPath(@Nonnull final String expression) { final String fhirPath = StringLiteral.stringToFhirPath(CodingLiteral.toLiteral(getValue())); return StringLiteralPath.fromString(fhirPath, this); } + + @Nonnull + @Override + public FhirPath withDataset(@Nonnull final Dataset dataset) { + return new CodingLiteralPath(dataset, idColumn, getValue()); + } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java index 13ffb63b0e..80e2b89c0f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java @@ -53,11 +53,6 @@ protected DateLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Co super(dataset, idColumn, literalValue); } - protected DateLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final DateType literalValue, @Nonnull final String expression) { - super(dataset, idColumn, literalValue, expression); - } - /** * Returns a new instance, parsed from a FHIRPath literal. * @@ -71,13 +66,13 @@ public static DateLiteralPath fromString(@Nonnull final String fhirPath, @Nonnull final FhirPath context) throws ParseException { final String dateString = fhirPath.replaceFirst("^@", ""); final DateType dateType = new DateType(dateString); - return new DateLiteralPath(context.getDataset(), context.getIdColumn(), dateType, fhirPath); + return new DateLiteralPath(context.getDataset(), context.getIdColumn(), dateType); } @Nonnull @Override public String getExpression() { - return expression.orElse("@" + getValue().asStringValue()); + return "@" + getValue().asStringValue(); } @Nonnull @@ -123,5 +118,11 @@ public FhirPath asStringPath(@Nonnull final String expression) { final String fhirPath = StringLiteral.stringToFhirPath(getValue().asStringValue()); return StringLiteralPath.fromString(fhirPath, this); } - + + @Nonnull + @Override + public FhirPath withDataset(@Nonnull final Dataset dataset) { + return new DateLiteralPath(dataset, getIdColumn(), getValue()); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java index 8b877dfe25..e4f1f7afc1 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java @@ -54,11 +54,6 @@ protected DateTimeLiteralPath(@Nonnull final Dataset dataset, @Nonnull fina super(dataset, idColumn, literalValue); } - protected DateTimeLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final BaseDateTimeType literalValue, @Nonnull final String expression) { - super(dataset, idColumn, literalValue, expression); - } - /** * Returns a new instance, parsed from a FHIRPath literal. * @@ -73,14 +68,13 @@ public static DateTimeLiteralPath fromString(@Nonnull final String fhirPath, @Nonnull final FhirPath context) throws ParseException { final String dateTimeString = fhirPath.replaceFirst("^@", ""); final DateTimeType dateTimeType = new DateTimeType(dateTimeString); - return new DateTimeLiteralPath(context.getDataset(), context.getIdColumn(), dateTimeType, - fhirPath); + return new DateTimeLiteralPath(context.getDataset(), context.getIdColumn(), dateTimeType); } @Nonnull @Override public String getExpression() { - return expression.orElse("@" + getValue().getValueAsString()); + return "@" + getValue().getValueAsString(); } @Nonnull @@ -128,4 +122,10 @@ public FhirPath asStringPath(@Nonnull final String expression) { return StringLiteralPath.fromString(fhirPath, this); } + @Nonnull + @Override + public FhirPath withDataset(@Nonnull final Dataset dataset) { + return new DateTimeLiteralPath(dataset, getIdColumn(), getValue()); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java index ed70d02574..faa8585677 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java @@ -145,5 +145,11 @@ public FhirPath asStringPath(@Nonnull final String expression) { final String fhirPath = StringLiteral.stringToFhirPath(getValue().asStringValue()); return StringLiteralPath.fromString(fhirPath, this); } - + + @Nonnull + @Override + public FhirPath withDataset(@Nonnull final Dataset dataset) { + return new DecimalLiteralPath(dataset, getIdColumn(), getValue()); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java index ba9cc0351b..98c05f74ee 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java @@ -135,4 +135,10 @@ public FhirPath asStringPath(@Nonnull final String expression) { return StringLiteralPath.fromString(fhirPath, this); } + @Nonnull + @Override + public FhirPath withDataset(@Nonnull final Dataset dataset) { + return new IntegerLiteralPath(dataset, getIdColumn(), getValue()); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java index dac56b966b..07a9f57f2f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java @@ -101,25 +101,12 @@ public abstract class LiteralPath implements FhirPath { @Getter protected ValueType value; - @Nonnull - protected final Optional expression; - protected LiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final ValueType value) { this.idColumn = idColumn; this.value = value; this.dataset = dataset; this.valueColumn = buildValueColumn(); - this.expression = Optional.empty(); - } - - protected LiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final ValueType value, @Nonnull final String expression) { - this.idColumn = idColumn; - this.value = value; - this.dataset = dataset; - this.valueColumn = buildValueColumn(); - this.expression = Optional.of(expression); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/NullLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/NullLiteralPath.java index d61c0ac881..a08b01882f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/NullLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/NullLiteralPath.java @@ -87,4 +87,10 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { return true; } + @Nonnull + @Override + public FhirPath withDataset(@Nonnull final Dataset dataset) { + return new NullLiteralPath(dataset, idColumn); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/QuantityLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/QuantityLiteralPath.java index ff24462e7d..4ddc4abed5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/QuantityLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/QuantityLiteralPath.java @@ -57,11 +57,6 @@ protected QuantityLiteralPath(@Nonnull final Dataset dataset, @Nonnull fina super(dataset, idColumn, literalValue); } - protected QuantityLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final Quantity literalValue, @Nonnull final String expression) { - super(dataset, idColumn, literalValue, expression); - } - /** * Returns a new instance, parsed from a FHIRPath literal. * @@ -95,7 +90,7 @@ public static QuantityLiteralPath fromUcumString(@Nonnull final String fhirPath, final BigDecimal decimalValue = getQuantityValue(value, context); @Nullable final String display = ucumService.getCommonDisplay(unit); - return buildLiteralPath(decimalValue, unit, Optional.ofNullable(display), context, fhirPath); + return buildLiteralPath(decimalValue, unit, Optional.ofNullable(display), context); } /** @@ -112,7 +107,7 @@ public static QuantityLiteralPath fromCalendarDurationString(@Nonnull final Stri @Nonnull final FhirPath context) { return new QuantityLiteralPath(context.getDataset(), context.getIdColumn(), - CalendarDurationUtils.parseCalendarDuration(fhirPath), fhirPath); + CalendarDurationUtils.parseCalendarDuration(fhirPath)); } private static BigDecimal getQuantityValue(final String value, final @Nonnull FhirPath context) { @@ -128,21 +123,20 @@ private static BigDecimal getQuantityValue(final String value, final @Nonnull Fh @Nonnull private static QuantityLiteralPath buildLiteralPath(@Nonnull final BigDecimal decimalValue, @Nonnull final String unit, @Nonnull final Optional display, - final @Nonnull FhirPath context, @Nonnull final String fhirPath) { + @Nonnull final FhirPath context) { final Quantity quantity = new Quantity(); quantity.setValue(decimalValue); quantity.setSystem(Ucum.SYSTEM_URI); quantity.setCode(unit); display.ifPresent(quantity::setUnit); - return new QuantityLiteralPath(context.getDataset(), context.getIdColumn(), quantity, fhirPath); + return new QuantityLiteralPath(context.getDataset(), context.getIdColumn(), quantity); } @Nonnull @Override public String getExpression() { - return expression.orElse( - getValue().getValue().toPlainString() + " '" + getValue().getUnit() + "'"); + return getValue().getValue().toPlainString() + " '" + getValue().getUnit() + "'"; } @Nonnull @@ -188,4 +182,10 @@ public Function getMathOperation(@Nonnull final MathOpe Optional.empty()); } + @Nonnull + @Override + public FhirPath withDataset(@Nonnull final Dataset dataset) { + return new QuantityLiteralPath(dataset, getIdColumn(), getValue()); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java index 7b88e84f1f..14a183e638 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java @@ -132,4 +132,10 @@ public FhirPath asStringPath(@Nonnull final String expression) { return this; } + @Nonnull + @Override + public FhirPath withDataset(@Nonnull final Dataset dataset) { + return new StringLiteralPath(dataset, getIdColumn(), getValue()); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java index f19d47f2af..f617b79405 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java @@ -45,11 +45,6 @@ protected TimeLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Co super(dataset, idColumn, literalValue); } - protected TimeLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final TimeType literalValue, @Nonnull final String expression) { - super(dataset, idColumn, literalValue, expression); - } - /** * Returns a new instance, parsed from a FHIRPath literal. * @@ -63,13 +58,13 @@ public static TimeLiteralPath fromString(@Nonnull final String fhirPath, @Nonnull final FhirPath context) { final String timeString = fhirPath.replaceFirst("^@T", ""); return new TimeLiteralPath(context.getDataset(), context.getIdColumn(), - new TimeType(timeString), fhirPath); + new TimeType(timeString)); } @Nonnull @Override public String getExpression() { - return expression.orElse("@T" + getValue().getValue()); + return "@T" + getValue().getValue(); } @Nonnull @@ -106,5 +101,11 @@ public FhirPath asStringPath(@Nonnull final String expression) { final String fhirPath = StringLiteral.stringToFhirPath(getValue().asStringValue()); return StringLiteralPath.fromString(fhirPath, this); } - + + @Nonnull + @Override + public FhirPath withDataset(@Nonnull final Dataset dataset) { + return new TimeLiteralPath(dataset, getIdColumn(), getValue()); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java index 3e13966411..19416544e4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java @@ -121,7 +121,7 @@ public ElementPath invoke(@Nonnull final PathTraversalInput input) { // If there is an element ID column, we need to add it to the parser context so that it can // be used within joins in certain situations, e.g. extract. if (unnestBehaviour == UnnestBehaviour.UNNEST && !maxCardinalityOfOne) { - eidColumn.ifPresent(c -> input.getContext().getNodeIdColumns().putIfAbsent(expression, c)); + eidColumn.ifPresent(c -> input.getContext().addNodeId(childDefinition, c)); } return ElementPath diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index 2d496589b0..1118f157ec 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -24,12 +24,16 @@ import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import javax.annotation.Nonnull; import lombok.Getter; import org.apache.spark.sql.Column; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; /** @@ -101,7 +105,7 @@ public class ParserContext { * using the string path. This is used for element and resource-identity aware joins. */ @Nonnull - private final Map nodeIdColumns; + private final Map> nodeIdColumns; @Nonnull private final UnnestBehaviour unnestBehaviour; @@ -125,7 +129,7 @@ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirCo @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataSource, @Nonnull final Optional terminologyServiceFactory, @Nonnull final List groupingColumns, - @Nonnull final Map nodeIdColumns) { + @Nonnull final Map> nodeIdColumns) { this(inputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, groupingColumns, nodeIdColumns, UnnestBehaviour.UNNEST, new HashMap<>()); } @@ -147,7 +151,7 @@ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirCo @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataSource, @Nonnull final Optional terminologyServiceFactory, @Nonnull final List groupingColumns, - @Nonnull final Map nodeIdColumns, + @Nonnull final Map> nodeIdColumns, @Nonnull final UnnestBehaviour unnestBehaviour, @Nonnull final Map variables) { this.inputContext = inputContext; @@ -177,13 +181,19 @@ public ParserContext withUnnestBehaviour(@Nonnull final UnnestBehaviour unnestBe } /** - * Creates a copy of the current parser context, but with the node IDs emptied out. + * Creates a copy of the current parser context, but with a different input dataset and the node + * IDs emptied out. * * @return a new {@link ParserContext} */ - public ParserContext unsetNodeIds() { - return new ParserContext(inputContext, fhirContext, sparkSession, dataSource, + public ParserContext withContextDataset(@Nonnull final Dataset contextDataset) { + final FhirPath newInputContext = inputContext.withDataset(contextDataset); + return new ParserContext(newInputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, groupingColumns, new HashMap<>(), unnestBehaviour, variables); } + public void addNodeId(@Nonnull final Object key, @Nonnull final Column value) { + nodeIdColumns.computeIfAbsent(key, k -> new HashSet<>()).add(value); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index d0c1383015..5cba8cff0f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -2,7 +2,6 @@ import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toUnmodifiableList; import static org.apache.spark.sql.functions.flatten; import au.csiro.pathling.QueryExecutor; @@ -72,8 +71,7 @@ public Dataset buildQuery(@Nonnull final FhirView view) { // Create a copy of the parser context that uses the specified unnest behaviour. final ParserContext variableContext = parserContext - .withUnnestBehaviour(unnestBehaviour) - .unsetNodeIds(); + .withUnnestBehaviour(unnestBehaviour); final Parser parser = new Parser(variableContext); FhirPath result = parser.parse(variable.getExpression()) @@ -104,14 +102,13 @@ public Dataset buildQuery(@Nonnull final FhirView view) { final List columnExpressions = view.getColumns().stream() .map(NamedExpression::getExpression) .collect(toList()); - final List columnParseResult = - parseExpressions(columnContext, columnExpressions); - final List columnPaths = columnParseResult.stream() - .map(FhirPathAndContext::getFhirPath) - .collect(toUnmodifiableList()); + // final List columnParseResult = + // parseExpressions(columnContext, columnExpressions); + final List columnParseResult = new ArrayList<>(); + final List columnPaths = columnParseResult; // Join the column expressions together. - final Dataset unfilteredDataset = joinAllColumns(columnParseResult); + final Dataset unfilteredDataset = columnPaths.get(columnPaths.size() - 1).getDataset(); // Filter the dataset. final Dataset filteredDataset = filterDataset(inputContext, view.getFilters(), @@ -142,19 +139,20 @@ private Dataset joinAllColumns( // Sort the columns by the nodes encountered while parsing. This ensures that we join them // together in order from the general to the specific. - final List sorted = columnsAndContexts.stream() - .sorted((a, b) -> { - final List nodesA = a.getContext().getNodeIdColumns().keySet().stream() - .sorted() - .collect(toList()); - final List nodesB = b.getContext().getNodeIdColumns().keySet().stream() - .sorted() - .collect(toList()); - final String sortStringA = String.join("|", nodesA); - final String sortStringB = String.join("|", nodesB); - return sortStringA.compareTo(sortStringB); - }) - .collect(toList()); + // final List sorted = columnsAndContexts.stream() + // .sorted((a, b) -> { + // final List nodesA = a.getContext().getNodeIdColumns().keySet().stream() + // .sorted() + // .collect(toList()); + // final List nodesB = b.getContext().getNodeIdColumns().keySet().stream() + // .sorted() + // .collect(toList()); + // final String sortStringA = String.join("|", nodesA); + // final String sortStringB = String.join("|", nodesB); + // return sortStringA.compareTo(sortStringB); + // }) + // .collect(toList()); + final List sorted = new ArrayList<>(columnsAndContexts); // Start with the first column and its unjoined dataset. FhirPathAndContext left = sorted.get(0); @@ -170,16 +168,16 @@ private Dataset joinAllColumns( rightJoinColumns.add(right.getFhirPath().getIdColumn()); // Add the intersection of the nodes present in both the left and right column contexts. - final List commonNodes = new ArrayList<>( + final List commonNodes = new ArrayList<>( left.getContext().getNodeIdColumns().keySet()); commonNodes.retainAll(right.getContext().getNodeIdColumns().keySet()); final FhirPathAndContext finalLeft = left; - leftJoinColumns.addAll(commonNodes.stream() - .map(key -> finalLeft.getContext().getNodeIdColumns().get(key)) - .collect(toList())); - rightJoinColumns.addAll(commonNodes.stream() - .map(key -> right.getContext().getNodeIdColumns().get(key)) - .collect(toList())); + // leftJoinColumns.addAll(commonNodes.stream() + // .map(key -> finalLeft.getContext().getNodeIdColumns().get(key)) + // .collect(toList())); + // rightJoinColumns.addAll(commonNodes.stream() + // .map(key -> right.getContext().getNodeIdColumns().get(key)) + // .collect(toList())); // Use a left outer join, so that we don't lose rows that don't have a value for the right // column. diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java index 06955007a8..eec39517fb 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.spark.sql.Column; @@ -62,7 +63,7 @@ public class ParserContextBuilder { private List groupingColumns; @Nonnull - private final Map nodeIdColumns; + private final Map> nodeIdColumns; public ParserContextBuilder(@Nonnull final SparkSession spark, @Nonnull final FhirContext fhirContext) { From 71da037446ffdc80197e22d4f6d94d964a3a103c Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sun, 9 Jul 2023 11:32:29 +1000 Subject: [PATCH 16/95] Fix import within NamedFunction --- .../java/au/csiro/pathling/fhirpath/function/NamedFunction.java | 1 + 1 file changed, 1 insertion(+) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java index 4722a55afb..13cf2a7db5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java @@ -25,6 +25,7 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.function.terminology.DesignationFunction; +import au.csiro.pathling.fhirpath.function.terminology.DisplayFunction; import au.csiro.pathling.fhirpath.function.terminology.MemberOfFunction; import au.csiro.pathling.fhirpath.function.terminology.PropertyFunction; import au.csiro.pathling.fhirpath.function.terminology.SubsumesFunction; From aebef99458dd551dd3a4dde205740b177f1c4625 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Wed, 12 Jul 2023 09:37:17 +1000 Subject: [PATCH 17/95] Add additional tests --- .../pathling/extract/ExtractQueryTest.java | 21 +++++- .../ExtractQueryTest/linkedUnnesting.csv | 11 +++ .../multipleReverseResolves2.csv | 71 +++++++++++++++++++ .../resources/test-data/fhir/Patient.ndjson | 2 +- .../extract/ExtractQueryExecutor.java | 11 +-- 5 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves2.csv diff --git a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java index 8b1a94a146..38358a7e1d 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java @@ -315,8 +315,7 @@ void whereInMultipleColumns() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .debugAllRows(); - // .hasRows(spark, "responses/ExtractQueryTest/whereInMultipleColumns.csv"); + .hasRows(spark, "responses/ExtractQueryTest/whereInMultipleColumns.csv"); } @Test @@ -336,6 +335,24 @@ void multipleNonSingularColumnsWithDifferentTypes() { "responses/ExtractQueryTest/multipleNonSingularColumnsWithDifferentTypes.csv"); } + @Test + void linkedUnnesting() { + subjectResource = ResourceType.PATIENT; + mockResource(subjectResource); + + final ExtractRequest request = new ExtractRequestBuilder(subjectResource) + .withColumn("id") + .withColumn("name.given") + .withColumn("name.family") + .withColumn("name.use") + .build(); + + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); + result.show(false); + assertThat(result) + .hasRows(spark, "responses/ExtractQueryTest/linkedUnnesting.csv"); + } + @Test void multipleIndependentUnnestings() { subjectResource = ResourceType.PATIENT; diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.csv new file mode 100644 index 0000000000..c8fc439bc3 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.csv @@ -0,0 +1,11 @@ +121503c8-9564-4b48-9086-a22df717948e,Ophelia894,Gleichner915,official +2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316,Ulibarri312,official +7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901,Heller342,official +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882,Krajcik437,official +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Arianne,Burchard,nickname +9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Oberbrunner298,official +9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Wuckert783,maiden +a7eb2ce7-1075-426c-addd-957b861b0e55,Shirley182,Botsford977,official +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Gilberto712,MacGyver246,official +beff242e-580b-47c0-9844-c1a68c36c5bf,Guy979,Towne435,official +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Su690,Ebert178,official diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves2.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves2.csv new file mode 100644 index 0000000000..6b022aa8c9 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves2.csv @@ -0,0 +1,71 @@ +121503c8-9564-4b48-9086-a22df717948e,f41b6458-2358-43e7-ae34-d2a0fbe083d8,http://snomed.info/sct,363406005 +121503c8-9564-4b48-9086-a22df717948e,e87fbe4b-74ef-44e4-8e36-70b2abb48895,http://snomed.info/sct,363406005 +121503c8-9564-4b48-9086-a22df717948e,df98a2ea-8129-4d1a-9a9f-13a0292f6d1d,http://snomed.info/sct,363406005 +121503c8-9564-4b48-9086-a22df717948e,d554ba19-e081-4979-9c0c-a72cd6e5e8ea,http://snomed.info/sct,363406005 +121503c8-9564-4b48-9086-a22df717948e,b8eccdce-7261-4402-9aa0-7360ae6bf53b,http://snomed.info/sct,363406005 +121503c8-9564-4b48-9086-a22df717948e,9e598086-27bb-4e50-8988-9a40eb3c178f,http://snomed.info/sct,363406005 +121503c8-9564-4b48-9086-a22df717948e,80787532-559e-4999-ac25-75c462ce4ef1,http://snomed.info/sct,363406005 +121503c8-9564-4b48-9086-a22df717948e,46d69851-eca3-42e2-b142-88c5578f6cff,http://snomed.info/sct,363406005 +121503c8-9564-4b48-9086-a22df717948e,248e951c-e0ce-4e4b-afae-6273fd109c9d,http://snomed.info/sct,363406005 +121503c8-9564-4b48-9086-a22df717948e,06e446da-6d66-4d97-a457-4abf2a0d2e24,http://snomed.info/sct,363406005 +2b36c1e2-bbe1-45ae-8124-4adad2677702,e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76,http://snomed.info/sct,10509002 +2b36c1e2-bbe1-45ae-8124-4adad2677702,db8c0b3e-48f0-43ac-aa03-9ae9a251cb82,http://snomed.info/sct,10509002 +2b36c1e2-bbe1-45ae-8124-4adad2677702,badbd940-9839-4472-aa66-3382d8823c80,http://snomed.info/sct,10509002 +7001ad9c-34d2-4eb5-8165-5fdc2147f469,f1fd855c-802c-417a-ab7c-14a3c9daafc6,http://snomed.info/sct,40055000 +7001ad9c-34d2-4eb5-8165-5fdc2147f469,eb373264-da60-4e98-af7c-e3021fdd8d4b,http://snomed.info/sct,40055000 +7001ad9c-34d2-4eb5-8165-5fdc2147f469,aa5a32b3-3993-4eb5-afce-489d0e7413cf,http://snomed.info/sct,40055000 +7001ad9c-34d2-4eb5-8165-5fdc2147f469,5300158c-92cb-40fe-bc14-a449d7b3c1c5,http://snomed.info/sct,40055000 +7001ad9c-34d2-4eb5-8165-5fdc2147f469,171ad058-c082-46ab-b9cc-d7bba6e4e3cd,http://snomed.info/sct,40055000 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,e620d7ee-6cfe-4f04-ba06-1d0b39f7624d,http://snomed.info/sct,444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,bb9c4fc1-795a-4492-b065-1f497fe18bb2,http://snomed.info/sct,444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc,http://snomed.info/sct,444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,a16f311d-d6dc-486e-8eb0-7cd53a5333ee,http://snomed.info/sct,444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8f3ad6ad-a457-484e-a455-f0711e77b2ba,http://snomed.info/sct,444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8dfd419b-0b83-4ba7-8fe6-324e2f382bd6,http://snomed.info/sct,444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,061521f0-7f7e-41a5-ad35-b30fe958dea7,http://snomed.info/sct,444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,031f1c86-16ec-42e1-a155-81456e778fed,http://snomed.info/sct,444814009 +9360820c-8602-4335-8b50-c88d627a0c20,f1dd0d5f-c410-484b-984c-2ba38cee79ba,http://snomed.info/sct,94260004 +9360820c-8602-4335-8b50-c88d627a0c20,eaee8845-e4ed-42e1-9098-99b3f7e14dc3,http://snomed.info/sct,94260004 +9360820c-8602-4335-8b50-c88d627a0c20,c892bf2f-a7bc-407e-9508-c778a9b8761c,http://snomed.info/sct,94260004 +9360820c-8602-4335-8b50-c88d627a0c20,c39927f7-d23a-475c-b325-c1de4b51e280,http://snomed.info/sct,94260004 +9360820c-8602-4335-8b50-c88d627a0c20,be4b757d-70f1-464c-bb29-6f9a0f6cb05e,http://snomed.info/sct,94260004 +9360820c-8602-4335-8b50-c88d627a0c20,8922ff1b-4bc5-41b4-8512-5aad1517e2eb,http://snomed.info/sct,94260004 +9360820c-8602-4335-8b50-c88d627a0c20,77fc5f45-e51d-41e2-8356-daa27ebd8268,http://snomed.info/sct,94260004 +9360820c-8602-4335-8b50-c88d627a0c20,6794fbaf-e715-4e22-bafa-b7e594550ff7,http://snomed.info/sct,94260004 +9360820c-8602-4335-8b50-c88d627a0c20,66b38727-96ea-43ad-bff7-5f4df5d779a9,http://snomed.info/sct,94260004 +9360820c-8602-4335-8b50-c88d627a0c20,55cde383-2eb1-42d9-b5c6-698d6eade389,http://snomed.info/sct,94260004 +9360820c-8602-4335-8b50-c88d627a0c20,549d3ef5-a05e-4b8e-acba-3d70f26a82f6,http://snomed.info/sct,94260004 +9360820c-8602-4335-8b50-c88d627a0c20,4dd7824b-4b41-4402-bc42-de016e1bfcd9,http://snomed.info/sct,94260004 +9360820c-8602-4335-8b50-c88d627a0c20,29386b12-9af9-4c21-9f82-858998b388bb,http://snomed.info/sct,94260004 +9360820c-8602-4335-8b50-c88d627a0c20,251ddff9-1588-4790-8f40-cd6ab1d9b7fc,http://snomed.info/sct,94260004 +9360820c-8602-4335-8b50-c88d627a0c20,1dcedab1-cf3c-46dc-859b-cc3fb63e8375,http://snomed.info/sct,94260004 +9360820c-8602-4335-8b50-c88d627a0c20,1cea8432-f8a0-4746-ab5c-8f195fc07c55,http://snomed.info/sct,94260004 +a7eb2ce7-1075-426c-addd-957b861b0e55,eaf99c09-419d-4162-8417-5a9d7e042cd4,http://snomed.info/sct,33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55,e35f5823-4533-49e5-9652-79d733be6bef,http://snomed.info/sct,33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55,b77b04ef-5eda-418c-a6fb-528a2d0a171a,http://snomed.info/sct,33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55,8ddb74fc-46d5-4e94-a0ee-6761b292ae95,http://snomed.info/sct,33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55,38171e22-b35d-4161-be15-80243be37b99,http://snomed.info/sct,33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55,31925a68-7a48-412c-b4f8-aef92498dda1,http://snomed.info/sct,33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55,258191b8-2b42-4473-9ba2-be0ba3561767,http://snomed.info/sct,33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55,1146cf52-573f-4667-97c3-abf235f9a83b,http://snomed.info/sct,33737001 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,da8db730-f051-42d0-a2db-a3577d96e9bd,http://snomed.info/sct,53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,d12274a5-9e03-4c78-ae3e-6cc27e91d737,http://snomed.info/sct,53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,abecd476-3911-4a6b-a1c6-b8ecea2fe63e,http://snomed.info/sct,53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc,http://snomed.info/sct,53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325,http://snomed.info/sct,53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,39170d67-8205-4636-84d7-d8575115d14c,http://snomed.info/sct,53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,31393667-dd7f-4c94-90c3-6cde75c67d3e,http://snomed.info/sct,53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,21372b39-2be0-4d0d-85c4-5d7e250d8f78,http://snomed.info/sct,53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,1452c50c-b0b9-472c-afb7-2f9e5a3c4717,http://snomed.info/sct,53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,01fac610-c309-46f8-9a0f-6b0f55c9915a,http://snomed.info/sct,53741008 +beff242e-580b-47c0-9844-c1a68c36c5bf,c879c300-7fdf-4b53-aa6a-a2b4a266b30c,http://snomed.info/sct,444814009 +beff242e-580b-47c0-9844-c1a68c36c5bf,7ed34fb1-0bbf-4c41-a101-1316ec483aa7,http://snomed.info/sct,444814009 +beff242e-580b-47c0-9844-c1a68c36c5bf,0447de38-1f2a-411c-9fd2-a9c62ab1f221,http://snomed.info/sct,444814009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,ad679e19-3e17-4c67-8b0d-dd4f7d862207,http://snomed.info/sct,195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,7d18555a-54c9-4c0a-bf3b-6305d0392a2a,http://snomed.info/sct,195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,6464e20b-55ec-4685-be3e-55bc3b9602d4,http://snomed.info/sct,195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,5ef84858-0480-4a34-8f43-fc962fe627b2,http://snomed.info/sct,195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,5378726d-7f2b-4c83-9762-eaf385915fa7,http://snomed.info/sct,195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,475461a2-3bd2-43fd-a5aa-7ce424203ae8,http://snomed.info/sct,195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,4025ceb3-04a6-41fb-8569-16a8dcce7ccc,http://snomed.info/sct,195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,http://snomed.info/sct,195662009 diff --git a/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson b/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson index ed940b44e4..ef49a62c8b 100644 --- a/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson +++ b/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson @@ -1,4 +1,4 @@ -{"resourceType":"Patient","id":"8ee183e2-b3c0-4151-be94-b945d6aa8c6d","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -1116549638004693619 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Onie555 Tremblay80"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Lawrence","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":2.442019549037137},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":45.557980450962866}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-21-1297"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99916275"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X27195897X"}],"name":[{"use":"official","family":"Krajcik437","given":["Seymour882"],"prefix":["Mr."],"suffix":["MD"]}],"telecom":[{"system":"phone","value":"555-757-3815","use":"home"}],"gender":"male","birthDate":"1970-11-22","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.27362325267794},{"url":"longitude","valueDecimal":-70.91799558593002}]}],"line":["855 Senger Union Suite 12"],"city":"Quincy","state":"Massachusetts","postalCode":"02169","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} +{"resourceType":"Patient","id":"8ee183e2-b3c0-4151-be94-b945d6aa8c6d","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -1116549638004693619 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Onie555 Tremblay80"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Lawrence","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":2.442019549037137},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":45.557980450962866}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-21-1297"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99916275"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X27195897X"}],"name":[{"use":"official","family":"Krajcik437","given":["Seymour882"],"prefix":["Mr."],"suffix":["MD"]},{"use":"nickname","family":"Burchard","given":["Arianne"],"prefix":["Ms."],"suffix":["MD"]}],"telecom":[{"system":"phone","value":"555-757-3815","use":"home"}],"gender":"male","birthDate":"1970-11-22","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.27362325267794},{"url":"longitude","valueDecimal":-70.91799558593002}]}],"line":["855 Senger Union Suite 12"],"city":"Quincy","state":"Massachusetts","postalCode":"02169","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} {"resourceType":"Patient","id":"beff242e-580b-47c0-9844-c1a68c36c5bf","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: 2489887534555043489 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Germaine912 Berge125"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Boston","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":0.11924342173460653},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":34.88075657826539}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"1f276fc3-7e91-4fc9-a287-be19228e8807"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"1f276fc3-7e91-4fc9-a287-be19228e8807"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-56-3056"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99940301"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X51286458X"}],"name":[{"use":"official","family":"Towne435","given":["Guy979"],"prefix":["Mr."]}],"telecom":[{"system":"phone","value":"555-273-5273","use":"home"}],"gender":"male","birthDate":"1983-09-06","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.416135340079045},{"url":"longitude","valueDecimal":-71.06798157703605}]}],"line":["598 Boyer Ramp"],"city":"Somerville","state":"Massachusetts","postalCode":"02138","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} {"resourceType":"Patient","id":"e62e52ae-2d75-4070-a0ae-3cc78d35ed08","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -4398174870245759556 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Idella49 Monahan736"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"F"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Chicopee","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":10.028345047642997},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":38.971654952357}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"2cc8ffdd-6233-4dd4-ba71-36eccb8204e2"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"2cc8ffdd-6233-4dd4-ba71-36eccb8204e2"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-43-1135"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99956022"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X85674863X"}],"name":[{"use":"official","family":"Ebert178","given":["Su690"],"prefix":["Ms."]}],"telecom":[{"system":"phone","value":"555-491-7400","use":"home"}],"gender":"female","birthDate":"1959-09-27","deceasedDateTime":"2009-09-27T09:11:55+00:00","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.39138295331103},{"url":"longitude","valueDecimal":-71.00439277761001}]}],"line":["460 Douglas Camp"],"city":"Boston","state":"Massachusetts","postalCode":"02108","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"S","display":"S"}],"text":"S"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} {"resourceType":"Patient","id":"2b36c1e2-bbe1-45ae-8124-4adad2677702","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: 346614667352111003 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2135-2","display":"Other"}},{"url":"text","valueString":"Other"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"María del Carmen27 Lozano749"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Ponce","state":"Puerto Rico","country":"PR"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":0.019152156118766007},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":19.980847843881232}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"2f9fd91d-9096-40e8-8307-825f68780599"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"2f9fd91d-9096-40e8-8307-825f68780599"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-42-8004"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99979084"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X28589103X"}],"name":[{"use":"official","family":"Ulibarri312","given":["Pedro316"],"prefix":["Mr."]}],"telecom":[{"system":"phone","value":"555-590-2206","use":"home"}],"gender":"male","birthDate":"1998-12-26","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.53265430498948},{"url":"longitude","valueDecimal":-73.25721481498516}]}],"line":["879 Hettinger Gardens"],"city":"Pittsfield","state":"Massachusetts","postalCode":"01201","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"S","display":"Never Married"}],"text":"Never Married"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"es","display":"Spanish"}],"text":"Spanish"}}]} diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java index 1248507905..c16d86e83f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java @@ -77,14 +77,15 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query, Collections.singletonList(inputContext.getIdColumn())); final List parsedColumns = parseExpressions(parserContext, query.getColumnsAsStrings()); - final List coercedColumns = - validateAndCoerceColumns(parsedColumns, resultType); - final Dataset queryDataset = joinAllColumns(coercedColumns); + // final List coercedColumns = + // validateAndCoerceColumns(parsedColumns, resultType); + // final Dataset queryDataset = joinAllColumns(coercedColumns); // Trim trailing nulls. // final Dataset trimmedDataset = trimTrailingNulls( // parserContext.getInputContext().getIdColumn(), coercedColumns, queryDataset); - final Dataset trimmedDataset = queryDataset; + final Dataset trimmedDataset = parsedColumns.get(parsedColumns.size() - 1).getFhirPath() + .getDataset(); // Apply the filters. final List filters = query.getFilters(); @@ -94,7 +95,7 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query, // Select the column values. final Column idColumn = inputContext.getIdColumn(); final Column[] columnValues = labelColumns( - coercedColumns.stream() + parsedColumns.stream() .map(FhirPathAndContext::getFhirPath) .map(FhirPath::getValueColumn), labelsAsStream(query.getColumns()) From 44f61bc814afdf69cd433e9b792fd1a9917364d4 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Mon, 17 Jul 2023 08:00:42 +1000 Subject: [PATCH 18/95] Remove column joins --- .../csiro/pathling/search/SearchExecutor.java | 8 +- .../pathling/extract/ExtractQueryTest.java | 13 +- .../multipleIndependentUnnestings.csv | 1 + ...leQuery.csv => simpleQueryWithAliases.csv} | 0 .../ExtractQueryTest/structuredResult.csv | 1 + .../toleranceOfColumnOrdering1.csv | 1 + .../toleranceOfColumnOrdering2.csv | 1 + .../java/au/csiro/pathling/QueryExecutor.java | 37 +-- .../java/au/csiro/pathling/QueryHelpers.java | 47 +++- .../aggregate/AggregateQueryExecutor.java | 7 +- .../extract/ExtractQueryExecutor.java | 130 +++------ .../au/csiro/pathling/fhirpath/FhirPath.java | 22 +- .../au/csiro/pathling/fhirpath/Nesting.java | 62 ++++ .../csiro/pathling/fhirpath/NestingKey.java | 5 + .../pathling/fhirpath/NodeDefinition.java | 10 + .../pathling/fhirpath/NonLiteralPath.java | 179 ++---------- .../fhirpath/ReferenceNestingKey.java | 16 ++ .../pathling/fhirpath/ResourceDefinition.java | 23 +- .../csiro/pathling/fhirpath/ResourcePath.java | 37 +-- .../au/csiro/pathling/fhirpath/Temporal.java | 10 +- .../fhirpath/TopologicalExpressionSorter.java | 20 -- .../fhirpath/UntypedResourcePath.java | 42 +-- .../fhirpath/element/BooleanPath.java | 6 +- .../pathling/fhirpath/element/CodingPath.java | 10 +- .../pathling/fhirpath/element/DatePath.java | 10 +- .../fhirpath/element/DateTimePath.java | 11 +- .../fhirpath/element/DecimalPath.java | 21 +- .../fhirpath/element/ElementDefinition.java | 36 ++- .../fhirpath/element/ElementPath.java | 60 ++-- .../fhirpath/element/ExtensionPath.java | 6 +- .../fhirpath/element/IntegerPath.java | 21 +- .../fhirpath/element/QuantityPath.java | 19 +- .../fhirpath/element/ReferenceDefinition.java | 6 +- .../element/ReferenceExtensionDefinition.java | 5 +- .../fhirpath/element/ReferencePath.java | 6 +- .../pathling/fhirpath/element/StringPath.java | 8 +- .../pathling/fhirpath/element/TimePath.java | 10 +- .../fhirpath/function/AggregateFunction.java | 25 +- .../fhirpath/function/CountFunction.java | 1 + .../fhirpath/function/EmptyFunction.java | 6 +- .../fhirpath/function/ExistsFunction.java | 8 +- .../fhirpath/function/ExtensionFunction.java | 3 +- .../fhirpath/function/FirstFunction.java | 10 +- .../fhirpath/function/IifFunction.java | 11 +- .../fhirpath/function/NotFunction.java | 4 +- .../fhirpath/function/ResolveFunction.java | 48 ++-- .../function/ReverseResolveFunction.java | 74 ++--- .../fhirpath/function/SumFunction.java | 1 + .../fhirpath/function/UntilFunction.java | 11 +- .../fhirpath/function/WhereFunction.java | 9 +- .../terminology/DesignationFunction.java | 15 +- .../function/terminology/DisplayFunction.java | 7 +- .../terminology/MemberOfFunction.java | 9 +- .../terminology/PropertyFunction.java | 19 +- .../terminology/SubsumesFunction.java | 11 +- .../terminology/TranslateFunction.java | 15 +- .../fhirpath/literal/LiteralPath.java | 44 ++- .../fhirpath/literal/NullLiteralPath.java | 4 +- .../fhirpath/literal/StringLiteralPath.java | 20 +- .../fhirpath/operator/BooleanOperator.java | 9 +- .../fhirpath/operator/CombineOperator.java | 16 +- .../fhirpath/operator/ComparisonOperator.java | 11 +- .../operator/DateArithmeticOperator.java | 3 +- .../fhirpath/operator/MathOperator.java | 3 +- .../fhirpath/operator/MembershipOperator.java | 9 +- .../operator/PathTraversalOperator.java | 63 ++--- .../fhirpath/parser/InvocationVisitor.java | 8 +- .../fhirpath/parser/ParserContext.java | 39 +-- .../pathling/fhirpath/parser/TermVisitor.java | 5 +- .../pathling/fhirpath/parser/Visitor.java | 4 +- .../pathling/views/FhirViewExecutor.java | 74 +---- .../pathling/fhirpath/NonLiteralPathTest.java | 252 ++++++++--------- .../pathling/fhirpath/OrderableTest.java | 265 ++++++++---------- .../function/ReverseResolveFunctionTest.java | 14 +- .../assertions/BaseFhirPathAssertion.java | 17 +- .../test/assertions/DatasetAssert.java | 2 + .../test/builders/ElementPathBuilder.java | 28 +- .../test/builders/ParserContextBuilder.java | 2 +- .../test/builders/ResourcePathBuilder.java | 3 +- .../builders/UntypedResourcePathBuilder.java | 6 - .../pathling/test/helpers/FhirHelpers.java | 3 +- .../pathling/test/helpers/SparkHelpers.java | 20 +- .../au/csiro/pathling/utilities/Strings.java | 9 +- 83 files changed, 938 insertions(+), 1189 deletions(-) rename fhir-server/src/test/resources/responses/ExtractQueryTest/{simpleQuery.csv => simpleQueryWithAliases.csv} (100%) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/NestingKey.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/NodeDefinition.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/ReferenceNestingKey.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/TopologicalExpressionSorter.java diff --git a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java index 27ab4a110a..935bc66d60 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java @@ -173,10 +173,10 @@ private Dataset initializeDataset() { } // Update the context to build the next expression from the same dataset. - currentContext = currentContext - .copy(currentContext.getExpression(), fhirPath.getDataset(), fhirPath.getIdColumn(), - currentContext.getEidColumn(), fhirPath.getValueColumn(), - currentContext.isSingular(), currentContext.getThisColumn()); + currentContext = currentContext.copy(currentContext.getExpression(), + fhirPath.getDataset(), fhirPath.getIdColumn(), fhirPath.getValueColumn(), + fhirPath.getOrderingColumn(), currentContext.isSingular(), + currentContext.getThisColumn()); } // Combine all the columns at this level with AND logic. diff --git a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java index 38358a7e1d..beadf0e4df 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java @@ -106,16 +106,16 @@ void simpleQueryWithAliases() { ExpressionWithLabel.withExpressionAsLabel("gender"), ExpressionWithLabel.of("name.given.first()", "given_name"), ExpressionWithLabel.of("reverseResolve(Condition.subject).count().toString()", - "patient_count") + "condition_count") ), List.of("gender = 'female'"), Optional.empty() ); final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); - assertArrayEquals(new String[]{"id", "gender", "given_name", "patient_count"}, + assertArrayEquals(new String[]{"id", "gender", "given_name", "condition_count"}, result.columns()); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/simpleQuery.csv"); + .hasRows(spark, "responses/ExtractQueryTest/simpleQueryWithAliases.csv"); } @Test @@ -198,9 +198,8 @@ void resolveAndCodingLiteralColumn() { .withLimit(10) .build(); - final Dataset result = executor.buildQuery(request); + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .debugAllRows() .hasRows(spark, "responses/ExtractQueryTest/resolveAndCodingLiteralColumn.csv"); } @@ -348,7 +347,6 @@ void linkedUnnesting() { .build(); final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); - result.show(false); assertThat(result) .hasRows(spark, "responses/ExtractQueryTest/linkedUnnesting.csv"); } @@ -384,13 +382,11 @@ void toleranceOfColumnOrdering() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .debugAllRows() .hasRows(spark, "responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv"); final ExtractRequest request2 = new ExtractRequestBuilder(subjectResource) .withColumn("name.given") .withColumn("id") - .withColumn("name.family") .build(); final Dataset result2 = executor.buildQuery(request2); @@ -472,6 +468,7 @@ void structuredResult() { ); assertThat(result) + .debugAllRows() .hasRows(spark, "responses/ExtractQueryTest/structuredResult.csv"); } diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv index bcb613d176..f4430a36d7 100644 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv @@ -2,6 +2,7 @@ 2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316,Ulibarri312,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S 7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901,Heller342,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S 8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882,Krajcik437,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Arianne,Burchard,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M 9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Oberbrunner298,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M 9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Wuckert783,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M 9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Oberbrunner298,http://snomed.info/sct,87915002 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/simpleQuery.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/simpleQueryWithAliases.csv similarity index 100% rename from fhir-server/src/test/resources/responses/ExtractQueryTest/simpleQuery.csv rename to fhir-server/src/test/resources/responses/ExtractQueryTest/simpleQueryWithAliases.csv diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv index 3d205484f4..707aa688da 100644 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv @@ -1,6 +1,7 @@ 121503c8-9564-4b48-9086-a22df717948e,Ophelia894 2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316 7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Arianne 8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882 9360820c-8602-4335-8b50-c88d627a0c20,Karina848 9360820c-8602-4335-8b50-c88d627a0c20,Karina848 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv index 4c014986b1..437063bafd 100644 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv @@ -2,6 +2,7 @@ 2b36c1e2-bbe1-45ae-8124-4adad2677702,Ulibarri312,Pedro316 7001ad9c-34d2-4eb5-8165-5fdc2147f469,Heller342,Cherryl901 8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437,Seymour882 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Burchard,Arianne 9360820c-8602-4335-8b50-c88d627a0c20,Oberbrunner298,Karina848 9360820c-8602-4335-8b50-c88d627a0c20,Wuckert783,Karina848 a7eb2ce7-1075-426c-addd-957b861b0e55,Botsford977,Shirley182 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv index 3e00c8c171..49a001cd9b 100644 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv @@ -2,6 +2,7 @@ Ophelia894,121503c8-9564-4b48-9086-a22df717948e,Gleichner915 Pedro316,2b36c1e2-bbe1-45ae-8124-4adad2677702,Ulibarri312 Cherryl901,7001ad9c-34d2-4eb5-8165-5fdc2147f469,Heller342 Seymour882,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437 +Arianne,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Burchard Karina848,9360820c-8602-4335-8b50-c88d627a0c20,Oberbrunner298 Karina848,9360820c-8602-4335-8b50-c88d627a0c20,Wuckert783 Shirley182,a7eb2ce7-1075-426c-addd-957b861b0e55,Botsford977 diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index ca98d7ff63..8fe315eb83 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -29,7 +29,6 @@ import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirPathAndContext; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.element.BooleanPath; import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; @@ -42,7 +41,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Optional; import java.util.function.BinaryOperator; @@ -92,25 +90,32 @@ protected QueryExecutor(@Nonnull final QueryConfiguration configuration, protected ParserContext buildParserContext(@Nonnull final FhirPath inputContext, @Nonnull final List groupingColumns) { return new ParserContext(inputContext, fhirContext, sparkSession, dataSource, - terminologyServiceFactory, groupingColumns, new HashMap<>()); + terminologyServiceFactory, groupingColumns); } @Nonnull - protected List parseExpressions( + protected List parseExpressions( @Nonnull final ParserContext parserContext, @Nonnull final Collection expressions) { - final List parsed = new ArrayList<>(); - ParserContext currentContext = parserContext; + return parseExpressions(parserContext, expressions, Optional.empty()); + } + + @Nonnull + protected List parseExpressions( + @Nonnull final ParserContext parserContext, @Nonnull final Collection expressions, + @Nonnull final Optional> contextDataset) { + final List parsed = new ArrayList<>(); + ParserContext currentContext = contextDataset.map(parserContext::withContextDataset).orElse( + parserContext); for (final String expression : expressions) { if (parsed.size() > 0) { - final FhirPathAndContext lastParsed = parsed.get(parsed.size() - 1); + final FhirPath lastParsed = parsed.get(parsed.size() - 1); // Create a new copy of the original parser context, except use the dataset from the last // column parse and reset the node IDs. - currentContext = parserContext.withContextDataset( - lastParsed.getFhirPath().getDataset()); + currentContext = parserContext.withContextDataset(lastParsed.getDataset()); } final Parser parser = new Parser(currentContext); // Add the parse result to the list of parsed expressions. - parsed.add(new FhirPathAndContext(parser.parse(expression), currentContext)); + parsed.add(parser.parse(expression)); } return parsed; } @@ -141,7 +146,7 @@ protected Dataset joinExpressionsAndFilters(final FhirPath inputContext, return filters.stream() .map(FhirPath::getDataset) .reduce(combinedGroupings, - ((result, element) -> join(element, idColumn, result, idColumn, JoinType.RIGHT_OUTER))); + ((result, element) -> join(result, idColumn, element, idColumn, JoinType.LEFT_OUTER))); } @Nonnull @@ -156,7 +161,7 @@ protected static Dataset joinExpressionsWithoutCorrelation(final FhirPath i // the use of RIGHT_OUTER join seems to be necessary to preserve the original // id column in the result .reduce(inputContext.getDataset(), - ((result, element) -> join(element, idColumn, result, idColumn, JoinType.RIGHT_OUTER))); + ((result, element) -> join(result, idColumn, element, idColumn, JoinType.LEFT_OUTER))); } @@ -245,10 +250,10 @@ private DatasetWithColumn getFilteredIds(@Nonnull final Iterable filters : operator.apply(filterColumn, filterValue); // Update the context to build the next expression from the same dataset. - currentContext = currentContext - .copy(currentContext.getExpression(), fhirPath.getDataset(), currentContext.getIdColumn(), - currentContext.getEidColumn(), currentContext.getValueColumn(), - currentContext.isSingular(), currentContext.getThisColumn()); + currentContext = currentContext.copy(currentContext.getExpression(), fhirPath.getDataset(), + currentContext.getIdColumn(), currentContext.getValueColumn(), + currentContext.getOrderingColumn(), currentContext.isSingular(), + currentContext.getThisColumn()); } requireNonNull(filterColumn); diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java b/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java index 84f4a13478..0d204f1237 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java @@ -19,8 +19,10 @@ import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static au.csiro.pathling.utilities.Strings.randomAlias; +import static java.util.stream.Collectors.toSet; import static org.apache.spark.sql.functions.col; import static org.apache.spark.sql.functions.lit; +import static org.apache.spark.sql.functions.posexplode_outer; import au.csiro.pathling.encoders.FhirEncoders; import au.csiro.pathling.fhirpath.FhirPath; @@ -44,6 +46,7 @@ import javax.annotation.Nonnull; import lombok.Getter; import lombok.Value; +import org.apache.commons.lang3.tuple.MutablePair; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; @@ -399,7 +402,7 @@ private static List checkColumnsAndFallback(@Nonnull final Dataset @Nonnull final List groupingColumns, @Nonnull final Column fallback) { final Set columnList = new HashSet<>(List.of(dataset.columns())); final Set groupingColumnNames = groupingColumns.stream().map(Column::toString) - .collect(Collectors.toSet()); + .collect(toSet()); if (columnList.containsAll(groupingColumnNames)) { return groupingColumns; } else { @@ -461,6 +464,32 @@ public static Dataset createEmptyDataset(@Nonnull final SparkSession spark, return spark.emptyDataset(encoder).toDF(); } + /** + * Explodes an array column from a provided dataset, preserving all the columns from this one and + * producing a new value and ordering column. + * + * @param dataset The dataset containing the array column. It should also contain all other + * columns that should be preserved alongside the new columns. + * @param arrayColumn The array column to explode + * @param outputColumns The output pair of columns: `left` is set to the new value column and + * `right` to the new ordering column + * @return the {@link Dataset} with the exploded array + */ + @Nonnull + public static Dataset explodeArray(@Nonnull final Dataset dataset, + @Nonnull final Column arrayColumn, + @Nonnull final MutablePair outputColumns) { + final Column[] allColumns = Stream.concat(Arrays.stream(dataset.columns()) + .map(dataset::col), Stream + .of(posexplode_outer(arrayColumn) + .as(new String[]{"index", "value"}))) + .toArray(Column[]::new); + final Dataset resultDataset = dataset.select(allColumns); + outputColumns.setLeft(resultDataset.col("value")); + outputColumns.setRight(resultDataset.col("index")); + return resultDataset; + } + /** * Represents a type of join that can be made between two {@link Dataset} objects. */ @@ -493,14 +522,6 @@ public enum JoinType { * Left outer join. */ LEFT_OUTER("left_outer"), - /** - * Right join. - */ - RIGHT("right"), - /** - * Right outer join. - */ - RIGHT_OUTER("right_outer"), /** * Left semi join. */ @@ -508,7 +529,11 @@ public enum JoinType { /** * Left anti join. */ - LEFT_ANTI("left_anti"); + LEFT_ANTI("left_anti"), + /** + * Right outer join. + */ + RIGHT_OUTER("right_outer"); @Nonnull @Getter @@ -553,7 +578,7 @@ public static class DatasetWithColumnMap { */ @Nonnull public Column getColumn(@Nonnull final Column originalColumn) { - return columnMap.get(originalColumn); + return Optional.ofNullable(columnMap.get(originalColumn)).orElse(originalColumn); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java index b41559a50f..976a7cb1f2 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java @@ -116,10 +116,9 @@ public ResultWithExpressions buildQuery(@Nonnull final AggregateRequest query) { // instead of just the raw resource. This is so that any aggregations that are performed // during the parse can use these columns for grouping, rather than the identity of each // resource. - final ResourcePath aggregationContext = inputContext - .copy(inputContext.getExpression(), groupingsAndFilters, idColumn, - inputContext.getEidColumn(), inputContext.getValueColumn(), inputContext.isSingular(), - Optional.empty()); + final ResourcePath aggregationContext = inputContext.copy(inputContext.getExpression(), + groupingsAndFilters, idColumn, inputContext.getValueColumn(), + inputContext.getOrderingColumn(), inputContext.isSingular(), Optional.empty()); final ParserContext aggregationParserContext = buildParserContext(aggregationContext, groupingColumns); final Parser aggregationParser = new Parser(aggregationParserContext); diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java index c16d86e83f..4fac0828ee 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java @@ -4,26 +4,20 @@ import static au.csiro.pathling.query.ExpressionWithLabel.labelsAsStream; import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; import au.csiro.pathling.QueryExecutor; -import au.csiro.pathling.QueryHelpers; import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.AbstractPath; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirPathAndContext; import au.csiro.pathling.fhirpath.Flat; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.fhirpath.TopologicalExpressionSorter; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -75,33 +69,37 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query, query.getSubjectResource().toCode(), true); final ParserContext parserContext = buildParserContext(inputContext, Collections.singletonList(inputContext.getIdColumn())); - final List parsedColumns = + final List parsedColumns = parseExpressions(parserContext, query.getColumnsAsStrings()); - // final List coercedColumns = - // validateAndCoerceColumns(parsedColumns, resultType); - // final Dataset queryDataset = joinAllColumns(coercedColumns); + final List coercedColumns = + validateAndCoerceColumns(parsedColumns, resultType); + final Dataset unfiltered = coercedColumns.get(parsedColumns.size() - 1).getDataset(); - // Trim trailing nulls. - // final Dataset trimmedDataset = trimTrailingNulls( - // parserContext.getInputContext().getIdColumn(), coercedColumns, queryDataset); - final Dataset trimmedDataset = parsedColumns.get(parsedColumns.size() - 1).getFhirPath() - .getDataset(); + final Dataset trimmed = trimTrailingNulls(inputContext.getIdColumn(), coercedColumns, + unfiltered); // Apply the filters. - final List filters = query.getFilters(); - final Dataset filteredDataset = filterDataset(inputContext, filters, trimmedDataset, - Column::and); + final Dataset filtered; + if (query.getFilters().isEmpty()) { + filtered = trimmed; + } else { + final List filters = query.getFilters(); + final List filterPaths = parseExpressions(parserContext, filters, + Optional.of(trimmed)); + final Dataset withFilters = filterPaths.get(filterPaths.size() - 1).getDataset(); + final Optional filterConstraint = filterPaths.stream() + .map(FhirPath::getValueColumn) + .reduce(Column::and); + filtered = filterConstraint.map(withFilters::filter).orElse(withFilters); + } // Select the column values. - final Column idColumn = inputContext.getIdColumn(); final Column[] columnValues = labelColumns( - parsedColumns.stream() - .map(FhirPathAndContext::getFhirPath) + coercedColumns.stream() .map(FhirPath::getValueColumn), labelsAsStream(query.getColumns()) ).toArray(Column[]::new); - final Dataset selectedDataset = filteredDataset.select(columnValues) - .filter(idColumn.isNotNull()); + final Dataset selectedDataset = filtered.select(columnValues); // If there is a limit, apply it. return query.getLimit().isPresent() @@ -109,28 +107,25 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query, : selectedDataset; } - private List validateAndCoerceColumns( - @Nonnull final List parsedColumns, + private List validateAndCoerceColumns( + @Nonnull final List columnParseResult, @Nonnull final ExtractResultType resultType) { // Perform any necessary String coercion. - final List coercedColumns = parsedColumns.stream() - .map(columnAndContext -> { - final FhirPath column = columnAndContext.getFhirPath(); + final List coerced = columnParseResult.stream() + .map(column -> { if (resultType == ExtractResultType.FLAT && !(column instanceof Flat) && column instanceof StringCoercible) { // If the result type is flat and the path is string-coercible, we can coerce it. final StringCoercible stringCoercible = (StringCoercible) column; - return new FhirPathAndContext(stringCoercible.asStringPath(column.getExpression()), - columnAndContext.getContext()); + return stringCoercible.asStringPath(column.getExpression()); } else { - return new FhirPathAndContext(column, columnAndContext.getContext()); + return column; } }).collect(toList()); // Validate the final set of paths. - for (final FhirPathAndContext columnAndContext : coercedColumns) { - final FhirPath column = columnAndContext.getFhirPath(); + for (final FhirPath column : coerced) { final boolean condition; if (resultType == ExtractResultType.FLAT) { // In flat mode, only flat columns are allowed. @@ -143,82 +138,23 @@ private List validateAndCoerceColumns( checkArgument(condition, "Column is not of a supported type: " + column.getExpression()); } - return coercedColumns; - } - - @Nonnull - private Dataset joinAllColumns( - @Nonnull final Collection columnsAndContexts) { - if (columnsAndContexts.isEmpty()) { - // If there are no columns, throw an error. - throw new IllegalArgumentException("No columns to join"); - - } else if (columnsAndContexts.size() == 1) { - // If there is only one column, skip joining and return its dataset. - final FhirPathAndContext fhirPathAndContext = columnsAndContexts.iterator().next(); - return fhirPathAndContext.getFhirPath().getDataset(); - } - - // Sort the columns by the nodes encountered while parsing. This ensures that we join them - // together in order from the general to the specific. - final List sorted = columnsAndContexts.stream() - .sorted(new TopologicalExpressionSorter()) - .collect(toList()); - - // Start with the first column and its unjoined dataset. - FhirPathAndContext left = sorted.get(0); - Dataset result = left.getFhirPath().getDataset(); - - // Move through the list of columns, joining each one to the result of the previous join. - for (final FhirPathAndContext right : sorted.subList(1, sorted.size())) { - final List leftJoinColumns = new ArrayList<>(); - final List rightJoinColumns = new ArrayList<>(); - - // The join column always includes the resource ID. - leftJoinColumns.add(left.getFhirPath().getIdColumn()); - rightJoinColumns.add(right.getFhirPath().getIdColumn()); - - // Add the intersection of the nodes present in both the left and right column contexts. - final List commonNodes = new ArrayList<>( - left.getContext().getNodeIdColumns().keySet()); - commonNodes.retainAll(right.getContext().getNodeIdColumns().keySet()); - final FhirPathAndContext finalLeft = left; - leftJoinColumns.addAll(commonNodes.stream() - .flatMap(key -> finalLeft.getContext().getNodeIdColumns().get(key).stream()) - .collect(toSet())); - rightJoinColumns.addAll(commonNodes.stream() - .flatMap(key -> right.getContext().getNodeIdColumns().get(key).stream()) - .collect(toSet())); - - // Use a left outer join, so that we don't lose rows that don't have a value for the right - // column. - result = QueryHelpers.join(result, leftJoinColumns, right.getFhirPath().getDataset(), - rightJoinColumns, JoinType.LEFT_OUTER); - - // The result of the join becomes the left side of the next join. - left = right; - } - - return result; + return coerced; } - // @Nonnull - // private Dataset filterInvalidRowCombinations(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, ) - @Nonnull private Dataset trimTrailingNulls(final @Nonnull Column idColumn, @Nonnull final List expressions, @Nonnull final Dataset dataset) { checkArgument(!expressions.isEmpty(), "At least one expression is required"); - final Column[] nonSingularColumns = expressions.stream() + final List nonSingularColumns = expressions.stream() .filter(fhirPath -> !fhirPath.isSingular()) .map(FhirPath::getValueColumn) - .toArray(Column[]::new); + .collect(toList()); - if (nonSingularColumns.length == 0) { + if (nonSingularColumns.isEmpty()) { return dataset; } else { - final Column additionalCondition = Arrays.stream(nonSingularColumns) + final Column additionalCondition = nonSingularColumns.stream() .map(Column::isNotNull) .reduce(Column::or) .get(); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java index 861631e5ca..023454308e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java @@ -28,7 +28,7 @@ * * @author John Grimes */ -public interface FhirPath extends Orderable { +public interface FhirPath { /** * @return the FHIRPath expression that represents this path @@ -42,6 +42,13 @@ public interface FhirPath extends Orderable { @Nonnull Dataset getDataset(); + /** + * @param nesting the nesting information to use for ordering + * @return the {@link Dataset} that can be used to evaluate this path against data, ordered + */ + @Nonnull + Dataset getOrderedDataset(@Nonnull Nesting nesting); + /** * @return a {@link Column} within the dataset containing the identity of the subject resource */ @@ -54,6 +61,12 @@ public interface FhirPath extends Orderable { @Nonnull Column getValueColumn(); + /** + * @return a {@link Column} that can be used to order the dataset, for paths that have a defined + * order + */ + Optional getOrderingColumn(); + /** * @return an indicator of whether this path represents a single-valued collection */ @@ -103,8 +116,6 @@ public interface FhirPath extends Orderable { * @param expression the FHIRPath expression that represents the result * @param idColumn a {@link Column} within the dataset containing the identity of the subject * resource - * @param eidColumn a {@link Column} that represents the unique ID for an element within a - * collection * @param valueColumn a {@link Column} within the dataset containing the values of the nodes * @param singular an indicator of whether this path represents a single-valued collection * @param thisColumn for paths that traverse from the {@code $this} keyword, this column refers to @@ -113,9 +124,8 @@ public interface FhirPath extends Orderable { */ @Nonnull NonLiteralPath combineWith(@Nonnull FhirPath target, @Nonnull Dataset dataset, - @Nonnull String expression, @Nonnull Column idColumn, @Nonnull Optional eidColumn, - @Nonnull Column valueColumn, boolean singular, @Nonnull Optional thisColumn); - + @Nonnull String expression, @Nonnull Column idColumn, @Nonnull Column valueColumn, + boolean singular, @Nonnull Optional thisColumn); /** * Prints out to stdout all the ids and values of all the elements in this path. For debugging diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java new file mode 100644 index 0000000000..44c8f09dcd --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java @@ -0,0 +1,62 @@ +package au.csiro.pathling.fhirpath; + +import static java.util.stream.Collectors.toList; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; + +public class Nesting { + + @Nonnull + private final Map nesting; + + public Nesting() { + this.nesting = new LinkedHashMap<>(); + } + + @Nonnull + public NonLiteralPath updateOrRetrieve(@Nonnull final NestingKey key, + @Nonnull final String expression, @Nonnull final Dataset dataset, + final boolean singular, @Nonnull final Optional thisColumn, + @Nonnull final Function updater) { + final boolean hit = nesting.containsKey(key); + final NonLiteralPath result = nesting.computeIfAbsent(key, updater); + if (hit) { + return result.copy(expression, dataset, result.getIdColumn(), result.getValueColumn(), + result.getOrderingColumn(), singular, thisColumn); + } else { + return result; + } + } + + public boolean isOrderable() { + return nesting.values().stream() + .allMatch(path -> path.getOrderingColumn().isPresent()); + } + + @Nonnull + public List getOrderingColumns() { + return nesting.values().stream() + .map(FhirPath::getOrderingColumn) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(toList()); + } + + public void removeLast() { + @Nullable NestingKey last = null; + for (final NestingKey key : nesting.keySet()) { + last = key; + } + nesting.remove(last); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NestingKey.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NestingKey.java new file mode 100644 index 0000000000..8327ef4e01 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NestingKey.java @@ -0,0 +1,5 @@ +package au.csiro.pathling.fhirpath; + +public interface NestingKey { + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NodeDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NodeDefinition.java new file mode 100644 index 0000000000..876894b9b1 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NodeDefinition.java @@ -0,0 +1,10 @@ +package au.csiro.pathling.fhirpath; + +import javax.annotation.Nonnull; + +public interface NodeDefinition { + + @Nonnull + NestingKey getNestingKey(); + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java index 363b3c26bb..439742c9c5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java @@ -17,18 +17,10 @@ package au.csiro.pathling.fhirpath; -import static au.csiro.pathling.QueryHelpers.createColumn; import static au.csiro.pathling.QueryHelpers.getUnionableColumns; import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static au.csiro.pathling.utilities.Preconditions.checkPresent; -import static org.apache.spark.sql.functions.array; -import static org.apache.spark.sql.functions.concat; -import static org.apache.spark.sql.functions.lit; -import static org.apache.spark.sql.functions.posexplode_outer; -import static org.apache.spark.sql.functions.struct; -import static org.apache.spark.sql.functions.when; -import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.function.NamedFunction; import au.csiro.pathling.fhirpath.literal.NullLiteralPath; @@ -38,7 +30,6 @@ import java.util.stream.Stream; import javax.annotation.Nonnull; import lombok.Getter; -import org.apache.commons.lang3.tuple.MutablePair; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; @@ -51,9 +42,6 @@ @Getter public abstract class NonLiteralPath implements FhirPath { - private static final String THIS_ORDERING_COLUMN_NAME = "eid"; - private static final String THIS_VALUE_COLUMN_NAME = "value"; - @Nonnull protected final String expression; @@ -63,12 +51,6 @@ public abstract class NonLiteralPath implements FhirPath { @Nonnull protected final Column idColumn; - /** - * A {@link Column} that represents the unique ID for an element within a collection. - */ - @Nonnull - protected final Optional eidColumn; - @Nonnull protected final Column valueColumn; @@ -92,8 +74,7 @@ public abstract class NonLiteralPath implements FhirPath { protected Optional thisColumn; protected NonLiteralPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn) { @@ -104,19 +85,32 @@ protected NonLiteralPath(@Nonnull final String expression, @Nonnull final Datase "Value column name not present in dataset"); thisColumn.ifPresent(col -> checkArgument(datasetColumns.contains(col.toString()), "$this column name not present in dataset")); - eidColumn.ifPresent(col -> checkArgument(datasetColumns.contains(col.toString()), - "eid column name not present in dataset")); this.expression = expression; this.dataset = dataset; this.idColumn = idColumn; - this.eidColumn = eidColumn; this.valueColumn = valueColumn; this.singular = singular; this.currentResource = currentResource; this.thisColumn = thisColumn; } + /** + * Get an ordering {@link Column} from any of the inputs, if there is one. + * + * @param inputs a collection of objects + * @return a {@link Column}, if one was found + */ + @Nonnull + public static Optional findOrderingColumn(@Nonnull final Object... inputs) { + return Stream.of(inputs) + .filter(input -> input instanceof NonLiteralPath) + .map(path -> (NonLiteralPath) path) + .filter(path -> path.getOrderingColumn().isPresent()) + .findFirst() + .flatMap(NonLiteralPath::getOrderingColumn); + } + /** * Gets a this {@link Column} from any of the inputs, if there is one. * @@ -133,23 +127,11 @@ public static Optional findThisColumn(@Nonnull final Object... inputs) { .flatMap(NonLiteralPath::getThisColumn); } - @Override - public boolean hasOrder() { - return isSingular() || eidColumn.isPresent(); - } - - @Nonnull - @Override - public Dataset getOrderedDataset() { - checkHasOrder(); - return eidColumn.map(c -> getDataset().orderBy(c)).orElse(getDataset()); - } - @Nonnull @Override - public Column getOrderingColumn() { - checkHasOrder(); - return eidColumn.orElse(ORDERING_NULL_VALUE); + public Dataset getOrderedDataset(@Nonnull final Nesting nesting) { + final Column[] sortColumns = nesting.getOrderingColumns().toArray(new Column[0]); + return getDataset().orderBy(sortColumns); } /** @@ -173,22 +155,6 @@ public Column getExtensionContainerColumn() { @Nonnull public abstract Optional getChildElement(@Nonnull final String name); - /** - * Get an element ID {@link Column} from any of the inputs, if there is one. - * - * @param inputs a collection of objects - * @return a {@link Column}, if one was found - */ - @Nonnull - public static Optional findEidColumn(@Nonnull final Object... inputs) { - return Stream.of(inputs) - .filter(input -> input instanceof NonLiteralPath) - .map(path -> (NonLiteralPath) path) - .filter(path -> path.getEidColumn().isPresent()) - .findFirst() - .flatMap(NonLiteralPath::getEidColumn); - } - /** * Creates a copy of this NonLiteralPath with an updated {@link Dataset}, ID and value * {@link Column}s. @@ -196,8 +162,8 @@ public static Optional findEidColumn(@Nonnull final Object... inputs) { * @param expression an updated expression to describe the new NonLiteralPath * @param dataset the new Dataset that can be used to evaluate this NonLiteralPath against data * @param idColumn the new resource identity column - * @param eidColumn the new element identity column * @param valueColumn the new expression value column + * @param orderingColumn the new ordering column * @param singular the new singular value * @param thisColumn a list of columns containing the collection being iterated, for cases where a * path is being created to represent the {@code $this} keyword @@ -205,19 +171,22 @@ public static Optional findEidColumn(@Nonnull final Object... inputs) { */ @Nonnull public abstract NonLiteralPath copy(@Nonnull String expression, @Nonnull Dataset dataset, - @Nonnull Column idColumn, @Nonnull Optional eidColumn, @Nonnull Column valueColumn, - boolean singular, @Nonnull Optional thisColumn); + @Nonnull Column idColumn, @Nonnull Column valueColumn, + @Nonnull Optional orderingColumn, boolean singular, + @Nonnull Optional thisColumn); @Nonnull @Override public FhirPath withExpression(@Nonnull final String expression) { - return copy(expression, dataset, idColumn, eidColumn, valueColumn, singular, thisColumn); + return copy(expression, dataset, idColumn, valueColumn, getOrderingColumn(), singular, + thisColumn); } @Nonnull @Override public FhirPath withDataset(@Nonnull final Dataset dataset) { - return copy(expression, dataset, idColumn, eidColumn, valueColumn, singular, thisColumn); + return copy(expression, dataset, idColumn, valueColumn, getOrderingColumn(), singular, + thisColumn); } /** @@ -227,96 +196,8 @@ public FhirPath withDataset(@Nonnull final Dataset dataset) { */ @Nonnull public NonLiteralPath toThisPath() { - final DatasetWithColumn inputWithThis = createColumn( - this.getDataset(), this.makeThisColumn()); - - return copy(NamedFunction.THIS, inputWithThis.getDataset(), this.getIdColumn(), - this.getEidColumn(), this.getValueColumn(), true, - Optional.of(inputWithThis.getColumn())); - } - - @Nonnull - public Optional getThisOrderingColumn() { - return getThisColumn().map(thisColumn -> thisColumn.getField(THIS_ORDERING_COLUMN_NAME)); - } - - @Nonnull - public Optional getThisValueColumn() { - return getThisColumn().map(thisColumn -> thisColumn.getField(THIS_VALUE_COLUMN_NAME)); - } - - /** - * Constructs a $this column for this path as a structure with two fields: `eid` and `value`. - * - * @return a new {@link Column} - */ - @Nonnull - private Column makeThisColumn() { - return struct( - getOrderingColumn().alias(THIS_ORDERING_COLUMN_NAME), - getValueColumn().alias(THIS_VALUE_COLUMN_NAME)); - } - - /** - * Constructs the new value of the element ID column, based on its current value in the parent - * path and the index of the element in the child path. - *

- * If the parent's eid is None it indicates that the parent is singular and the new eid needs to - * be created based on the value of the indexColumn: - *

    - *
  • if the indexColumns is null then the eid can be set to null.
  • - *
  • otherwise it should be a one element array with the indexColumn value.
  • - *
- *

- * If the parent eid exists then the value of the index column needs to be appended to the - * existing id. - *

    - *
  • if the existing eid is null then the index must be null as well and the new id should be - * null.
  • - *
  • otherwise the existing eid needs to be extended with the value of indexColumn or 0 if index - * column is null.
  • - *
- * - * @param indexColumn the {@link Column} with the child path element index - * @return the element ID {@link Column} for the child path. - */ - @Nonnull - public Column expandEid(@Nonnull final Column indexColumn) { - final Column indexOrZero = - when(indexColumn.isNotNull(), array(indexColumn)) - .otherwise(array(lit(0))); - final Column indexOrNull = - when(indexColumn.isNotNull(), array(indexColumn)) - .otherwise(ORDERING_NULL_VALUE); - return getEidColumn() - .map(eid -> when(eid.isNull(), ORDERING_NULL_VALUE).otherwise( - concat(eid, indexOrZero))).orElse(indexOrNull); - } - - /** - * Explodes an array column from a provided dataset preserving all the columns from this one and - * producing updated element ids. - * - * @param arrayDataset the dataset containing the array column. It should also contain all columns - * from this dataset. - * @param arrayCol the array column to explode. - * @param outValueAndEidCols the output pair of columns: `left` is set to the new value column and - * `right` to the new eid column. - * @return the {@link Dataset} with the exploded array. - */ - @Nonnull - public Dataset explodeArray(@Nonnull final Dataset arrayDataset, - @Nonnull final Column arrayCol, - @Nonnull final MutablePair outValueAndEidCols) { - final Column[] allColumns = Stream.concat(Arrays.stream(dataset.columns()) - .map(dataset::col), Stream - .of(posexplode_outer(arrayCol) - .as(new String[]{"index", "value"}))) - .toArray(Column[]::new); - final Dataset resultDataset = arrayDataset.select(allColumns); - outValueAndEidCols.setLeft(resultDataset.col("value")); - outValueAndEidCols.setRight(expandEid(resultDataset.col("index"))); - return resultDataset; + return copy(NamedFunction.THIS, this.getDataset(), this.getIdColumn(), this.getValueColumn(), + this.getOrderingColumn(), true, Optional.of(this.getValueColumn())); } @Override diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ReferenceNestingKey.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ReferenceNestingKey.java new file mode 100644 index 0000000000..9134f4839e --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ReferenceNestingKey.java @@ -0,0 +1,16 @@ +package au.csiro.pathling.fhirpath; + +import au.csiro.pathling.fhirpath.element.ElementDefinition; +import javax.annotation.Nonnull; +import lombok.Value; + +@Value +public class ReferenceNestingKey implements NestingKey { + + @Nonnull + ElementDefinition referenceDefinition; + + @Nonnull + ResourceDefinition resourceDefinition; + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourceDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourceDefinition.java index b3ee66de33..923a54d02c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourceDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourceDefinition.java @@ -33,7 +33,7 @@ * * @author John Grimes */ -public class ResourceDefinition { +public class ResourceDefinition implements NestingKey { /** * The HAPI FHIR resource type. @@ -45,14 +45,24 @@ public class ResourceDefinition { @Nonnull private final RuntimeResourceDefinition definition; + /** + * The parent definition of this resource. This may be empty, in the case of the root resource. It + * may also be populated with the element definition that linked to this resource, for example a + * reference element. + */ + @Nonnull + private final Optional parent; + /** * @param resourceType The {@link ResourceType} that describes this resource * @param definition The HAPI {@link RuntimeResourceDefinition} for this resource */ public ResourceDefinition(@Nonnull final ResourceType resourceType, - @Nonnull final RuntimeResourceDefinition definition) { + @Nonnull final RuntimeResourceDefinition definition, + @Nonnull final Optional parent) { this.resourceType = resourceType; this.definition = definition; + this.parent = parent; } /** @@ -65,7 +75,7 @@ public ResourceDefinition(@Nonnull final ResourceType resourceType, public Optional getChildElement(@Nonnull final String name) { final Optional childDefinition = Optional .ofNullable(definition.getChildByName(name)); - return childDefinition.map(definition -> ElementDefinition.build(definition, name)); + return childDefinition.map(definition -> ElementDefinition.build(definition, name, this)); } /** @@ -95,12 +105,15 @@ public boolean equals(final Object o) { return false; } final ResourceDefinition that = (ResourceDefinition) o; - return resourceType == that.resourceType; + return resourceType == that.resourceType + // Recursively compare parent definitions. + && Objects.equals(parent, that.parent); } @Override public int hashCode() { - return Objects.hash(resourceType); + // Recursively hash parent definitions. + return Objects.hash(resourceType, parent); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java index aa0c1d15b4..724639f8df 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java @@ -18,7 +18,6 @@ package au.csiro.pathling.fhirpath; import static au.csiro.pathling.QueryHelpers.aliasAllColumns; -import static au.csiro.pathling.QueryHelpers.createColumns; import static au.csiro.pathling.utilities.Preconditions.checkPresent; import static java.util.Objects.requireNonNull; import static org.apache.spark.sql.functions.col; @@ -62,11 +61,10 @@ public class ResourcePath extends NonLiteralPath { private final Map elementsToColumns; protected ResourcePath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, final boolean singular, @Nonnull final Optional thisColumn, @Nonnull final ResourceDefinition definition, @Nonnull final Map elementsToColumns) { - super(expression, dataset, idColumn, eidColumn, valueColumn, singular, Optional.empty(), + super(expression, dataset, idColumn, valueColumn, singular, Optional.empty(), thisColumn); this.definition = definition; this.elementsToColumns = elementsToColumns; @@ -110,7 +108,8 @@ public static ResourcePath build(@Nonnull final FhirContext fhirContext, final String resourceCode = resourceType.toCode(); final RuntimeResourceDefinition hapiDefinition = fhirContext .getResourceDefinition(resourceCode); - final ResourceDefinition definition = new ResourceDefinition(resourceType, hapiDefinition); + final ResourceDefinition definition = new ResourceDefinition(resourceType, hapiDefinition, + Optional.empty()); // Retrieve the dataset for the resource type using the supplied resource reader. final Dataset dataset = dataSource.read(resourceType); @@ -126,6 +125,7 @@ public static ResourcePath build(@Nonnull final FhirContext fhirContext, // search). finalDataset = dataset; finalIdColumn = idColumn; + //noinspection ReturnOfNull elementsToColumns = Stream.of(dataset.columns()) .collect(Collectors.toMap(Function.identity(), functions::col, (a, b) -> null)); } else { @@ -134,13 +134,14 @@ public static ResourcePath build(@Nonnull final FhirContext fhirContext, final DatasetWithColumnMap datasetWithColumnMap = aliasAllColumns(dataset); finalDataset = datasetWithColumnMap.getDataset(); final Map columnMap = datasetWithColumnMap.getColumnMap(); + //noinspection ReturnOfNull elementsToColumns = columnMap.keySet().stream() .collect(Collectors.toMap(Column::toString, columnMap::get, (a, b) -> null)); finalIdColumn = elementsToColumns.get(idColumn.toString()); } // We use the ID column as the value column for a ResourcePath. - return new ResourcePath(expression, finalDataset, finalIdColumn, Optional.empty(), + return new ResourcePath(expression, finalDataset, finalIdColumn, finalIdColumn, singular, Optional.empty(), definition, elementsToColumns); } @@ -180,17 +181,17 @@ public void setCurrentResource(@Nonnull final ResourcePath currentResource) { @Nonnull @Override public ResourcePath copy(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional thisColumn) { + return new ResourcePath(expression, dataset, idColumn, valueColumn, singular, thisColumn, + definition, elementsToColumns); + } - final DatasetWithColumnMap datasetWithColumns = eidColumn.map(eidCol -> createColumns(dataset, - eidCol, valueColumn)).orElseGet(() -> createColumns(dataset, valueColumn)); - - return new ResourcePath(expression, datasetWithColumns.getDataset(), idColumn, - eidColumn.map(datasetWithColumns::getColumn), - datasetWithColumns.getColumn(valueColumn), singular, thisColumn, definition, - elementsToColumns); + @Nonnull + @Override + public Optional getOrderingColumn() { + return Optional.empty(); } @Override @@ -205,13 +206,13 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { @Nonnull public NonLiteralPath combineWith(@Nonnull final FhirPath target, @Nonnull final Dataset dataset, @Nonnull final String expression, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, final boolean singular, @Nonnull final Optional thisColumn) { if (target instanceof ResourcePath && definition .equals(((ResourcePath) target).getDefinition())) { // Two ResourcePaths can be merged together if they have the same definition. - return copy(expression, dataset, idColumn, eidColumn, valueColumn, singular, thisColumn); + return copy(expression, dataset, idColumn, valueColumn, getOrderingColumn(), singular, + thisColumn); } // Anything else is invalid. throw new InvalidUserInputError( diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java index faec3e1bd6..fcaab04d4c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java @@ -40,8 +40,8 @@ public interface Temporal { /** * Gets a function that can take the {@link QuantityLiteralPath} representing a time duration and * return a {@link FhirPath} that contains the result of date arithmetic operation for this path - * and the provided duration. The type of operation is controlled by supplying a {@link - * MathOperation}. + * and the provided duration. The type of operation is controlled by supplying a + * {@link MathOperation}. * * @param operation The {@link MathOperation} type to retrieve a result for * @param dataset The {@link Dataset} to use within the result @@ -77,7 +77,6 @@ static Function buildDateArithmeticOperation( final String additionFunctionName, final String subtractionFunctionName) { return target -> { final String functionName; - final Optional eidColumn = NonLiteralPath.findEidColumn(source, target); final Optional thisColumn = NonLiteralPath.findThisColumn(source, target); switch (operation) { @@ -93,9 +92,8 @@ static Function buildDateArithmeticOperation( final Column valueColumn = functions.callUDF(functionName, source.getValueColumn(), target.getValueColumn()); - return ElementPath.build(expression, dataset, source.getIdColumn(), eidColumn, valueColumn, - true, - Optional.empty(), thisColumn, FHIRDefinedType.DATETIME); + return ElementPath.build(expression, dataset, source.getIdColumn(), valueColumn, + Optional.empty(), true, Optional.empty(), thisColumn, FHIRDefinedType.DATETIME); }; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TopologicalExpressionSorter.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TopologicalExpressionSorter.java deleted file mode 100644 index 6f56aecce9..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TopologicalExpressionSorter.java +++ /dev/null @@ -1,20 +0,0 @@ -package au.csiro.pathling.fhirpath; - -import java.util.Comparator; -import java.util.HashSet; -import java.util.Set; - -public class TopologicalExpressionSorter implements Comparator { - - @Override - public int compare(final FhirPathAndContext fpc1, final FhirPathAndContext fpc2) { - final Set nodes1 = fpc1.getContext().getNodeIdColumns().keySet(); - final Set nodes2 = fpc2.getContext().getNodeIdColumns().keySet(); - - final Set intersection = new HashSet<>(nodes1); - intersection.retainAll(nodes2); - - return nodes1.size() - intersection.size() - (nodes2.size() - intersection.size()); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java index c7947fa81d..2cd7d8be38 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java @@ -17,9 +17,6 @@ package au.csiro.pathling.fhirpath; -import static au.csiro.pathling.QueryHelpers.createColumns; - -import au.csiro.pathling.QueryHelpers.DatasetWithColumnMap; import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ReferencePath; @@ -38,11 +35,11 @@ public class UntypedResourcePath extends ReferencePath implements AbstractPath { public UntypedResourcePath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, eidColumn, valueColumn, singular, currentResource, + super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, thisColumn, fhirType); } @@ -55,9 +52,10 @@ public UntypedResourcePath(@Nonnull final String expression, @Nonnull final Data public static UntypedResourcePath build(@Nonnull final ReferencePath referencePath, @Nonnull final String expression) { return new UntypedResourcePath(expression, referencePath.getDataset(), - referencePath.getIdColumn(), referencePath.getEidColumn(), referencePath.getValueColumn(), - referencePath.isSingular(), referencePath.getCurrentResource(), - referencePath.getThisColumn(), referencePath.getFhirType()); + referencePath.getIdColumn(), referencePath.getValueColumn(), + referencePath.getOrderingColumn(), referencePath.isSingular(), + referencePath.getCurrentResource(), referencePath.getThisColumn(), + referencePath.getFhirType()); } @Nonnull @@ -65,17 +63,6 @@ public Column getReferenceColumn() { return valueColumn.getField(Referrer.REFERENCE_FIELD_NAME); } - @Nonnull - public Column getResourceEquality(@Nonnull final ResourcePath resourcePath) { - return Referrer.resourceEqualityFor(this, resourcePath); - } - - @Nonnull - public Column getResourceEquality(@Nonnull final Column targetId, - @Nonnull final Column targetCode) { - return Referrer.resourceEqualityFor(this, targetCode, targetId); - } - @Nonnull @Override public Optional getChildElement(@Nonnull final String name) { @@ -86,14 +73,9 @@ public Optional getChildElement(@Nonnull final String name) { @Override public UntypedResourcePath copy(@Nonnull final String expression, @Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final Optional eidColumn, @Nonnull final Column valueColumn, + @Nonnull final Column valueColumn, @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional thisColumn) { - - final DatasetWithColumnMap datasetWithColumns = eidColumn.map(eidCol -> createColumns(dataset, - eidCol, valueColumn)).orElseGet(() -> createColumns(dataset, valueColumn)); - - return new UntypedResourcePath(expression, datasetWithColumns.getDataset(), idColumn, - eidColumn.map(datasetWithColumns::getColumn), datasetWithColumns.getColumn(valueColumn), + return new UntypedResourcePath(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, thisColumn, getFhirType()); } @@ -101,11 +83,11 @@ public UntypedResourcePath copy(@Nonnull final String expression, @Nonnull public NonLiteralPath combineWith(@Nonnull final FhirPath target, @Nonnull final Dataset dataset, @Nonnull final String expression, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, final boolean singular, @Nonnull final Optional thisColumn) { if (target instanceof UntypedResourcePath) { - return copy(expression, dataset, idColumn, eidColumn, valueColumn, singular, thisColumn); + return copy(expression, dataset, idColumn, valueColumn, getOrderingColumn(), singular, + thisColumn); } // Anything else is invalid. throw new InvalidUserInputError( diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/BooleanPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/BooleanPath.java index 19aac1a2f4..6e73bb52ce 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/BooleanPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/BooleanPath.java @@ -44,11 +44,11 @@ public class BooleanPath extends ElementPath implements FhirValue, .of(BooleanPath.class, BooleanLiteralPath.class, NullLiteralPath.class); protected BooleanPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, eidColumn, valueColumn, singular, currentResource, + super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, thisColumn, fhirType); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/CodingPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/CodingPath.java index f144d5c19d..9448264181 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/CodingPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/CodingPath.java @@ -50,11 +50,11 @@ public class CodingPath extends ElementPath implements FhirValue, Compar .of(CodingPath.class, CodingLiteralPath.class, NullLiteralPath.class); protected CodingPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, eidColumn, valueColumn, singular, currentResource, + super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, thisColumn, fhirType); } @@ -121,8 +121,8 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { @Override public FhirPath asStringPath(@Nonnull final String expression) { final Column valueColumn = callUDF(CodingToLiteral.FUNCTION_NAME, this.valueColumn); - return ElementPath.build(expression, getDataset(), getIdColumn(), getEidColumn(), valueColumn, - isSingular(), getCurrentResource(), getThisColumn(), FHIRDefinedType.STRING); + return ElementPath.build(expression, getDataset(), getIdColumn(), valueColumn, + getOrderingColumn(), isSingular(), getCurrentResource(), getThisColumn(), FHIRDefinedType.STRING); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java index 8e186efc6e..f2cd209b29 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java @@ -53,11 +53,11 @@ public class DatePath extends ElementPath implements FhirValue, Compar Temporal, StringCoercible { protected DatePath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, eidColumn, valueColumn, singular, currentResource, + super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, thisColumn, fhirType); } @@ -127,8 +127,8 @@ public Function getDateArithmeticOperation( @Nonnull @Override public FhirPath asStringPath(@Nonnull final String expression) { - return ElementPath.build(expression, getDataset(), getIdColumn(), getEidColumn(), - getValueColumn(), isSingular(), getCurrentResource(), getThisColumn(), + return ElementPath.build(expression, getDataset(), getIdColumn(), getValueColumn(), + getOrderingColumn(), isSingular(), getCurrentResource(), getThisColumn(), FHIRDefinedType.STRING); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java index 41f5178e85..00727167a8 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java @@ -61,11 +61,11 @@ public class DateTimePath extends ElementPath implements FhirValue dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, eidColumn, valueColumn, singular, currentResource, + super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, thisColumn, fhirType); } @@ -138,8 +138,9 @@ public FhirPath asStringPath(@Nonnull final String expression) { } else { valueColumn = getValueColumn(); } - return ElementPath.build(expression, getDataset(), getIdColumn(), getEidColumn(), valueColumn, - isSingular(), getCurrentResource(), getThisColumn(), FHIRDefinedType.STRING); + return ElementPath.build(expression, getDataset(), getIdColumn(), valueColumn, + getOrderingColumn(), isSingular(), getCurrentResource(), getThisColumn(), + FHIRDefinedType.STRING); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java index c4fae63e42..c406e8723e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java @@ -52,11 +52,11 @@ public class DecimalPath extends ElementPath implements FhirValue, .createDecimalType(DecimalCustomCoder.precision(), DecimalCustomCoder.scale()); protected DecimalPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, eidColumn, valueColumn, singular, currentResource, + super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, thisColumn, fhirType); } @@ -152,7 +152,7 @@ public static Function buildMathOperation(@Nonnull fina Column valueColumn = operation.getSparkFunction() .apply(source.getNumericValueColumn(), target.getNumericValueColumn()); final Column idColumn = source.getIdColumn(); - final Optional eidColumn = findEidColumn(source, target); + final Optional orderingColumn = findOrderingColumn(source, target); final Optional thisColumn = findThisColumn(source, target); switch (operation) { @@ -162,13 +162,13 @@ public static Function buildMathOperation(@Nonnull fina case DIVISION: valueColumn = valueColumn.cast(getDecimalType()); return ElementPath - .build(expression, dataset, idColumn, eidColumn, valueColumn, true, Optional.empty(), - thisColumn, source.getFhirType()); + .build(expression, dataset, idColumn, valueColumn, orderingColumn, true, + Optional.empty(), thisColumn, source.getFhirType()); case MODULUS: valueColumn = valueColumn.cast(DataTypes.LongType); return ElementPath - .build(expression, dataset, idColumn, eidColumn, valueColumn, true, Optional.empty(), - thisColumn, FHIRDefinedType.INTEGER); + .build(expression, dataset, idColumn, valueColumn, orderingColumn, true, + Optional.empty(), thisColumn, FHIRDefinedType.INTEGER); default: throw new AssertionError("Unsupported math operation encountered: " + operation); } @@ -184,8 +184,9 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { @Nonnull public FhirPath asStringPath(@Nonnull final String expression) { final Column valueColumn = getValueColumn().cast(DataTypes.StringType); - return ElementPath.build(expression, getDataset(), getIdColumn(), getEidColumn(), valueColumn, - isSingular(), getCurrentResource(), getThisColumn(), FHIRDefinedType.STRING); + return ElementPath.build(expression, getDataset(), getIdColumn(), valueColumn, + getOrderingColumn(), isSingular(), getCurrentResource(), getThisColumn(), + FHIRDefinedType.STRING); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementDefinition.java index 6e5f9c1b8a..9523817b25 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementDefinition.java @@ -17,6 +17,7 @@ package au.csiro.pathling.fhirpath.element; +import au.csiro.pathling.fhirpath.NestingKey; import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition; @@ -40,7 +41,7 @@ * * @author John Grimes */ -public class ElementDefinition { +public class ElementDefinition implements NestingKey { // See https://hl7.org/fhir/fhirpath.html#types. @Nonnull @@ -79,9 +80,18 @@ public class ElementDefinition { @Nonnull private final Optional elementDefinition; + /** + * The parent definition for this element. There will always be a parent definition, whether it be + * another {@link org.hl7.fhir.r4.model.ElementDefinition} or a + * {@link au.csiro.pathling.fhirpath.ResourceDefinition}. + */ + @Nonnull + private final NestingKey parent; + protected ElementDefinition(@Nonnull final BaseRuntimeChildDefinition childDefinition, - @Nonnull final String elementName) { + @Nonnull final String elementName, @Nonnull final NestingKey parent) { this.childDefinition = childDefinition; + this.parent = parent; elementDefinition = Optional.ofNullable(childDefinition.getChildByName(elementName)); } @@ -92,13 +102,14 @@ protected ElementDefinition(@Nonnull final BaseRuntimeChildDefinition childDefin */ @Nonnull public static ElementDefinition build(@Nonnull final BaseRuntimeChildDefinition childDefinition, - @Nonnull final String elementName) { + @Nonnull final String elementName, @Nonnull final NestingKey parent) { if (elementName.equals("valueReference") && childDefinition instanceof RuntimeChildAny) { - return new ReferenceExtensionDefinition(childDefinition, elementName); + return new ReferenceExtensionDefinition(childDefinition, elementName, parent); } else if (childDefinition instanceof RuntimeChildResourceDefinition) { - return new ReferenceDefinition((RuntimeChildResourceDefinition) childDefinition, elementName); + return new ReferenceDefinition((RuntimeChildResourceDefinition) childDefinition, elementName, + parent); } else { - return new ElementDefinition(childDefinition, elementName); + return new ElementDefinition(childDefinition, elementName, parent); } } @@ -118,7 +129,9 @@ public Optional getChildElement(@Nonnull final String name) { if (newChild == null) { return Optional.empty(); } - return Optional.of(ElementDefinition.build(newChild, name)); + // When building a new element definition from a child of this one, specify this element as + // the parent. + return Optional.of(ElementDefinition.build(newChild, name, this)); } else { return Optional.empty(); } @@ -188,12 +201,15 @@ public boolean equals(final Object o) { return false; } final ElementDefinition that = (ElementDefinition) o; - return Objects.equals(childDefinition, that.childDefinition); + return Objects.equals(childDefinition, that.childDefinition) + // Recursively compare parent definitions. + && Objects.equals(parent, that.parent); } @Override public int hashCode() { - return Objects.hash(childDefinition); + // Recursively hash parent definitions. + return Objects.hash(childDefinition, parent); } - + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java index 2306370024..504e05c2aa 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java @@ -58,14 +58,19 @@ public class ElementPath extends NonLiteralPath { @Nonnull private Optional definition = Optional.empty(); + @Getter + @Nonnull + private final Optional orderingColumn; + protected ElementPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, eidColumn, valueColumn, singular, currentResource, + super(expression, dataset, idColumn, valueColumn, singular, currentResource, thisColumn); this.fhirType = fhirType; + this.orderingColumn = orderingColumn; } /** @@ -78,8 +83,9 @@ protected ElementPath(@Nonnull final String expression, @Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final Optional eidColumn, @Nonnull final Column valueColumn, + @Nonnull final Column valueColumn, @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final ElementDefinition definition) { final Optional optionalFhirType = definition.getFhirType(); if (optionalFhirType.isPresent()) { final FHIRDefinedType fhirType = optionalFhirType.get(); final ElementPath path = ElementPath - .build(expression, dataset, idColumn, eidColumn, valueColumn, singular, currentResource, - thisColumn, fhirType); + .build(expression, dataset, idColumn, valueColumn, orderingColumn, singular, + currentResource, thisColumn, fhirType); path.definition = Optional.of(definition); return path; } else { @@ -115,8 +121,9 @@ public static ElementPath build(@Nonnull final String expression, * @param dataset a {@link Dataset} that can be used to evaluate this path against data * @param idColumn a {@link Column} within the dataset containing the identity of the subject * resource - * @param eidColumn a {@link Column} within the dataset containing the element identity * @param valueColumn a {@link Column} within the dataset containing the values of the nodes + * @param orderingColumn a {@link Column} within the dataset containing the ordering of the + * elements * @param singular an indicator of whether this path represents a single-valued collection * @param currentResource the current resource within this path * @param thisColumn collection values where this path originated from {@code $this} @@ -126,36 +133,37 @@ public static ElementPath build(@Nonnull final String expression, @Nonnull public static ElementPath build(@Nonnull final String expression, @Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final Optional eidColumn, @Nonnull final Column valueColumn, + @Nonnull final Column valueColumn, @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - return getInstance(expression, dataset, idColumn, eidColumn, valueColumn, singular, + return getInstance(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, thisColumn, fhirType); } @Nonnull private static ElementPath getInstance(@Nonnull final String expression, @Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final Optional eidColumn, @Nonnull final Column valueColumn, + @Nonnull final Column valueColumn, @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { // Look up the class that represents an element with the specified FHIR type. final Class elementPathClass = ElementDefinition .elementClassForType(fhirType).orElse(ElementPath.class); - final DatasetWithColumnMap datasetWithColumns = eidColumn.map(eidCol -> createColumns(dataset, - eidCol, valueColumn)).orElseGet(() -> createColumns(dataset, valueColumn)); + final DatasetWithColumnMap datasetWithColumns = orderingColumn + .map(column -> createColumns(dataset, column, valueColumn)) + .orElseGet(() -> createColumns(dataset, valueColumn)); try { // Call its constructor and return. final Constructor constructor = elementPathClass - .getDeclaredConstructor(String.class, Dataset.class, Column.class, Optional.class, - Column.class, boolean.class, Optional.class, Optional.class, FHIRDefinedType.class); + .getDeclaredConstructor(String.class, Dataset.class, Column.class, Column.class, + Optional.class, boolean.class, Optional.class, Optional.class, FHIRDefinedType.class); return constructor .newInstance(expression, datasetWithColumns.getDataset(), idColumn, - eidColumn.map(datasetWithColumns::getColumn), - datasetWithColumns.getColumn(valueColumn), singular, currentResource, thisColumn, - fhirType); + datasetWithColumns.getColumn(valueColumn), + orderingColumn.map(datasetWithColumns::getColumn), singular, currentResource, + thisColumn, fhirType); } catch (final NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Problem building an ElementPath class", e); @@ -171,16 +179,16 @@ public Optional getChildElement(@Nonnull final String name) { @Nonnull @Override public ElementPath copy(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional thisColumn) { return definition .map(elementDefinition -> ElementPath - .build(expression, dataset, idColumn, eidColumn, valueColumn, singular, currentResource, - thisColumn, elementDefinition)) + .build(expression, dataset, idColumn, valueColumn, orderingColumn, singular, + currentResource, thisColumn, elementDefinition)) .orElseGet( () -> ElementPath - .build(expression, dataset, idColumn, eidColumn, valueColumn, singular, + .build(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, thisColumn, fhirType)); } @@ -201,11 +209,11 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { @Nonnull public NonLiteralPath combineWith(@Nonnull final FhirPath target, @Nonnull final Dataset dataset, @Nonnull final String expression, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, final boolean singular, @Nonnull final Optional thisColumn) { if (canBeCombinedWith(target)) { - return copy(expression, dataset, idColumn, eidColumn, valueColumn, singular, thisColumn); + return copy(expression, dataset, idColumn, valueColumn, Optional.empty(), singular, + thisColumn); } // Anything else is invalid. throw new InvalidUserInputError( diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ExtensionPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ExtensionPath.java index cac4a82f31..d13439069d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ExtensionPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ExtensionPath.java @@ -42,12 +42,12 @@ public class ExtensionPath extends ElementPath { protected ExtensionPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final Enumerations.FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, eidColumn, valueColumn, singular, currentResource, + super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, thisColumn, fhirType); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java index 4c56afc70f..f2c3a30d90 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java @@ -56,11 +56,11 @@ public class IntegerPath extends ElementPath implements FhirValue NullLiteralPath.class); protected IntegerPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, eidColumn, valueColumn, singular, currentResource, + super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, thisColumn, fhirType); } @@ -165,7 +165,7 @@ public static Function buildMathOperation(@Nonnull fina Column valueColumn = operation.getSparkFunction() .apply(source.getNumericValueColumn(), targetValueColumn); final Column idColumn = source.getIdColumn(); - final Optional eidColumn = findEidColumn(source, target); + final Optional orderingColumn = findOrderingColumn(source, target); final Optional thisColumn = findThisColumn(source, target); switch (operation) { @@ -177,14 +177,14 @@ public static Function buildMathOperation(@Nonnull fina valueColumn = valueColumn.cast(DataTypes.LongType); } return ElementPath - .build(expression, dataset, idColumn, eidColumn, valueColumn, true, Optional.empty(), - thisColumn, source.getFhirType()); + .build(expression, dataset, idColumn, valueColumn, orderingColumn, true, + Optional.empty(), thisColumn, source.getFhirType()); case DIVISION: final Column numerator = source.getValueColumn().cast(DecimalPath.getDecimalType()); valueColumn = operation.getSparkFunction().apply(numerator, targetValueColumn); return ElementPath - .build(expression, dataset, idColumn, eidColumn, valueColumn, true, Optional.empty(), - thisColumn, FHIRDefinedType.DECIMAL); + .build(expression, dataset, idColumn, valueColumn, orderingColumn, true, + Optional.empty(), thisColumn, FHIRDefinedType.DECIMAL); default: throw new AssertionError("Unsupported math operation encountered: " + operation); } @@ -200,8 +200,9 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { @Nonnull public FhirPath asStringPath(@Nonnull final String expression) { final Column valueColumn = getValueColumn().cast(DataTypes.StringType); - return ElementPath.build(expression, getDataset(), getIdColumn(), getEidColumn(), valueColumn, - isSingular(), getCurrentResource(), getThisColumn(), FHIRDefinedType.STRING); + return ElementPath.build(expression, getDataset(), getIdColumn(), valueColumn, + getOrderingColumn(), isSingular(), getCurrentResource(), getThisColumn(), + FHIRDefinedType.STRING); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/QuantityPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/QuantityPath.java index 8069d982fb..d2da652794 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/QuantityPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/QuantityPath.java @@ -51,11 +51,11 @@ public class QuantityPath extends ElementPath implements Comparable, Numeric { .of(QuantityPath.class, QuantityLiteralPath.class, NullLiteralPath.class); protected QuantityPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, eidColumn, valueColumn, singular, currentResource, + super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, thisColumn, fhirType); } @@ -192,16 +192,15 @@ public static Function buildMathOperation(@Nonnull fina null).otherwise(validResult); final Column idColumn = source.getIdColumn(); - final Optional eidColumn = findEidColumn(source, target); + final Optional orderingColumn = findOrderingColumn(source, target); final Optional thisColumn = findThisColumn(source, target); return elementDefinition.map(definition -> ElementPath - .build(expression, dataset, idColumn, eidColumn, resultQuantityColumn, true, - Optional.empty(), - thisColumn, definition)).orElseGet(() -> ElementPath - .build(expression, dataset, idColumn, eidColumn, resultQuantityColumn, true, - Optional.empty(), - thisColumn, FHIRDefinedType.QUANTITY)); + .build(expression, dataset, idColumn, resultQuantityColumn, orderingColumn, true, + Optional.empty(), thisColumn, definition)) + .orElseGet(() -> ElementPath + .build(expression, dataset, idColumn, resultQuantityColumn, orderingColumn, true, + Optional.empty(), thisColumn, FHIRDefinedType.QUANTITY)); }; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferenceDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferenceDefinition.java index 264ad24f33..f25a96dd9b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferenceDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferenceDefinition.java @@ -19,10 +19,10 @@ import static java.util.Objects.requireNonNull; +import au.csiro.pathling.fhirpath.NestingKey; import ca.uhn.fhir.context.RuntimeChildResourceDefinition; import java.lang.reflect.InvocationTargetException; import java.util.List; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nonnull; @@ -41,8 +41,8 @@ public class ReferenceDefinition extends ElementDefinition { private final RuntimeChildResourceDefinition childDefinition; protected ReferenceDefinition(@Nonnull final RuntimeChildResourceDefinition childDefinition, - @Nonnull final String elementName) { - super(childDefinition, elementName); + @Nonnull final String elementName, final NestingKey parent) { + super(childDefinition, elementName, parent); this.childDefinition = childDefinition; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferenceExtensionDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferenceExtensionDefinition.java index bd42e55eed..8938603843 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferenceExtensionDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferenceExtensionDefinition.java @@ -17,6 +17,7 @@ package au.csiro.pathling.fhirpath.element; +import au.csiro.pathling.fhirpath.NestingKey; import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import java.util.Set; import javax.annotation.Nonnull; @@ -30,8 +31,8 @@ public class ReferenceExtensionDefinition extends ElementDefinition { protected ReferenceExtensionDefinition(@Nonnull final BaseRuntimeChildDefinition childDefinition, - @Nonnull final String elementName) { - super(childDefinition, elementName); + @Nonnull final String elementName, final NestingKey parent) { + super(childDefinition, elementName, parent); } @Override diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java index 61fa51bd55..2f1f8b3178 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java @@ -37,11 +37,11 @@ public class ReferencePath extends ElementPath implements Referrer { protected ReferencePath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, eidColumn, valueColumn, singular, currentResource, + super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, thisColumn, fhirType); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/StringPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/StringPath.java index c632d3c19b..113321f289 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/StringPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/StringPath.java @@ -55,11 +55,11 @@ public class StringPath extends ElementPath implements FhirValue, .of(StringPath.class, StringLiteralPath.class, NullLiteralPath.class); protected StringPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, eidColumn, valueColumn, singular, currentResource, + super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, thisColumn, fhirType); } @@ -130,5 +130,5 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { public FhirPath asStringPath(@Nonnull final String expression) { return this; } - + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java index ea3275d68d..3005410a1b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java @@ -46,11 +46,11 @@ public class TimePath extends ElementPath implements FhirValue, Compar .of(TimePath.class, TimeLiteralPath.class, NullLiteralPath.class); protected TimePath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, @Nonnull final Optional currentResource, @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, eidColumn, valueColumn, singular, currentResource, + super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, thisColumn, fhirType); } @@ -99,8 +99,8 @@ public boolean canBeCombinedWith(@Nonnull final FhirPath target) { @Nonnull @Override public FhirPath asStringPath(@Nonnull final String expression) { - return ElementPath.build(expression, getDataset(), getIdColumn(), getEidColumn(), - getValueColumn(), isSingular(), getCurrentResource(), getThisColumn(), + return ElementPath.build(expression, getDataset(), getIdColumn(), getValueColumn(), + getOrderingColumn(), isSingular(), getCurrentResource(), getThisColumn(), FHIRDefinedType.STRING); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java index 6716c06953..ab9d5e20e6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java @@ -110,8 +110,8 @@ protected ElementPath buildAggregateResult(@Nonnull final Dataset dataset, return buildAggregateResult(dataset, parserContext, inputs, valueColumn, expression, // Create the result as an ElementPath of the given FHIR type. - (exp, ds, id, eid, value, singular, thisColumn) -> ElementPath - .build(exp, ds, id, eid, value, true, Optional.empty(), thisColumn, fhirType)); + (exp, ds, id, value, ordering, singular, thisColumn) -> ElementPath.build(exp, ds, id, + value, ordering, true, Optional.empty(), thisColumn, fhirType)); } @Nonnull @@ -152,23 +152,20 @@ private T buildAggregateResult(@Nonnull final Dataset final Column[] remainingSelection = selection.stream().skip(1).toArray(Column[]::new); // Get any this columns that may be present in the inputs. - // TODO: This is very error prone as a collection can be passed here instead of an array. - // How can we make it more stringent? - @SuppressWarnings("ConfusingArgumentToVarargsMethod") final Optional thisColumn = NonLiteralPath - .findThisColumn(inputs.toArray(new FhirPath[0])); + .findThisColumn((Object[]) inputs.toArray(new FhirPath[0])); final Dataset finalDataset = dataset .groupBy(groupBy) .agg(firstSelection, remainingSelection); final Column finalValueColumn = col("value"); - // Clear out the node ID columns in the parser context - as they are no longer valid for joining. - parserContext.getNodeIdColumns().clear(); + // Remove the last nesting entry, so that it is possible to traverse back into this path again + // without it being rolled up into an aggregation. + parserContext.getNesting().removeLast(); - // empty eid column as the result is singular return resultPathFactory - .create(expression, finalDataset, idColumn, Optional.empty(), finalValueColumn, true, + .create(expression, finalDataset, idColumn, finalValueColumn, Optional.empty(), true, thisColumn); } @@ -185,16 +182,16 @@ private interface ResultPathFactory { * @param expression an updated expression to describe the new FhirPath * @param dataset the new Dataset that can be used to evaluate this FhirPath against data * @param idColumn the new resource identity column - * @param eidColumn the new element identity column * @param valueColumn the new expression value column + * @param orderingColumn the new ordering column * @param singular the new singular value * @param thisColumn a column containing the collection being iterated, for cases where a path * is being created to represent the {@code $this} keyword * @return a new instance of T */ - T create(@Nonnull String expression, @Nonnull Dataset dataset, - @Nonnull Column idColumn, @Nonnull Optional eidColumn, @Nonnull Column valueColumn, - boolean singular, @Nonnull Optional thisColumn); + T create(@Nonnull String expression, @Nonnull Dataset dataset, @Nonnull Column idColumn, + @Nonnull Column valueColumn, @Nonnull Optional orderingColumn, boolean singular, + @Nonnull Optional thisColumn); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java index d1b236d66e..77dcc46035 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java @@ -24,6 +24,7 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; import java.util.function.Function; +import java.util.function.UnaryOperator; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; import org.apache.spark.sql.functions; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java index 2d278aa457..4d710a73a2 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java @@ -52,9 +52,9 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Dataset dataset = countResult.getDataset(); final Column valueColumn = countResult.getValueColumn().equalTo(0); - return ElementPath - .build(expression, dataset, inputPath.getIdColumn(), Optional.empty(), valueColumn, true, - Optional.empty(), inputPath.getThisColumn(), FHIRDefinedType.BOOLEAN); + return ElementPath.build(expression, dataset, inputPath.getIdColumn(), valueColumn, + Optional.empty(), true, Optional.empty(), inputPath.getThisColumn(), + FHIRDefinedType.BOOLEAN); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java index cdd333a039..de8698cd24 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java @@ -52,13 +52,13 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final BooleanPath emptyResult = (BooleanPath) new EmptyFunction().invoke(input); final Column valueColumn = not(emptyResult.getValueColumn()); return emptyResult.copy(expression, emptyResult.getDataset(), emptyResult.getIdColumn(), - emptyResult.getEidColumn(), valueColumn, emptyResult.isSingular(), + valueColumn, emptyResult.getOrderingColumn(), emptyResult.isSingular(), emptyResult.getThisColumn()); } else { final FhirPath argument = input.getArguments().get(0); checkUserInput(argument.isSingular() && argument instanceof BooleanPath, "Argument to exists function must be a singular Boolean: " + argument.getExpression()); - + // If there is an argument, first invoke the `where` function. final NonLiteralPath whereResult = (NonLiteralPath) new WhereFunction().invoke(input); // Then invoke the `exists` function on the result, with no arguments. @@ -66,8 +66,8 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { whereResult, Collections.emptyList()); final BooleanPath existsResult = (BooleanPath) invoke(existsInput); return existsResult.copy(expression, existsResult.getDataset(), existsResult.getIdColumn(), - existsResult.getEidColumn(), existsResult.getValueColumn(), existsResult.isSingular(), - existsResult.getThisColumn()); + existsResult.getValueColumn(), existsResult.getOrderingColumn(), + existsResult.isSingular(), existsResult.getThisColumn()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java index b8f10010b1..09a63285ca 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java @@ -23,7 +23,6 @@ import au.csiro.pathling.fhirpath.Comparable.ComparisonOperation; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.element.ElementPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.operator.ComparisonOperator; import au.csiro.pathling.fhirpath.operator.OperatorInput; @@ -55,7 +54,7 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { "extension function must have argument of type String literal: " + expression); final NonLiteralPath inputPath = input.getInput(); - final ElementPath extensionPath = new PathTraversalOperator() + final NonLiteralPath extensionPath = new PathTraversalOperator() .invoke(new PathTraversalInput(input.getContext(), inputPath, ExtensionSupport.EXTENSION_ELEMENT_NAME())); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java index f868a0ef87..bdae04ea1d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java @@ -19,10 +19,13 @@ import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; +import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.first; import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.Nesting; import au.csiro.pathling.fhirpath.NonLiteralPath; +import java.util.function.UnaryOperator; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; @@ -47,8 +50,13 @@ protected FirstFunction() { public FhirPath invoke(@Nonnull final NamedFunctionInput input) { checkNoArguments("first", input); + final Nesting nesting = input.getContext().getNesting(); + checkUserInput(nesting.isOrderable(), + "The input path to the first function is not orderable: " + input.getInput() + .getExpression()); + final NonLiteralPath inputPath = input.getInput(); - final Dataset dataset = inputPath.getOrderedDataset(); + final Dataset dataset = inputPath.getOrderedDataset(nesting); final String expression = expressionFromInput(input, NAME); final Column finalValueColumn = first(inputPath.getValueColumn(), true); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java index d383b7ca06..cef6186efd 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java @@ -17,20 +17,15 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.QueryHelpers.join; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.when; -import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.element.BooleanPath; -import java.util.Arrays; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; /** * This function takes three arguments, Returns the second argument if the first argument evaluates @@ -62,8 +57,6 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { // Join the three datasets together and create a value column. final FhirPath ifTrue = input.getArguments().get(1); final FhirPath otherwise = input.getArguments().get(2); - final Dataset dataset = join(input.getContext(), - Arrays.asList(conditionBoolean, ifTrue, otherwise), JoinType.LEFT_OUTER); final Column valueColumn = when(conditionBoolean.getValueColumn().equalTo(true), ifTrue.getValueColumn()) .otherwise(otherwise.getValueColumn()); @@ -71,8 +64,8 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { // Build a new ElementPath based on the type of the literal `ifTrue` and `otherwise` arguments, // and populate it with the dataset and calculated value column. final String expression = expressionFromInput(input, NAME); - return ifTrue.combineWith(otherwise, dataset, expression, inputPath.getIdColumn(), - inputPath.getEidColumn(), valueColumn, inputPath.isSingular(), inputPath.getThisColumn()); + return ifTrue.combineWith(otherwise, otherwise.getDataset(), expression, + inputPath.getIdColumn(), valueColumn, inputPath.isSingular(), inputPath.getThisColumn()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NotFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NotFunction.java index dc9b1fa257..84b13e4dbd 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NotFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NotFunction.java @@ -55,8 +55,8 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Column valueColumn = not(inputPath.getValueColumn()); return ElementPath - .build(expression, inputPath.getDataset(), inputPath.getIdColumn(), - inputPath.getEidColumn(), valueColumn, inputPath.isSingular(), Optional.empty(), + .build(expression, inputPath.getDataset(), inputPath.getIdColumn(), valueColumn, + inputPath.getOrderingColumn(), inputPath.isSingular(), Optional.empty(), inputPath.getThisColumn(), FHIRDefinedType.BOOLEAN); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java index 495856db9a..46c2ade5ea 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java @@ -21,18 +21,19 @@ import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import static au.csiro.pathling.utilities.Preconditions.check; +import static au.csiro.pathling.utilities.Preconditions.checkPresent; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.ReferenceNestingKey; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.UntypedResourcePath; +import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ReferencePath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; import ca.uhn.fhir.context.FhirContext; -import java.util.List; -import java.util.Optional; import java.util.Set; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -94,26 +95,29 @@ public static FhirPath resolveMonomorphicReference(@Nonnull final ReferencePath @Nonnull final ParserContext context) { // If this is a monomorphic reference, we just need to retrieve the appropriate table and // create a dataset with the full resources. - final ResourcePath resourcePath = ResourcePath - .build(fhirContext, dataSource, resourceType, expression, referencePath.isSingular()); - - // Join the resource dataset to the reference dataset. - final Column joinCondition = referencePath.getResourceEquality(resourcePath); - final Dataset dataset = join(referencePath.getDataset(), resourcePath.getDataset(), - joinCondition, JoinType.LEFT_OUTER); - - final Column inputId = referencePath.getIdColumn(); - final Optional inputEid = referencePath.getEidColumn(); - - // We need to add the resource ID column to the parser context so that it can be used within - // joins in certain situations, e.g. extract. - final Object nodeKey = List.of(referencePath.getDefinition(), resourcePath.getDefinition()); - context.addNodeId(nodeKey, resourcePath.getElementColumn("id")); - - final ResourcePath result = resourcePath.copy(expression, dataset, inputId, inputEid, - resourcePath.getValueColumn(), referencePath.isSingular(), referencePath.getThisColumn()); - result.setCurrentResource(resourcePath); - return result; + final ResourcePath resourcePath = ResourcePath.build(fhirContext, dataSource, resourceType, + expression, referencePath.isSingular()); + final ElementDefinition referenceDefinition = checkPresent(referencePath.getDefinition()); + final ReferenceNestingKey referenceNestingKey = new ReferenceNestingKey(referenceDefinition, + resourcePath.getDefinition()); + + return context.getNesting() + .updateOrRetrieve(referenceNestingKey, expression, referencePath.getDataset(), + referencePath.isSingular(), referencePath.getThisColumn(), key -> { + // Join the resource dataset to the reference dataset. + final Column joinCondition = referencePath.getResourceEquality(resourcePath); + final Dataset dataset = join(referencePath.getDataset(), + resourcePath.getDataset(), + joinCondition, JoinType.LEFT_OUTER); + + final Column inputId = referencePath.getIdColumn(); + final ResourcePath result = resourcePath.copy(expression, dataset, inputId, + resourcePath.getValueColumn(), resourcePath.getOrderingColumn(), + referencePath.isSingular(), referencePath.getThisColumn()); + result.setCurrentResource(resourcePath); + + return result; + }); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java index 9881990af5..4df525c833 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java @@ -18,27 +18,21 @@ package au.csiro.pathling.fhirpath.function; import static au.csiro.pathling.QueryHelpers.join; +import static au.csiro.pathling.utilities.Preconditions.checkPresent; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static org.apache.spark.sql.functions.lit; -import static org.apache.spark.sql.functions.row_number; -import static org.apache.spark.sql.functions.when; -import au.csiro.pathling.QueryHelpers; -import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; +import au.csiro.pathling.fhirpath.ReferenceNestingKey; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ReferencePath; -import java.util.List; -import java.util.Optional; import java.util.Set; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; -import org.apache.spark.sql.expressions.Window; -import org.apache.spark.sql.expressions.WindowSpec; import org.hl7.fhir.r4.model.Enumerations.ResourceType; /** @@ -65,20 +59,6 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final FhirPath argument = input.getArguments().get(0); checkUserInput(argument instanceof ReferencePath, "Argument to reverseResolve function must be a Reference: " + argument.getExpression()); - final ReferencePath referencePath = (ReferencePath) argument; - - // Check that the input type is one of the possible types specified by the argument. - final Set argumentTypes = ((ReferencePath) argument).getResourceTypes(); - final ResourceType inputType = inputPath.getResourceType(); - checkUserInput(argumentTypes.contains(inputType), - "Reference in argument to reverseResolve does not support input resource type: " - + expression); - - // Do a left outer join from the input to the argument dataset using the reference field in the - // argument. - final Column joinCondition = referencePath.getResourceEquality(inputPath); - final Dataset dataset = join(referencePath.getDataset(), inputPath.getDataset(), - joinCondition, JoinType.RIGHT_OUTER); // Check the argument for information about the current resource that it originated from - if it // is not present, reverse reference resolution will not be possible. @@ -88,31 +68,33 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + "target resource type: " + expression); final ResourcePath currentResource = nonLiteralArgument.getCurrentResource().get(); - final Optional thisColumn = inputPath.getThisColumn(); - - // TODO: Consider removing in the future once we separate ordering from element ID. - // Create an synthetic element ID column for reverse resolved resources. - final Column currentResourceValue = currentResource.getValueColumn(); - final WindowSpec windowSpec = Window - .partitionBy(inputPath.getIdColumn(), inputPath.getOrderingColumn()) - .orderBy(currentResourceValue); + final ReferencePath referencePath = (ReferencePath) argument; + final ElementDefinition referenceDefinition = checkPresent(referencePath.getDefinition()); + final ReferenceNestingKey referenceNestingKey = new ReferenceNestingKey(referenceDefinition, + currentResource.getDefinition()); - // row_number() is 1-based, and we use 0-based indexes - thus (minus(1)). - final Column currentResourceIndex = when(currentResourceValue.isNull(), lit(null)) - .otherwise(row_number().over(windowSpec).minus(lit(1))); + return input.getContext().getNesting() + .updateOrRetrieve(referenceNestingKey, expression, inputPath.getDataset(), false, + inputPath.getThisColumn(), key -> { + // Check that the input type is one of the possible types specified by the argument. + final Set argumentTypes = ((ReferencePath) argument).getResourceTypes(); + final ResourceType inputType = inputPath.getResourceType(); + checkUserInput(argumentTypes.contains(inputType), + "Reference in argument to reverseResolve does not support input resource type: " + + expression); - // We need to add the synthetic EID column to the parser context so that it can be used within - // joins in certain situations, e.g. extract. - final Column syntheticEid = inputPath.expandEid(currentResourceIndex); - final DatasetWithColumn datasetWithEid = QueryHelpers.createColumn(dataset, syntheticEid); - final List nodeKey = List.of(referencePath.getDefinition(), - currentResource.getDefinition()); - input.getContext().addNodeId(nodeKey, datasetWithEid.getColumn()); + // Do a left outer join from the input to the argument dataset using the reference field in + // the argument. + final Column joinCondition = referencePath.getResourceEquality(inputPath); + final Dataset dataset = join(inputPath.getDataset(), referencePath.getDataset(), + joinCondition, JoinType.LEFT_OUTER); - final ResourcePath result = currentResource - .copy(expression, datasetWithEid.getDataset(), inputPath.getIdColumn(), - Optional.of(syntheticEid), currentResource.getValueColumn(), false, thisColumn); - result.setCurrentResource(currentResource); - return result; + final ResourcePath result = currentResource.copy(expression, dataset, + inputPath.getIdColumn(), + currentResource.getValueColumn(), currentResource.getOrderingColumn(), false, + inputPath.getThisColumn()); + result.setCurrentResource(currentResource); + return result; + }); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java index fe3477b315..10053d520f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java @@ -25,6 +25,7 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.Numeric; +import java.util.function.UnaryOperator; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/UntilFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/UntilFunction.java index 3c7aa694d1..32d76be0ee 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/UntilFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/UntilFunction.java @@ -18,7 +18,6 @@ package au.csiro.pathling.fhirpath.function; import static au.csiro.pathling.QueryHelpers.join; -import static au.csiro.pathling.fhirpath.NonLiteralPath.findEidColumn; import static au.csiro.pathling.fhirpath.NonLiteralPath.findThisColumn; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.callUDF; @@ -33,12 +32,12 @@ import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.sql.misc.TemporalDifferenceFunction; +import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import java.util.Optional; /** * This function computes the time interval (duration) between two paths representing dates or dates @@ -84,13 +83,11 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { fromArgument.getValueColumn(), toArgument.getValueColumn(), calendarDurationArgument.getValueColumn()); final String expression = NamedFunction.expressionFromInput(input, NAME); - - final Optional eidColumn = findEidColumn(fromArgument, toArgument); final Optional thisColumn = findThisColumn(fromArgument, toArgument); - return ElementPath.build(expression, dataset, fromArgument.getIdColumn(), - eidColumn, valueColumn, true, - fromArgument.getCurrentResource(), thisColumn, FHIRDefinedType.INTEGER); + return ElementPath.build(expression, dataset, fromArgument.getIdColumn(), valueColumn, + fromArgument.getOrderingColumn(), true, fromArgument.getCurrentResource(), thisColumn, + FHIRDefinedType.INTEGER); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java index 4dd79cfc62..34fadfcabf 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java @@ -63,16 +63,13 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { // The result is the input value if it is equal to true, or null otherwise (signifying the // absence of a value). final Column idColumn = argumentPath.getIdColumn(); - final Column thisValue = checkPresent(argumentPath.getThisValueColumn()); - final Column thisEid = checkPresent(argumentPath.getThisOrderingColumn()); + final Column thisValue = checkPresent(argumentPath.getThisColumn()); final Column valueColumn = when(argumentValue.equalTo(true), thisValue).otherwise(lit(null)); final String expression = expressionFromInput(input, NAME); - return inputPath - .copy(expression, argumentPath.getDataset(), idColumn, - inputPath.getEidColumn().map(c -> thisEid), valueColumn, inputPath.isSingular(), - inputPath.getThisColumn()); + return inputPath.copy(expression, argumentPath.getDataset(), idColumn, valueColumn, + inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getThisColumn()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunction.java index 5a69319f5c..0b8f91139b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunction.java @@ -20,6 +20,7 @@ import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import static au.csiro.pathling.sql.Terminology.designation; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; +import static org.apache.spark.sql.functions.explode_outer; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.element.ElementPath; @@ -33,7 +34,6 @@ import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.apache.commons.lang3.tuple.MutablePair; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; @@ -68,14 +68,11 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Dataset dataset = inputPath.getDataset(); final Column designations = designation(inputPath.getValueColumn(), use, languageCode); - // // The result is an array of designations per each input element, which we now - // // need to explode in the same way as for path traversal, creating unique element ids. - final MutablePair valueAndEidColumns = new MutablePair<>(); - final Dataset resultDataset = inputPath - .explodeArray(dataset, designations, valueAndEidColumns); - return ElementPath.build(expression, resultDataset, inputPath.getIdColumn(), - Optional.of(valueAndEidColumns.getRight()), - valueAndEidColumns.getLeft(), inputPath.isSingular(), inputPath.getCurrentResource(), + // The result is an array of designations per each input element, which we now need to explode + // in the same way as for path traversal, creating unique element ids. + final Column explodedDesignations = explode_outer(designations); + return ElementPath.build(expression, dataset, inputPath.getIdColumn(), explodedDesignations, + Optional.empty(), inputPath.isSingular(), inputPath.getCurrentResource(), inputPath.getThisColumn(), FHIRDefinedType.STRING); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunction.java index 33bda0d881..46091f439e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunction.java @@ -60,10 +60,9 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Dataset dataset = inputPath.getDataset(); final Column resultColumn = display(inputPath.getValueColumn(), acceptLanguage.map(StringType::getValue).orElse(null)); - return ElementPath - .build(expression, dataset, inputPath.getIdColumn(), inputPath.getEidColumn(), - resultColumn, inputPath.isSingular(), inputPath.getCurrentResource(), - inputPath.getThisColumn(), FHIRDefinedType.STRING); + return ElementPath.build(expression, dataset, inputPath.getIdColumn(), resultColumn, + inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getCurrentResource(), + inputPath.getThisColumn(), FHIRDefinedType.STRING); } private void validateInput(@Nonnull final NamedFunctionInput input) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunction.java index e6194e6880..8fdf410ed0 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunction.java @@ -31,8 +31,6 @@ import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** @@ -67,10 +65,9 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final String expression = expressionFromInput(input, NAME); return ElementPath - .build(expression, inputPath.getDataset(), inputPath.getIdColumn(), - inputPath.getEidColumn(), resultColumn, inputPath.isSingular(), - inputPath.getCurrentResource(), inputPath.getThisColumn(), - FHIRDefinedType.BOOLEAN); + .build(expression, inputPath.getDataset(), inputPath.getIdColumn(), resultColumn, + inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getCurrentResource(), + inputPath.getThisColumn(), FHIRDefinedType.BOOLEAN); } private void validateInput(@Nonnull final NamedFunctionInput input) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunction.java index dc77677d12..42912471f2 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunction.java @@ -17,6 +17,7 @@ package au.csiro.pathling.fhirpath.function.terminology; +import static au.csiro.pathling.QueryHelpers.explodeArray; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import static au.csiro.pathling.sql.Terminology.property_of; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; @@ -70,24 +71,24 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Dataset dataset = inputPath.getDataset(); final Column propertyValues = property_of(inputPath.getValueColumn(), propertyCode, propertyType, preferredLanguage.map(StringType::getValue).orElse(null)); - + // // The result is an array of property values per each input element, which we now // // need to explode in the same way as for path traversal, creating unique element ids. - final MutablePair valueAndEidColumns = new MutablePair<>(); - final Dataset resultDataset = inputPath - .explodeArray(dataset, propertyValues, valueAndEidColumns); + final MutablePair valueAndOrderingColumns = new MutablePair<>(); + final Dataset resultDataset = explodeArray(dataset, propertyValues, + valueAndOrderingColumns); if (FHIRDefinedType.CODING.equals(propertyType)) { // Special case for CODING properties: we use the Coding definition form // the input path so that the results can be further traversed. return inputPath.copy(expression, resultDataset, inputPath.getIdColumn(), - Optional.of(valueAndEidColumns.getRight()), - valueAndEidColumns.getLeft(), inputPath.isSingular(), inputPath.getThisColumn()); + valueAndOrderingColumns.getLeft(), Optional.of(valueAndOrderingColumns.getRight()), + inputPath.isSingular(), inputPath.getThisColumn()); } else { return ElementPath.build(expression, resultDataset, inputPath.getIdColumn(), - Optional.of(valueAndEidColumns.getRight()), - valueAndEidColumns.getLeft(), inputPath.isSingular(), inputPath.getCurrentResource(), - inputPath.getThisColumn(), propertyType); + valueAndOrderingColumns.getLeft(), Optional.of(valueAndOrderingColumns.getRight()), + inputPath.isSingular(), inputPath.getCurrentResource(), inputPath.getThisColumn(), + propertyType); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunction.java index 53cacfd8a7..0d9324f962 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunction.java @@ -22,7 +22,6 @@ import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import static au.csiro.pathling.sql.Terminology.subsumed_by; import static au.csiro.pathling.sql.Terminology.subsumes; -import static au.csiro.pathling.utilities.Preconditions.checkPresent; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.array; import static org.apache.spark.sql.functions.col; @@ -30,14 +29,12 @@ import static org.apache.spark.sql.functions.explode_outer; import static org.apache.spark.sql.functions.when; -import au.csiro.pathling.terminology.TerminologyServiceFactory; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.element.ElementPath; import au.csiro.pathling.fhirpath.function.NamedFunction; import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.terminology.TerminologyFunctions; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; import org.apache.spark.sql.Column; @@ -100,7 +97,7 @@ public SubsumesFunction(final boolean inverted) { public FhirPath invoke(@Nonnull final NamedFunctionInput input) { validateInput(input); - final NonLiteralPath inputFhirPath = input.getInput(); + final NonLiteralPath inputPath = input.getInput(); final Dataset idAndCodingSet = createJoinedDataset(input.getInput(), input.getArguments().get(0)); final Column leftCodings = idAndCodingSet.col(COL_INPUT_CODINGS); @@ -111,9 +108,9 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { // Construct a new result expression. final String expression = expressionFromInput(input, functionName); - return ElementPath.build(expression, idAndCodingSet, inputFhirPath.getIdColumn(), - inputFhirPath.getEidColumn(), resultColumn, inputFhirPath.isSingular(), - inputFhirPath.getCurrentResource(), inputFhirPath.getThisColumn(), FHIRDefinedType.BOOLEAN); + return ElementPath.build(expression, idAndCodingSet, inputPath.getIdColumn(), resultColumn, + inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getCurrentResource(), + inputPath.getThisColumn(), FHIRDefinedType.BOOLEAN); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java index ca971c8eb6..51e847ab5b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java @@ -17,6 +17,7 @@ package au.csiro.pathling.fhirpath.function.terminology; +import static au.csiro.pathling.QueryHelpers.explodeArray; import static au.csiro.pathling.fhirpath.TerminologyUtils.getCodingColumn; import static au.csiro.pathling.fhirpath.TerminologyUtils.isCodeableConcept; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; @@ -101,15 +102,15 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { // // The result is an array of translations per each input element, which we now // // need to explode in the same way as for path traversal, creating unique element ids. - final MutablePair valueAndEidColumns = new MutablePair<>(); - final Dataset resultDataset = inputPath - .explodeArray(dataset, translatedCodings, valueAndEidColumns); + final MutablePair valueAndOrderingColumns = new MutablePair<>(); + final Dataset resultDataset = explodeArray(dataset, translatedCodings, + valueAndOrderingColumns); final String expression = expressionFromInput(input, NAME); - return ElementPath.build(expression, resultDataset, idColumn, - Optional.of(valueAndEidColumns.getRight()), valueAndEidColumns.getLeft(), false, - inputPath.getCurrentResource(), inputPath.getThisColumn(), resultDefinition); + return ElementPath.build(expression, resultDataset, idColumn, valueAndOrderingColumns.getLeft(), + Optional.of(valueAndOrderingColumns.getRight()), false, inputPath.getCurrentResource(), + inputPath.getThisColumn(), resultDefinition); } private void validateInput(@Nonnull final NamedFunctionInput input) { @@ -132,5 +133,5 @@ private void validateInput(@Nonnull final NamedFunctionInput input) { checkUserInput(arguments.size() <= 2 || arguments.get(2) instanceof StringLiteralPath, String.format("Function `%s` expects `%s` as argument %s", NAME, "String literal", 3)); } - + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java index 0a1327b191..c20d364466 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java @@ -22,6 +22,7 @@ import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.Nesting; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.element.ElementPath; import com.google.common.collect.ImmutableMap; @@ -104,7 +105,7 @@ public abstract class LiteralPath implements FhirPath { @Nonnull protected final Optional expression; - + private LiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final ValueType value, @Nonnull final Optional expression) { this.idColumn = idColumn; @@ -164,23 +165,6 @@ public boolean isSingular() { return true; } - @Override - public boolean hasOrder() { - return true; - } - - @Nonnull - @Override - public Dataset getOrderedDataset() { - return getDataset(); - } - - @Nonnull - @Override - public Column getOrderingColumn() { - return ORDERING_NULL_VALUE; - } - /** * @return A column representing the value for this literal. */ @@ -207,21 +191,18 @@ public FhirPath withExpression(@Nonnull final String expression) { @Nonnull public NonLiteralPath combineWith(@Nonnull final FhirPath target, @Nonnull final Dataset dataset, @Nonnull final String expression, - @Nonnull final Column idColumn, @Nonnull final Optional eidColumn, - @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, final boolean singular, @Nonnull final Optional thisColumn) { if (target instanceof LiteralPath && getClass().equals(target.getClass())) { // If the target is another LiteralPath, we can merge it if they have the same FHIR type, as // decided by our mapping of literal FHIRPath types to FHIR types. final FHIRDefinedType fhirType = fhirPathToFhirType(getClass()); - return ElementPath - .build(expression, dataset, idColumn, eidColumn, valueColumn, singular, Optional.empty(), - thisColumn, fhirType); + return ElementPath.build(expression, dataset, idColumn, valueColumn, Optional.empty(), + singular, Optional.empty(), thisColumn, fhirType); } else if (target instanceof ElementPath) { // If the target is an ElementPath, we delegate off to the ElementPath to do the merging. - return target - .combineWith(this, dataset, expression, idColumn, eidColumn, valueColumn, singular, - thisColumn); + return target.combineWith(this, dataset, expression, idColumn, valueColumn, singular, + thisColumn); } // Anything else is invalid. throw new InvalidUserInputError( @@ -240,4 +221,15 @@ public Dataset getUnionableDataset(@Nonnull final FhirPath target) { return getDataset().select(getUnionableColumns(this, target).toArray(new Column[]{})); } + @Override + public Optional getOrderingColumn() { + return Optional.empty(); + } + + @Nonnull + @Override + public Dataset getOrderedDataset(@Nonnull final Nesting nesting) { + return dataset; + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/NullLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/NullLiteralPath.java index a08b01882f..482927601c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/NullLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/NullLiteralPath.java @@ -41,8 +41,8 @@ protected NullLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Co // We put a dummy String value in here as a placeholder so that we can satisfy the nullability // constraints within LiteralValue. It is never accessed. super(dataset, idColumn, new StringType(EXPRESSION)); - // But then we also need to override the value column - // to be a null literal rather then the placeholder value literal. + // We also need to override the value column to be a null literal rather than the placeholder + // value literal. this.valueColumn = lit(null); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java index f4bef51da2..d3ee51a6be 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java @@ -18,6 +18,7 @@ package au.csiro.pathling.fhirpath.literal; import static au.csiro.pathling.fhirpath.literal.StringLiteral.stringToFhirPath; +import static au.csiro.pathling.fhirpath.literal.StringLiteral.unescapeFhirPathString; import static au.csiro.pathling.utilities.Strings.unSingleQuote; import static org.apache.spark.sql.functions.lit; @@ -85,25 +86,6 @@ public Column buildValueColumn() { return lit(getValue().getValueAsString()); } - /** - * This method implements the rules for dealing with strings in the FHIRPath specification. - * - * @param value the string to be unescaped - * @return the unescaped result - * @see String - */ - @Nonnull - public static String unescapeFhirPathString(@Nonnull String value) { - value = value.replaceAll("\\\\/", "/"); - value = value.replaceAll("\\\\f", "\u000C"); - value = value.replaceAll("\\\\n", "\n"); - value = value.replaceAll("\\\\r", "\r"); - value = value.replaceAll("\\\\t", "\u0009"); - value = value.replaceAll("\\\\`", "`"); - value = value.replaceAll("\\\\'", "'"); - return value.replaceAll("\\\\\\\\", "\\\\"); - } - @Override @Nonnull public Function getComparison(@Nonnull final ComparisonOperation operation) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java index d6f7cc95d7..855497983a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java @@ -17,13 +17,10 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.QueryHelpers.join; -import static au.csiro.pathling.fhirpath.NonLiteralPath.findEidColumn; import static au.csiro.pathling.fhirpath.NonLiteralPath.findThisColumn; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.when; -import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.element.BooleanPath; import au.csiro.pathling.fhirpath.element.ElementPath; @@ -105,14 +102,12 @@ public FhirPath invoke(@Nonnull final OperatorInput input) { } final String expression = left.getExpression() + " " + type + " " + right.getExpression(); - final Dataset dataset = join(input.getContext(), left, right, JoinType.LEFT_OUTER); final Column idColumn = left.getIdColumn(); - final Optional eidColumn = findEidColumn(left, right); final Optional thisColumn = findThisColumn(left, right); return ElementPath - .build(expression, dataset, idColumn, eidColumn, valueColumn, true, Optional.empty(), - thisColumn, FHIRDefinedType.BOOLEAN); + .build(expression, right.getDataset(), idColumn, valueColumn, Optional.empty(), true, + Optional.empty(), thisColumn, FHIRDefinedType.BOOLEAN); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java index 9eb878d1cf..7f3586a235 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java @@ -18,13 +18,10 @@ package au.csiro.pathling.fhirpath.operator; import static au.csiro.pathling.QueryHelpers.createColumn; -import static org.apache.spark.sql.functions.array; -import static org.apache.spark.sql.functions.monotonically_increasing_id; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; -import java.util.Arrays; import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -50,19 +47,14 @@ public FhirPath invoke(@Nonnull final OperatorInput input) { final Dataset leftTrimmed = left.getUnionableDataset(right); final Dataset rightTrimmed = right.getUnionableDataset(left); - // the value column is always the last column - final int valueColumnIndex = leftTrimmed.columns().length - 1; + final Dataset dataset = leftTrimmed.union(rightTrimmed); - final String valueColumnName = dataset.columns()[valueColumnIndex]; - final DatasetWithColumn datasetWithColumn = createColumn(dataset, - dataset.col(valueColumnName)); - final Optional eidColumn = Optional.of(array(monotonically_increasing_id())); + final DatasetWithColumn datasetWithColumn = createColumn(dataset, left.getValueColumn()); final Optional thisColumn = left instanceof NonLiteralPath ? ((NonLiteralPath) left).getThisColumn() : Optional.empty(); - return left - .combineWith(right, datasetWithColumn.getDataset(), expression, left.getIdColumn(), - eidColumn, datasetWithColumn.getColumn(), false, thisColumn); + return left.combineWith(right, datasetWithColumn.getDataset(), expression, left.getIdColumn(), + datasetWithColumn.getColumn(), false, thisColumn); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java index f91a257eb0..992535eec5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java @@ -18,15 +18,12 @@ package au.csiro.pathling.fhirpath.operator; import static au.csiro.pathling.QueryHelpers.createColumn; -import static au.csiro.pathling.QueryHelpers.join; -import static au.csiro.pathling.fhirpath.NonLiteralPath.findEidColumn; import static au.csiro.pathling.fhirpath.NonLiteralPath.findThisColumn; import static au.csiro.pathling.fhirpath.operator.Operator.buildExpression; import static au.csiro.pathling.fhirpath.operator.Operator.checkArgumentsAreComparable; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; -import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.Comparable.ComparisonOperation; import au.csiro.pathling.fhirpath.FhirPath; @@ -69,18 +66,18 @@ public FhirPath invoke(@Nonnull final OperatorInput input) { checkArgumentsAreComparable(input, type.toString()); final String expression = buildExpression(input, type.toString()); - final Dataset dataset = join(input.getContext(), left, right, JoinType.LEFT_OUTER); + final Dataset dataset = right.getDataset(); final Comparable leftComparable = (Comparable) left; final Comparable rightComparable = (Comparable) right; final Column valueColumn = leftComparable.getComparison(type).apply(rightComparable); final Column idColumn = left.getIdColumn(); - final Optional eidColumn = findEidColumn(left, right); final Optional thisColumn = findThisColumn(left, right); final DatasetWithColumn datasetWithColumn = createColumn(dataset, valueColumn); - return ElementPath.build(expression, datasetWithColumn.getDataset(), idColumn, eidColumn, - datasetWithColumn.getColumn(), true, Optional.empty(), thisColumn, FHIRDefinedType.BOOLEAN); + return ElementPath.build(expression, datasetWithColumn.getDataset(), idColumn, + datasetWithColumn.getColumn(), Optional.empty(), true, Optional.empty(), thisColumn, + FHIRDefinedType.BOOLEAN); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/DateArithmeticOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/DateArithmeticOperator.java index 43540c7ef4..1f91fc47b5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/DateArithmeticOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/DateArithmeticOperator.java @@ -70,9 +70,8 @@ public FhirPath invoke(@Nonnull final OperatorInput input) { final Temporal temporal = (Temporal) left; final String expression = buildExpression(input, type.toString()); - final Dataset dataset = join(input.getContext(), left, right, JoinType.LEFT_OUTER); - return temporal.getDateArithmeticOperation(type, dataset, expression) + return temporal.getDateArithmeticOperation(type, right.getDataset(), expression) .apply(calendarDuration); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MathOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MathOperator.java index c3e37b0977..4dcdf57dec 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MathOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MathOperator.java @@ -80,11 +80,10 @@ public FhirPath invoke(@Nonnull final OperatorInput input) { + type + " " + right.getExpression()); final String expression = buildExpression(input, type.toString()); - final Dataset dataset = join(input.getContext(), left, right, JoinType.LEFT_OUTER); final Numeric leftNumeric = (Numeric) left; final Numeric rightNumeric = (Numeric) right; - return leftNumeric.getMathOperation(type, expression, dataset).apply(rightNumeric); + return leftNumeric.getMathOperation(type, expression, right.getDataset()).apply(rightNumeric); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java index 4e0e9c00e2..602385144a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java @@ -17,14 +17,12 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.QueryHelpers.join; import static au.csiro.pathling.fhirpath.operator.Operator.checkArgumentsAreComparable; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.lit; import static org.apache.spark.sql.functions.max; import static org.apache.spark.sql.functions.when; -import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.Comparable.ComparisonOperation; import au.csiro.pathling.fhirpath.FhirPath; @@ -32,8 +30,6 @@ import java.util.Arrays; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** @@ -85,14 +81,11 @@ public FhirPath invoke(@Nonnull final OperatorInput input) { .when(collectionValue.isNull(), lit(false)) .otherwise(equality); - // We need to join the datasets in order to access values from both operands. - final Dataset dataset = join(input.getContext(), left, right, JoinType.LEFT_OUTER); - // In order to reduce the result to a single Boolean, we take the max of the boolean equality // values. final Column valueColumn = max(equalityWithNullChecks); - return buildAggregateResult(dataset, input.getContext(), Arrays.asList(left, right), + return buildAggregateResult(right.getDataset(), input.getContext(), Arrays.asList(left, right), valueColumn, expression, FHIRDefinedType.BOOLEAN); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java index 19416544e4..a9131be415 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java @@ -17,15 +17,15 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.QueryHelpers.createColumns; +import static au.csiro.pathling.QueryHelpers.explodeArray; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.lit; import static org.apache.spark.sql.functions.when; -import au.csiro.pathling.QueryHelpers.DatasetWithColumnMap; import au.csiro.pathling.encoders.ExtensionSupport; import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.Nesting; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.element.ElementDefinition; @@ -54,7 +54,7 @@ public class PathTraversalOperator { * @return A {@link FhirPath} object representing the resulting expression */ @Nonnull - public ElementPath invoke(@Nonnull final PathTraversalInput input) { + public NonLiteralPath invoke(@Nonnull final PathTraversalInput input) { checkUserInput(input.getLeft() instanceof NonLiteralPath, "Path traversal operator cannot be invoked on a literal value: " + input.getLeft() .getExpression()); @@ -74,6 +74,11 @@ public ElementPath invoke(@Nonnull final PathTraversalInput input) { final ElementDefinition childDefinition = optionalChild.get(); final Dataset leftDataset = left.getDataset(); + final Nesting nesting = input.getContext().getNesting(); + + // If the element has a max cardinality of more than one, it will need to be unnested. + final boolean maxCardinalityOfOne = childDefinition.getMaxCardinality() == 1; + final boolean resultSingular = left.isSingular() && maxCardinalityOfOne; final Column field; if (ExtensionSupport.EXTENSION_ELEMENT_NAME().equals(right)) { @@ -84,49 +89,31 @@ public ElementPath invoke(@Nonnull final PathTraversalInput input) { field = getValueField(left, right); } - // If the element has a max cardinality of more than one, it will need to be "exploded" out into - // multiple rows. - final boolean maxCardinalityOfOne = childDefinition.getMaxCardinality() == 1; - final boolean resultSingular = left.isSingular() && maxCardinalityOfOne; - - final Column valueColumn; - final Optional eidColumnCandidate; - final Dataset resultDataset; final UnnestBehaviour unnestBehaviour = input.getContext().getUnnestBehaviour(); - if (maxCardinalityOfOne || unnestBehaviour == UnnestBehaviour.NOOP) { - valueColumn = field; - eidColumnCandidate = left.getEidColumn(); - resultDataset = leftDataset; - } else if (unnestBehaviour == UnnestBehaviour.ERROR) { + if (!maxCardinalityOfOne && unnestBehaviour == UnnestBehaviour.ERROR) { throw new InvalidUserInputError("Encountered a repeating element: " + expression); + + } else if (maxCardinalityOfOne || unnestBehaviour == UnnestBehaviour.NOOP) { + return ElementPath.build(expression, leftDataset, left.getIdColumn(), field, + Optional.empty(), resultSingular, left.getCurrentResource(), left.getThisColumn(), + childDefinition); + } else if (unnestBehaviour == UnnestBehaviour.UNNEST) { - final MutablePair valueAndEidColumns = new MutablePair<>(); - final Dataset explodedDataset = left.explodeArray(leftDataset, field, - valueAndEidColumns); - final DatasetWithColumnMap datasetWithColumnMap = createColumns(explodedDataset, - valueAndEidColumns.getLeft(), valueAndEidColumns.getRight()); - resultDataset = datasetWithColumnMap.getDataset(); - valueColumn = datasetWithColumnMap.getColumn(valueAndEidColumns.getLeft()); - eidColumnCandidate = Optional.of( - datasetWithColumnMap.getColumn(valueAndEidColumns.getRight())); + return nesting.updateOrRetrieve(childDefinition, expression, leftDataset, false, + left.getThisColumn(), key -> { + final MutablePair valueAndOrderingColumns = new MutablePair<>(); + final Dataset resultDataset = explodeArray(left.getDataset(), field, + valueAndOrderingColumns); + return ElementPath.build(expression, resultDataset, left.getIdColumn(), + valueAndOrderingColumns.getLeft(), Optional.of(valueAndOrderingColumns.getRight()), + false, left.getCurrentResource(), left.getThisColumn(), childDefinition); + }); + } else { throw new UnsupportedOperationException("Unsupported unnest behaviour: " + unnestBehaviour); } - final Optional eidColumn = resultSingular - ? Optional.empty() - : eidColumnCandidate; - - // If there is an element ID column, we need to add it to the parser context so that it can - // be used within joins in certain situations, e.g. extract. - if (unnestBehaviour == UnnestBehaviour.UNNEST && !maxCardinalityOfOne) { - eidColumn.ifPresent(c -> input.getContext().addNodeId(childDefinition, c)); - } - - return ElementPath - .build(expression, resultDataset, left.getIdColumn(), eidColumn, valueColumn, - resultSingular, left.getCurrentResource(), left.getThisColumn(), childDefinition); } @Nonnull diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index 11e0569673..9a7075047f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -128,6 +128,7 @@ public FhirPath visitMemberInvocation(@Nullable final MemberInvocationContext ct // could be: // (1) a path traversal from the input context; // (2) a reference to a resource type. + final FhirPath thisContext = context.getThisContext().get(); // Check if the expression is a reference to a known resource type. final ResourceType resourceType; @@ -137,7 +138,7 @@ public FhirPath visitMemberInvocation(@Nullable final MemberInvocationContext ct // If the expression is not a resource reference, treat it as a path traversal from the // input context. final PathTraversalInput pathTraversalInput = new PathTraversalInput(context, - context.getThisContext().get(), fhirPath); + thisContext, fhirPath); return new PathTraversalOperator().invoke(pathTraversalInput); } @@ -193,14 +194,15 @@ public FhirPath visitFunctionInvocation(@Nullable final FunctionInvocationContex // aggregations that occur within the arguments are in the context of an element. Otherwise, // we add the resource ID column to the groupings. final List argumentGroupings = new ArrayList<>(context.getGroupingColumns()); - thisPath.getEidColumn().ifPresentOrElse(argumentGroupings::add, + thisPath.getOrderingColumn().ifPresentOrElse(argumentGroupings::add, () -> argumentGroupings.add(thisPath.getIdColumn())); // Create a new ParserContext, which includes information about how to evaluate the `$this` // expression. final ParserContext argumentContext = new ParserContext(context.getInputContext(), context.getFhirContext(), context.getSparkSession(), context.getDataSource(), - context.getTerminologyServiceFactory(), argumentGroupings, context.getNodeIdColumns()); + context.getTerminologyServiceFactory(), argumentGroupings, context.getUnnestBehaviour(), + context.getVariables(), context.getNesting()); argumentContext.setThisContext(thisPath); // Parse each of the expressions passed as arguments to the function. diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index 1118f157ec..81762750ff 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -19,16 +19,15 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirPathAndContext; +import au.csiro.pathling.fhirpath.Nesting; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyService; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import javax.annotation.Nonnull; import lombok.Getter; import org.apache.spark.sql.Column; @@ -100,19 +99,15 @@ public class ParserContext { @Nonnull private Optional thisContext = Optional.empty(); - /** - * Stores away columns relating to the identity of resources and elements, for later retrieval - * using the string path. This is used for element and resource-identity aware joins. - */ - @Nonnull - private final Map> nodeIdColumns; - @Nonnull private final UnnestBehaviour unnestBehaviour; @Nonnull private final Map variables; + @Nonnull + private final Nesting nesting; + /** * @param inputContext the input context from which the FHIRPath is to be evaluated * @param fhirContext a {@link FhirContext} that can be used to do FHIR stuff @@ -121,17 +116,15 @@ public class ParserContext { * @param dataSource for retrieving data relating to resource references * @param terminologyServiceFactory a factory for {@link TerminologyService} objects, used for * parallel processing - * @param groupingColumns the list of columns to group on when aggregating - * @param nodeIdColumns columns relating to the identity of resources and elements for different - * paths parsed within this context + * @param groupingColumns the list of columns to group on when aggregating paths parsed within + * this context */ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataSource, @Nonnull final Optional terminologyServiceFactory, - @Nonnull final List groupingColumns, - @Nonnull final Map> nodeIdColumns) { + @Nonnull final List groupingColumns) { this(inputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, - groupingColumns, nodeIdColumns, UnnestBehaviour.UNNEST, new HashMap<>()); + groupingColumns, UnnestBehaviour.UNNEST, new HashMap<>(), new Nesting()); } /** @@ -143,26 +136,24 @@ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirCo * @param terminologyServiceFactory a factory for {@link TerminologyService} objects, used for * parallel processing * @param groupingColumns the list of columns to group on when aggregating - * @param nodeIdColumns columns relating to the identity of resources and elements for different - * paths parsed within this context * @param unnestBehaviour the execution context to use */ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataSource, @Nonnull final Optional terminologyServiceFactory, @Nonnull final List groupingColumns, - @Nonnull final Map> nodeIdColumns, @Nonnull final UnnestBehaviour unnestBehaviour, - @Nonnull final Map variables) { + @Nonnull final Map variables, + @Nonnull final Nesting nesting) { this.inputContext = inputContext; this.fhirContext = fhirContext; this.sparkSession = sparkSession; this.dataSource = dataSource; this.terminologyServiceFactory = terminologyServiceFactory; this.groupingColumns = groupingColumns; - this.nodeIdColumns = nodeIdColumns; this.unnestBehaviour = unnestBehaviour; this.variables = variables; + this.nesting = nesting; } public void setThisContext(@Nonnull final FhirPath thisContext) { @@ -177,7 +168,7 @@ public void setThisContext(@Nonnull final FhirPath thisContext) { */ public ParserContext withUnnestBehaviour(@Nonnull final UnnestBehaviour unnestBehaviour) { return new ParserContext(inputContext, fhirContext, sparkSession, dataSource, - terminologyServiceFactory, groupingColumns, nodeIdColumns, unnestBehaviour, variables); + terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, nesting); } /** @@ -189,11 +180,7 @@ public ParserContext withUnnestBehaviour(@Nonnull final UnnestBehaviour unnestBe public ParserContext withContextDataset(@Nonnull final Dataset contextDataset) { final FhirPath newInputContext = inputContext.withDataset(contextDataset); return new ParserContext(newInputContext, fhirContext, sparkSession, dataSource, - terminologyServiceFactory, groupingColumns, new HashMap<>(), unnestBehaviour, variables); - } - - public void addNodeId(@Nonnull final Object key, @Nonnull final Column value) { - nodeIdColumns.computeIfAbsent(key, k -> new HashSet<>()).add(value); + terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, nesting); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java index 6f1ab188b9..d46206c28e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java @@ -72,15 +72,14 @@ public FhirPath visitExternalConstantTerm(@Nullable final ExternalConstantTermCo // In the case of %resource and %context, the new expression will be the input context with the // expression updated to match the external constant term. return inputContext.copy(term, inputContext.getDataset(), inputContext.getIdColumn(), - inputContext.getEidColumn(), inputContext.getValueColumn(), inputContext.isSingular(), - inputContext.getThisColumn()); + inputContext.getValueColumn(), inputContext.getOrderingColumn(), + inputContext.isSingular(), inputContext.getThisColumn()); } else { final String variableName = term.replaceFirst("%", ""); final FhirPathAndContext variable = context.getVariables().get(variableName); if (variable == null) { throw new IllegalArgumentException("Unknown variable: " + variableName); } - context.getNodeIdColumns().putAll(variable.getContext().getNodeIdColumns()); return variable.getFhirPath(); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java index b6f2ae103c..73b940aaaf 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java @@ -39,7 +39,6 @@ import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.TermExpressionContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.TypeExpressionContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.UnionExpressionContext; -import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.antlr.v4.runtime.tree.ParseTree; @@ -93,7 +92,8 @@ private FhirPath visitBinaryOperator(@Nullable final ParseTree leftContext, // Parse the left and right expressions. final FhirPath left = new Visitor(context).visit(leftContext); - final FhirPath right = new Visitor(context).visit(rightContext); + final FhirPath right = new Visitor(context.withContextDataset(left.getDataset())) + .visit(rightContext); // Retrieve an Operator instance based upon the operator string. final Operator operator = Operator.getInstance(operatorName); diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index 5cba8cff0f..9f7e09988e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -5,8 +5,6 @@ import static org.apache.spark.sql.functions.flatten; import au.csiro.pathling.QueryExecutor; -import au.csiro.pathling.QueryHelpers; -import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirPathAndContext; @@ -20,7 +18,6 @@ import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -84,8 +81,8 @@ public Dataset buildQuery(@Nonnull final FhirView view) { final NonLiteralPath nonLiteralResult = (NonLiteralPath) result; final Column flattenedValue = flatten(nonLiteralResult.getValueColumn()); result = nonLiteralResult.copy(nonLiteralResult.getExpression(), - nonLiteralResult.getDataset(), nonLiteralResult.getIdColumn(), - nonLiteralResult.getEidColumn(), flattenedValue, nonLiteralResult.isSingular(), + nonLiteralResult.getDataset(), nonLiteralResult.getIdColumn(), flattenedValue, + nonLiteralResult.getOrderingColumn(), nonLiteralResult.isSingular(), nonLiteralResult.getThisColumn()); } @@ -124,71 +121,4 @@ public Dataset buildQuery(@Nonnull final FhirView view) { .filter(idColumn.isNotNull()); } - @Nonnull - private Dataset joinAllColumns( - @Nonnull final Collection columnsAndContexts) { - if (columnsAndContexts.isEmpty()) { - // If there are no columns, throw an error. - throw new IllegalArgumentException("No columns to join"); - - } else if (columnsAndContexts.size() == 1) { - // If there is only one column, skip joining and return its dataset. - final FhirPathAndContext fhirPathAndContext = columnsAndContexts.iterator().next(); - return fhirPathAndContext.getFhirPath().getDataset(); - } - - // Sort the columns by the nodes encountered while parsing. This ensures that we join them - // together in order from the general to the specific. - // final List sorted = columnsAndContexts.stream() - // .sorted((a, b) -> { - // final List nodesA = a.getContext().getNodeIdColumns().keySet().stream() - // .sorted() - // .collect(toList()); - // final List nodesB = b.getContext().getNodeIdColumns().keySet().stream() - // .sorted() - // .collect(toList()); - // final String sortStringA = String.join("|", nodesA); - // final String sortStringB = String.join("|", nodesB); - // return sortStringA.compareTo(sortStringB); - // }) - // .collect(toList()); - final List sorted = new ArrayList<>(columnsAndContexts); - - // Start with the first column and its unjoined dataset. - FhirPathAndContext left = sorted.get(0); - Dataset result = left.getFhirPath().getDataset(); - - // Move through the list of columns, joining each one to the result of the previous join. - for (final FhirPathAndContext right : sorted.subList(1, sorted.size())) { - final List leftJoinColumns = new ArrayList<>(); - final List rightJoinColumns = new ArrayList<>(); - - // The join column always includes the resource ID. - leftJoinColumns.add(left.getFhirPath().getIdColumn()); - rightJoinColumns.add(right.getFhirPath().getIdColumn()); - - // Add the intersection of the nodes present in both the left and right column contexts. - final List commonNodes = new ArrayList<>( - left.getContext().getNodeIdColumns().keySet()); - commonNodes.retainAll(right.getContext().getNodeIdColumns().keySet()); - final FhirPathAndContext finalLeft = left; - // leftJoinColumns.addAll(commonNodes.stream() - // .map(key -> finalLeft.getContext().getNodeIdColumns().get(key)) - // .collect(toList())); - // rightJoinColumns.addAll(commonNodes.stream() - // .map(key -> right.getContext().getNodeIdColumns().get(key)) - // .collect(toList())); - - // Use a left outer join, so that we don't lose rows that don't have a value for the right - // column. - result = QueryHelpers.join(result, leftJoinColumns, right.getFhirPath().getDataset(), - rightJoinColumns, JoinType.LEFT_OUTER); - - // The result of the join becomes the left side of the next join. - left = right; - } - - return result; - } - } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/NonLiteralPathTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/NonLiteralPathTest.java index 999c7ba7a8..4e4d8e8c4d 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/NonLiteralPathTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/NonLiteralPathTest.java @@ -17,25 +17,8 @@ package au.csiro.pathling.fhirpath; -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.element.ElementPath; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.DatasetBuilder; -import au.csiro.pathling.test.builders.ElementPathBuilder; -import java.util.Arrays; -import java.util.Collections; -import org.apache.commons.lang3.tuple.MutablePair; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; -import org.apache.spark.sql.functions; -import org.apache.spark.sql.types.DataTypes; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; /** @@ -49,122 +32,121 @@ class NonLiteralPathTest { @Autowired SparkSession spark; - @Test - void testSingularNonLiteralEidExpansion() { - // Check the result. - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", "Jude") // when: "two values" expect: "Jude" - .build(); - - final ElementPath testPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(inputDataset) - .idAndValueColumns() - .expression("Patient.name") - .singular(true) - .build(); - - final Column newNonNullEid = testPath - .expandEid(functions.lit(2)); - assertEquals(Collections.singletonList(2), - inputDataset.select(newNonNullEid).first().getList(0)); - - final Column newNullEid = testPath - .expandEid(functions.lit(null)); - assertTrue(inputDataset.select(newNullEid).first().isNullAt(0)); - } - - @Test - void testNonSingularNonLiteralEidExpansion() { - // Check the result. - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", DatasetBuilder.makeEid(2, 3), "Adam") - .withRow("patient-1", DatasetBuilder.makeEid(1, 3), "Jude") - .withRow("patient-2", null, null) - .build(); - - final ElementPath testPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("Patient.name") - .singular(false) - .build(); - - final Column idCol = testPath.getIdColumn(); - final Dataset pathDataset = testPath.getOrderedDataset(); - - // Test non-null element ID. - final Column newNonNullEid = testPath - .expandEid(functions.lit(2)); - assertEquals(Arrays.asList(1, 3, 2), - pathDataset.where(idCol.equalTo("patient-1")).select(newNonNullEid).first() - .getList(0)); - assertTrue( - pathDataset.where(idCol.equalTo("patient-2")).select(newNonNullEid).first() - .isNullAt(0)); - - // Test null element ID. - final Column newNullEid = testPath - .expandEid(functions.lit(null)); - - assertEquals(Arrays.asList(1, 3, 0), - pathDataset.where(idCol.equalTo("patient-1")).select(newNullEid).first() - .getList(0)); - assertTrue(pathDataset.where(idCol.equalTo("patient-2")).select(newNullEid).first() - .isNullAt(0)); - } - - - @Test - void testArrayExplode() { - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", DatasetBuilder.makeEid(2, 3), "Adam,Eve") - .withRow("patient-1", DatasetBuilder.makeEid(1, 3), "Jude") - .withRow("patient-2", DatasetBuilder.makeEid(0, 0), null) - .withRow("patient-3", null, null) - .build(); - - final ElementPath testPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("Patient.name") - .singular(false) - .build(); - - final Dataset arrayDataset = testPath.getDataset() - .withColumn("arrayCol", functions.split(testPath.getValueColumn(), ",")); - - final MutablePair valueAndEidColumns = new MutablePair<>(); - final Dataset explodedDataset = testPath - .explodeArray(arrayDataset, arrayDataset.col("arrayCol"), valueAndEidColumns); - - final Dataset actualDataset = explodedDataset - .select(testPath.getIdColumn(), valueAndEidColumns.getRight(), testPath.getValueColumn(), - valueAndEidColumns.getLeft()) - .orderBy(testPath.getIdColumn(), valueAndEidColumns.getRight()); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withColumn(DataTypes.StringType) - .withRow("patient-1", DatasetBuilder.makeEid(1, 3, 0), "Jude", "Jude") - .withRow("patient-1", DatasetBuilder.makeEid(2, 3, 0), "Adam,Eve", "Adam") - .withRow("patient-1", DatasetBuilder.makeEid(2, 3, 1), "Adam,Eve", "Eve") - .withRow("patient-2", DatasetBuilder.makeEid(0, 0, 0), null, null) - .withRow("patient-3", null, null, null) - .build(); - - assertThat(actualDataset).hasRows(expectedDataset); - } + // @Test + // void testSingularNonLiteralEidExpansion() { + // // Check the result. + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "Jude") // when: "two values" expect: "Jude" + // .build(); + // + // final ElementPath testPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(inputDataset) + // .idAndValueColumns() + // .expression("Patient.name") + // .singular(true) + // .build(); + // + // final Column newNonNullEid = testPath + // .expandEid(functions.lit(2)); + // assertEquals(Collections.singletonList(2), + // inputDataset.select(newNonNullEid).first().getList(0)); + // + // final Column newNullEid = testPath + // .expandEid(functions.lit(null)); + // assertTrue(inputDataset.select(newNullEid).first().isNullAt(0)); + // } + + // @Test + // void testNonSingularNonLiteralEidExpansion() { + // // Check the result. + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", DatasetBuilder.makeEid(2, 3), "Adam") + // .withRow("patient-1", DatasetBuilder.makeEid(1, 3), "Jude") + // .withRow("patient-2", null, null) + // .build(); + // + // final ElementPath testPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("Patient.name") + // .singular(false) + // .build(); + // + // final Column idCol = testPath.getIdColumn(); + // final Dataset pathDataset = testPath.getOrderedDataset(); + // + // // Test non-null element ID. + // final Column newNonNullEid = testPath + // .expandEid(functions.lit(2)); + // assertEquals(Arrays.asList(1, 3, 2), + // pathDataset.where(idCol.equalTo("patient-1")).select(newNonNullEid).first() + // .getList(0)); + // assertTrue( + // pathDataset.where(idCol.equalTo("patient-2")).select(newNonNullEid).first() + // .isNullAt(0)); + // + // // Test null element ID. + // final Column newNullEid = testPath + // .expandEid(functions.lit(null)); + // + // assertEquals(Arrays.asList(1, 3, 0), + // pathDataset.where(idCol.equalTo("patient-1")).select(newNullEid).first() + // .getList(0)); + // assertTrue(pathDataset.where(idCol.equalTo("patient-2")).select(newNullEid).first() + // .isNullAt(0)); + // } + + // @Test + // void testArrayExplode() { + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", DatasetBuilder.makeEid(2, 3), "Adam,Eve") + // .withRow("patient-1", DatasetBuilder.makeEid(1, 3), "Jude") + // .withRow("patient-2", DatasetBuilder.makeEid(0, 0), null) + // .withRow("patient-3", null, null) + // .build(); + // + // final ElementPath testPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("Patient.name") + // .singular(false) + // .build(); + // + // final Dataset arrayDataset = testPath.getDataset() + // .withColumn("arrayCol", functions.split(testPath.getValueColumn(), ",")); + // + // final MutablePair valueAndEidColumns = new MutablePair<>(); + // final Dataset explodedDataset = testPath + // .explodeArray(arrayDataset, arrayDataset.col("arrayCol"), valueAndEidColumns); + // + // final Dataset actualDataset = explodedDataset + // .select(testPath.getIdColumn(), valueAndEidColumns.getRight(), testPath.getValueColumn(), + // valueAndEidColumns.getLeft()) + // .orderBy(testPath.getIdColumn(), valueAndEidColumns.getRight()); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", DatasetBuilder.makeEid(1, 3, 0), "Jude", "Jude") + // .withRow("patient-1", DatasetBuilder.makeEid(2, 3, 0), "Adam,Eve", "Adam") + // .withRow("patient-1", DatasetBuilder.makeEid(2, 3, 1), "Adam,Eve", "Eve") + // .withRow("patient-2", DatasetBuilder.makeEid(0, 0, 0), null, null) + // .withRow("patient-3", null, null, null) + // .build(); + // + // assertThat(actualDataset).hasRows(expectedDataset); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/OrderableTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/OrderableTest.java index e982e29408..bb0f1d93c2 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/OrderableTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/OrderableTest.java @@ -17,25 +17,7 @@ package au.csiro.pathling.fhirpath; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.DatasetBuilder; -import au.csiro.pathling.test.builders.ElementPathBuilder; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -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.Test; -import org.junit.jupiter.api.function.Executable; -import org.springframework.beans.factory.annotation.Autowired; /** * Test some basic Orderable behaviour across different FhirPath types. @@ -45,132 +27,123 @@ @SpringBootUnitTest class OrderableTest { - @Autowired - SparkSession spark; - - @Test - void testLiteralHasOrder() { - - // Check the result. - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", "Jude") // when: "two values" expect: "Jude" - .build(); - - final ElementPath contextPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(inputDataset) - .idAndValueColumns() - .expression("Patient.name") - .build(); - - final StringLiteralPath testLiteralPath = StringLiteralPath.fromString("test", contextPath); - - assertTrue(testLiteralPath.hasOrder()); - assertEquals(testLiteralPath.getDataset(), testLiteralPath.getOrderedDataset()); - assertEquals(Orderable.ORDERING_NULL_VALUE, testLiteralPath.getOrderingColumn()); - testLiteralPath.checkHasOrder(); - } - - @Test - void testSingularNonLiteralHasOrder() { - // Check the result. - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", "Jude") // when: "two values" expect: "Jude" - .build(); - - final ElementPath testPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(inputDataset) - .idAndValueColumns() - .expression("Patient.name") - .singular(true) - .build(); - - assertTrue(testPath.hasOrder()); - assertEquals(testPath.getDataset(), testPath.getOrderedDataset()); - assertEquals(Orderable.ORDERING_NULL_VALUE, testPath.getOrderingColumn()); - testPath.checkHasOrder(); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", null, "Jude") - .build(); - - assertThat(testPath) - .selectOrderedResultWithEid() - .hasRows(expectedDataset); - } - - static void assertFailsOrderCheck(final Executable e) { - final IllegalStateException error = assertThrows(IllegalStateException.class, e); - assertEquals("Orderable path expected", error.getMessage()); - } - - @Test - void testNonSingularNonLiteralWithEidHasOrder() { - // Check the result. - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", DatasetBuilder.makeEid(2, 3), "Adam") - .withRow("patient-1", DatasetBuilder.makeEid(1, 3), "Jude") - .withRow("patient-2", null, null) - .build(); - - final ElementPath testPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("Patient.name") - .singular(false) - .build(); - - assertTrue(testPath.hasOrder()); - assertTrue(testPath.getEidColumn().isPresent()); - assertEquals(testPath.getEidColumn().get(), testPath.getOrderingColumn()); - testPath.checkHasOrder(); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", DatasetBuilder.makeEid(1, 3), "Jude") - .withRow("patient-1", DatasetBuilder.makeEid(2, 3), "Adam") - .withRow("patient-2", null, null) - .build(); - - assertThat(testPath) - .selectOrderedResultWithEid() - .hasRows(expectedDataset); - } - - @Test - void testNonSingularNonLiteralWithoutEidHasNoOrder() { - // Check the result. - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", "Adam") - .build(); - - final ElementPath testPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(inputDataset) - .idAndValueColumns() - .expression("Patient.name") - .singular(false) - .build(); - - assertFalse(testPath.hasOrder()); - assertFailsOrderCheck(testPath::checkHasOrder); - assertFailsOrderCheck(testPath::getOrderedDataset); - assertFailsOrderCheck(testPath::getOrderingColumn); - } + // @Autowired + // SparkSession spark; + // + // @Test + // void testLiteralHasOrder() { + // + // // Check the result. + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "Jude") // when: "two values" expect: "Jude" + // .build(); + // + // final ElementPath contextPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(inputDataset) + // .idAndValueColumns() + // .expression("Patient.name") + // .build(); + // + // final StringLiteralPath testLiteralPath = StringLiteralPath.fromString("test", contextPath); + // + // assertTrue(testLiteralPath.getOrderingColumn().isPresent()); + // assertEquals(testLiteralPath.getDataset(), testLiteralPath.getOrderedDataset()); + // } + // + // @Test + // void testSingularNonLiteralHasOrder() { + // // Check the result. + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "Jude") // when: "two values" expect: "Jude" + // .build(); + // + // final ElementPath testPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(inputDataset) + // .idAndValueColumns() + // .expression("Patient.name") + // .singular(true) + // .build(); + // + // assertTrue(testPath.hasOrder()); + // assertEquals(testPath.getDataset(), testPath.getOrderedDataset()); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", null, "Jude") + // .build(); + // + // assertThat(testPath) + // .selectOrderedResultWithEid() + // .hasRows(expectedDataset); + // } + // + // static void assertFailsOrderCheck(final Executable e) { + // final IllegalStateException error = assertThrows(IllegalStateException.class, e); + // assertEquals("Orderable path expected", error.getMessage()); + // } + // + // @Test + // void testNonSingularNonLiteralWithEidHasOrder() { + // // Check the result. + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", DatasetBuilder.makeEid(2, 3), "Adam") + // .withRow("patient-1", DatasetBuilder.makeEid(1, 3), "Jude") + // .withRow("patient-2", null, null) + // .build(); + // + // final ElementPath testPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("Patient.name") + // .singular(false) + // .build(); + // + // assertTrue(testPath.hasOrder()); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", DatasetBuilder.makeEid(1, 3), "Jude") + // .withRow("patient-1", DatasetBuilder.makeEid(2, 3), "Adam") + // .withRow("patient-2", null, null) + // .build(); + // + // assertThat(testPath) + // .selectOrderedResultWithEid() + // .hasRows(expectedDataset); + // } + // + // @Test + // void testNonSingularNonLiteralWithoutEidHasNoOrder() { + // // Check the result. + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "Adam") + // .build(); + // + // final ElementPath testPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(inputDataset) + // .idAndValueColumns() + // .expression("Patient.name") + // .singular(false) + // .build(); + // + // assertFalse(testPath.hasOrder()); + // assertFailsOrderCheck(testPath::getOrderedDataset); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java index 858c500dcb..a82df0ab38 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java @@ -29,6 +29,7 @@ import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.ResourceDefinition; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; @@ -44,6 +45,7 @@ import au.csiro.pathling.test.helpers.SparkHelpers.IdAndValueColumns; import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; import java.util.Arrays; import java.util.Collections; import java.util.Optional; @@ -242,10 +244,14 @@ void throwsErrorIfArgumentTypeDoesNotMatchInput() { .resourceType(ResourceType.PATIENT) .expression("Patient") .build(); - final BaseRuntimeChildDefinition childDefinition = fhirContext - .getResourceDefinition("Encounter").getChildByName("episodeOfCare"); - final ElementDefinition definition = ElementDefinition - .build(childDefinition, "episodeOfCare"); + final RuntimeResourceDefinition hapiResourceDef = fhirContext.getResourceDefinition( + "Encounter"); + final ResourceDefinition resourceDefinition = new ResourceDefinition(ResourceType.ENCOUNTER, + hapiResourceDef, Optional.empty()); + final BaseRuntimeChildDefinition childDefinition = hapiResourceDef.getChildByName( + "episodeOfCare"); + final ElementDefinition definition = ElementDefinition.build(childDefinition, "episodeOfCare", + resourceDefinition); final ElementPath argument = new ElementPathBuilder(spark) .expression("Encounter.episodeOfCare") .fhirType(FHIRDefinedType.REFERENCE) diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java index f95d39af9b..9d3bf7b23a 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java @@ -54,11 +54,8 @@ public DatasetAssert selectResult() { @Nonnull public DatasetAssert selectOrderedResult() { final Column[] selection = new Column[]{fhirPath.getIdColumn(), fhirPath.getValueColumn()}; - final Column[] ordering = new Column[]{fhirPath.getIdColumn(), fhirPath.getOrderingColumn()}; - - return new DatasetAssert(fhirPath.getOrderedDataset() - .select(selection) - .orderBy(ordering)); + // TODO: Update this to make sure that it is ordered. + return new DatasetAssert(fhirPath.getDataset().select(selection)); } @Nonnull @@ -80,13 +77,9 @@ private DatasetAssert selectGroupingResult(@Nonnull final List groupingC @Nonnull public DatasetAssert selectOrderedResultWithEid() { - final Column[] selection = new Column[]{fhirPath.getIdColumn(), fhirPath.getOrderingColumn(), - fhirPath.getValueColumn()}; - final Column[] ordering = new Column[]{fhirPath.getIdColumn(), fhirPath.getOrderingColumn()}; - - return new DatasetAssert(fhirPath.getOrderedDataset() - .select(selection) - .orderBy(ordering)); + final Column[] selection = new Column[]{fhirPath.getIdColumn(), fhirPath.getValueColumn()}; + // TODO: Update this to make sure that it is ordered. + return new DatasetAssert(fhirPath.getDataset().select(selection)); } @Nonnull diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java index 046c89957d..b6e9399f99 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java @@ -86,6 +86,8 @@ public DatasetAssert hasRows(@Nonnull final Dataset expected) { @Nonnull public DatasetAssert hasRows(@Nonnull final SparkSession spark, @Nonnull final String expectedCsvPath) { + dataset.explain(); + dataset.show(1000, false); assertDatasetAgainstCsv(spark, expectedCsvPath, dataset); return this; } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java index fe8da482b0..6d20f442c6 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java @@ -21,6 +21,7 @@ import static org.apache.spark.sql.functions.col; import static org.mockito.Mockito.mock; +import au.csiro.pathling.fhirpath.Nesting; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; @@ -49,14 +50,14 @@ public class ElementPathBuilder { @Nonnull private Column idColumn; - @Nonnull - private Optional eidColumn; - @Nonnull private Column valueColumn; private boolean singular; + @Nonnull + private Nesting nesting; + @Nonnull private FHIRDefinedType fhirType; @@ -77,8 +78,8 @@ public ElementPathBuilder(@Nonnull final SparkSession spark) { .build(); idColumn = col(dataset.columns()[0]); valueColumn = col(dataset.columns()[1]); - eidColumn = Optional.empty(); singular = false; + nesting = new Nesting(); fhirType = FHIRDefinedType.NULL; definition = mock(ElementDefinition.class); } @@ -95,7 +96,6 @@ public ElementPathBuilder idAndValueColumns() { public ElementPathBuilder idAndEidAndValueColumns() { final IdAndValueColumns idAndValueColumns = getIdAndValueColumns(dataset, true); idColumn = idAndValueColumns.getId(); - eidColumn = idAndValueColumns.getEid(); valueColumn = idAndValueColumns.getValues().get(0); return this; } @@ -130,6 +130,12 @@ public ElementPathBuilder singular(final boolean singular) { return this; } + @Nonnull + public ElementPathBuilder nestingColumns(@Nonnull final Nesting nesting) { + this.nesting = nesting; + return this; + } + @Nonnull public ElementPathBuilder fhirType(@Nonnull final FHIRDefinedType fhirType) { this.fhirType = fhirType; @@ -156,17 +162,13 @@ public ElementPathBuilder definition(@Nonnull final ElementDefinition definition @Nonnull public ElementPath build() { - return ElementPath - .build(expression, dataset, idColumn, eidColumn, - valueColumn, singular, Optional.ofNullable(currentResource), - Optional.ofNullable(thisColumn), fhirType); + return ElementPath.build(expression, dataset, idColumn, valueColumn, Optional.empty(), singular, + Optional.ofNullable(currentResource), Optional.ofNullable(thisColumn), fhirType); } @Nonnull public ElementPath buildDefined() { - return ElementPath - .build(expression, dataset, idColumn, eidColumn, - valueColumn, singular, Optional.ofNullable(currentResource), - Optional.ofNullable(thisColumn), definition); + return ElementPath.build(expression, dataset, idColumn, valueColumn, Optional.empty(), singular, + Optional.ofNullable(currentResource), Optional.ofNullable(thisColumn), definition); } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java index eec39517fb..6d9c35ff88 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java @@ -117,7 +117,7 @@ public ParserContextBuilder groupingColumns(@Nonnull final List grouping @Nonnull public ParserContext build() { return new ParserContext(inputContext, fhirContext, spark, dataSource, - Optional.ofNullable(terminologyServiceFactory), groupingColumns, nodeIdColumns); + Optional.ofNullable(terminologyServiceFactory), groupingColumns); } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java index 20c2f03fbc..ff2c78c9f3 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java @@ -169,7 +169,8 @@ public ResourcePath buildCustom() { final String resourceCode = resourceType.toCode(); final RuntimeResourceDefinition hapiDefinition = fhirContext .getResourceDefinition(resourceCode); - final ResourceDefinition definition = new ResourceDefinition(resourceType, hapiDefinition); + final ResourceDefinition definition = new ResourceDefinition(resourceType, hapiDefinition, + Optional.empty()); // in most cases value column should be the same as id final DatasetWithColumn datasetWithColumn = createColumn(dataset, valueColumn); diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/UntypedResourcePathBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/UntypedResourcePathBuilder.java index e72d59327a..d6ba70b9ed 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/UntypedResourcePathBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/UntypedResourcePathBuilder.java @@ -48,9 +48,6 @@ public class UntypedResourcePathBuilder { @Nonnull private Column idColumn; - @Nonnull - private Optional eidColumn; - @Nonnull private Column valueColumn; @@ -61,7 +58,6 @@ public class UntypedResourcePathBuilder { public UntypedResourcePathBuilder(@Nonnull final SparkSession spark) { expression = ""; - eidColumn = Optional.empty(); dataset = new DatasetBuilder(spark) .withIdColumn() .withColumn(DataTypes.StringType) @@ -81,7 +77,6 @@ public UntypedResourcePathBuilder idAndValueColumns() { @Nonnull public UntypedResourcePathBuilder idEidAndValueColumns() { idColumn = functions.col(dataset.columns()[0]); - eidColumn = Optional.of(functions.col(dataset.columns()[1])); valueColumn = functions.col(dataset.columns()[2]); return this; } @@ -120,7 +115,6 @@ public UntypedResourcePathBuilder thisColumn(@Nonnull final Column thisColumn) { public UntypedResourcePath build() { final ReferencePath referencePath = mock(ReferencePath.class); when(referencePath.getIdColumn()).thenReturn(idColumn); - when(referencePath.getEidColumn()).thenReturn(eidColumn); when(referencePath.getValueColumn()).thenReturn(valueColumn); when(referencePath.getDataset()).thenReturn(dataset); when(referencePath.isSingular()).thenReturn(singular); diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java index e2ce3fde71..5fe60018f7 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java @@ -23,7 +23,6 @@ import au.csiro.pathling.fhirpath.element.ElementDefinition; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; -import java.util.Objects; import java.util.Optional; import javax.annotation.Nonnull; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -43,7 +42,7 @@ public static Optional getChildOfResource( .getResourceDefinition(resourceCode); requireNonNull(hapiDefinition); final ResourceDefinition definition = new ResourceDefinition( - ResourceType.fromCode(resourceCode), hapiDefinition); + ResourceType.fromCode(resourceCode), hapiDefinition, Optional.empty()); return definition.getChildElement(elementName); } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java index af7fd3ac51..08b6f24ff3 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java @@ -18,16 +18,19 @@ package au.csiro.pathling.test.helpers; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toList; import static org.apache.spark.sql.functions.col; import au.csiro.pathling.encoders.datatypes.DecimalCustomCoder; +import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.encoding.QuantityEncoding; +import au.csiro.pathling.fhirpath.parser.ParserContext; import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import javax.annotation.Nonnull; import lombok.Value; import org.apache.spark.sql.Column; @@ -134,7 +137,7 @@ public static Row rowFromCodeableConcept(@Nonnull final CodeableConcept codeable requireNonNull(coding); final List codings = coding.stream().map(SparkHelpers::rowFromCoding) - .collect(Collectors.toList()); + .collect(toList()); final Buffer buffer = JavaConverters.asScalaBuffer(codings); requireNonNull(buffer); @@ -177,6 +180,19 @@ public static Row rowForUcumQuantity(@Nonnull final String value, return rowForUcumQuantity(new BigDecimal(value), unit); } + @Nonnull + public static Dataset selectValuesAndNodes(@Nonnull final Dataset dataset, + @Nonnull final List paths, @Nonnull final ParserContext context) { + final List columns = new ArrayList<>(); + columns.addAll( + paths.stream() + .map(FhirPath::getValueColumn) + .collect(toList()) + ); + columns.addAll(new ArrayList<>(context.getNesting().getOrderingColumns())); + return dataset.select(columns.toArray(new Column[0])); + } + @Value public static class IdAndValueColumns { diff --git a/utilities/src/main/java/au/csiro/pathling/utilities/Strings.java b/utilities/src/main/java/au/csiro/pathling/utilities/Strings.java index 52ef50ffbe..2b9ed2ddb5 100644 --- a/utilities/src/main/java/au/csiro/pathling/utilities/Strings.java +++ b/utilities/src/main/java/au/csiro/pathling/utilities/Strings.java @@ -24,7 +24,7 @@ */ public abstract class Strings { - private static final Pattern ALIAS_PATTERN = Pattern.compile("_[a-z0-9]{1,6}"); + private static final Pattern ALIAS_PATTERN = Pattern.compile("@[a-z0-9]{1,6}"); /** * @param value a String value @@ -50,7 +50,12 @@ public static String unSingleQuote(@Nonnull final String value) { @Nonnull public static String randomAlias() { final int randomNumber = Math.abs(new Random().nextInt()); - return "_" + Integer.toString(randomNumber, Character.MAX_RADIX); + return "@" + Integer.toString(randomNumber, Character.MAX_RADIX); + } + + @Nonnull + public static String hashedAlias(@Nonnull final Object input) { + return "@" + Integer.toString(input.hashCode(), Character.MAX_RADIX); } /** From 108922ed1db74891f94cff438908e73560053419 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Mon, 17 Jul 2023 11:02:59 +1000 Subject: [PATCH 19/95] Get ExtractQueryTest passing --- .../pathling/extract/ExtractQueryTest.java | 3 +- .../multiplePolymorphicResolves.csv | 55 ++++++++++++------- .../java/au/csiro/pathling/QueryHelpers.java | 6 +- .../fhirpath/UntypedResourcePath.java | 5 +- .../fhirpath/element/ElementPath.java | 2 +- .../test/assertions/DatasetAssert.java | 2 +- 6 files changed, 44 insertions(+), 29 deletions(-) diff --git a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java index beadf0e4df..736b291ff4 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java @@ -169,6 +169,7 @@ void multiplePolymorphicResolves() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) + .debugAllRows() .hasRows(spark, "responses/ExtractQueryTest/multiplePolymorphicResolves.csv"); } @@ -399,7 +400,7 @@ void emptyColumn() { subjectResource = ResourceType.PATIENT; mockResource(ResourceType.PATIENT); - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + final IllegalArgumentException error = assertThrows(IllegalArgumentException.class, () -> new ExtractRequestBuilder(subjectResource) .withColumn("id") .withColumn("") diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.csv index 3d63a6ebe6..4845d6b94d 100644 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.csv +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.csv @@ -2,10 +2,12 @@ 08f08f2d-4d77-44f5-ab06-a38545fc050e,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 0a9a0b5a-5be9-4205-984e-94ceef8d6ba6,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 0d8d0441-a4ad-4f15-bc61-5d9694ed3d48,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 +0f1004c8-e9e9-477d-8c43-1eddc445664d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 0f1004c8-e9e9-477d-8c43-1eddc445664d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 +10321ec0-3613-4741-a9c3-47bafbc3fae4,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 10321ec0-3613-4741-a9c3-47bafbc3fae4,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -113e8a00-6fd2-489c-867b-6eedf314c774,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 113e8a00-6fd2-489c-867b-6eedf314c774,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +113e8a00-6fd2-489c-867b-6eedf314c774,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 13a2e7a0-1cf8-475f-8065-6d03b6538c9a,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 13c95f69-822b-44c6-ada3-d73aa1466290,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 16aa313e-aa66-408b-a819-33fb6d169ce0,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 @@ -15,16 +17,17 @@ 2540bf5e-5bc9-48be-8e66-37a2358ae75d,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 2c1dce4c-aefa-4ef8-8970-52aab627de48,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 2c67dbd4-0609-43d3-be6b-e88220c77b75,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -2d19750d-c270-4be9-a84a-ddcdbb64085a,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 2d19750d-c270-4be9-a84a-ddcdbb64085a,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +2d19750d-c270-4be9-a84a-ddcdbb64085a,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 2d4189ac-8eb3-4f4a-9bb1-0f754d715aee,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 2d9c0f33-155b-42ad-8158-1291e14c6da7,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 2e2cebf2-8c0e-4a89-8139-c2b233343dd7,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 +304a8806-48e6-4d35-bb17-121f1190b475,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 304a8806-48e6-4d35-bb17-121f1190b475,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 31b93f2e-caa4-4a7f-82ca-5b59f1fc2fdd,2b36c1e2-bbe1-45ae-8124-4adad2677702,male,Pedro316,Ulibarri312 335f8324-ce20-4ebc-a00c-ae085c4516b7,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -35a7f624-22be-48b3-b82c-45cf2667c527,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 35a7f624-22be-48b3-b82c-45cf2667c527,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +35a7f624-22be-48b3-b82c-45cf2667c527,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 365c1b4a-c9c5-4d28-8578-7460dec01ad1,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 37af6887-b8ba-480d-bb1c-4409711b8e2f,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 3e56e1ac-af2e-4711-8050-e3d7cee66b91,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 @@ -35,35 +38,40 @@ 460deb7a-74bc-43e2-bc60-fc6f97159b59,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 4d79c3df-899d-42a4-b17d-e93770921d6b,2b36c1e2-bbe1-45ae-8124-4adad2677702,male,Pedro316,Ulibarri312 53238300-6e57-4fc7-99d4-f1c4bcc3366a,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 -537f22b5-8139-4bcc-b048-5342139154fd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 537f22b5-8139-4bcc-b048-5342139154fd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -53bf45a8-d83f-429c-9b91-26b086a405ea,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +537f22b5-8139-4bcc-b048-5342139154fd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 53bf45a8-d83f-429c-9b91-26b086a405ea,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +53bf45a8-d83f-429c-9b91-26b086a405ea,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 5514df98-79c6-4818-b444-9417c93a64f4,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -5be1aab8-ecae-4c29-8f55-fe37535fd2c6,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 5be1aab8-ecae-4c29-8f55-fe37535fd2c6,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +5be1aab8-ecae-4c29-8f55-fe37535fd2c6,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +5e7b39f8-773d-4ffb-83e1-735923586881,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 5e7b39f8-773d-4ffb-83e1-735923586881,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 5eaf7eae-ffb9-44ec-8d46-fa2b87bedefc,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -6017f9b9-36a1-4cc2-a0ae-af31dc92b089,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 6017f9b9-36a1-4cc2-a0ae-af31dc92b089,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +6017f9b9-36a1-4cc2-a0ae-af31dc92b089,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 61bb4baa-6233-4444-8dd2-9c4806338faf,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 +69ea0b24-7d91-4498-991f-49f4b07914c0,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 69ea0b24-7d91-4498-991f-49f4b07914c0,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -6b9ffc9c-80b0-40c2-8cf7-f4d51b5448ab,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 6b9ffc9c-80b0-40c2-8cf7-f4d51b5448ab,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +6b9ffc9c-80b0-40c2-8cf7-f4d51b5448ab,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 73f52a06-5ac5-4324-a046-7a4caffb6c48,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -764a0e55-a10a-4d19-aa18-d414cb911a8d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 764a0e55-a10a-4d19-aa18-d414cb911a8d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +764a0e55-a10a-4d19-aa18-d414cb911a8d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +76b9935f-b44e-42b6-a991-e2e724b42334,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 76b9935f-b44e-42b6-a991-e2e724b42334,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 773d711a-5f70-4ac1-98c5-41b7c94e8a16,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 791b93c5-dfab-4a85-a810-5b01b3531c09,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 7e490679-3095-4431-95be-d3d9eb3b40f8,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 +7ed4db56-6f72-4dd1-b306-85d80f3a3a87,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 7ed4db56-6f72-4dd1-b306-85d80f3a3a87,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 +807ac3db-8499-4876-8508-7839776afc9a,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 807ac3db-8499-4876-8508-7839776afc9a,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 80b37d35-ee1e-49ff-82d3-6e10b0798fd6,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -81297f01-422b-4aba-98df-3609ea25e480,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 81297f01-422b-4aba-98df-3609ea25e480,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -84a1a6b4-583f-4525-b048-d676da5d7533,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +81297f01-422b-4aba-98df-3609ea25e480,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 84a1a6b4-583f-4525-b048-d676da5d7533,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +84a1a6b4-583f-4525-b048-d676da5d7533,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 88fd1ebd-ecca-4986-868c-e850eb3f5e5e,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 8b05436f-e4ea-4f8e-85ff-4ce195645c0e,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 8b9a7f2a-1bb1-4b5a-8b20-072ed6ae6ef9,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 @@ -71,54 +79,59 @@ 91faf3db-94ce-4508-87d2-206b19ba0302,beff242e-580b-47c0-9844-c1a68c36c5bf,male,Guy979,Towne435 922d533c-3cc0-43d1-8d62-817da745358b,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 9404f7b4-1c0c-4e87-b71f-ff267d0552e6,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -9868ffc0-762a-46d7-91db-4eee888484ae,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 9868ffc0-762a-46d7-91db-4eee888484ae,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +9868ffc0-762a-46d7-91db-4eee888484ae,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 9cc30d0d-de95-4242-81fc-e4e1910beb8c,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 9ce65331-faf8-477a-b338-25678919973b,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 9f1b1a87-d763-406b-b5b7-bd6f7417db2d,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 +a0fcef9d-b5bf-4930-b520-cf2dee3b9341,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard a0fcef9d-b5bf-4930-b520-cf2dee3b9341,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 +aae88d89-dc3f-40e0-9866-ae4095549d32,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard aae88d89-dc3f-40e0-9866-ae4095549d32,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 ad2dceaf-b8e6-49a0-952c-bcda78fc7b0b,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 +ad7d9189-d6b1-4e98-9900-af4bcb9e50b8,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard ad7d9189-d6b1-4e98-9900-af4bcb9e50b8,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 b2216cf0-b3df-44e0-b03a-2acfb42bdb87,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 b2e43429-d6c7-4332-9ed4-b239aa470e72,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -b5129712-51e2-4d6d-a229-e1c45f78aad4,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 b5129712-51e2-4d6d-a229-e1c45f78aad4,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -b566351c-0d0f-49a8-b7f0-063975f0ea43,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +b5129712-51e2-4d6d-a229-e1c45f78aad4,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 b566351c-0d0f-49a8-b7f0-063975f0ea43,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +b566351c-0d0f-49a8-b7f0-063975f0ea43,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 b64a7247-1000-4701-8771-caa6252cc905,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 b9adaf8a-35f6-475e-90f0-89fcd999c457,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 ba21bde9-8111-4cf4-bc55-1e265470a145,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 ba99aa15-c5a4-48e9-9858-df6a8c4a53d2,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 +bc243663-b435-43a0-912f-40a6f3bfcc2b,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard bc243663-b435-43a0-912f-40a6f3bfcc2b,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 c3034cd6-9395-4b50-b14b-e457b4077e23,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 c47a1fdb-3594-4c0f-944a-655e7df848a8,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 c678e476-64fd-4d12-8f66-20a29f1ce9de,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 c6fbf259-f45f-4266-b988-4b568b8c36c2,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,male,Gilberto712,MacGyver246 -c809b71f-844e-4312-8549-401f1cad25bb,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 c809b71f-844e-4312-8549-401f1cad25bb,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +c809b71f-844e-4312-8549-401f1cad25bb,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +c98f9644-7da9-4fc4-bf4d-5da3df697616,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard c98f9644-7da9-4fc4-bf4d-5da3df697616,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -cdd25034-c562-4788-aa9e-e53d8006bcbd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 cdd25034-c562-4788-aa9e-e53d8006bcbd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +cdd25034-c562-4788-aa9e-e53d8006bcbd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 cddcbfdd-4b15-4e81-bb2a-8fa998f2e628,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -d19f556d-976c-4529-a2ae-f465b489503c,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 d19f556d-976c-4529-a2ae-f465b489503c,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +d19f556d-976c-4529-a2ae-f465b489503c,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 d2353195-134f-44f1-835c-d0801a8f31b2,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 d336c1e0-852b-4ba9-bf38-ff6c4698a154,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 d8196d79-824c-4632-aa78-7fae27d05d96,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 dad66b19-6c91-4513-aa29-abd08fa256cf,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -ddefa5f2-0d73-4a62-a938-920ee87f7d66,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 ddefa5f2-0d73-4a62-a938-920ee87f7d66,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -df0e5bbc-9657-4e73-bfbc-0a92ae5da74d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +ddefa5f2-0d73-4a62-a938-920ee87f7d66,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 df0e5bbc-9657-4e73-bfbc-0a92ae5da74d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +df0e5bbc-9657-4e73-bfbc-0a92ae5da74d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 e744c7f9-9756-4edd-b7a7-3df5487657b9,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 f04628bf-b618-4674-b6eb-1d386eb1d637,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 f4ae2f38-086a-450a-bd38-8460ee704763,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -f51b905a-6a43-47d5-bf78-abf1b94c2ba1,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 f51b905a-6a43-47d5-bf78-abf1b94c2ba1,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +f51b905a-6a43-47d5-bf78-abf1b94c2ba1,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 f5d1f0e1-9e1c-44ee-b31b-e881bd69c1bb,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 f8bb88e4-6301-4eab-83b8-f49c72bb448f,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 f9994712-1f5e-4a3b-9d64-369b1e3e8f4b,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 fa507deb-a262-475c-938d-5666c502ebae,beff242e-580b-47c0-9844-c1a68c36c5bf,male,Guy979,Towne435 -fd73b5ca-079f-47a5-90b1-e49a547d1554,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 fd73b5ca-079f-47a5-90b1-e49a547d1554,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +fd73b5ca-079f-47a5-90b1-e49a547d1554,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java b/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java index 0d204f1237..6b4c9fa5da 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java @@ -436,16 +436,14 @@ private static Dataset applySelection(@Nonnull final Dataset dataset, @Nonnull public static List getUnionableColumns(@Nonnull final FhirPath source, @Nonnull final FhirPath target) { - // The columns will be those common to both datasets, plus the value column. + // The columns will be those common to both datasets. final Set commonColumnNames = new HashSet<>(List.of(source.getDataset().columns())); commonColumnNames.retainAll(List.of(target.getDataset().columns())); - final List selection = commonColumnNames.stream() + return commonColumnNames.stream() .map(functions::col) // We sort the columns so that they line up when we execute the union. .sorted(Comparator.comparing(Column::toString)) .collect(Collectors.toList()); - selection.add(source.getValueColumn()); - return selection; } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java index 2cd7d8be38..e74902ebf6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java @@ -51,11 +51,14 @@ public UntypedResourcePath(@Nonnull final String expression, @Nonnull final Data @Nonnull public static UntypedResourcePath build(@Nonnull final ReferencePath referencePath, @Nonnull final String expression) { - return new UntypedResourcePath(expression, referencePath.getDataset(), + final UntypedResourcePath result = new UntypedResourcePath(expression, + referencePath.getDataset(), referencePath.getIdColumn(), referencePath.getValueColumn(), referencePath.getOrderingColumn(), referencePath.isSingular(), referencePath.getCurrentResource(), referencePath.getThisColumn(), referencePath.getFhirType()); + result.definition = referencePath.getDefinition(); + return result; } @Nonnull diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java index 504e05c2aa..1de32e1b46 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java @@ -56,7 +56,7 @@ public class ElementPath extends NonLiteralPath { @Getter(AccessLevel.PUBLIC) @Nonnull - private Optional definition = Optional.empty(); + protected Optional definition = Optional.empty(); @Getter @Nonnull diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java index b6e9399f99..8934dcff61 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java @@ -157,7 +157,7 @@ public DatasetAssert debugRows() { @Nonnull @SuppressWarnings({"unused", "UnusedReturnValue"}) public DatasetAssert debugAllRows() { - dataset.collectAsList().forEach(System.out::println); + dataset.collectAsList().forEach(row -> System.out.println(row.mkString(","))); return this; } From f050a58bc612ccd400c98f03c00ede0ff0d34388 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Mon, 17 Jul 2023 11:40:33 +1000 Subject: [PATCH 20/95] Add comments and remove unnecessary classes --- .../extract/ExtractQueryExecutor.java | 31 +++++++++++++++++-- .../pathling/fhirpath/FhirPathAndContext.java | 5 +++ .../fhirpath/FhirPathContextAndResult.java | 21 ------------- .../au/csiro/pathling/fhirpath/Nesting.java | 22 +++++++++++++ .../csiro/pathling/fhirpath/NestingKey.java | 6 ++++ .../pathling/fhirpath/NodeDefinition.java | 10 ------ .../fhirpath/ReferenceNestingKey.java | 6 ++++ 7 files changed, 68 insertions(+), 33 deletions(-) delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathContextAndResult.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/NodeDefinition.java diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java index 4fac0828ee..daa3faa776 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java @@ -29,6 +29,11 @@ import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; +/** + * Builds the overall query responsible for executing an extract request. + * + * @author John Grimes + */ @Slf4j public class ExtractQueryExecutor extends QueryExecutor { @@ -69,12 +74,19 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query, query.getSubjectResource().toCode(), true); final ParserContext parserContext = buildParserContext(inputContext, Collections.singletonList(inputContext.getIdColumn())); + + // Parse each of the column expressions. final List parsedColumns = parseExpressions(parserContext, query.getColumnsAsStrings()); + + // Validate and coerce the types of the columns where necessary. final List coercedColumns = validateAndCoerceColumns(parsedColumns, resultType); + + // Get the dataset from the last column. final Dataset unfiltered = coercedColumns.get(parsedColumns.size() - 1).getDataset(); + // Trim trailing nulls from the dataset. final Dataset trimmed = trimTrailingNulls(inputContext.getIdColumn(), coercedColumns, unfiltered); @@ -84,16 +96,24 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query, filtered = trimmed; } else { final List filters = query.getFilters(); + + // Parse each of the filter expressions, final List filterPaths = parseExpressions(parserContext, filters, Optional.of(trimmed)); + + // Get the dataset from the last filter. final Dataset withFilters = filterPaths.get(filterPaths.size() - 1).getDataset(); + + // Combine all the filter value columns using the and operator. final Optional filterConstraint = filterPaths.stream() .map(FhirPath::getValueColumn) .reduce(Column::and); + + // Filter the dataset using the constraint. filtered = filterConstraint.map(withFilters::filter).orElse(withFilters); } - // Select the column values. + // Select the column values from the dataset, applying labelling where requested. final Column[] columnValues = labelColumns( coercedColumns.stream() .map(FhirPath::getValueColumn), @@ -101,7 +121,7 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query, ).toArray(Column[]::new); final Dataset selectedDataset = filtered.select(columnValues); - // If there is a limit, apply it. + // If there is a row limit, apply it. return query.getLimit().isPresent() ? selectedDataset.limit(query.getLimit().get()) : selectedDataset; @@ -146,18 +166,23 @@ private Dataset trimTrailingNulls(final @Nonnull Column idColumn, @Nonnull final List expressions, @Nonnull final Dataset dataset) { checkArgument(!expressions.isEmpty(), "At least one expression is required"); + // Get the value columns associated with non-singular paths. final List nonSingularColumns = expressions.stream() .filter(fhirPath -> !fhirPath.isSingular()) .map(FhirPath::getValueColumn) .collect(toList()); if (nonSingularColumns.isEmpty()) { + // If there are no non-singular paths, there is nothing to do. return dataset; } else { + // Build a condition that checks whether any of the non-singular columns are not null. final Column additionalCondition = nonSingularColumns.stream() .map(Column::isNotNull) .reduce(Column::or) .get(); + + // Build a dataset that contains the distinct values of all singular columns. final List filteringColumns = new ArrayList<>(); filteringColumns.add(idColumn); final List singularColumns = expressions.stream() @@ -168,6 +193,8 @@ private Dataset trimTrailingNulls(final @Nonnull Column idColumn, final Dataset filteringDataset = dataset .select(filteringColumns.toArray(new Column[0])) .distinct(); + + // Join the dataset to the filtering dataset, using a right outer join. return join(dataset, filteringColumns, filteringDataset, filteringColumns, additionalCondition, JoinType.RIGHT_OUTER); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java index 88301b2bf9..0e0b073f66 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java @@ -4,6 +4,11 @@ import javax.annotation.Nonnull; import lombok.Value; +/** + * Holds the value of a {@link FhirPath} and an associated {@link ParserContext}. + * + * @author John Grimes + */ @Value public class FhirPathAndContext { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathContextAndResult.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathContextAndResult.java deleted file mode 100644 index 79d172507f..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathContextAndResult.java +++ /dev/null @@ -1,21 +0,0 @@ -package au.csiro.pathling.fhirpath; - -import au.csiro.pathling.fhirpath.parser.ParserContext; -import javax.annotation.Nonnull; -import lombok.Value; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; - -@Value -public class FhirPathContextAndResult { - - @Nonnull - FhirPath fhirPath; - - @Nonnull - ParserContext context; - - @Nonnull - Dataset result; - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java index 44c8f09dcd..c972499e53 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java @@ -13,6 +13,14 @@ import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; +/** + * Stores information about traversal of nested elements and resources within a FHIRPath expression. + * This allows us to reuse exploded and joined values, and to avoid unnecessary joins. It is also + * used to retrieve the necessary columns required to order a path when applying operations that are + * sensitive to element ordering, such as {@code first()}. + * + * @author John Grimes + */ public class Nesting { @Nonnull @@ -30,18 +38,28 @@ public NonLiteralPath updateOrRetrieve(@Nonnull final NestingKey key, final boolean hit = nesting.containsKey(key); final NonLiteralPath result = nesting.computeIfAbsent(key, updater); if (hit) { + // If the path has been retrieved from the cache, we need to copy it and substitute the + // supplied values for expression, dataset, singular and this column. return result.copy(expression, dataset, result.getIdColumn(), result.getValueColumn(), result.getOrderingColumn(), singular, thisColumn); } else { + // If the path has been computed fresh, we don't need to update anything. return result; } } + /** + * @return whether the traversed nesting levels are all orderable, which tells us whether ordering + * can be determined in this context + */ public boolean isOrderable() { return nesting.values().stream() .allMatch(path -> path.getOrderingColumn().isPresent()); } + /** + * @return all ordering columns within nesting paths that have been traversed + */ @Nonnull public List getOrderingColumns() { return nesting.values().stream() @@ -51,6 +69,10 @@ public List getOrderingColumns() { .collect(toList()); } + /** + * Removes the last element from the nesting stack. This is used when applying aggregate + * functions, so that subsequent unnestings at this level can be dealt with correctly. + */ public void removeLast() { @Nullable NestingKey last = null; for (final NestingKey key : nesting.keySet()) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NestingKey.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NestingKey.java index 8327ef4e01..84fb862c9e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NestingKey.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NestingKey.java @@ -1,5 +1,11 @@ package au.csiro.pathling.fhirpath; +/** + * Designates a class that is capable of being used as a key for storing the details of a nesting + * level, using the {@link Nesting} class. + * + * @author John Grimes + */ public interface NestingKey { } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NodeDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NodeDefinition.java deleted file mode 100644 index 876894b9b1..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NodeDefinition.java +++ /dev/null @@ -1,10 +0,0 @@ -package au.csiro.pathling.fhirpath; - -import javax.annotation.Nonnull; - -public interface NodeDefinition { - - @Nonnull - NestingKey getNestingKey(); - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ReferenceNestingKey.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ReferenceNestingKey.java index 9134f4839e..00976f5d11 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ReferenceNestingKey.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ReferenceNestingKey.java @@ -4,6 +4,12 @@ import javax.annotation.Nonnull; import lombok.Value; +/** + * A specialisation of a {@link NestingKey} that is used to store the details of a resource + * reference. + * + * @author John Grimes + */ @Value public class ReferenceNestingKey implements NestingKey { From c9c4e9599e17100bd35fab1fa4b00e4048fdd643 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Mon, 17 Jul 2023 15:58:11 +1000 Subject: [PATCH 21/95] Refactor aggregate execution to remove joins --- ...ueryMultipleGroupingCounts.Parameters.json | 21 +++ ...ueryWithWhereInAggregation.Parameters.json | 25 +++ .../java/au/csiro/pathling/QueryExecutor.java | 77 +-------- .../aggregate/AggregateQueryExecutor.java | 153 +++++++++--------- .../fhirpath/function/WhereFunction.java | 9 +- .../fhirpath/parser/ParserContext.java | 15 +- 6 files changed, 148 insertions(+), 152 deletions(-) diff --git a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleGroupingCounts.Parameters.json b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleGroupingCounts.Parameters.json index a66f0d7903..79472438e6 100644 --- a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleGroupingCounts.Parameters.json +++ b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleGroupingCounts.Parameters.json @@ -85,6 +85,27 @@ } ] }, + { + "name": "grouping", + "part": [ + { + "name": "label", + "valueString": "Arianne" + }, + { + "name": "label", + "valueString": "Ms." + }, + { + "name": "result", + "valueUnsignedInt": 1 + }, + { + "name": "drillDown", + "valueString": "((name.given) contains 'Arianne') and ((name.prefix) contains 'Ms.')" + } + ] + }, { "name": "grouping", "part": [ diff --git a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithWhereInAggregation.Parameters.json b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithWhereInAggregation.Parameters.json index ac25a9cc10..5bbf5ec60d 100644 --- a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithWhereInAggregation.Parameters.json +++ b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithWhereInAggregation.Parameters.json @@ -101,6 +101,31 @@ } ] }, + { + "name": "grouping", + "part": [ + { + "name": "label", + "valueCode": "female" + }, + { + "name": "label", + "valueCoding": { + "system": "http://snomed.info/sct", + "code": "87915002", + "display": "Married" + } + }, + { + "name": "result", + "valueUnsignedInt": 1 + }, + { + "name": "drillDown", + "valueString": "((gender) = 'female') and ((maritalStatus.coding) contains http://snomed.info/sct|87915002||Married)" + } + ] + }, { "name": "grouping", "part": [ diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index 8fe315eb83..91be79285a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -22,7 +22,6 @@ import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static au.csiro.pathling.utilities.Strings.randomAlias; import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.toList; import static org.apache.spark.sql.functions.col; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; @@ -121,77 +120,15 @@ protected List parseExpressions( } @Nonnull - protected List parseFilters(@Nonnull final Parser parser, - @Nonnull final Collection filters) { - return filters.stream().map(expression -> { - final FhirPath result = parser.parse(expression); + protected void validateFilters(@Nonnull final Collection filters) { + for (final FhirPath filter : filters) { // Each filter expression must evaluate to a singular Boolean value, or a user error will be // thrown. - checkUserInput(result instanceof BooleanPath, - "Filter expression is not a non-literal boolean: " + expression); - checkUserInput(result.isSingular(), - "Filter expression must represent a singular value: " + expression); - return result; - }).collect(toList()); - } - - @Nonnull - protected Dataset joinExpressionsAndFilters(final FhirPath inputContext, - @Nonnull final Collection expressions, - @Nonnull final Collection filters, @Nonnull final Column idColumn) { - - final Dataset combinedGroupings = joinExpressionsWithoutCorrelation( - inputContext, expressions, idColumn); - - return filters.stream() - .map(FhirPath::getDataset) - .reduce(combinedGroupings, - ((result, element) -> join(result, idColumn, element, idColumn, JoinType.LEFT_OUTER))); - } - - @Nonnull - protected static Dataset joinExpressionsWithoutCorrelation(final FhirPath inputContext, - final @Nonnull Collection expressions, final @Nonnull Column idColumn) { - // We need to remove any trailing null values from non-empty collections, so that aggregations do - // not count non-empty collections in the empty collection grouping. - // We start from the inputContext's dataset and then outer join subsequent expressions datasets - // where the value is not null. - return expressions.stream() - .map(expr -> expr.getDataset().filter(expr.getValueColumn().isNotNull())) - // the use of RIGHT_OUTER join seems to be necessary to preserve the original - // id column in the result - .reduce(inputContext.getDataset(), - ((result, element) -> join(result, idColumn, element, idColumn, JoinType.LEFT_OUTER))); - } - - - /** - * Joins the datasets in a list together the provided set of shared columns. - * - * @param expressions a list of expressions to join - * @param joinColumns the columns to join by; all columns must be present in all expressions. - * @return the joined {@link Dataset} - */ - @Nonnull - protected static Dataset joinExpressionsByColumns( - @Nonnull final Collection expressions, @Nonnull final List joinColumns) { - checkArgument(!expressions.isEmpty(), "expressions must not be empty"); - - final Optional> maybeJoinResult = expressions.stream() - .map(FhirPath::getDataset) - .reduce((l, r) -> join(l, joinColumns, r, joinColumns, JoinType.LEFT_OUTER)); - return maybeJoinResult.orElseThrow(); - } - - @Nonnull - protected static Dataset applyFilters(@Nonnull final Dataset dataset, - @Nonnull final Collection filters) { - // Get the value column from each filter expression, and combine them with AND logic. - return filters.stream() - .map(FhirPath::getValueColumn) - .reduce(Column::and) - .flatMap(filter -> Optional.of(dataset.filter(filter))) - .orElse(dataset); + checkUserInput(filter instanceof BooleanPath, + "Filter expression is not a non-literal boolean: " + filter); + checkUserInput(filter.isSingular(), + "Filter expression must represent a singular value: " + filter); + } } protected Dataset filterDataset(@Nonnull final ResourcePath inputContext, diff --git a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java index 976a7cb1f2..ecf4b80078 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java @@ -20,15 +20,14 @@ import static au.csiro.pathling.QueryHelpers.createColumns; import static au.csiro.pathling.query.ExpressionWithLabel.labelsAsStream; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; +import static java.util.stream.Collectors.toList; import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.QueryHelpers; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirPathAndContext; import au.csiro.pathling.fhirpath.FhirValue; import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.parser.Parser; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.Database; import au.csiro.pathling.io.source.DataSource; @@ -40,7 +39,6 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nonnull; import lombok.Value; @@ -50,6 +48,11 @@ import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; +/** + * Builds the overall query responsible for executing an aggregate request. + * + * @author John Grimes + */ @Slf4j public class AggregateQueryExecutor extends QueryExecutor { @@ -79,102 +82,98 @@ public AggregateQueryExecutor(@Nonnull final QueryConfiguration configuration, public ResultWithExpressions buildQuery(@Nonnull final AggregateRequest query) { log.info("Executing request: {}", query); - // Build a new expression parser, and parse all of the filter and grouping expressions within - // the query. + /// Set up the parser context for the grouping and filtering expressions. final ResourcePath inputContext = ResourcePath .build(getFhirContext(), getDataSource(), query.getSubjectResource(), query.getSubjectResource().toCode(), true); - final ParserContext groupingAndFilterContext = buildParserContext(inputContext, + final ParserContext groupingFilteringContext = buildParserContext(inputContext, Collections.singletonList(inputContext.getIdColumn())); - final Parser parser = new Parser(groupingAndFilterContext); - final List filters = parseFilters(parser, query.getFilters()); - // final List groupingParseResult = parseExpressions( - // groupingAndFilterContext, query.getGroupings()); - final List groupingParseResult = new ArrayList<>(); - // validateGroupings(groupingParseResult); - final List groupings = groupingParseResult; - - // Join all filter and grouping expressions together. - final Column idColumn = inputContext.getIdColumn(); - Dataset groupingsAndFilters = joinExpressionsAndFilters(inputContext, groupings, filters, - idColumn); - // Apply filters. - groupingsAndFilters = applyFilters(groupingsAndFilters, filters); + + // Parse the filter expressions. + final Optional> filteredDataset; + final List filters; + if (query.getFilters().isEmpty()) { + filteredDataset = Optional.empty(); + filters = Collections.emptyList(); + } else { + filters = parseExpressions(groupingFilteringContext, query.getFilters()); + validateFilters(filters); + final Dataset filterDataset = filters.get(filters.size() - 1).getDataset(); + final Optional filterConstraint = filters.stream() + .map(FhirPath::getValueColumn) + .reduce(Column::and); + filteredDataset = Optional.of(filterConstraint.map(filterDataset::filter) + .orElse(filterDataset)); + } + + // Parse the grouping expressions. + final Optional> groupingFilteringDataset; + final List groupings; + if (query.getGroupings().isEmpty()) { + groupingFilteringDataset = filteredDataset; + groupings = Collections.emptyList(); + } else { + groupings = parseExpressions(groupingFilteringContext, query.getGroupings(), + filteredDataset); + validateGroupings(groupings); + groupingFilteringDataset = Optional.of(groupings.get(groupings.size() - 1).getDataset()); + } // Remove synthetic fields from struct values (such as _fid) before grouping. - final QueryHelpers.DatasetWithColumnMap datasetWithNormalizedGroupings = createColumns( - groupingsAndFilters, groupings.stream().map(FhirPath::getValueColumn) - .map(SqlExpressions::pruneSyntheticFields).toArray(Column[]::new)); - - groupingsAndFilters = datasetWithNormalizedGroupings.getDataset(); - - final List groupingColumns = new ArrayList<>( - datasetWithNormalizedGroupings.getColumnMap().values()); - - // The input context will be identical to that used for the groupings and filters, except that - // it will use the dataset that resulted from the parsing of the groupings and filters, - // instead of just the raw resource. This is so that any aggregations that are performed - // during the parse can use these columns for grouping, rather than the identity of each - // resource. - final ResourcePath aggregationContext = inputContext.copy(inputContext.getExpression(), - groupingsAndFilters, idColumn, inputContext.getValueColumn(), - inputContext.getOrderingColumn(), inputContext.isSingular(), Optional.empty()); - final ParserContext aggregationParserContext = buildParserContext(aggregationContext, - groupingColumns); - final Parser aggregationParser = new Parser(aggregationParserContext); - - // Parse the aggregations, and grab the updated grouping columns. When aggregations are - // performed during an aggregation parse, the grouping columns need to be updated, as any - // aggregation operation erases the previous columns that were built up within the dataset. - final List aggregations = parseAggregations(aggregationParser, - query.getAggregations()); - - // Join the aggregations together, using equality of the grouping column values as the join - // condition. - final List aggregationColumns = - aggregations.stream().map(FhirPath::getValueColumn).collect(Collectors.toList()); - Dataset joinedAggregations = joinExpressionsByColumns(aggregations, groupingColumns); - if (groupingColumns.isEmpty()) { - joinedAggregations = joinedAggregations.limit(1); + final Optional> prunedDataset; + final List prunedGroupings; + if (groupingFilteringDataset.isPresent()) { + final QueryHelpers.DatasetWithColumnMap datasetWithNormalizedGroupings = createColumns( + groupingFilteringDataset.get(), groupings.stream().map(FhirPath::getValueColumn) + .map(SqlExpressions::pruneSyntheticFields).toArray(Column[]::new)); + prunedDataset = Optional.of(datasetWithNormalizedGroupings.getDataset()); + prunedGroupings = new ArrayList<>( + datasetWithNormalizedGroupings.getColumnMap().values()); + } else { + prunedDataset = Optional.empty(); + prunedGroupings = Collections.emptyList(); } + // Parse the aggregation expressions. + final ParserContext aggregationContext = groupingFilteringContext.withGroupingColumns( + prunedGroupings); + final List aggregations = parseExpressions(aggregationContext, + query.getAggregations(), prunedDataset); + validateAggregations(aggregations); + final List aggregationColumns = aggregations.stream().map(FhirPath::getValueColumn) + .collect(toList()); + final Dataset aggregationDataset = aggregations.get(aggregations.size() - 1) + .getDataset(); + // The final column selection will be the grouping columns, followed by the aggregation // columns. - final Column[] finalSelection = Stream.concat( - labelColumns(groupingColumns.stream(), labelsAsStream(query.getGroupingsWithLabels())), + final Column[] selection = Stream.concat( + labelColumns(prunedGroupings.stream(), labelsAsStream(query.getGroupingsWithLabels())), labelColumns(aggregationColumns.stream(), labelsAsStream(query.getAggregationsWithLabels())) ) .toArray(Column[]::new); + final Dataset finalDataset = aggregationDataset.select(selection); - final Dataset finalDataset = joinedAggregations - .select(finalSelection) - // This is needed to cater for the scenario where a literal value is used within an - // aggregation expression. - .distinct(); return new ResultWithExpressions(finalDataset, aggregations, groupings, filters); } - @Nonnull - private List parseAggregations(@Nonnull final Parser parser, - @Nonnull final Collection aggregations) { - return aggregations.stream().map(aggregation -> { - final FhirPath result = parser.parse(aggregation); + private void validateAggregations(@Nonnull final Collection aggregations) { + for (final FhirPath aggregation : aggregations) { // An aggregation expression must be able to be extracted into a FHIR value. - checkUserInput(result instanceof FhirValue, - "Aggregation expression is not of a supported type: " + aggregation); + checkUserInput(aggregation instanceof FhirValue, + "Aggregation expression is not of a supported type: " + aggregation.getExpression()); // An aggregation expression must be singular, relative to its input context. - checkUserInput(result.isSingular(), - "Aggregation expression does not evaluate to a singular value: " + aggregation); - return result; - }).collect(Collectors.toList()); + checkUserInput(aggregation.isSingular(), + "Aggregation expression does not evaluate to a singular value: " + + aggregation.getExpression()); + } } - private void validateGroupings(final List groupingParseResult) { - for (final FhirPathAndContext fhirPathAndContext : groupingParseResult) { - final FhirPath fhirPath = fhirPathAndContext.getFhirPath(); + private void validateGroupings(@Nonnull final Collection groupings) { + for (final FhirPath grouping : groupings) { // A grouping expression must be able to be extracted into a FHIR value. - checkUserInput(fhirPath instanceof FhirValue, - "Grouping expression is not of a supported type: " + fhirPath); + checkUserInput(grouping instanceof FhirValue, + "Grouping expression is not of a supported type: " + grouping.getExpression()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java index 34fadfcabf..9fcff0c695 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java @@ -17,12 +17,14 @@ package au.csiro.pathling.fhirpath.function; +import static au.csiro.pathling.QueryHelpers.createColumn; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import static au.csiro.pathling.utilities.Preconditions.checkPresent; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.lit; import static org.apache.spark.sql.functions.when; +import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.element.BooleanPath; @@ -67,9 +69,12 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Column valueColumn = when(argumentValue.equalTo(true), thisValue).otherwise(lit(null)); final String expression = expressionFromInput(input, NAME); + final DatasetWithColumn datasetWithColumn = createColumn(argumentPath.getDataset(), + valueColumn); - return inputPath.copy(expression, argumentPath.getDataset(), idColumn, valueColumn, - inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getThisColumn()); + return inputPath.copy(expression, datasetWithColumn.getDataset(), idColumn, + datasetWithColumn.getColumn(), inputPath.getOrderingColumn(), inputPath.isSingular(), + inputPath.getThisColumn()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index 81762750ff..c6f53cbef4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -161,7 +161,7 @@ public void setThisContext(@Nonnull final FhirPath thisContext) { } /** - * Creates a copy of the current parser context, but with the specified unnest behaviour. + * Creates a copy of the current parser context with the specified unnest behaviour. * * @param unnestBehaviour the {@link UnnestBehaviour} to use * @return a new #{link ParserContext} @@ -172,8 +172,7 @@ public ParserContext withUnnestBehaviour(@Nonnull final UnnestBehaviour unnestBe } /** - * Creates a copy of the current parser context, but with a different input dataset and the node - * IDs emptied out. + * Creates a copy of the current parser context with a different input dataset. * * @return a new {@link ParserContext} */ @@ -183,4 +182,14 @@ public ParserContext withContextDataset(@Nonnull final Dataset contextDatas terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, nesting); } + /** + * Creates a copy of the current parser context with a different set of grouping columns. + * + * @return a new {@link ParserContext} + */ + public ParserContext withGroupingColumns(@Nonnull final List groupingColumns) { + return new ParserContext(inputContext, fhirContext, sparkSession, dataSource, + terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, nesting); + } + } From f20e23c24f939386043dab626c2987fbf8382d08 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Tue, 18 Jul 2023 10:33:31 +1000 Subject: [PATCH 22/95] Move to windowing functions for aggregate and where functions, remove trailing null join --- .../whereInMultipleColumns.csv | 27 ++--- .../extract/ExtractQueryExecutor.java | 51 +-------- .../au/csiro/pathling/fhirpath/Nesting.java | 10 ++ .../fhirpath/function/AggregateFunction.java | 103 ++++++++++-------- .../function/BooleansTestFunction.java | 6 +- .../fhirpath/function/CountFunction.java | 20 +--- .../fhirpath/function/FirstFunction.java | 6 +- .../fhirpath/function/SumFunction.java | 6 +- .../fhirpath/function/WhereFunction.java | 25 +++-- .../fhirpath/operator/MembershipOperator.java | 5 +- 10 files changed, 114 insertions(+), 145 deletions(-) diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/whereInMultipleColumns.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/whereInMultipleColumns.csv index e051896e1a..de173153d3 100644 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/whereInMultipleColumns.csv +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/whereInMultipleColumns.csv @@ -1,18 +1,9 @@ -121503c8-9564-4b48-9086-a22df717948e,"",999-77-4115 -121503c8-9564-4b48-9086-a22df717948e,f34e77c9-df31-49c4-92e2-e871fa76026e,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"",999-42-8004 -2b36c1e2-bbe1-45ae-8124-4adad2677702,2f9fd91d-9096-40e8-8307-825f68780599,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"",999-14-8633 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,11fd8dbd-dd20-479f-b82b-558fed60bae7,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"",999-21-1297 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,0dc85075-4f59-4e4f-b75d-a2f601d0cf24,"" -9360820c-8602-4335-8b50-c88d627a0c20,"",999-23-9134 -9360820c-8602-4335-8b50-c88d627a0c20,eb4458b4-cfda-463e-ba15-eaad8d291d1d,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"",999-36-4411 -a7eb2ce7-1075-426c-addd-957b861b0e55,3d67a22e-4e26-4d7a-a586-ea79114faa50,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"",999-38-1164 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,e993ce4e-4cb9-4e7c-b082-a9144e9efb03,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"",999-56-3056 -beff242e-580b-47c0-9844-c1a68c36c5bf,1f276fc3-7e91-4fc9-a287-be19228e8807,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"",999-43-1135 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,2cc8ffdd-6233-4dd4-ba71-36eccb8204e2,"" +121503c8-9564-4b48-9086-a22df717948e,f34e77c9-df31-49c4-92e2-e871fa76026e,999-77-4115 +2b36c1e2-bbe1-45ae-8124-4adad2677702,2f9fd91d-9096-40e8-8307-825f68780599,999-42-8004 +7001ad9c-34d2-4eb5-8165-5fdc2147f469,11fd8dbd-dd20-479f-b82b-558fed60bae7,999-14-8633 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,0dc85075-4f59-4e4f-b75d-a2f601d0cf24,999-21-1297 +9360820c-8602-4335-8b50-c88d627a0c20,eb4458b4-cfda-463e-ba15-eaad8d291d1d,999-23-9134 +a7eb2ce7-1075-426c-addd-957b861b0e55,3d67a22e-4e26-4d7a-a586-ea79114faa50,999-36-4411 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,e993ce4e-4cb9-4e7c-b082-a9144e9efb03,999-38-1164 +beff242e-580b-47c0-9844-c1a68c36c5bf,1f276fc3-7e91-4fc9-a287-be19228e8807,999-56-3056 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,2cc8ffdd-6233-4dd4-ba71-36eccb8204e2,999-43-1135 diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java index daa3faa776..f36e2318ca 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java @@ -1,12 +1,10 @@ package au.csiro.pathling.extract; -import static au.csiro.pathling.QueryHelpers.join; import static au.csiro.pathling.query.ExpressionWithLabel.labelsAsStream; import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static java.util.stream.Collectors.toList; import au.csiro.pathling.QueryExecutor; -import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.AbstractPath; import au.csiro.pathling.fhirpath.FhirPath; @@ -17,11 +15,9 @@ import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; import org.apache.spark.sql.Column; @@ -86,20 +82,16 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query, // Get the dataset from the last column. final Dataset unfiltered = coercedColumns.get(parsedColumns.size() - 1).getDataset(); - // Trim trailing nulls from the dataset. - final Dataset trimmed = trimTrailingNulls(inputContext.getIdColumn(), coercedColumns, - unfiltered); - // Apply the filters. final Dataset filtered; if (query.getFilters().isEmpty()) { - filtered = trimmed; + filtered = unfiltered; } else { final List filters = query.getFilters(); // Parse each of the filter expressions, final List filterPaths = parseExpressions(parserContext, filters, - Optional.of(trimmed)); + Optional.of(unfiltered)); // Get the dataset from the last filter. final Dataset withFilters = filterPaths.get(filterPaths.size() - 1).getDataset(); @@ -161,43 +153,4 @@ private List validateAndCoerceColumns( return coerced; } - @Nonnull - private Dataset trimTrailingNulls(final @Nonnull Column idColumn, - @Nonnull final List expressions, @Nonnull final Dataset dataset) { - checkArgument(!expressions.isEmpty(), "At least one expression is required"); - - // Get the value columns associated with non-singular paths. - final List nonSingularColumns = expressions.stream() - .filter(fhirPath -> !fhirPath.isSingular()) - .map(FhirPath::getValueColumn) - .collect(toList()); - - if (nonSingularColumns.isEmpty()) { - // If there are no non-singular paths, there is nothing to do. - return dataset; - } else { - // Build a condition that checks whether any of the non-singular columns are not null. - final Column additionalCondition = nonSingularColumns.stream() - .map(Column::isNotNull) - .reduce(Column::or) - .get(); - - // Build a dataset that contains the distinct values of all singular columns. - final List filteringColumns = new ArrayList<>(); - filteringColumns.add(idColumn); - final List singularColumns = expressions.stream() - .filter(FhirPath::isSingular) - .map(FhirPath::getValueColumn) - .collect(Collectors.toList()); - filteringColumns.addAll(singularColumns); - final Dataset filteringDataset = dataset - .select(filteringColumns.toArray(new Column[0])) - .distinct(); - - // Join the dataset to the filtering dataset, using a right outer join. - return join(dataset, filteringColumns, filteringDataset, filteringColumns, - additionalCondition, JoinType.RIGHT_OUTER); - } - } - } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java index c972499e53..e58a55eccf 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java @@ -57,6 +57,16 @@ public boolean isOrderable() { .allMatch(path -> path.getOrderingColumn().isPresent()); } + /** + * @return all value columns within nesting paths that have been traversed + */ + @Nonnull + public List getColumns() { + return nesting.values().stream() + .map(FhirPath::getValueColumn) + .collect(toList()); + } + /** * @return all ordering columns within nesting paths that have been traversed */ diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java index ab9d5e20e6..73e512c8e8 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java @@ -17,11 +17,14 @@ package au.csiro.pathling.fhirpath.function; +import static au.csiro.pathling.QueryHelpers.createColumn; import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static au.csiro.pathling.utilities.Preconditions.checkPresent; -import static org.apache.spark.sql.functions.col; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; import static org.apache.spark.sql.functions.first; +import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.element.ElementPath; @@ -31,13 +34,14 @@ import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; +import java.util.function.UnaryOperator; import java.util.stream.Stream; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; +import org.apache.spark.sql.expressions.Window; +import org.apache.spark.sql.expressions.WindowSpec; import org.apache.spark.sql.functions; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; @@ -55,17 +59,18 @@ public abstract class AggregateFunction { * @param dataset the {@link Dataset} that will be used in the result * @param parserContext the current {@link ParserContext} * @param input the {@link FhirPath} objects being aggregated - * @param valueColumn a {@link Column} describing the resulting value + * @param aggregateColumn a {@link Column} describing the resulting value * @param expression the FHIRPath expression for the result * @return a new {@link ElementPath} representing the result */ @Nonnull protected NonLiteralPath buildAggregateResult(@Nonnull final Dataset dataset, @Nonnull final ParserContext parserContext, @Nonnull final NonLiteralPath input, - @Nonnull final Column valueColumn, @Nonnull final String expression) { + @Nonnull final Column aggregateColumn, + @Nonnull final UnaryOperator valueColumnProducer, @Nonnull final String expression) { return buildAggregateResult(dataset, parserContext, Collections.singletonList(input), - valueColumn, expression, input::copy); + aggregateColumn, valueColumnProducer, expression, input::copy); } /** @@ -74,7 +79,9 @@ protected NonLiteralPath buildAggregateResult(@Nonnull final Dataset datase * @param dataset the {@link Dataset} that will be used in the result * @param parserContext the current {@link ParserContext} * @param input the {@link FhirPath} objects being aggregated - * @param valueColumn a {@link Column} describing the resulting value + * @param aggregateColumn a {@link Column} describing the resulting value + * @param valueColumnProducer a {@link UnaryOperator} that produces a {@link Column} describing + * the final value * @param expression the FHIRPath expression for the result * @param fhirType the {@link FHIRDefinedType} of the result * @return a new {@link ElementPath} representing the result @@ -83,11 +90,12 @@ protected NonLiteralPath buildAggregateResult(@Nonnull final Dataset datase @Nonnull protected ElementPath buildAggregateResult(@Nonnull final Dataset dataset, @Nonnull final ParserContext parserContext, @Nonnull final FhirPath input, - @Nonnull final Column valueColumn, @Nonnull final String expression, + @Nonnull final Column aggregateColumn, + @Nonnull final UnaryOperator valueColumnProducer, @Nonnull final String expression, @Nonnull final FHIRDefinedType fhirType) { return buildAggregateResult(dataset, parserContext, Collections.singletonList(input), - valueColumn, expression, fhirType); + aggregateColumn, valueColumnProducer, expression, fhirType); } /** @@ -97,7 +105,9 @@ protected ElementPath buildAggregateResult(@Nonnull final Dataset dataset, * @param dataset the {@link Dataset} that will be used in the result * @param parserContext the current {@link ParserContext} * @param inputs the {@link FhirPath} objects being aggregated - * @param valueColumn a {@link Column} describing the resulting value + * @param aggregateColumn a {@link Column} describing the aggregation operation + * @param valueColumnProducer a {@link UnaryOperator} that produces a {@link Column} describing + * the final value * @param expression the FHIRPath expression for the result * @param fhirType the {@link FHIRDefinedType} of the result * @return a new {@link ElementPath} representing the result @@ -105,10 +115,12 @@ protected ElementPath buildAggregateResult(@Nonnull final Dataset dataset, @Nonnull protected ElementPath buildAggregateResult(@Nonnull final Dataset dataset, @Nonnull final ParserContext parserContext, @Nonnull final Collection inputs, - @Nonnull final Column valueColumn, @Nonnull final String expression, + @Nonnull final Column aggregateColumn, + @Nonnull final UnaryOperator valueColumnProducer, @Nonnull final String expression, @Nonnull final FHIRDefinedType fhirType) { - return buildAggregateResult(dataset, parserContext, inputs, valueColumn, expression, + return buildAggregateResult(dataset, parserContext, inputs, aggregateColumn, + valueColumnProducer, expression, // Create the result as an ElementPath of the given FHIR type. (exp, ds, id, value, ordering, singular, thisColumn) -> ElementPath.build(exp, ds, id, value, ordering, true, Optional.empty(), thisColumn, fhirType)); @@ -117,7 +129,8 @@ protected ElementPath buildAggregateResult(@Nonnull final Dataset dataset, @Nonnull private T buildAggregateResult(@Nonnull final Dataset dataset, @Nonnull final ParserContext parserContext, @Nonnull final Collection inputs, - @Nonnull final Column valueColumn, @Nonnull final String expression, + @Nonnull final Column aggregateColumn, + @Nonnull final UnaryOperator valueColumnProducer, @Nonnull final String expression, @Nonnull final ResultPathFactory resultPathFactory) { checkArgument(!inputs.isEmpty(), "Collection of inputs cannot be empty"); @@ -126,47 +139,45 @@ private T buildAggregateResult(@Nonnull final Dataset final Column idColumn = inputs.stream().findFirst().get().getIdColumn(); // Get the grouping columns from the parser context. - final List groupByList = parserContext.getGroupingColumns(); - - // Drop the requested grouping columns that are not present in the provided dataset. - // This handles the situation where `%resource` is used in `where()`. - // The columns requested for aggregation may include $this element ID, which is not present in - // datasets originating from `%resource` and thus should not be actually used for evaluation of - // the aggregation. - final Set existingColumns = Stream.of(dataset.columns()).collect(Collectors.toSet()); - final Column[] groupBy = groupByList.stream() - .filter(c -> existingColumns.contains(c.toString())) - .toArray(Column[]::new); - - // The selection will be the first function applied to each column except the grouping columns, - // plus the value column. - final Predicate groupingFilter = column -> !groupByList.contains(column); - final List selection = Stream.of(dataset.columns()) + final List groupingColumns = parserContext.getGroupingColumns(); + final Column[] partitionBy = groupingColumns.toArray(new Column[0]); + + // Use a windowing function to aggregate within the column, while preserving the rest of the + // dataset. + final WindowSpec window = Window.partitionBy(partitionBy); + final Column valueColumn = valueColumnProducer.apply(aggregateColumn.over(window)); + final DatasetWithColumn datasetWithColumn = createColumn(dataset, valueColumn); + + // Erase the last level of nesting present within the context. + parserContext.getNesting().removeLast(); + + // Perform a subsequent aggregation that results in a single aggregate result at the current + // nesting level (taking into account the erasure). + // Collect the grouping and non-grouping columns into separate collections + final Set nonGroupingColumns = Stream.of(datasetWithColumn.getDataset().columns()) .map(functions::col) - .filter(groupingFilter) + .collect(toSet()); + groupingColumns.forEach(nonGroupingColumns::remove); + // Wrap the non-grouping columns in a "first" aggregation. + final List selection = nonGroupingColumns.stream() .map(column -> first(column, true).alias(column.toString())) - .collect(Collectors.toList()); - selection.add(valueColumn.alias("value")); - + .collect(toList()); + // Separate the first column from the rest, so that we can pass all the columns to the "agg" + // method. final Column firstSelection = checkPresent(selection.stream().limit(1).findFirst()); final Column[] remainingSelection = selection.stream().skip(1).toArray(Column[]::new); + // Perform the final aggregation, the first row of each non-grouping column grouped by the + // grouping columns. + final Dataset result = datasetWithColumn.getDataset() + .groupBy(groupingColumns.toArray(new Column[0])) + .agg(firstSelection, remainingSelection); - // Get any this columns that may be present in the inputs. + // Get any "this" columns that may be present in the inputs. final Optional thisColumn = NonLiteralPath .findThisColumn((Object[]) inputs.toArray(new FhirPath[0])); - final Dataset finalDataset = dataset - .groupBy(groupBy) - .agg(firstSelection, remainingSelection); - final Column finalValueColumn = col("value"); - - // Remove the last nesting entry, so that it is possible to traverse back into this path again - // without it being rolled up into an aggregation. - parserContext.getNesting().removeLast(); - - return resultPathFactory - .create(expression, finalDataset, idColumn, finalValueColumn, Optional.empty(), true, - thisColumn); + return resultPathFactory.create(expression, result, idColumn, + datasetWithColumn.getColumn(), Optional.empty(), true, thisColumn); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java index 8b2c16b63d..87a7d3b98d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java @@ -59,9 +59,9 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Column inputColumn = inputPath.getValueColumn(); final String expression = expressionFromInput(input, type.getFunctionName()); - final Column valueColumn = type.getEquality().apply(inputColumn); - return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, valueColumn, - expression); + final Column aggregateColumn = type.getEquality().apply(inputColumn); + return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, + aggregateColumn, UnaryOperator.identity(), expression); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java index 77dcc46035..5e35a6c353 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java @@ -19,15 +19,14 @@ import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; +import static org.apache.spark.sql.functions.count; import static org.apache.spark.sql.functions.when; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; -import java.util.function.Function; import java.util.function.UnaryOperator; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; -import org.apache.spark.sql.functions; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** @@ -51,21 +50,14 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final String expression = expressionFromInput(input, NAME); final Column subjectColumn = inputPath.getValueColumn(); - // When we are counting resources from the input context, we use the distinct count to account - // for the fact that there may be duplicate IDs in the dataset. - // When we are counting anything else, we use a non-distinct count, to account for the fact that - // it is valid to have multiple of the same value. - final Function countFunction = inputPath == input.getContext().getInputContext() - ? functions::countDistinct - : functions::count; - + final Column aggregateColumn = count(subjectColumn); // According to the FHIRPath specification, the count function must return 0 when invoked on an // empty collection. - final Column valueColumn = when(countFunction.apply(subjectColumn).isNull(), 0L) - .otherwise(countFunction.apply(subjectColumn)); + final UnaryOperator valueColumnProducer = column -> when(column.isNull(), 0L) + .otherwise(column); - return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, valueColumn, - expression, FHIRDefinedType.UNSIGNEDINT); + return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, + aggregateColumn, valueColumnProducer, expression, FHIRDefinedType.UNSIGNEDINT); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java index bdae04ea1d..ad75a4d6fe 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java @@ -58,9 +58,9 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final NonLiteralPath inputPath = input.getInput(); final Dataset dataset = inputPath.getOrderedDataset(nesting); final String expression = expressionFromInput(input, NAME); - final Column finalValueColumn = first(inputPath.getValueColumn(), true); + final Column aggregateColumn = first(inputPath.getValueColumn(), true); - return buildAggregateResult(dataset, input.getContext(), inputPath, finalValueColumn, - expression); + return buildAggregateResult(dataset, input.getContext(), inputPath, aggregateColumn, + UnaryOperator.identity(), expression); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java index 10053d520f..7d41da8eeb 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java @@ -54,10 +54,10 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final NonLiteralPath inputPath = input.getInput(); final Dataset dataset = inputPath.getDataset(); final String expression = expressionFromInput(input, NAME); - final Column finalValueColumn = sum(inputPath.getValueColumn()); + final Column aggregateColumn = sum(inputPath.getValueColumn()); - return buildAggregateResult(dataset, input.getContext(), inputPath, finalValueColumn, - expression); + return buildAggregateResult(dataset, input.getContext(), inputPath, aggregateColumn, + UnaryOperator.identity(), expression); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java index 9fcff0c695..4b1f3a44b4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java @@ -22,6 +22,7 @@ import static au.csiro.pathling.utilities.Preconditions.checkPresent; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.lit; +import static org.apache.spark.sql.functions.row_number; import static org.apache.spark.sql.functions.when; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; @@ -30,6 +31,10 @@ import au.csiro.pathling.fhirpath.element.BooleanPath; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.expressions.Window; +import org.apache.spark.sql.expressions.WindowSpec; /** * Describes a function which can scope down the previous invocation within a FHIRPath expression, @@ -66,15 +71,21 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { // absence of a value). final Column idColumn = argumentPath.getIdColumn(); final Column thisValue = checkPresent(argumentPath.getThisColumn()); - final Column valueColumn = when(argumentValue.equalTo(true), thisValue).otherwise(lit(null)); - final String expression = expressionFromInput(input, NAME); - final DatasetWithColumn datasetWithColumn = createColumn(argumentPath.getDataset(), - valueColumn); - return inputPath.copy(expression, datasetWithColumn.getDataset(), idColumn, - datasetWithColumn.getColumn(), inputPath.getOrderingColumn(), inputPath.isSingular(), - inputPath.getThisColumn()); + // We use a windowing function to remove all null values except one. A single null value against + // a resource signifies the absence of a value for a particular element. + final WindowSpec window = Window.partitionBy(idColumn).orderBy(valueColumn.asc_nulls_last()); + final DatasetWithColumn datasetWithRowNumber = createColumn(argumentPath.getDataset(), + row_number().over(window)); + final Dataset dataset = datasetWithRowNumber.getDataset() + .filter(valueColumn.isNotNull().or(datasetWithRowNumber.getColumn().equalTo(1))); + + input.getContext().getNesting().removeLast(); + + final String expression = expressionFromInput(input, NAME); + return inputPath.copy(expression, dataset, idColumn, valueColumn, + inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getThisColumn()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java index 602385144a..c9cc5ffcef 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java @@ -28,6 +28,7 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.function.AggregateFunction; import java.util.Arrays; +import java.util.function.UnaryOperator; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; @@ -83,10 +84,10 @@ public FhirPath invoke(@Nonnull final OperatorInput input) { // In order to reduce the result to a single Boolean, we take the max of the boolean equality // values. - final Column valueColumn = max(equalityWithNullChecks); + final Column aggregateColumn = max(equalityWithNullChecks); return buildAggregateResult(right.getDataset(), input.getContext(), Arrays.asList(left, right), - valueColumn, expression, FHIRDefinedType.BOOLEAN); + aggregateColumn, UnaryOperator.identity(), expression, FHIRDefinedType.BOOLEAN); } /** From 80c9b2ccdf681219665d0a471bd0d1070918256a Mon Sep 17 00:00:00 2001 From: John Grimes Date: Tue, 18 Jul 2023 10:35:36 +1000 Subject: [PATCH 23/95] Improve comments in WhereFunction and AggregateFunction --- .../au/csiro/pathling/fhirpath/function/AggregateFunction.java | 3 ++- .../au/csiro/pathling/fhirpath/function/WhereFunction.java | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java index 73e512c8e8..e6fe9ce78e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java @@ -153,7 +153,8 @@ private T buildAggregateResult(@Nonnull final Dataset // Perform a subsequent aggregation that results in a single aggregate result at the current // nesting level (taking into account the erasure). - // Collect the grouping and non-grouping columns into separate collections + // Collect the grouping and non-grouping columns into separate collections so that we can feed + // them to the "agg" method. final Set nonGroupingColumns = Stream.of(datasetWithColumn.getDataset().columns()) .map(functions::col) .collect(toSet()); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java index 4b1f3a44b4..0709d7dde0 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java @@ -81,6 +81,7 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Dataset dataset = datasetWithRowNumber.getDataset() .filter(valueColumn.isNotNull().or(datasetWithRowNumber.getColumn().equalTo(1))); + // Erase the last level of nesting present within the context. input.getContext().getNesting().removeLast(); final String expression = expressionFromInput(input, NAME); From 98193d18b3e8433447f5c6d6867fb52f979d92ad Mon Sep 17 00:00:00 2001 From: John Grimes Date: Tue, 18 Jul 2023 11:21:30 +1000 Subject: [PATCH 24/95] Change count function to use collect_set and size --- .../au/csiro/pathling/aggregate/AggregateExecutor.java | 2 ++ .../pathling/fhirpath/function/CountFunction.java | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java index 63230d791c..654816cc46 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java @@ -73,6 +73,8 @@ public AggregateExecutor(@Nonnull final QueryConfiguration configuration, public AggregateResponse execute(@Nonnull final AggregateRequest query) { final ResultWithExpressions resultWithExpressions = buildQuery( query); + resultWithExpressions.getDataset().explain(); + resultWithExpressions.getDataset().show(1_000, false); // Translate the result into a response object to be passed back to the user. return buildResponse(resultWithExpressions); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java index 5e35a6c353..031714c4aa 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java @@ -19,7 +19,8 @@ import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static org.apache.spark.sql.functions.count; +import static org.apache.spark.sql.functions.collect_set; +import static org.apache.spark.sql.functions.size; import static org.apache.spark.sql.functions.when; import au.csiro.pathling.fhirpath.FhirPath; @@ -50,11 +51,12 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final String expression = expressionFromInput(input, NAME); final Column subjectColumn = inputPath.getValueColumn(); - final Column aggregateColumn = count(subjectColumn); + final Column aggregateColumn = collect_set(subjectColumn); // According to the FHIRPath specification, the count function must return 0 when invoked on an // empty collection. - final UnaryOperator valueColumnProducer = column -> when(column.isNull(), 0L) - .otherwise(column); + final UnaryOperator valueColumnProducer = column -> when( + size(column).equalTo(-1), 0L + ).otherwise(size(column)); return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, aggregateColumn, valueColumnProducer, expression, FHIRDefinedType.UNSIGNEDINT); From efdabb3b8b73b439e7a8523efdb4548ee2ee8614 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Tue, 18 Jul 2023 11:24:09 +1000 Subject: [PATCH 25/95] Differentiate between distinct and non-distinct count scenarios --- .../pathling/fhirpath/function/CountFunction.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java index 031714c4aa..80b7b247dc 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java @@ -20,6 +20,7 @@ import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import static org.apache.spark.sql.functions.collect_set; +import static org.apache.spark.sql.functions.count; import static org.apache.spark.sql.functions.size; import static org.apache.spark.sql.functions.when; @@ -51,7 +52,14 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final String expression = expressionFromInput(input, NAME); final Column subjectColumn = inputPath.getValueColumn(); - final Column aggregateColumn = collect_set(subjectColumn); + // When we are counting resources from the input context, we use the distinct count to account + // for the fact that there may be duplicate IDs in the dataset. + // When we are counting anything else, we use a non-distinct count, to account for the fact that + // it is valid to have multiple of the same value. + final Column aggregateColumn = inputPath == input.getContext().getInputContext() + ? collect_set(subjectColumn) + : count(subjectColumn); + // According to the FHIRPath specification, the count function must return 0 when invoked on an // empty collection. final UnaryOperator valueColumnProducer = column -> when( From 067b134b0e725ea5e91009bed8702cc119fe03aa Mon Sep 17 00:00:00 2001 From: John Grimes Date: Tue, 18 Jul 2023 14:15:14 +1000 Subject: [PATCH 26/95] Rewrite count function to take account of counting in unnested datasets --- .../au/csiro/pathling/fhirpath/Nesting.java | 7 ++++ .../fhirpath/function/CountFunction.java | 39 +++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java index e58a55eccf..1bee4415fc 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java @@ -79,6 +79,13 @@ public List getOrderingColumns() { .collect(toList()); } + /** + * @return whether the nesting stack is empty + */ + public boolean isEmpty() { + return nesting.isEmpty(); + } + /** * Removes the last element from the nesting stack. This is used when applying aggregate * functions, so that subsequent unnestings at this level can be dealt with correctly. diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java index 80b7b247dc..9f0f0d0c1c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java @@ -21,14 +21,14 @@ import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import static org.apache.spark.sql.functions.collect_set; import static org.apache.spark.sql.functions.count; -import static org.apache.spark.sql.functions.size; -import static org.apache.spark.sql.functions.when; import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.Nesting; import au.csiro.pathling.fhirpath.NonLiteralPath; import java.util.function.UnaryOperator; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; +import org.apache.spark.sql.functions; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** @@ -50,21 +50,28 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { checkNoArguments("count", input); final NonLiteralPath inputPath = input.getInput(); final String expression = expressionFromInput(input, NAME); - final Column subjectColumn = inputPath.getValueColumn(); + final Nesting nesting = input.getContext().getNesting(); - // When we are counting resources from the input context, we use the distinct count to account - // for the fact that there may be duplicate IDs in the dataset. - // When we are counting anything else, we use a non-distinct count, to account for the fact that - // it is valid to have multiple of the same value. - final Column aggregateColumn = inputPath == input.getContext().getInputContext() - ? collect_set(subjectColumn) - : count(subjectColumn); - - // According to the FHIRPath specification, the count function must return 0 when invoked on an - // empty collection. - final UnaryOperator valueColumnProducer = column -> when( - size(column).equalTo(-1), 0L - ).otherwise(size(column)); + final Column aggregateColumn; + final UnaryOperator valueColumnProducer; + if (nesting.isEmpty()) { + final Column subjectColumn = inputPath.getValueColumn(); + // When we are counting anything else, we use a non-distinct count, to account for the fact + // that it is valid to have multiple of the same value. + aggregateColumn = count(subjectColumn); + valueColumnProducer = UnaryOperator.identity(); + } else { + // Use the ordering column if it exists, otherwise use the value column (which should only + // ever be a resource ID). + final Column subjectColumn = inputPath.getOrderingColumn() + .orElse(inputPath.getValueColumn()); + // When we are counting values within an unnested dataset, we use a distinct count to account + // for the fact that there may be duplicates. This is implemented here using the combination + // of "collect_set" and "size", to work around the fact that Spark does not support + // "countDistinct" with windowing functions. + aggregateColumn = collect_set(subjectColumn); + valueColumnProducer = functions::size; + } return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, aggregateColumn, valueColumnProducer, expression, FHIRDefinedType.UNSIGNEDINT); From 03d489930fda05649133ef09773e4e0c1a577cfc Mon Sep 17 00:00:00 2001 From: John Grimes Date: Tue, 18 Jul 2023 15:52:07 +1000 Subject: [PATCH 27/95] Get SearchExecutorTest passing --- .editorconfig | 2 +- .../pathling/aggregate/AggregateExecutor.java | 4 +- .../csiro/pathling/search/SearchExecutor.java | 106 +++++++----------- .../pathling/search/SearchExecutorTest.java | 4 +- .../combineResultInSecondFilter.Bundle.json | 73 +++++++++--- .../java/au/csiro/pathling/QueryExecutor.java | 9 +- .../aggregate/AggregateQueryExecutor.java | 3 +- .../csiro/pathling/fhirpath/ResourcePath.java | 1 + .../function/BooleansTestFunction.java | 21 ++-- 9 files changed, 122 insertions(+), 101 deletions(-) diff --git a/.editorconfig b/.editorconfig index 450c3d9d44..87afa11674 100644 --- a/.editorconfig +++ b/.editorconfig @@ -900,7 +900,7 @@ ij_json_keep_indents_on_empty_lines = false ij_json_keep_line_breaks = true ij_json_space_after_colon = true ij_json_space_after_comma = true -ij_json_space_before_colon = true +ij_json_space_before_colon = false ij_json_space_before_comma = false ij_json_spaces_within_braces = true ij_json_spaces_within_brackets = false diff --git a/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java index 654816cc46..adcc3edae7 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java @@ -17,6 +17,8 @@ package au.csiro.pathling.aggregate; +import static java.util.stream.Collectors.toList; + import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirValue; @@ -97,7 +99,7 @@ private AggregateResponse buildResponse( .map(mapRowToGrouping(resultWithExpressions.getParsedAggregations(), resultWithExpressions.getParsedGroupings(), resultWithExpressions.getParsedFilters())) - .collect(Collectors.toList()); + .collect(toList()); return new AggregateResponse(groupings); } diff --git a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java index 935bc66d60..9cb0649583 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java @@ -17,20 +17,16 @@ package au.csiro.pathling.search; -import static au.csiro.pathling.utilities.Preconditions.check; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static au.csiro.pathling.utilities.Strings.randomAlias; import static java.util.Objects.requireNonNull; -import static org.apache.spark.sql.functions.col; +import static java.util.stream.Collectors.toList; import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.BooleanPath; -import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; -import au.csiro.pathling.fhirpath.parser.Parser; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.Database; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -40,8 +36,6 @@ import ca.uhn.fhir.rest.param.StringAndListParam; import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringParam; -import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; @@ -120,11 +114,8 @@ public SearchExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull private Dataset initializeDataset() { final ResourcePath resourcePath = ResourcePath - .build(getFhirContext(), getDataSource(), subjectResource, subjectResource.toCode(), - true, true); + .build(getFhirContext(), getDataSource(), subjectResource, subjectResource.toCode(), true); final Dataset subjectDataset = resourcePath.getDataset(); - final Column subjectIdColumn = resourcePath.getIdColumn(); - final Dataset dataset; if (filters.isEmpty() || filters.get().getValuesAsQueryTokens().isEmpty()) { @@ -132,70 +123,49 @@ private Dataset initializeDataset() { dataset = subjectDataset; } else { - final Collection fhirPaths = new ArrayList<>(); - @Nullable Column filterIdColumn = null; - @Nullable Column filterColumn = null; - - ResourcePath currentContext = ResourcePath - .build(getFhirContext(), getDataSource(), subjectResource, subjectResource.toCode(), - true); + final ParserContext parserContext = buildParserContext(resourcePath, + Collections.singletonList(resourcePath.getIdColumn())); + Dataset currentDataset = subjectDataset; + @Nullable Column filterCondition = null; // Parse each of the supplied filter expressions, building up a filter column. This captures // the AND/OR conditions possible through the FHIR API, see // https://hl7.org/fhir/R4/search.html#combining. for (final StringOrListParam orParam : filters.get().getValuesAsQueryTokens()) { - @Nullable Column orColumn = null; - - for (final StringParam param : orParam.getValuesAsQueryTokens()) { - final ParserContext parserContext = buildParserContext(currentContext, - Collections.singletonList(currentContext.getIdColumn())); - final Parser parser = new Parser(parserContext); - final String expression = param.getValue(); - checkUserInput(!expression.isBlank(), "Filter expression cannot be blank"); - - final FhirPath fhirPath = parser.parse(expression); - checkUserInput(fhirPath instanceof BooleanPath || fhirPath instanceof BooleanLiteralPath, - "Filter expression must be of Boolean type: " + fhirPath.getExpression()); - final Column filterValue = fhirPath.getValueColumn(); - - // Add each expression to a list that will later be joined. - fhirPaths.add(fhirPath); - - // Combine all the OR columns with OR logic. - orColumn = orColumn == null - ? filterValue - : orColumn.or(filterValue); - - // We save away the first encountered ID column so that we can use it later to join the - // subject resource dataset with the joined filter datasets. - if (filterIdColumn == null) { - filterIdColumn = fhirPath.getIdColumn(); - } - - // Update the context to build the next expression from the same dataset. - currentContext = currentContext.copy(currentContext.getExpression(), - fhirPath.getDataset(), fhirPath.getIdColumn(), fhirPath.getValueColumn(), - fhirPath.getOrderingColumn(), currentContext.isSingular(), - currentContext.getThisColumn()); - } - - // Combine all the columns at this level with AND logic. - filterColumn = filterColumn == null - ? orColumn - : filterColumn.and(orColumn); + + // Parse all the filter expressions within this AND condition. + final List filterExpressions = orParam.getValuesAsQueryTokens().stream() + .map(StringParam::getValue) + .collect(toList()); + checkUserInput(filterExpressions.stream().noneMatch(String::isBlank), + "Filter expression cannot be blank"); + final List filters = parseExpressions(parserContext, filterExpressions, + Optional.of(currentDataset)); + validateFilters(filters); + + // Get the dataset from the last filter. + currentDataset = filters.get(filters.size() - 1).getDataset(); + + // Combine all the columns with OR logic. + final Column orColumn = filters.stream() + .map(FhirPath::getValueColumn) + .reduce(Column::or) + .orElseThrow(); + + // Combine OR-grouped columns with AND logic. + filterCondition = filterCondition == null + ? orColumn + : filterCondition.and(orColumn); } - requireNonNull(filterIdColumn); - requireNonNull(filterColumn); - check(!fhirPaths.isEmpty()); - - // Get the full resources which are present in the filtered dataset. - final String filterIdAlias = randomAlias(); - final Dataset filteredIds = currentContext.getDataset().select(filterIdColumn.alias( - filterIdAlias)).filter(filterColumn); - dataset = subjectDataset - .join(filteredIds, subjectIdColumn.equalTo(col(filterIdAlias)), "left_semi"); + dataset = currentDataset.filter(filterCondition); } + final Column[] resourceColumns = resourcePath.getElementsToColumns().keySet().stream() + .map(colName -> resourcePath.getElementsToColumns().get(colName).alias(colName)) + .toArray(Column[]::new); + final Dataset result = dataset.select(resourceColumns); + result.explain(); + if (getConfiguration().getCacheResults()) { // We cache the dataset because we know it will be accessed for both the total and the record // retrieval. @@ -203,7 +173,7 @@ private Dataset initializeDataset() { dataset.cache(); } - return dataset; + return result; } @Override diff --git a/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorTest.java b/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorTest.java index 54cdbd2daf..8a6a1b1f16 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorTest.java @@ -91,7 +91,7 @@ void setUp() { void simpleSearchWithMemberOf() { final StringAndListParam params = new StringAndListParam(); params.addAnd(new StringParam( - "reverseResolve(Condition.subject).code.memberOf('http://snomed.info/sct?fhir_vs=ecl/^ 32570581000036105 : << 263502005 = << 90734009')")); + "reverseResolve(Condition.subject).code.memberOf('http://snomed.info/sct?fhir_vs=ecl/^ 32570581000036105 : << 263502005 = << 90734009').anyTrue()")); final SearchExecutorBuilder builder = searchBuilder() .withSubjectResource(ResourceType.PATIENT) .withFilters(params); @@ -143,7 +143,7 @@ void throwsInvalidInputOnNonBooleanFilter() { .withSubjectResource(ResourceType.CAREPLAN) .withFilters(params) .build()); - assertEquals("Filter expression must be of Boolean type: category.coding", error.getMessage()); + assertEquals("Filter expression must be a Boolean: category.coding", error.getMessage()); } @Test diff --git a/fhir-server/src/test/resources/responses/SearchExecutorTest/combineResultInSecondFilter.Bundle.json b/fhir-server/src/test/resources/responses/SearchExecutorTest/combineResultInSecondFilter.Bundle.json index 1316984eb5..51ed3a0cab 100644 --- a/fhir-server/src/test/resources/responses/SearchExecutorTest/combineResultInSecondFilter.Bundle.json +++ b/fhir-server/src/test/resources/responses/SearchExecutorTest/combineResultInSecondFilter.Bundle.json @@ -77,8 +77,12 @@ { "use": "official", "family": "Ulibarri312", - "given": ["Pedro316"], - "prefix": ["Mr."] + "given": [ + "Pedro316" + ], + "prefix": [ + "Mr." + ] } ], "telecom": [ @@ -92,7 +96,9 @@ "birthDate": "1998-12-26", "address": [ { - "line": ["879 Hettinger Gardens"], + "line": [ + "879 Hettinger Gardens" + ], "city": "Pittsfield", "state": "Massachusetts", "postalCode": "01201", @@ -200,9 +206,28 @@ { "use": "official", "family": "Krajcik437", - "given": ["Seymour882"], - "prefix": ["Mr."], - "suffix": ["MD"] + "given": [ + "Seymour882" + ], + "prefix": [ + "Mr." + ], + "suffix": [ + "MD" + ] + }, + { + "use": "nickname", + "family": "Burchard", + "given": [ + "Arianne" + ], + "prefix": [ + "Ms." + ], + "suffix": [ + "MD" + ] } ], "telecom": [ @@ -216,7 +241,9 @@ "birthDate": "1970-11-22", "address": [ { - "line": ["855 Senger Union Suite 12"], + "line": [ + "855 Senger Union Suite 12" + ], "city": "Quincy", "state": "Massachusetts", "postalCode": "02169", @@ -324,8 +351,12 @@ { "use": "official", "family": "Botsford977", - "given": ["Shirley182"], - "prefix": ["Mr."] + "given": [ + "Shirley182" + ], + "prefix": [ + "Mr." + ] } ], "telecom": [ @@ -339,7 +370,9 @@ "birthDate": "1957-06-06", "address": [ { - "line": ["329 Prosacco Highlands Suite 8"], + "line": [ + "329 Prosacco Highlands Suite 8" + ], "city": "Malden", "state": "Massachusetts", "postalCode": "02148", @@ -419,7 +452,9 @@ { "use": "official", "family": "MacGyver246", - "given": ["Gilberto712"] + "given": [ + "Gilberto712" + ] } ], "telecom": [ @@ -434,7 +469,9 @@ "deceasedDateTime": "1967-06-08T08:27:40+00:00", "address": [ { - "line": ["1096 Kulas Rest"], + "line": [ + "1096 Kulas Rest" + ], "city": "Malden", "state": "Massachusetts", "postalCode": "02148", @@ -542,8 +579,12 @@ { "use": "official", "family": "Towne435", - "given": ["Guy979"], - "prefix": ["Mr."] + "given": [ + "Guy979" + ], + "prefix": [ + "Mr." + ] } ], "telecom": [ @@ -557,7 +598,9 @@ "birthDate": "1983-09-06", "address": [ { - "line": ["598 Boyer Ramp"], + "line": [ + "598 Boyer Ramp" + ], "city": "Somerville", "state": "Massachusetts", "postalCode": "02138", diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index 91be79285a..bae945378f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -17,15 +17,12 @@ package au.csiro.pathling; -import static au.csiro.pathling.QueryHelpers.join; -import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static au.csiro.pathling.utilities.Strings.randomAlias; import static java.util.Objects.requireNonNull; import static org.apache.spark.sql.functions.col; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; -import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.ResourcePath; @@ -124,10 +121,10 @@ protected void validateFilters(@Nonnull final Collection filters) { for (final FhirPath filter : filters) { // Each filter expression must evaluate to a singular Boolean value, or a user error will be // thrown. - checkUserInput(filter instanceof BooleanPath, - "Filter expression is not a non-literal boolean: " + filter); + checkUserInput(filter instanceof BooleanPath || filter instanceof BooleanLiteralPath, + "Filter expression must be a Boolean: " + filter.getExpression()); checkUserInput(filter.isSingular(), - "Filter expression must represent a singular value: " + filter); + "Filter expression must be a singular value: " + filter.getExpression()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java index ecf4b80078..c41d86f91b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java @@ -140,7 +140,8 @@ public ResultWithExpressions buildQuery(@Nonnull final AggregateRequest query) { final List aggregations = parseExpressions(aggregationContext, query.getAggregations(), prunedDataset); validateAggregations(aggregations); - final List aggregationColumns = aggregations.stream().map(FhirPath::getValueColumn) + final List aggregationColumns = aggregations.stream() + .map(FhirPath::getValueColumn) .collect(toList()); final Dataset aggregationDataset = aggregations.get(aggregations.size() - 1) .getDataset(); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java index 724639f8df..935491ec95 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java @@ -58,6 +58,7 @@ public class ResourcePath extends NonLiteralPath { private final ResourceDefinition definition; @Nonnull + @Getter private final Map elementsToColumns; protected ResourcePath(@Nonnull final String expression, @Nonnull final Dataset dataset, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java index 87a7d3b98d..6c128d2b6d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java @@ -59,9 +59,9 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Column inputColumn = inputPath.getValueColumn(); final String expression = expressionFromInput(input, type.getFunctionName()); - final Column aggregateColumn = type.getEquality().apply(inputColumn); + final Column aggregateColumn = type.getAggregateColumnProducer().apply(inputColumn); return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, - aggregateColumn, UnaryOperator.identity(), expression); + aggregateColumn, type.getValueColumnProducer(), expression); } /** @@ -73,25 +73,32 @@ public enum BooleansTestType { /** * "Any true" test */ - ANY_TRUE("anyTrue", input -> max(coalesce(input, lit(false))).equalTo(lit(true))), + ANY_TRUE("anyTrue", input -> max(coalesce(input, lit(false))), + column -> column.equalTo(lit(true))), /** * "Any false" test */ - ANY_FALSE("anyFalse", input -> min(coalesce(input, lit(true))).equalTo(lit(false))), + ANY_FALSE("anyFalse", input -> min(coalesce(input, lit(true))), + column -> column.equalTo(lit(false))), /** * "All true" test */ - ALL_TRUE("allTrue", input -> min(coalesce(input, lit(true))).equalTo(lit(true))), + ALL_TRUE("allTrue", input -> min(coalesce(input, lit(true))), + column -> column.equalTo(lit(true))), /** * "All false" test */ - ALL_FALSE("allFalse", input -> max(coalesce(input, lit(false))).equalTo(lit(false))); + ALL_FALSE("allFalse", input -> max(coalesce(input, lit(false))), + column -> column.equalTo(lit(false))); @Nonnull private final String functionName; @Nonnull - private final UnaryOperator equality; + private final UnaryOperator aggregateColumnProducer; + + @Nonnull + private final UnaryOperator valueColumnProducer; @Override public String toString() { From 88e383070233330fe7b00e6474d71c1b4f739ce7 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Tue, 18 Jul 2023 16:42:29 +1000 Subject: [PATCH 28/95] Remove join from offset and limit in search --- .../csiro/pathling/search/SearchExecutor.java | 33 +- .../pathling/search/SearchExecutorTest.java | 23 +- .../searchWithOffset.Bundle.json | 297 ++++++++++++++++++ .../simpleSearchWithMemberOf.Bundle.json | 102 +++--- 4 files changed, 397 insertions(+), 58 deletions(-) create mode 100644 fhir-server/src/test/resources/responses/SearchExecutorTest/searchWithOffset.Bundle.json diff --git a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java index 9cb0649583..3daf0cc85d 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java @@ -18,9 +18,10 @@ package au.csiro.pathling.search; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static au.csiro.pathling.utilities.Strings.randomAlias; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; +import static org.apache.spark.sql.functions.col; +import static org.apache.spark.sql.functions.row_number; import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.config.QueryConfiguration; @@ -49,6 +50,8 @@ import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder; +import org.apache.spark.sql.expressions.Window; +import org.apache.spark.sql.expressions.WindowSpec; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -163,17 +166,20 @@ private Dataset initializeDataset() { final Column[] resourceColumns = resourcePath.getElementsToColumns().keySet().stream() .map(colName -> resourcePath.getElementsToColumns().get(colName).alias(colName)) .toArray(Column[]::new); + // Resources are ordered by ID to ensure consistent paging. final Dataset result = dataset.select(resourceColumns); - result.explain(); + final WindowSpec window = Window.orderBy(col("id")); + final Dataset withRowNumbers = result.withColumn("row_number", + row_number().over(window)); if (getConfiguration().getCacheResults()) { // We cache the dataset because we know it will be accessed for both the total and the record // retrieval. log.debug("Caching search dataset"); - dataset.cache(); + withRowNumbers.cache(); } - return result; + return withRowNumbers; } @Override @@ -187,26 +193,25 @@ public IPrimitiveType getPublished() { public List getResources(final int theFromIndex, final int theToIndex) { log.info("Retrieving search results ({}-{})", theFromIndex + 1, theToIndex); - Dataset resources = result; + @Nullable Column filterCondition = null; if (theFromIndex != 0) { - // Spark does not have an "offset" concept, so we create a list of rows to exclude and - // subtract them from the dataset using a left anti-join. - final String excludeAlias = randomAlias(); - final Dataset exclude = resources.limit(theFromIndex) - .select(resources.col("id").alias(excludeAlias)); - resources = resources - .join(exclude, resources.col("id").equalTo(exclude.col(excludeAlias)), "left_anti"); + filterCondition = col("row_number").geq(theFromIndex + 1); } - // The dataset is trimmed to the requested size. if (theToIndex != 0) { - resources = resources.limit(theToIndex - theFromIndex); + filterCondition = filterCondition == null + ? col("row_number").lt(theToIndex + 1) + : filterCondition.and(col("row_number").lt(theToIndex + 1)); } + final Dataset filtered = Optional.ofNullable(filterCondition) + .map(result::filter).orElse(result); + final Dataset resources = filtered.drop("row_number"); // The requested resources are encoded into HAPI FHIR objects, and then collected. @Nullable final ExpressionEncoder encoder = fhirEncoders .of(subjectResource.toCode()); requireNonNull(encoder); reportQueryPlan(resources); + resources.explain(); return resources.as(encoder).collectAsList(); } diff --git a/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorTest.java b/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorTest.java index 8a6a1b1f16..940c994c69 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorTest.java @@ -109,6 +109,16 @@ void simpleSearchWithMemberOf() { assertResponse("SearchExecutorTest/simpleSearchWithMemberOf.Bundle.json", executor); } + @Test + void searchWithOffset() { + final SearchExecutorBuilder builder = searchBuilder() + .withSubjectResource(ResourceType.PATIENT); + TestHelpers.mockResource(builder.getDatabase(), sparkSession, ResourceType.PATIENT); + + final SearchExecutor executor = builder.build(); + assertResponse("SearchExecutorTest/searchWithOffset.Bundle.json", executor, 3, 5); + } + @Test void searchOfQuestionnaire() { final SearchExecutorBuilder builder = searchBuilder() @@ -165,15 +175,18 @@ SearchExecutorBuilder searchBuilder() { fhirEncoders, terminologyServiceFactory); } - @SuppressWarnings("SameParameterValue") - void assertResponse(@Nonnull final String expectedPath, - @Nonnull final IBundleProvider executor) { + void assertResponse(@Nonnull final String expectedPath, @Nonnull final IBundleProvider executor) { + assertResponse(expectedPath, executor, 0, 0); + } + + void assertResponse(@Nonnull final String expectedPath, @Nonnull final IBundleProvider executor, + final int fromIndex, final int toIndex) { final String expectedJson = getResourceAsString("responses/" + expectedPath); final Bundle expectedBundle = (Bundle) jsonParser.parseResource(expectedJson); assertEquals(expectedBundle.getTotal(), executor.size()); - final List actualResources = executor.getResources(0, expectedBundle.getTotal()); + final List actualResources = executor.getResources(fromIndex, toIndex); final Bundle actualBundle = new Bundle(); actualBundle.setEntry(actualResources.stream().map(resource -> { final BundleEntryComponent entry = new BundleEntryComponent(); @@ -184,6 +197,6 @@ void assertResponse(@Nonnull final String expectedPath, actualBundle.setType(BundleType.SEARCHSET); final String actualJson = jsonParser.encodeResourceToString(actualBundle); - JSONAssert.assertEquals(expectedJson, actualJson, JSONCompareMode.LENIENT); + JSONAssert.assertEquals(expectedJson, actualJson, JSONCompareMode.STRICT_ORDER); } } diff --git a/fhir-server/src/test/resources/responses/SearchExecutorTest/searchWithOffset.Bundle.json b/fhir-server/src/test/resources/responses/SearchExecutorTest/searchWithOffset.Bundle.json new file mode 100644 index 0000000000..aeb4290764 --- /dev/null +++ b/fhir-server/src/test/resources/responses/SearchExecutorTest/searchWithOffset.Bundle.json @@ -0,0 +1,297 @@ +{ + "resourceType": "Bundle", + "type": "searchset", + "total": 9, + "entry": [ + { + "resource": { + "resourceType": "Patient", + "id": "8ee183e2-b3c0-4151-be94-b945d6aa8c6d", + "text": { + "status": "generated", + "div": "
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -1116549638004693619 Population seed: 1567659637983
" + }, + "identifier": [ + { + "system": "https://github.com/synthetichealth/synthea", + "value": "0dc85075-4f59-4e4f-b75d-a2f601d0cf24" + }, + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "MR", + "display": "Medical Record Number" + } + ], + "text": "Medical Record Number" + }, + "system": "http://hospital.smarthealthit.org", + "value": "0dc85075-4f59-4e4f-b75d-a2f601d0cf24" + }, + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "SS", + "display": "Social Security Number" + } + ], + "text": "Social Security Number" + }, + "system": "http://hl7.org/fhir/sid/us-ssn", + "value": "999-21-1297" + }, + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "DL", + "display": "Driver's License" + } + ], + "text": "Driver's License" + }, + "system": "urn:oid:2.16.840.1.113883.4.3.25", + "value": "S99916275" + }, + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "PPN", + "display": "Passport Number" + } + ], + "text": "Passport Number" + }, + "system": "http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber", + "value": "X27195897X" + } + ], + "name": [ + { + "use": "official", + "family": "Krajcik437", + "given": [ + "Seymour882" + ], + "prefix": [ + "Mr." + ], + "suffix": [ + "MD" + ] + }, + { + "use": "nickname", + "family": "Burchard", + "given": [ + "Arianne" + ], + "prefix": [ + "Ms." + ], + "suffix": [ + "MD" + ] + } + ], + "telecom": [ + { + "system": "phone", + "value": "555-757-3815", + "use": "home" + } + ], + "gender": "male", + "birthDate": "1970-11-22", + "address": [ + { + "line": [ + "855 Senger Union Suite 12" + ], + "city": "Quincy", + "state": "Massachusetts", + "postalCode": "02169", + "country": "US" + } + ], + "maritalStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v3-MaritalStatus", + "code": "M", + "display": "M" + } + ], + "text": "M" + }, + "multipleBirthBoolean": false, + "communication": [ + { + "language": { + "coding": [ + { + "system": "urn:ietf:bcp:47", + "code": "en-US", + "display": "English" + } + ], + "text": "English" + } + } + ] + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "9360820c-8602-4335-8b50-c88d627a0c20", + "text": { + "status": "generated", + "div": "
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: 7586416063432214179 Population seed: 1567659637983
" + }, + "identifier": [ + { + "system": "https://github.com/synthetichealth/synthea", + "value": "eb4458b4-cfda-463e-ba15-eaad8d291d1d" + }, + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "MR", + "display": "Medical Record Number" + } + ], + "text": "Medical Record Number" + }, + "system": "http://hospital.smarthealthit.org", + "value": "eb4458b4-cfda-463e-ba15-eaad8d291d1d" + }, + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "SS", + "display": "Social Security Number" + } + ], + "text": "Social Security Number" + }, + "system": "http://hl7.org/fhir/sid/us-ssn", + "value": "999-23-9134" + }, + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "DL", + "display": "Driver's License" + } + ], + "text": "Driver's License" + }, + "system": "urn:oid:2.16.840.1.113883.4.3.25", + "value": "S99938323" + }, + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "PPN", + "display": "Passport Number" + } + ], + "text": "Passport Number" + }, + "system": "http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber", + "value": "X48845477X" + } + ], + "name": [ + { + "use": "official", + "family": "Oberbrunner298", + "given": [ + "Karina848" + ], + "prefix": [ + "Mrs." + ] + }, + { + "use": "maiden", + "family": "Wuckert783", + "given": [ + "Karina848" + ], + "prefix": [ + "Mrs." + ] + } + ], + "telecom": [ + { + "system": "phone", + "value": "555-441-4475", + "use": "home" + } + ], + "gender": "female", + "birthDate": "1959-09-27", + "deceasedDateTime": "2011-09-17T09:11:55+00:00", + "address": [ + { + "line": [ + "796 Wiza Rue Unit 94" + ], + "city": "Boston", + "state": "Massachusetts", + "postalCode": "02108", + "country": "US" + } + ], + "maritalStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v3-MaritalStatus", + "code": "M", + "display": "M" + }, + { + "system": "http://snomed.info/sct", + "code": "87915002", + "display": "Married" + } + ], + "text": "M" + }, + "multipleBirthBoolean": false, + "communication": [ + { + "language": { + "coding": [ + { + "system": "urn:ietf:bcp:47", + "code": "en-US", + "display": "English" + } + ], + "text": "English" + } + } + ] + } + } + ] +} diff --git a/fhir-server/src/test/resources/responses/SearchExecutorTest/simpleSearchWithMemberOf.Bundle.json b/fhir-server/src/test/resources/responses/SearchExecutorTest/simpleSearchWithMemberOf.Bundle.json index f5a5f986ff..14c7997161 100644 --- a/fhir-server/src/test/resources/responses/SearchExecutorTest/simpleSearchWithMemberOf.Bundle.json +++ b/fhir-server/src/test/resources/responses/SearchExecutorTest/simpleSearchWithMemberOf.Bundle.json @@ -77,8 +77,12 @@ { "use": "official", "family": "Gleichner915", - "given": ["Ophelia894"], - "prefix": ["Ms."] + "given": [ + "Ophelia894" + ], + "prefix": [ + "Ms." + ] } ], "telecom": [ @@ -93,7 +97,9 @@ "deceasedDateTime": "2018-02-17T09:11:55+00:00", "address": [ { - "line": ["256 Rohan Frontage road Suite 89"], + "line": [ + "256 Rohan Frontage road Suite 89" + ], "city": "Boston", "state": "Massachusetts", "postalCode": "02108", @@ -201,8 +207,12 @@ { "use": "official", "family": "Heller342", - "given": ["Cherryl901"], - "prefix": ["Ms."] + "given": [ + "Cherryl901" + ], + "prefix": [ + "Ms." + ] } ], "telecom": [ @@ -216,7 +226,9 @@ "birthDate": "1959-09-27", "address": [ { - "line": ["128 Langworth Hollow Unit 50"], + "line": [ + "128 Langworth Hollow Unit 50" + ], "city": "Boston", "state": "Massachusetts", "postalCode": "02108", @@ -253,15 +265,15 @@ { "resource": { "resourceType": "Patient", - "id": "e62e52ae-2d75-4070-a0ae-3cc78d35ed08", + "id": "a7eb2ce7-1075-426c-addd-957b861b0e55", "text": { "status": "generated", - "div": "
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -4398174870245759556 Population seed: 1567659637983
" + "div": "
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -8922649844579538715 Population seed: 1567659637983
" }, "identifier": [ { "system": "https://github.com/synthetichealth/synthea", - "value": "2cc8ffdd-6233-4dd4-ba71-36eccb8204e2" + "value": "3d67a22e-4e26-4d7a-a586-ea79114faa50" }, { "type": { @@ -275,7 +287,7 @@ "text": "Medical Record Number" }, "system": "http://hospital.smarthealthit.org", - "value": "2cc8ffdd-6233-4dd4-ba71-36eccb8204e2" + "value": "3d67a22e-4e26-4d7a-a586-ea79114faa50" }, { "type": { @@ -289,7 +301,7 @@ "text": "Social Security Number" }, "system": "http://hl7.org/fhir/sid/us-ssn", - "value": "999-43-1135" + "value": "999-36-4411" }, { "type": { @@ -303,7 +315,7 @@ "text": "Driver's License" }, "system": "urn:oid:2.16.840.1.113883.4.3.25", - "value": "S99956022" + "value": "S99932632" }, { "type": { @@ -317,33 +329,38 @@ "text": "Passport Number" }, "system": "http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber", - "value": "X85674863X" + "value": "X69119058X" } ], "name": [ { "use": "official", - "family": "Ebert178", - "given": ["Su690"], - "prefix": ["Ms."] + "family": "Botsford977", + "given": [ + "Shirley182" + ], + "prefix": [ + "Mr." + ] } ], "telecom": [ { "system": "phone", - "value": "555-491-7400", + "value": "555-845-4560", "use": "home" } ], - "gender": "female", - "birthDate": "1959-09-27", - "deceasedDateTime": "2009-09-27T09:11:55+00:00", + "gender": "male", + "birthDate": "1957-06-06", "address": [ { - "line": ["460 Douglas Camp"], - "city": "Boston", + "line": [ + "329 Prosacco Highlands Suite 8" + ], + "city": "Malden", "state": "Massachusetts", - "postalCode": "02108", + "postalCode": "02148", "country": "US" } ], @@ -377,15 +394,15 @@ { "resource": { "resourceType": "Patient", - "id": "a7eb2ce7-1075-426c-addd-957b861b0e55", + "id": "e62e52ae-2d75-4070-a0ae-3cc78d35ed08", "text": { "status": "generated", - "div": "
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -8922649844579538715 Population seed: 1567659637983
" + "div": "
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -4398174870245759556 Population seed: 1567659637983
" }, "identifier": [ { "system": "https://github.com/synthetichealth/synthea", - "value": "3d67a22e-4e26-4d7a-a586-ea79114faa50" + "value": "2cc8ffdd-6233-4dd4-ba71-36eccb8204e2" }, { "type": { @@ -399,7 +416,7 @@ "text": "Medical Record Number" }, "system": "http://hospital.smarthealthit.org", - "value": "3d67a22e-4e26-4d7a-a586-ea79114faa50" + "value": "2cc8ffdd-6233-4dd4-ba71-36eccb8204e2" }, { "type": { @@ -413,7 +430,7 @@ "text": "Social Security Number" }, "system": "http://hl7.org/fhir/sid/us-ssn", - "value": "999-36-4411" + "value": "999-43-1135" }, { "type": { @@ -427,7 +444,7 @@ "text": "Driver's License" }, "system": "urn:oid:2.16.840.1.113883.4.3.25", - "value": "S99932632" + "value": "S99956022" }, { "type": { @@ -441,32 +458,39 @@ "text": "Passport Number" }, "system": "http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber", - "value": "X69119058X" + "value": "X85674863X" } ], "name": [ { "use": "official", - "family": "Botsford977", - "given": ["Shirley182"], - "prefix": ["Mr."] + "family": "Ebert178", + "given": [ + "Su690" + ], + "prefix": [ + "Ms." + ] } ], "telecom": [ { "system": "phone", - "value": "555-845-4560", + "value": "555-491-7400", "use": "home" } ], - "gender": "male", - "birthDate": "1957-06-06", + "gender": "female", + "birthDate": "1959-09-27", + "deceasedDateTime": "2009-09-27T09:11:55+00:00", "address": [ { - "line": ["329 Prosacco Highlands Suite 8"], - "city": "Malden", + "line": [ + "460 Douglas Camp" + ], + "city": "Boston", "state": "Massachusetts", - "postalCode": "02148", + "postalCode": "02108", "country": "US" } ], From d9a2635462dc8c38add04af8dfa7a294a903d5ff Mon Sep 17 00:00:00 2001 From: John Grimes Date: Wed, 19 Jul 2023 06:11:55 +1000 Subject: [PATCH 29/95] Clear all nesting upon aggregation or filtering --- .../java/au/csiro/pathling/fhirpath/Nesting.java | 12 +++--------- .../fhirpath/function/AggregateFunction.java | 4 ++-- .../pathling/fhirpath/function/WhereFunction.java | 4 ++-- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java index 1bee4415fc..1b0e204ef4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java @@ -8,7 +8,6 @@ import java.util.Optional; import java.util.function.Function; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; @@ -87,15 +86,10 @@ public boolean isEmpty() { } /** - * Removes the last element from the nesting stack. This is used when applying aggregate - * functions, so that subsequent unnestings at this level can be dealt with correctly. + * Clears the nesting stack. */ - public void removeLast() { - @Nullable NestingKey last = null; - for (final NestingKey key : nesting.keySet()) { - last = key; - } - nesting.remove(last); + public void clear() { + nesting.clear(); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java index e6fe9ce78e..d401e2e0e4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java @@ -148,8 +148,8 @@ private T buildAggregateResult(@Nonnull final Dataset final Column valueColumn = valueColumnProducer.apply(aggregateColumn.over(window)); final DatasetWithColumn datasetWithColumn = createColumn(dataset, valueColumn); - // Erase the last level of nesting present within the context. - parserContext.getNesting().removeLast(); + // Clear the nesting present within the context. + parserContext.getNesting().clear(); // Perform a subsequent aggregation that results in a single aggregate result at the current // nesting level (taking into account the erasure). diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java index 0709d7dde0..6f276c08a4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java @@ -81,8 +81,8 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Dataset dataset = datasetWithRowNumber.getDataset() .filter(valueColumn.isNotNull().or(datasetWithRowNumber.getColumn().equalTo(1))); - // Erase the last level of nesting present within the context. - input.getContext().getNesting().removeLast(); + // Clear the nesting present within the context. + input.getContext().getNesting().clear(); final String expression = expressionFromInput(input, NAME); return inputPath.copy(expression, dataset, idColumn, valueColumn, From d5de5d8aa5cfc90102e499f2bfdff2f0ecf3efcb Mon Sep 17 00:00:00 2001 From: John Grimes Date: Wed, 19 Jul 2023 10:22:36 +1000 Subject: [PATCH 30/95] Alias where function value column before onward processing --- .../au/csiro/pathling/fhirpath/function/WhereFunction.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java index 6f276c08a4..b80b5b838d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java @@ -72,11 +72,13 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Column idColumn = argumentPath.getIdColumn(); final Column thisValue = checkPresent(argumentPath.getThisColumn()); final Column valueColumn = when(argumentValue.equalTo(true), thisValue).otherwise(lit(null)); + final DatasetWithColumn datasetWithColumn = createColumn(argumentPath.getDataset(), + valueColumn); // We use a windowing function to remove all null values except one. A single null value against // a resource signifies the absence of a value for a particular element. final WindowSpec window = Window.partitionBy(idColumn).orderBy(valueColumn.asc_nulls_last()); - final DatasetWithColumn datasetWithRowNumber = createColumn(argumentPath.getDataset(), + final DatasetWithColumn datasetWithRowNumber = createColumn(datasetWithColumn.getDataset(), row_number().over(window)); final Dataset dataset = datasetWithRowNumber.getDataset() .filter(valueColumn.isNotNull().or(datasetWithRowNumber.getColumn().equalTo(1))); @@ -85,7 +87,7 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { input.getContext().getNesting().clear(); final String expression = expressionFromInput(input, NAME); - return inputPath.copy(expression, dataset, idColumn, valueColumn, + return inputPath.copy(expression, dataset, idColumn, datasetWithColumn.getColumn(), inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getThisColumn()); } From 3473f2098802135e932a4c697e8aab745e42593d Mon Sep 17 00:00:00 2001 From: John Grimes Date: Wed, 19 Jul 2023 12:58:34 +1000 Subject: [PATCH 31/95] Handle disaggregation within binary operation with aggregated left operand --- .../java/au/csiro/pathling/fhirpath/Nesting.java | 9 +++++++++ .../pathling/fhirpath/parser/InvocationVisitor.java | 2 +- .../pathling/fhirpath/parser/ParserContext.java | 13 +++++++++++++ .../au/csiro/pathling/fhirpath/parser/Visitor.java | 6 +++++- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java index 1b0e204ef4..e8c83fa2fc 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java @@ -8,6 +8,7 @@ import java.util.Optional; import java.util.function.Function; import javax.annotation.Nonnull; +import lombok.Getter; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; @@ -25,6 +26,13 @@ public class Nesting { @Nonnull private final Map nesting; + /** + * Indicates whether the root level of the dataset has been rolled up due to aggregation. This is + * a trigger to reconstitute the root level when traversing back into the root. + */ + @Getter + private boolean rootErased = false; + public Nesting() { this.nesting = new LinkedHashMap<>(); } @@ -90,6 +98,7 @@ public boolean isEmpty() { */ public void clear() { nesting.clear(); + rootErased = true; } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index 9a7075047f..12e9eb39da 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -126,7 +126,7 @@ public FhirPath visitMemberInvocation(@Nullable final MemberInvocationContext ct } else { // If we're in the context of a function's arguments, there are two valid things this // could be: - // (1) a path traversal from the input context; + // (1) a path traversal from the "this" context; // (2) a reference to a resource type. final FhirPath thisContext = context.getThisContext().get(); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index c6f53cbef4..bde614db73 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -192,4 +192,17 @@ public ParserContext withGroupingColumns(@Nonnull final List groupingCol terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, nesting); } + /** + * Creates a copy of the current parser context with a clean nesting context. + * + * @return a new {@link ParserContext} + */ + public ParserContext disaggregate(@Nonnull final FhirPath aggregatedPath) { + final Dataset newDataset = inputContext.getDataset() + .crossJoin(aggregatedPath.getDataset().select(aggregatedPath.getValueColumn())); + final FhirPath newInputContext = inputContext.withDataset(newDataset); + return new ParserContext(newInputContext, fhirContext, sparkSession, dataSource, + terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, new Nesting()); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java index 73b940aaaf..755b4ba78d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java @@ -92,7 +92,11 @@ private FhirPath visitBinaryOperator(@Nullable final ParseTree leftContext, // Parse the left and right expressions. final FhirPath left = new Visitor(context).visit(leftContext); - final FhirPath right = new Visitor(context.withContextDataset(left.getDataset())) + // If the root nesting level has been erased by the parsing of the left expression (i.e. there has been aggregation or use of where) + final ParserContext rightParserContext = context.getNesting().isRootErased() + ? context.disaggregate(left) + : context.withContextDataset(left.getDataset()); + final FhirPath right = new Visitor(rightParserContext) .visit(rightContext); // Retrieve an Operator instance based upon the operator string. From d75f26db59d8fa29ba4c9349b333e92d43b29b6b Mon Sep 17 00:00:00 2001 From: John Grimes Date: Wed, 19 Jul 2023 17:16:34 +1000 Subject: [PATCH 32/95] Get AggregateQueryTest#queryWithNestedAggregation working --- ...queryWithNestedAggregation.Parameters.json | 2 +- .../fhirpath/function/AggregateFunction.java | 38 +++++++++---------- .../fhirpath/function/CountFunction.java | 30 +++++++++------ .../fhirpath/parser/InvocationVisitor.java | 6 +-- .../fhirpath/parser/ParserContext.java | 25 ++++++++++-- .../pathling/fhirpath/parser/Visitor.java | 20 +++++++--- 6 files changed, 78 insertions(+), 43 deletions(-) diff --git a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregation.Parameters.json b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregation.Parameters.json index 58647309cb..35f2fc78c7 100644 --- a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregation.Parameters.json +++ b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregation.Parameters.json @@ -27,7 +27,7 @@ }, { "name": "result", - "valueUnsignedInt": 10 + "valueUnsignedInt": 12 }, { "name": "drillDown", diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java index d401e2e0e4..ba046d700b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java @@ -17,14 +17,14 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.QueryHelpers.createColumn; import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static au.csiro.pathling.utilities.Preconditions.checkPresent; +import static au.csiro.pathling.utilities.Strings.randomAlias; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; +import static org.apache.spark.sql.functions.col; import static org.apache.spark.sql.functions.first; -import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.element.ElementPath; @@ -40,8 +40,6 @@ import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; -import org.apache.spark.sql.expressions.Window; -import org.apache.spark.sql.expressions.WindowSpec; import org.apache.spark.sql.functions; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; @@ -140,45 +138,45 @@ private T buildAggregateResult(@Nonnull final Dataset // Get the grouping columns from the parser context. final List groupingColumns = parserContext.getGroupingColumns(); - final Column[] partitionBy = groupingColumns.toArray(new Column[0]); - - // Use a windowing function to aggregate within the column, while preserving the rest of the - // dataset. - final WindowSpec window = Window.partitionBy(partitionBy); - final Column valueColumn = valueColumnProducer.apply(aggregateColumn.over(window)); - final DatasetWithColumn datasetWithColumn = createColumn(dataset, valueColumn); + final Column[] groupBy = groupingColumns.toArray(new Column[0]); // Clear the nesting present within the context. parserContext.getNesting().clear(); - // Perform a subsequent aggregation that results in a single aggregate result at the current - // nesting level (taking into account the erasure). - // Collect the grouping and non-grouping columns into separate collections so that we can feed - // them to the "agg" method. - final Set nonGroupingColumns = Stream.of(datasetWithColumn.getDataset().columns()) + // Perform the aggregation, while also preserving all columns that are not being aggregated + // using the "first()" function. + // First, collect the grouping and non-grouping columns into separate collections so that we can + // feed them to the "agg" method. + final Set nonGroupingColumns = Stream.of(dataset.columns()) .map(functions::col) .collect(toSet()); groupingColumns.forEach(nonGroupingColumns::remove); + // Wrap the non-grouping columns in a "first" aggregation. final List selection = nonGroupingColumns.stream() .map(column -> first(column, true).alias(column.toString())) .collect(toList()); + final String valueColumnName = randomAlias(); + selection.add(valueColumnProducer.apply(aggregateColumn).alias(valueColumnName)); + // Separate the first column from the rest, so that we can pass all the columns to the "agg" // method. final Column firstSelection = checkPresent(selection.stream().limit(1).findFirst()); final Column[] remainingSelection = selection.stream().skip(1).toArray(Column[]::new); + // Perform the final aggregation, the first row of each non-grouping column grouped by the // grouping columns. - final Dataset result = datasetWithColumn.getDataset() - .groupBy(groupingColumns.toArray(new Column[0])) + final Dataset result = dataset + .groupBy(groupBy) .agg(firstSelection, remainingSelection); + final Column valueColumn = col(valueColumnName); // Get any "this" columns that may be present in the inputs. final Optional thisColumn = NonLiteralPath .findThisColumn((Object[]) inputs.toArray(new FhirPath[0])); - return resultPathFactory.create(expression, result, idColumn, - datasetWithColumn.getColumn(), Optional.empty(), true, thisColumn); + return resultPathFactory.create(expression, result, idColumn, valueColumn, Optional.empty(), + true, thisColumn); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java index 9f0f0d0c1c..59fce2bdee 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java @@ -19,16 +19,18 @@ import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static org.apache.spark.sql.functions.collect_set; +import static au.csiro.pathling.utilities.Preconditions.checkPresent; import static org.apache.spark.sql.functions.count; +import static org.apache.spark.sql.functions.countDistinct; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.Nesting; import au.csiro.pathling.fhirpath.NonLiteralPath; +import java.util.ArrayList; +import java.util.List; import java.util.function.UnaryOperator; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; -import org.apache.spark.sql.functions; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** @@ -61,16 +63,22 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { aggregateColumn = count(subjectColumn); valueColumnProducer = UnaryOperator.identity(); } else { - // Use the ordering column if it exists, otherwise use the value column (which should only - // ever be a resource ID). - final Column subjectColumn = inputPath.getOrderingColumn() - .orElse(inputPath.getValueColumn()); + // Use the ordering columns if they are present, otherwise use the value column (which should + // only ever be a resource ID). + final List orderingColumns = nesting.getOrderingColumns(); + if (orderingColumns.isEmpty()) { + aggregateColumn = countDistinct(inputPath.getValueColumn()); + } else { + final List countColumns = new ArrayList<>(); + countColumns.add(inputPath.getIdColumn()); + countColumns.addAll(orderingColumns); + final Column first = checkPresent(countColumns.stream().limit(1).findFirst()); + final Column[] remaining = countColumns.stream().skip(1).toArray(Column[]::new); + aggregateColumn = countDistinct(first, remaining); + } // When we are counting values within an unnested dataset, we use a distinct count to account - // for the fact that there may be duplicates. This is implemented here using the combination - // of "collect_set" and "size", to work around the fact that Spark does not support - // "countDistinct" with windowing functions. - aggregateColumn = collect_set(subjectColumn); - valueColumnProducer = functions::size; + // for the fact that there may be duplicates. + valueColumnProducer = UnaryOperator.identity(); } return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index 12e9eb39da..70e4a731a8 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -190,9 +190,9 @@ public FhirPath visitFunctionInvocation(@Nullable final FunctionInvocationContex // Create and alias the $this column. final NonLiteralPath thisPath = nonLiteral.toThisPath(); - // If the this context has an element ID, we need to add this to the grouping columns so that - // aggregations that occur within the arguments are in the context of an element. Otherwise, - // we add the resource ID column to the groupings. + // If the "this" context has an element ID, we need to add this to the grouping columns so + // that aggregations that occur within the arguments are in the context of an element. + // Otherwise, we add the resource ID column to the groupings. final List argumentGroupings = new ArrayList<>(context.getGroupingColumns()); thisPath.getOrderingColumn().ifPresentOrElse(argumentGroupings::add, () -> argumentGroupings.add(thisPath.getIdColumn())); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index bde614db73..fb797d30ed 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -17,6 +17,9 @@ package au.csiro.pathling.fhirpath.parser; +import static au.csiro.pathling.QueryHelpers.join; + +import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirPathAndContext; import au.csiro.pathling.fhirpath.Nesting; @@ -24,6 +27,7 @@ import au.csiro.pathling.terminology.TerminologyService; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -193,13 +197,28 @@ public ParserContext withGroupingColumns(@Nonnull final List groupingCol } /** - * Creates a copy of the current parser context with a clean nesting context. + * Used for the scenario where a dataset is aggregated at the root, and that aggregated result + * needs to be used in onward parsing. Takes the aggregated path and joins it to the input context + * as an additional column. * * @return a new {@link ParserContext} */ public ParserContext disaggregate(@Nonnull final FhirPath aggregatedPath) { - final Dataset newDataset = inputContext.getDataset() - .crossJoin(aggregatedPath.getDataset().select(aggregatedPath.getValueColumn())); + final Dataset newDataset; + if (groupingColumns.isEmpty()) { + // If there are no grouping columns, we can do a cross-join as the target should only have one + // row. + newDataset = inputContext.getDataset() + .crossJoin(aggregatedPath.getDataset().select(aggregatedPath.getValueColumn())); + } else { + // If there are grouping columns, we need to join on those columns. + final List aggregatedSelection = new ArrayList<>(groupingColumns); + aggregatedSelection.add(aggregatedPath.getValueColumn()); + final Dataset aggregatedDataset = aggregatedPath.getDataset().select(aggregatedSelection + .toArray(new Column[0])); + newDataset = join(inputContext.getDataset(), groupingColumns, aggregatedDataset, + groupingColumns, JoinType.LEFT_OUTER); + } final FhirPath newInputContext = inputContext.withDataset(newDataset); return new ParserContext(newInputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, new Nesting()); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java index 755b4ba78d..57179fd6af 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java @@ -90,14 +90,24 @@ private FhirPath visitBinaryOperator(@Nullable final ParseTree leftContext, @Nullable final ParseTree rightContext, @Nullable final String operatorName) { requireNonNull(operatorName); - // Parse the left and right expressions. + // Parse the left expression. final FhirPath left = new Visitor(context).visit(leftContext); - // If the root nesting level has been erased by the parsing of the left expression (i.e. there has been aggregation or use of where) - final ParserContext rightParserContext = context.getNesting().isRootErased() + + // If there are no grouping columns and the root nesting level has been erased by the parsing of + // the left expression (i.e. there has been aggregation or use of where), disaggregate the input + // context before parsing the right expression. + final boolean disaggregationRequired = context.getNesting().isRootErased() && + // This disaggregation only happens in the root context, not in the context of function + // arguments. + context.getThisContext().isEmpty() && + !(context.getGroupingColumns().size() == 1 + && context.getGroupingColumns().get(0).equals(context.getInputContext().getIdColumn())); + final ParserContext rightParserContext = disaggregationRequired ? context.disaggregate(left) : context.withContextDataset(left.getDataset()); - final FhirPath right = new Visitor(rightParserContext) - .visit(rightContext); + + // Parse the right expression, using the possibly modified context. + final FhirPath right = new Visitor(rightParserContext).visit(rightContext); // Retrieve an Operator instance based upon the operator string. final Operator operator = Operator.getInstance(operatorName); From f029eee9cc9136bfdf6545545646d889c0077cad Mon Sep 17 00:00:00 2001 From: John Grimes Date: Wed, 19 Jul 2023 20:00:54 +1000 Subject: [PATCH 33/95] Restore original count function implementation, simplify AggregateFunction signature --- .../fhirpath/function/AggregateFunction.java | 24 +++------ .../function/BooleansTestFunction.java | 23 +++----- .../fhirpath/function/CountFunction.java | 54 +++++++------------ .../fhirpath/function/FirstFunction.java | 3 +- .../fhirpath/function/SumFunction.java | 3 +- .../fhirpath/operator/MembershipOperator.java | 3 +- 6 files changed, 37 insertions(+), 73 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java index ba046d700b..eb7fc892e3 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java @@ -64,11 +64,10 @@ public abstract class AggregateFunction { @Nonnull protected NonLiteralPath buildAggregateResult(@Nonnull final Dataset dataset, @Nonnull final ParserContext parserContext, @Nonnull final NonLiteralPath input, - @Nonnull final Column aggregateColumn, - @Nonnull final UnaryOperator valueColumnProducer, @Nonnull final String expression) { + @Nonnull final Column aggregateColumn, @Nonnull final String expression) { return buildAggregateResult(dataset, parserContext, Collections.singletonList(input), - aggregateColumn, valueColumnProducer, expression, input::copy); + aggregateColumn, expression, input::copy); } /** @@ -78,8 +77,6 @@ protected NonLiteralPath buildAggregateResult(@Nonnull final Dataset datase * @param parserContext the current {@link ParserContext} * @param input the {@link FhirPath} objects being aggregated * @param aggregateColumn a {@link Column} describing the resulting value - * @param valueColumnProducer a {@link UnaryOperator} that produces a {@link Column} describing - * the final value * @param expression the FHIRPath expression for the result * @param fhirType the {@link FHIRDefinedType} of the result * @return a new {@link ElementPath} representing the result @@ -88,12 +85,11 @@ protected NonLiteralPath buildAggregateResult(@Nonnull final Dataset datase @Nonnull protected ElementPath buildAggregateResult(@Nonnull final Dataset dataset, @Nonnull final ParserContext parserContext, @Nonnull final FhirPath input, - @Nonnull final Column aggregateColumn, - @Nonnull final UnaryOperator valueColumnProducer, @Nonnull final String expression, + @Nonnull final Column aggregateColumn, @Nonnull final String expression, @Nonnull final FHIRDefinedType fhirType) { return buildAggregateResult(dataset, parserContext, Collections.singletonList(input), - aggregateColumn, valueColumnProducer, expression, fhirType); + aggregateColumn, expression, fhirType); } /** @@ -104,7 +100,6 @@ protected ElementPath buildAggregateResult(@Nonnull final Dataset dataset, * @param parserContext the current {@link ParserContext} * @param inputs the {@link FhirPath} objects being aggregated * @param aggregateColumn a {@link Column} describing the aggregation operation - * @param valueColumnProducer a {@link UnaryOperator} that produces a {@link Column} describing * the final value * @param expression the FHIRPath expression for the result * @param fhirType the {@link FHIRDefinedType} of the result @@ -113,12 +108,10 @@ protected ElementPath buildAggregateResult(@Nonnull final Dataset dataset, @Nonnull protected ElementPath buildAggregateResult(@Nonnull final Dataset dataset, @Nonnull final ParserContext parserContext, @Nonnull final Collection inputs, - @Nonnull final Column aggregateColumn, - @Nonnull final UnaryOperator valueColumnProducer, @Nonnull final String expression, + @Nonnull final Column aggregateColumn, @Nonnull final String expression, @Nonnull final FHIRDefinedType fhirType) { - return buildAggregateResult(dataset, parserContext, inputs, aggregateColumn, - valueColumnProducer, expression, + return buildAggregateResult(dataset, parserContext, inputs, aggregateColumn, expression, // Create the result as an ElementPath of the given FHIR type. (exp, ds, id, value, ordering, singular, thisColumn) -> ElementPath.build(exp, ds, id, value, ordering, true, Optional.empty(), thisColumn, fhirType)); @@ -127,8 +120,7 @@ protected ElementPath buildAggregateResult(@Nonnull final Dataset dataset, @Nonnull private T buildAggregateResult(@Nonnull final Dataset dataset, @Nonnull final ParserContext parserContext, @Nonnull final Collection inputs, - @Nonnull final Column aggregateColumn, - @Nonnull final UnaryOperator valueColumnProducer, @Nonnull final String expression, + @Nonnull final Column aggregateColumn, @Nonnull final String expression, @Nonnull final ResultPathFactory resultPathFactory) { checkArgument(!inputs.isEmpty(), "Collection of inputs cannot be empty"); @@ -157,7 +149,7 @@ private T buildAggregateResult(@Nonnull final Dataset .map(column -> first(column, true).alias(column.toString())) .collect(toList()); final String valueColumnName = randomAlias(); - selection.add(valueColumnProducer.apply(aggregateColumn).alias(valueColumnName)); + selection.add(aggregateColumn.alias(valueColumnName)); // Separate the first column from the rest, so that we can pass all the columns to the "agg" // method. diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java index 6c128d2b6d..8b2c16b63d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java @@ -59,9 +59,9 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Column inputColumn = inputPath.getValueColumn(); final String expression = expressionFromInput(input, type.getFunctionName()); - final Column aggregateColumn = type.getAggregateColumnProducer().apply(inputColumn); - return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, - aggregateColumn, type.getValueColumnProducer(), expression); + final Column valueColumn = type.getEquality().apply(inputColumn); + return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, valueColumn, + expression); } /** @@ -73,32 +73,25 @@ public enum BooleansTestType { /** * "Any true" test */ - ANY_TRUE("anyTrue", input -> max(coalesce(input, lit(false))), - column -> column.equalTo(lit(true))), + ANY_TRUE("anyTrue", input -> max(coalesce(input, lit(false))).equalTo(lit(true))), /** * "Any false" test */ - ANY_FALSE("anyFalse", input -> min(coalesce(input, lit(true))), - column -> column.equalTo(lit(false))), + ANY_FALSE("anyFalse", input -> min(coalesce(input, lit(true))).equalTo(lit(false))), /** * "All true" test */ - ALL_TRUE("allTrue", input -> min(coalesce(input, lit(true))), - column -> column.equalTo(lit(true))), + ALL_TRUE("allTrue", input -> min(coalesce(input, lit(true))).equalTo(lit(true))), /** * "All false" test */ - ALL_FALSE("allFalse", input -> max(coalesce(input, lit(false))), - column -> column.equalTo(lit(false))); + ALL_FALSE("allFalse", input -> max(coalesce(input, lit(false))).equalTo(lit(false))); @Nonnull private final String functionName; @Nonnull - private final UnaryOperator aggregateColumnProducer; - - @Nonnull - private final UnaryOperator valueColumnProducer; + private final UnaryOperator equality; @Override public String toString() { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java index 59fce2bdee..d1b236d66e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java @@ -19,18 +19,14 @@ import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.utilities.Preconditions.checkPresent; -import static org.apache.spark.sql.functions.count; -import static org.apache.spark.sql.functions.countDistinct; +import static org.apache.spark.sql.functions.when; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Nesting; import au.csiro.pathling.fhirpath.NonLiteralPath; -import java.util.ArrayList; -import java.util.List; -import java.util.function.UnaryOperator; +import java.util.function.Function; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; +import org.apache.spark.sql.functions; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** @@ -52,37 +48,23 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { checkNoArguments("count", input); final NonLiteralPath inputPath = input.getInput(); final String expression = expressionFromInput(input, NAME); - final Nesting nesting = input.getContext().getNesting(); + final Column subjectColumn = inputPath.getValueColumn(); - final Column aggregateColumn; - final UnaryOperator valueColumnProducer; - if (nesting.isEmpty()) { - final Column subjectColumn = inputPath.getValueColumn(); - // When we are counting anything else, we use a non-distinct count, to account for the fact - // that it is valid to have multiple of the same value. - aggregateColumn = count(subjectColumn); - valueColumnProducer = UnaryOperator.identity(); - } else { - // Use the ordering columns if they are present, otherwise use the value column (which should - // only ever be a resource ID). - final List orderingColumns = nesting.getOrderingColumns(); - if (orderingColumns.isEmpty()) { - aggregateColumn = countDistinct(inputPath.getValueColumn()); - } else { - final List countColumns = new ArrayList<>(); - countColumns.add(inputPath.getIdColumn()); - countColumns.addAll(orderingColumns); - final Column first = checkPresent(countColumns.stream().limit(1).findFirst()); - final Column[] remaining = countColumns.stream().skip(1).toArray(Column[]::new); - aggregateColumn = countDistinct(first, remaining); - } - // When we are counting values within an unnested dataset, we use a distinct count to account - // for the fact that there may be duplicates. - valueColumnProducer = UnaryOperator.identity(); - } + // When we are counting resources from the input context, we use the distinct count to account + // for the fact that there may be duplicate IDs in the dataset. + // When we are counting anything else, we use a non-distinct count, to account for the fact that + // it is valid to have multiple of the same value. + final Function countFunction = inputPath == input.getContext().getInputContext() + ? functions::countDistinct + : functions::count; - return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, - aggregateColumn, valueColumnProducer, expression, FHIRDefinedType.UNSIGNEDINT); + // According to the FHIRPath specification, the count function must return 0 when invoked on an + // empty collection. + final Column valueColumn = when(countFunction.apply(subjectColumn).isNull(), 0L) + .otherwise(countFunction.apply(subjectColumn)); + + return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, valueColumn, + expression, FHIRDefinedType.UNSIGNEDINT); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java index ad75a4d6fe..e99fdc56e0 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java @@ -25,7 +25,6 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.Nesting; import au.csiro.pathling.fhirpath.NonLiteralPath; -import java.util.function.UnaryOperator; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; @@ -61,6 +60,6 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Column aggregateColumn = first(inputPath.getValueColumn(), true); return buildAggregateResult(dataset, input.getContext(), inputPath, aggregateColumn, - UnaryOperator.identity(), expression); + expression); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java index 7d41da8eeb..fb1e82a63c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java @@ -25,7 +25,6 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.Numeric; -import java.util.function.UnaryOperator; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; @@ -57,7 +56,7 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Column aggregateColumn = sum(inputPath.getValueColumn()); return buildAggregateResult(dataset, input.getContext(), inputPath, aggregateColumn, - UnaryOperator.identity(), expression); + expression); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java index c9cc5ffcef..0c0009cc8b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java @@ -28,7 +28,6 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.function.AggregateFunction; import java.util.Arrays; -import java.util.function.UnaryOperator; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; @@ -87,7 +86,7 @@ public FhirPath invoke(@Nonnull final OperatorInput input) { final Column aggregateColumn = max(equalityWithNullChecks); return buildAggregateResult(right.getDataset(), input.getContext(), Arrays.asList(left, right), - aggregateColumn, UnaryOperator.identity(), expression, FHIRDefinedType.BOOLEAN); + aggregateColumn, expression, FHIRDefinedType.BOOLEAN); } /** From 74a627b1a85bfca241eff8e0a91e5a04f5eec8a2 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Wed, 19 Jul 2023 21:52:42 +1000 Subject: [PATCH 34/95] Update this context when threading dataset through to right operand of binary operation --- .../aggregate/AggregateQueryTest.java | 2 +- .../fhirpath/parser/ParserContext.java | 29 ++++++++++++++----- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryTest.java b/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryTest.java index 297cf594f2..d5781f5aa9 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryTest.java @@ -309,7 +309,7 @@ void queryWithWhereAsComparisonOperand() { final AggregateRequest request = new AggregateRequestBuilder(subjectResource) .withAggregation("count()") .withGrouping("reverseResolve(MedicationRequest.subject).where(" - + "$this.medicationCodeableConcept.coding" + "" + + "$this.medicationCodeableConcept.coding" + ".where(system = 'http://www.nlm.nih.gov/research/umls/rxnorm').code contains '313782' " + "and $this.authoredOn < @2019-06-21).count() > 0") .build(); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index fb797d30ed..5fa0877e58 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -171,8 +171,11 @@ public void setThisContext(@Nonnull final FhirPath thisContext) { * @return a new #{link ParserContext} */ public ParserContext withUnnestBehaviour(@Nonnull final UnnestBehaviour unnestBehaviour) { - return new ParserContext(inputContext, fhirContext, sparkSession, dataSource, - terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, nesting); + final ParserContext context = new ParserContext(inputContext, fhirContext, sparkSession, + dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, + nesting); + thisContext.ifPresent(context::setThisContext); + return context; } /** @@ -182,8 +185,12 @@ public ParserContext withUnnestBehaviour(@Nonnull final UnnestBehaviour unnestBe */ public ParserContext withContextDataset(@Nonnull final Dataset contextDataset) { final FhirPath newInputContext = inputContext.withDataset(contextDataset); - return new ParserContext(newInputContext, fhirContext, sparkSession, dataSource, - terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, nesting); + final ParserContext parserContext = new ParserContext(newInputContext, fhirContext, + sparkSession, dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, + variables, nesting); + thisContext.ifPresent( + thisContext -> parserContext.setThisContext(thisContext.withDataset(contextDataset))); + return parserContext; } /** @@ -192,8 +199,11 @@ public ParserContext withContextDataset(@Nonnull final Dataset contextDatas * @return a new {@link ParserContext} */ public ParserContext withGroupingColumns(@Nonnull final List groupingColumns) { - return new ParserContext(inputContext, fhirContext, sparkSession, dataSource, - terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, nesting); + final ParserContext context = new ParserContext(inputContext, fhirContext, sparkSession, + dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, + nesting); + thisContext.ifPresent(context::setThisContext); + return context; } /** @@ -220,8 +230,11 @@ public ParserContext disaggregate(@Nonnull final FhirPath aggregatedPath) { groupingColumns, JoinType.LEFT_OUTER); } final FhirPath newInputContext = inputContext.withDataset(newDataset); - return new ParserContext(newInputContext, fhirContext, sparkSession, dataSource, - terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, new Nesting()); + final ParserContext parserContext = new ParserContext(newInputContext, fhirContext, + sparkSession, dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, + variables, new Nesting()); + thisContext.ifPresent(parserContext::setThisContext); + return parserContext; } } From c1ed6b25a6f884708a48a474f8e6a7245fa3f3ba Mon Sep 17 00:00:00 2001 From: John Grimes Date: Wed, 19 Jul 2023 23:11:47 +1000 Subject: [PATCH 35/95] Disaggregate when threading dataset through multiple expressions --- .../queryMultipleCountAggregations.Parameters.json | 4 ++-- ...hNestedAggregationAndNoGroupings.Parameters.json | 2 +- .../main/java/au/csiro/pathling/QueryExecutor.java | 13 ++++++++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleCountAggregations.Parameters.json b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleCountAggregations.Parameters.json index 653091843f..4fe4672f53 100644 --- a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleCountAggregations.Parameters.json +++ b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleCountAggregations.Parameters.json @@ -31,11 +31,11 @@ }, { "name": "result", - "valueUnsignedInt": 5 + "valueUnsignedInt": 6 }, { "name": "result", - "valueUnsignedInt": 4 + "valueUnsignedInt": 5 }, { "name": "drillDown", diff --git a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregationAndNoGroupings.Parameters.json b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregationAndNoGroupings.Parameters.json index 81e6e09c0b..f3e0fc0df7 100644 --- a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregationAndNoGroupings.Parameters.json +++ b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregationAndNoGroupings.Parameters.json @@ -6,7 +6,7 @@ "part": [ { "name": "result", - "valueUnsignedInt": 20 + "valueUnsignedInt": 22 } ] } diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index bae945378f..5d752d6be9 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -105,9 +105,16 @@ protected List parseExpressions( for (final String expression : expressions) { if (parsed.size() > 0) { final FhirPath lastParsed = parsed.get(parsed.size() - 1); - // Create a new copy of the original parser context, except use the dataset from the last - // column parse and reset the node IDs. - currentContext = parserContext.withContextDataset(lastParsed.getDataset()); + // If there are no grouping columns and the root nesting level has been erased by the + // parsing of the previous expression (i.e. there has been aggregation or use of where), + // disaggregate the input context before parsing the right expression. + final boolean disaggregationRequired = currentContext.getNesting().isRootErased() && + !(currentContext.getGroupingColumns().size() == 1 + && currentContext.getGroupingColumns().get(0) + .equals(currentContext.getInputContext().getIdColumn())); + currentContext = disaggregationRequired + ? currentContext.disaggregate(lastParsed) + : currentContext.withContextDataset(lastParsed.getDataset()); } final Parser parser = new Parser(currentContext); // Add the parse result to the list of parsed expressions. From 787fce7ed6b1c8e6bab05ac28daa26e5fc4c9dd6 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Thu, 20 Jul 2023 11:12:40 +1000 Subject: [PATCH 36/95] Aggregate following aggregation expressions where no aggregation has occurred --- .../aggregate/AggregateQueryExecutor.java | 28 +++++++++++++++++-- .../fhirpath/parser/ParserContext.java | 17 +++++++++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java index c41d86f91b..8596c49cd6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java @@ -20,7 +20,10 @@ import static au.csiro.pathling.QueryHelpers.createColumns; import static au.csiro.pathling.query.ExpressionWithLabel.labelsAsStream; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; +import static au.csiro.pathling.utilities.Strings.randomAlias; import static java.util.stream.Collectors.toList; +import static org.apache.spark.sql.functions.col; +import static org.apache.spark.sql.functions.first; import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.QueryHelpers; @@ -143,8 +146,14 @@ public ResultWithExpressions buildQuery(@Nonnull final AggregateRequest query) { final List aggregationColumns = aggregations.stream() .map(FhirPath::getValueColumn) .collect(toList()); - final Dataset aggregationDataset = aggregations.get(aggregations.size() - 1) - .getDataset(); + final FhirPath lastAggregation = aggregations.get(aggregations.size() - 1); + Dataset aggregationDataset = lastAggregation.getDataset(); + // If the result of the aggregation expressions is not actually aggregated, we need to apply + // a final aggregation step that reduces it down to one row per group. + if (!aggregationContext.getNesting().isRootErased()) { + aggregationDataset = applyAggregation(aggregationContext, aggregationColumns, lastAggregation, + aggregationDataset); + } // The final column selection will be the grouping columns, followed by the aggregation // columns. @@ -178,6 +187,21 @@ private void validateGroupings(@Nonnull final Collection groupings) { } } + @Nonnull + private static Dataset applyAggregation(@Nonnull final ParserContext context, + @Nonnull final List aggregationColumns, @Nonnull final FhirPath lastAggregation, + @Nonnull final Dataset disaggregated) { + // Group the dataset by the grouping columns, and take the first row. + final Column[] groupBy = context.getGroupingColumns().toArray(new Column[0]); + final String aggregatedColumnName = randomAlias(); + final Dataset aggregated = disaggregated.groupBy(groupBy) + .agg(first(lastAggregation.getValueColumn()).as(aggregatedColumnName)); + // Replace the last column with the aggregated column. + aggregationColumns.remove(aggregationColumns.size() - 1); + aggregationColumns.add(col(aggregatedColumnName)); + return aggregated; + } + @Value public static class ResultWithExpressions { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index 5fa0877e58..b7465675e4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -18,11 +18,13 @@ package au.csiro.pathling.fhirpath.parser; import static au.csiro.pathling.QueryHelpers.join; +import static au.csiro.pathling.utilities.Preconditions.check; import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirPathAndContext; import au.csiro.pathling.fhirpath.Nesting; +import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyService; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -199,9 +201,18 @@ public ParserContext withContextDataset(@Nonnull final Dataset contextDatas * @return a new {@link ParserContext} */ public ParserContext withGroupingColumns(@Nonnull final List groupingColumns) { - final ParserContext context = new ParserContext(inputContext, fhirContext, sparkSession, - dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, - nesting); + // Make the input context non-singular - when we are grouping, the input context is a collection + // of resources. + check(inputContext instanceof NonLiteralPath); + final NonLiteralPath nonLiteralInputContext = (NonLiteralPath) inputContext; + final NonLiteralPath nonSingularInputContext = nonLiteralInputContext.copy( + nonLiteralInputContext.getExpression(), + nonLiteralInputContext.getDataset(), nonLiteralInputContext.getIdColumn(), + nonLiteralInputContext.getValueColumn(), nonLiteralInputContext.getOrderingColumn(), false, + nonLiteralInputContext.getThisColumn()); + final ParserContext context = new ParserContext(nonSingularInputContext, fhirContext, + sparkSession, dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, + variables, nesting); thisContext.ifPresent(context::setThisContext); return context; } From 16e9580606a49c1f38bd300822f947bd06ff238d Mon Sep 17 00:00:00 2001 From: John Grimes Date: Thu, 20 Jul 2023 11:24:12 +1000 Subject: [PATCH 37/95] Reset root erasure after processing of binary operator --- .../main/java/au/csiro/pathling/fhirpath/Nesting.java | 2 ++ .../au/csiro/pathling/fhirpath/parser/Visitor.java | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java index e8c83fa2fc..f58c1122fe 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java @@ -9,6 +9,7 @@ import java.util.function.Function; import javax.annotation.Nonnull; import lombok.Getter; +import lombok.Setter; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; @@ -31,6 +32,7 @@ public class Nesting { * a trigger to reconstitute the root level when traversing back into the root. */ @Getter + @Setter private boolean rootErased = false; public Nesting() { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java index 57179fd6af..98ac803665 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java @@ -102,9 +102,13 @@ private FhirPath visitBinaryOperator(@Nullable final ParseTree leftContext, context.getThisContext().isEmpty() && !(context.getGroupingColumns().size() == 1 && context.getGroupingColumns().get(0).equals(context.getInputContext().getIdColumn())); - final ParserContext rightParserContext = disaggregationRequired - ? context.disaggregate(left) - : context.withContextDataset(left.getDataset()); + final ParserContext rightParserContext; + if (disaggregationRequired) { + rightParserContext = context.disaggregate(left); + context.getNesting().setRootErased(false); + } else { + rightParserContext = context.withContextDataset(left.getDataset()); + } // Parse the right expression, using the possibly modified context. final FhirPath right = new Visitor(rightParserContext).visit(rightContext); From 20f5f924475c90b756c8e0f31f24c5348706b531 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Thu, 20 Jul 2023 13:28:41 +1000 Subject: [PATCH 38/95] Set dataset caching to false for library --- .../java/au/csiro/pathling/library/io/source/DeltaSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library-api/src/main/java/au/csiro/pathling/library/io/source/DeltaSource.java b/library-api/src/main/java/au/csiro/pathling/library/io/source/DeltaSource.java index 26e7d8782e..b212b6ea9c 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/io/source/DeltaSource.java +++ b/library-api/src/main/java/au/csiro/pathling/library/io/source/DeltaSource.java @@ -36,7 +36,7 @@ public DeltaSource(@Nonnull final PathlingContext context, @Nonnull final String @Nonnull private static Database buildDatabase(final @Nonnull PathlingContext context, final @Nonnull String path) { - return Database.forFileSystem(context.getSpark(), context.getFhirEncoders(), path, true); + return Database.forFileSystem(context.getSpark(), context.getFhirEncoders(), path, false); } } From d258027a693e66eb88d152d69680630c3e842339 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Thu, 20 Jul 2023 18:01:40 +1000 Subject: [PATCH 39/95] Revert to SLF4J 1.7.36 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a518f42dd3..168be810dd 100644 --- a/pom.xml +++ b/pom.xml @@ -92,7 +92,7 @@ 14.0.11.Final 2.13.4 2.39.1 - 2.0.7 + 1.7.36 1.2.11 3.1.2 From 2710607882624a46632cdea38c10b6f3b3a6c684 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Thu, 20 Jul 2023 21:35:10 +1000 Subject: [PATCH 40/95] Reimplement combine operator using array and explode --- .../security/ga4gh/ManifestConverterTest.java | 3 ++- .../java/au/csiro/pathling/QueryHelpers.java | 19 +++++++++++-------- .../fhirpath/operator/CombineOperator.java | 11 ++++------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/fhir-server/src/test/java/au/csiro/pathling/security/ga4gh/ManifestConverterTest.java b/fhir-server/src/test/java/au/csiro/pathling/security/ga4gh/ManifestConverterTest.java index 7d4a532b22..ea79d5890a 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/security/ga4gh/ManifestConverterTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/security/ga4gh/ManifestConverterTest.java @@ -18,6 +18,7 @@ package au.csiro.pathling.security.ga4gh; import static au.csiro.pathling.test.TestResources.assertJson; +import static org.apache.spark.sql.functions.col; import static org.junit.jupiter.api.Assertions.assertTrue; import au.csiro.pathling.config.ServerConfiguration; @@ -124,7 +125,7 @@ void convertsManifest() { final Dataset dataset = assertThatResultOf(resourceType, filter) .isElementPath(BooleanPath.class) .selectResult() - .apply(result -> result.filter(result.columns()[1])) + .apply(result -> result.filter(col(result.columns()[1]))) .getDataset(); if (dataset.count() > 0) { found = true; diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java b/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java index 6b4c9fa5da..8eb351ae62 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java @@ -19,6 +19,7 @@ import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static au.csiro.pathling.utilities.Strings.randomAlias; +import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static org.apache.spark.sql.functions.col; import static org.apache.spark.sql.functions.lit; @@ -104,7 +105,7 @@ public static DatasetWithColumnMap aliasAllColumns(@Nonnull final Dataset d final List columns = Stream.of(dataset.columns()) .map(dataset::col) - .collect(Collectors.toList()); + .collect(toList()); final DatasetWithColumnMap datasetWithColumnMap = aliasColumns(dataset, columns); final Dataset finalDataset = datasetWithColumnMap.getDataset(); @@ -130,7 +131,7 @@ private static DatasetWithColumnMap aliasColumns(@Nonnull final Dataset dat // Don't preserve anything that is not already aliased. .filter(Strings::looksLikeAlias) .map(dataset::col) - .collect(Collectors.toList()); + .collect(toList()); // Create an aliased column for each of the new columns, and add it to the selection and the // map. @@ -171,7 +172,7 @@ private static Dataset join(@Nonnull final Dataset left, final List leftColumnNames = Arrays.asList(aliasedLeft.columns()); final List rightColumnNames = rightColumns.stream() .map(Column::toString) - .collect(Collectors.toList()); + .collect(toList()); // Exclude the columns in the right dataset from the trimmed left dataset. final Dataset trimmedLeft = applySelection(aliasedLeft, Collections.emptyList(), @@ -314,7 +315,7 @@ public static Dataset join(@Nonnull final ParserContext parserContext, // Only non-literal paths will trigger a join. final List nonLiteralTargets = joinTargets.stream() .filter(t -> t instanceof NonLiteralPath) - .collect(Collectors.toList()); + .collect(toList()); if (left instanceof NonLiteralPath && nonLiteralTargets.isEmpty()) { // If the only non-literal path is on the left, we can just return the left without any need // to join. @@ -409,7 +410,7 @@ private static List checkColumnsAndFallback(@Nonnull final Dataset final Set fallbackGroupingColumnNames = new HashSet<>(groupingColumnNames); fallbackGroupingColumnNames.retainAll(columnList); fallbackGroupingColumnNames.add(fallback.toString()); - return fallbackGroupingColumnNames.stream().map(dataset::col).collect(Collectors.toList()); + return fallbackGroupingColumnNames.stream().map(dataset::col).collect(toList()); } } @@ -436,14 +437,16 @@ private static Dataset applySelection(@Nonnull final Dataset dataset, @Nonnull public static List getUnionableColumns(@Nonnull final FhirPath source, @Nonnull final FhirPath target) { - // The columns will be those common to both datasets. + // The columns will be those common to both datasets, plus the value column of the source. final Set commonColumnNames = new HashSet<>(List.of(source.getDataset().columns())); commonColumnNames.retainAll(List.of(target.getDataset().columns())); - return commonColumnNames.stream() + final List columns = commonColumnNames.stream() .map(functions::col) // We sort the columns so that they line up when we execute the union. .sorted(Comparator.comparing(Column::toString)) - .collect(Collectors.toList()); + .collect(toList()); + columns.add(source.getValueColumn()); + return columns; } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java index 7f3586a235..ed3d7aa1aa 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java @@ -18,6 +18,8 @@ package au.csiro.pathling.fhirpath.operator; import static au.csiro.pathling.QueryHelpers.createColumn; +import static org.apache.spark.sql.functions.array; +import static org.apache.spark.sql.functions.explode_outer; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.FhirPath; @@ -25,8 +27,6 @@ import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; /** * Merges the left and right operands into a single collection. @@ -45,11 +45,8 @@ public FhirPath invoke(@Nonnull final OperatorInput input) { final FhirPath left = input.getLeft(); final FhirPath right = input.getRight(); - final Dataset leftTrimmed = left.getUnionableDataset(right); - final Dataset rightTrimmed = right.getUnionableDataset(left); - - final Dataset dataset = leftTrimmed.union(rightTrimmed); - final DatasetWithColumn datasetWithColumn = createColumn(dataset, left.getValueColumn()); + final Column valueColumn = explode_outer(array(left.getValueColumn(), right.getValueColumn())); + final DatasetWithColumn datasetWithColumn = createColumn(right.getDataset(), valueColumn); final Optional thisColumn = left instanceof NonLiteralPath ? ((NonLiteralPath) left).getThisColumn() : Optional.empty(); From 02540429d7281c6083005310e261c5b12d7577f5 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Thu, 20 Jul 2023 21:51:38 +1000 Subject: [PATCH 41/95] Compact nulls in combine operator --- .../pathling/fhirpath/operator/CombineOperator.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java index ed3d7aa1aa..d02fac42fc 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java @@ -19,7 +19,9 @@ import static au.csiro.pathling.QueryHelpers.createColumn; import static org.apache.spark.sql.functions.array; +import static org.apache.spark.sql.functions.array_except; import static org.apache.spark.sql.functions.explode_outer; +import static org.apache.spark.sql.functions.lit; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.FhirPath; @@ -45,7 +47,13 @@ public FhirPath invoke(@Nonnull final OperatorInput input) { final FhirPath left = input.getLeft(); final FhirPath right = input.getRight(); - final Column valueColumn = explode_outer(array(left.getValueColumn(), right.getValueColumn())); + // Create an array of the two operands, excluding nulls, then explode it into rows. + final Column valueColumn = explode_outer( + array_except( + array(left.getValueColumn(), right.getValueColumn()), + array(lit(null)) + ) + ); final DatasetWithColumn datasetWithColumn = createColumn(right.getDataset(), valueColumn); final Optional thisColumn = left instanceof NonLiteralPath ? ((NonLiteralPath) left).getThisColumn() From 01f82c1dea4126e4c4c918e860cc4b5960c9127f Mon Sep 17 00:00:00 2001 From: John Grimes Date: Fri, 21 Jul 2023 10:47:42 +1000 Subject: [PATCH 42/95] Disable metrics in Infinispan --- .../caching/InMemoryCachingTerminologyService.java | 7 ++++++- .../caching/PersistentCachingTerminologyService.java | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/terminology/src/main/java/au/csiro/pathling/terminology/caching/InMemoryCachingTerminologyService.java b/terminology/src/main/java/au/csiro/pathling/terminology/caching/InMemoryCachingTerminologyService.java index 80abf4de30..a03329e2f3 100644 --- a/terminology/src/main/java/au/csiro/pathling/terminology/caching/InMemoryCachingTerminologyService.java +++ b/terminology/src/main/java/au/csiro/pathling/terminology/caching/InMemoryCachingTerminologyService.java @@ -24,6 +24,7 @@ import org.infinispan.Cache; import org.infinispan.configuration.cache.Configuration; import org.infinispan.configuration.cache.ConfigurationBuilder; +import org.infinispan.configuration.global.GlobalConfigurationBuilder; import org.infinispan.eviction.EvictionStrategy; import org.infinispan.manager.DefaultCacheManager; import org.infinispan.manager.EmbeddedCacheManager; @@ -43,7 +44,11 @@ public InMemoryCachingTerminologyService(@Nonnull final TerminologyClient termin @Override protected EmbeddedCacheManager buildCacheManager() { - return new DefaultCacheManager(); + final GlobalConfigurationBuilder globalConfigBuilder = new GlobalConfigurationBuilder(); + globalConfigBuilder.metrics() + .gauges(false) + .histograms(false); + return new DefaultCacheManager(globalConfigBuilder.build()); } @Override diff --git a/terminology/src/main/java/au/csiro/pathling/terminology/caching/PersistentCachingTerminologyService.java b/terminology/src/main/java/au/csiro/pathling/terminology/caching/PersistentCachingTerminologyService.java index 6958dd872b..1d98294885 100644 --- a/terminology/src/main/java/au/csiro/pathling/terminology/caching/PersistentCachingTerminologyService.java +++ b/terminology/src/main/java/au/csiro/pathling/terminology/caching/PersistentCachingTerminologyService.java @@ -61,6 +61,9 @@ protected EmbeddedCacheManager buildCacheManager() { .marshaller(new JavaSerializationMarshaller()) .allowList() .addRegexp(".*"); + globalConfigBuilder.metrics() + .gauges(false) + .histograms(false); final GlobalConfiguration globalConfig = globalConfigBuilder.build(); return new DefaultCacheManager(globalConfig); } From 3bd86cc62a7ddcd520e7caacf01a3e08a37af5c0 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Fri, 21 Jul 2023 13:11:28 +1000 Subject: [PATCH 43/95] Add additional tests to ExtractQueryTest relating to combine --- .gitignore | 1 + .../pathling/extract/ExtractQueryTest.java | 32 +++++++++++++++++++ .../ExtractQueryTest/combineWithLiterals.csv | 18 +++++++++++ .../combineWithUnequalCardinalities.csv | 24 ++++++++++++++ .../resources/test-data/fhir/Patient.ndjson | 2 +- .../au/csiro/pathling/utilities/Datasets.java | 19 ++++++----- 6 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithLiterals.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithUnequalCardinalities.csv diff --git a/.gitignore b/.gitignore index 3579b3d071..571873efff 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ _version.py metastore_db derby.log spark-warehouse +.*.crc diff --git a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java index 736b291ff4..d93b0247a8 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java @@ -473,6 +473,38 @@ void structuredResult() { .hasRows(spark, "responses/ExtractQueryTest/structuredResult.csv"); } + @Test + void combineWithLiterals() { + subjectResource = ResourceType.PATIENT; + mockResource(subjectResource); + + final ExtractRequest request = new ExtractRequestBuilder(subjectResource) + .withColumn("id") + .withColumn("'foo' combine 'bar'") + .build(); + + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); + assertThat(result) + .hasRows(spark, "responses/ExtractQueryTest/combineWithLiterals.csv"); + } + + @Test + void combineWithUnequalCardinalities() { + subjectResource = ResourceType.PATIENT; + mockResource(subjectResource); + + final ExtractRequest request = new ExtractRequestBuilder(subjectResource) + .withColumn("id") + .withColumn("name.given") + .withColumn("name.family") + .withColumn("name.given combine name.family") + .build(); + + final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); + assertThat(result) + .hasRows(spark, "responses/ExtractQueryTest/combineWithUnequalCardinalities.csv"); + } + void mockResource(final ResourceType... resourceTypes) { TestHelpers.mockResource(dataSource, spark, resourceTypes); } diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithLiterals.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithLiterals.csv new file mode 100644 index 0000000000..655d985142 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithLiterals.csv @@ -0,0 +1,18 @@ +121503c8-9564-4b48-9086-a22df717948e,foo +121503c8-9564-4b48-9086-a22df717948e,bar +2b36c1e2-bbe1-45ae-8124-4adad2677702,foo +2b36c1e2-bbe1-45ae-8124-4adad2677702,bar +7001ad9c-34d2-4eb5-8165-5fdc2147f469,foo +7001ad9c-34d2-4eb5-8165-5fdc2147f469,bar +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,foo +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,bar +9360820c-8602-4335-8b50-c88d627a0c20,foo +9360820c-8602-4335-8b50-c88d627a0c20,bar +a7eb2ce7-1075-426c-addd-957b861b0e55,foo +a7eb2ce7-1075-426c-addd-957b861b0e55,bar +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,foo +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,bar +beff242e-580b-47c0-9844-c1a68c36c5bf,foo +beff242e-580b-47c0-9844-c1a68c36c5bf,bar +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,foo +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,bar diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithUnequalCardinalities.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithUnequalCardinalities.csv new file mode 100644 index 0000000000..fbca6c0545 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithUnequalCardinalities.csv @@ -0,0 +1,24 @@ +121503c8-9564-4b48-9086-a22df717948e,Ophelia894,Gleichner915,Ophelia894 +121503c8-9564-4b48-9086-a22df717948e,Ophelia894,Gleichner915,Gleichner915 +2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316,Ulibarri312,Pedro316 +2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316,Ulibarri312,Ulibarri312 +7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901,Heller342,Cherryl901 +7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901,Heller342,Heller342 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882,Krajcik437,Seymour882 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882,Krajcik437,Krajcik437 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Moyna,Krajcik437,Moyna +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Moyna,Krajcik437,Krajcik437 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Arianne,Burchard,Arianne +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Arianne,Burchard,Burchard +9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Oberbrunner298,Karina848 +9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Oberbrunner298,Oberbrunner298 +9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Wuckert783,Karina848 +9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Wuckert783,Wuckert783 +a7eb2ce7-1075-426c-addd-957b861b0e55,Shirley182,Botsford977,Shirley182 +a7eb2ce7-1075-426c-addd-957b861b0e55,Shirley182,Botsford977,Botsford977 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Gilberto712,MacGyver246,Gilberto712 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Gilberto712,MacGyver246,MacGyver246 +beff242e-580b-47c0-9844-c1a68c36c5bf,Guy979,Towne435,Guy979 +beff242e-580b-47c0-9844-c1a68c36c5bf,Guy979,Towne435,Towne435 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Su690,Ebert178,Su690 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Su690,Ebert178,Ebert178 diff --git a/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson b/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson index ef49a62c8b..873be56212 100644 --- a/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson +++ b/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson @@ -1,4 +1,4 @@ -{"resourceType":"Patient","id":"8ee183e2-b3c0-4151-be94-b945d6aa8c6d","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -1116549638004693619 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Onie555 Tremblay80"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Lawrence","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":2.442019549037137},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":45.557980450962866}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-21-1297"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99916275"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X27195897X"}],"name":[{"use":"official","family":"Krajcik437","given":["Seymour882"],"prefix":["Mr."],"suffix":["MD"]},{"use":"nickname","family":"Burchard","given":["Arianne"],"prefix":["Ms."],"suffix":["MD"]}],"telecom":[{"system":"phone","value":"555-757-3815","use":"home"}],"gender":"male","birthDate":"1970-11-22","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.27362325267794},{"url":"longitude","valueDecimal":-70.91799558593002}]}],"line":["855 Senger Union Suite 12"],"city":"Quincy","state":"Massachusetts","postalCode":"02169","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} +{"resourceType":"Patient","id":"8ee183e2-b3c0-4151-be94-b945d6aa8c6d","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -1116549638004693619 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Onie555 Tremblay80"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Lawrence","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":2.442019549037137},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":45.557980450962866}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-21-1297"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99916275"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X27195897X"}],"name":[{"use":"official","family":"Krajcik437","given":["Seymour882","Moyna"],",prefix":["Mr."],"suffix":["MD"]},{"use":"nickname","family":"Burchard","given":["Arianne"],"prefix":["Ms."],"suffix":["MD"]}],"telecom":[{"system":"phone","value":"555-757-3815","use":"home"}],"gender":"male","birthDate":"1970-11-22","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.27362325267794},{"url":"longitude","valueDecimal":-70.91799558593002}]}],"line":["855 Senger Union Suite 12"],"city":"Quincy","state":"Massachusetts","postalCode":"02169","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} {"resourceType":"Patient","id":"beff242e-580b-47c0-9844-c1a68c36c5bf","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: 2489887534555043489 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Germaine912 Berge125"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Boston","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":0.11924342173460653},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":34.88075657826539}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"1f276fc3-7e91-4fc9-a287-be19228e8807"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"1f276fc3-7e91-4fc9-a287-be19228e8807"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-56-3056"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99940301"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X51286458X"}],"name":[{"use":"official","family":"Towne435","given":["Guy979"],"prefix":["Mr."]}],"telecom":[{"system":"phone","value":"555-273-5273","use":"home"}],"gender":"male","birthDate":"1983-09-06","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.416135340079045},{"url":"longitude","valueDecimal":-71.06798157703605}]}],"line":["598 Boyer Ramp"],"city":"Somerville","state":"Massachusetts","postalCode":"02138","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} {"resourceType":"Patient","id":"e62e52ae-2d75-4070-a0ae-3cc78d35ed08","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -4398174870245759556 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Idella49 Monahan736"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"F"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Chicopee","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":10.028345047642997},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":38.971654952357}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"2cc8ffdd-6233-4dd4-ba71-36eccb8204e2"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"2cc8ffdd-6233-4dd4-ba71-36eccb8204e2"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-43-1135"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99956022"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X85674863X"}],"name":[{"use":"official","family":"Ebert178","given":["Su690"],"prefix":["Ms."]}],"telecom":[{"system":"phone","value":"555-491-7400","use":"home"}],"gender":"female","birthDate":"1959-09-27","deceasedDateTime":"2009-09-27T09:11:55+00:00","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.39138295331103},{"url":"longitude","valueDecimal":-71.00439277761001}]}],"line":["460 Douglas Camp"],"city":"Boston","state":"Massachusetts","postalCode":"02108","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"S","display":"S"}],"text":"S"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} {"resourceType":"Patient","id":"2b36c1e2-bbe1-45ae-8124-4adad2677702","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: 346614667352111003 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2135-2","display":"Other"}},{"url":"text","valueString":"Other"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"María del Carmen27 Lozano749"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Ponce","state":"Puerto Rico","country":"PR"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":0.019152156118766007},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":19.980847843881232}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"2f9fd91d-9096-40e8-8307-825f68780599"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"2f9fd91d-9096-40e8-8307-825f68780599"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-42-8004"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99979084"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X28589103X"}],"name":[{"use":"official","family":"Ulibarri312","given":["Pedro316"],"prefix":["Mr."]}],"telecom":[{"system":"phone","value":"555-590-2206","use":"home"}],"gender":"male","birthDate":"1998-12-26","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.53265430498948},{"url":"longitude","valueDecimal":-73.25721481498516}]}],"line":["879 Hettinger Gardens"],"city":"Pittsfield","state":"Massachusetts","postalCode":"01201","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"S","display":"Never Married"}],"text":"Never Married"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"es","display":"Spanish"}],"text":"Spanish"}}]} diff --git a/utilities/src/main/java/au/csiro/pathling/utilities/Datasets.java b/utilities/src/main/java/au/csiro/pathling/utilities/Datasets.java index 443bf83458..ee2b8e084e 100644 --- a/utilities/src/main/java/au/csiro/pathling/utilities/Datasets.java +++ b/utilities/src/main/java/au/csiro/pathling/utilities/Datasets.java @@ -17,6 +17,14 @@ package au.csiro.pathling.utilities; +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import lombok.extern.slf4j.Slf4j; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; @@ -25,15 +33,6 @@ import org.apache.spark.sql.SaveMode; import org.apache.spark.sql.SparkSession; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; - -import static java.util.Objects.requireNonNull; - @Slf4j public abstract class Datasets { @@ -48,7 +47,7 @@ public abstract class Datasets { public static String writeCsv(@Nonnull final Dataset result, @Nonnull final String fileUrl, @Nonnull final SaveMode saveMode) { - Preconditions.check(fileUrl.endsWith(".csv"), "fileUrl must have .cvs extension"); + Preconditions.check(fileUrl.endsWith(".csv"), "fileUrl must have .csv extension"); final SparkSession spark = result.sparkSession(); From 185eb28e879e491d6902469e2931c68cd78b1303 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Fri, 21 Jul 2023 13:16:51 +1000 Subject: [PATCH 44/95] Fix problem with key of name prefix in test data --- fhir-server/src/test/resources/test-data/fhir/Patient.ndjson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson b/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson index 873be56212..1a1d13782a 100644 --- a/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson +++ b/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson @@ -1,4 +1,4 @@ -{"resourceType":"Patient","id":"8ee183e2-b3c0-4151-be94-b945d6aa8c6d","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -1116549638004693619 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Onie555 Tremblay80"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Lawrence","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":2.442019549037137},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":45.557980450962866}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-21-1297"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99916275"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X27195897X"}],"name":[{"use":"official","family":"Krajcik437","given":["Seymour882","Moyna"],",prefix":["Mr."],"suffix":["MD"]},{"use":"nickname","family":"Burchard","given":["Arianne"],"prefix":["Ms."],"suffix":["MD"]}],"telecom":[{"system":"phone","value":"555-757-3815","use":"home"}],"gender":"male","birthDate":"1970-11-22","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.27362325267794},{"url":"longitude","valueDecimal":-70.91799558593002}]}],"line":["855 Senger Union Suite 12"],"city":"Quincy","state":"Massachusetts","postalCode":"02169","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} +{"resourceType":"Patient","id":"8ee183e2-b3c0-4151-be94-b945d6aa8c6d","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -1116549638004693619 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Onie555 Tremblay80"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Lawrence","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":2.442019549037137},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":45.557980450962866}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-21-1297"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99916275"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X27195897X"}],"name":[{"use":"official","family":"Krajcik437","given":["Seymour882","Moyna"],"prefix":["Mr."],"suffix":["MD"]},{"use":"nickname","family":"Burchard","given":["Arianne"],"prefix":["Ms."],"suffix":["MD"]}],"telecom":[{"system":"phone","value":"555-757-3815","use":"home"}],"gender":"male","birthDate":"1970-11-22","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.27362325267794},{"url":"longitude","valueDecimal":-70.91799558593002}]}],"line":["855 Senger Union Suite 12"],"city":"Quincy","state":"Massachusetts","postalCode":"02169","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} {"resourceType":"Patient","id":"beff242e-580b-47c0-9844-c1a68c36c5bf","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: 2489887534555043489 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Germaine912 Berge125"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Boston","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":0.11924342173460653},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":34.88075657826539}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"1f276fc3-7e91-4fc9-a287-be19228e8807"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"1f276fc3-7e91-4fc9-a287-be19228e8807"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-56-3056"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99940301"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X51286458X"}],"name":[{"use":"official","family":"Towne435","given":["Guy979"],"prefix":["Mr."]}],"telecom":[{"system":"phone","value":"555-273-5273","use":"home"}],"gender":"male","birthDate":"1983-09-06","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.416135340079045},{"url":"longitude","valueDecimal":-71.06798157703605}]}],"line":["598 Boyer Ramp"],"city":"Somerville","state":"Massachusetts","postalCode":"02138","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} {"resourceType":"Patient","id":"e62e52ae-2d75-4070-a0ae-3cc78d35ed08","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -4398174870245759556 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Idella49 Monahan736"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"F"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Chicopee","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":10.028345047642997},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":38.971654952357}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"2cc8ffdd-6233-4dd4-ba71-36eccb8204e2"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"2cc8ffdd-6233-4dd4-ba71-36eccb8204e2"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-43-1135"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99956022"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X85674863X"}],"name":[{"use":"official","family":"Ebert178","given":["Su690"],"prefix":["Ms."]}],"telecom":[{"system":"phone","value":"555-491-7400","use":"home"}],"gender":"female","birthDate":"1959-09-27","deceasedDateTime":"2009-09-27T09:11:55+00:00","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.39138295331103},{"url":"longitude","valueDecimal":-71.00439277761001}]}],"line":["460 Douglas Camp"],"city":"Boston","state":"Massachusetts","postalCode":"02108","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"S","display":"S"}],"text":"S"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} {"resourceType":"Patient","id":"2b36c1e2-bbe1-45ae-8124-4adad2677702","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: 346614667352111003 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2135-2","display":"Other"}},{"url":"text","valueString":"Other"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"María del Carmen27 Lozano749"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Ponce","state":"Puerto Rico","country":"PR"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":0.019152156118766007},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":19.980847843881232}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"2f9fd91d-9096-40e8-8307-825f68780599"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"2f9fd91d-9096-40e8-8307-825f68780599"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-42-8004"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99979084"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X28589103X"}],"name":[{"use":"official","family":"Ulibarri312","given":["Pedro316"],"prefix":["Mr."]}],"telecom":[{"system":"phone","value":"555-590-2206","use":"home"}],"gender":"male","birthDate":"1998-12-26","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.53265430498948},{"url":"longitude","valueDecimal":-73.25721481498516}]}],"line":["879 Hettinger Gardens"],"city":"Pittsfield","state":"Massachusetts","postalCode":"01201","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"S","display":"Never Married"}],"text":"Never Married"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"es","display":"Spanish"}],"text":"Spanish"}}]} From 2bb4f43e9d40c678ef3d53223ab26a00d2b4194d Mon Sep 17 00:00:00 2001 From: John Grimes Date: Fri, 21 Jul 2023 14:31:08 +1000 Subject: [PATCH 45/95] Fix expectations and thread dataset through multiple function arguments --- .../pathling/fhirpath/parser/ParserTest.java | 6 +- ...yMultipleCountAggregations.Parameters.json | 2 +- ...ueryMultipleGroupingCounts.Parameters.json | 41 +++++++--- ...queryWithNestedAggregation.Parameters.json | 2 +- ...dAggregationAndNoGroupings.Parameters.json | 2 +- .../ExtractQueryTest/linkedUnnesting.csv | 1 + .../multipleIndependentUnnestings.csv | 1 + .../multiplePolymorphicResolves.csv | 81 +++++++++++-------- .../ExtractQueryTest/structuredResult.csv | 1 + .../toleranceOfColumnOrdering1.csv | 1 + .../toleranceOfColumnOrdering2.csv | 1 + .../ParserTest/testCombineOperator.csv | 33 ++++---- .../combineResultInSecondFilter.Bundle.json | 3 +- .../searchWithOffset.Bundle.json | 3 +- .../fhirpath/function/IifFunction.java | 8 +- .../fhirpath/parser/InvocationVisitor.java | 18 +++-- 16 files changed, 128 insertions(+), 76 deletions(-) diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java index ab17db6576..8058e03509 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java @@ -91,8 +91,9 @@ private void setupMockDisplayFor_195662009_444814009() { private void setupMockPropertiesFor_195662009_444814009() { TerminologyServiceHelpers.setupLookup(terminologyService) - .withProperty(CD_SNOMED_195662009, "child", (String)null, CD_SNOMED_40055000, CD_SNOMED_403190006) - .withProperty(CD_SNOMED_444814009, "child", (String)null, CD_SNOMED_284551006) + .withProperty(CD_SNOMED_195662009, "child", (String) null, CD_SNOMED_40055000, + CD_SNOMED_403190006) + .withProperty(CD_SNOMED_444814009, "child", (String) null, CD_SNOMED_284551006) .withDesignation(CD_SNOMED_195662009, CD_SNOMED_900000000000003001, "en", "Acute viral pharyngitis : disorder") .withDesignation(CD_SNOMED_444814009, CD_SNOMED_900000000000003001, "en", @@ -531,6 +532,7 @@ void testDesignationFunctionWithNoLanguage() { assertThatResultOf(ResourceType.CONDITION, "code.coding.designation(http://terminology.hl7.org/CodeSystem/designation-usage|display)") .selectOrderedResult() + .debugAllRows() .hasRows(spark, "responses/ParserTest/testDesignationFunctionWithNoLanguage.csv"); } diff --git a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleCountAggregations.Parameters.json b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleCountAggregations.Parameters.json index 4fe4672f53..d4d35d4588 100644 --- a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleCountAggregations.Parameters.json +++ b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleCountAggregations.Parameters.json @@ -31,7 +31,7 @@ }, { "name": "result", - "valueUnsignedInt": 6 + "valueUnsignedInt": 7 }, { "name": "result", diff --git a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleGroupingCounts.Parameters.json b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleGroupingCounts.Parameters.json index 79472438e6..a03924067c 100644 --- a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleGroupingCounts.Parameters.json +++ b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryMultipleGroupingCounts.Parameters.json @@ -85,6 +85,27 @@ } ] }, + { + "name": "grouping", + "part": [ + { + "name": "label", + "valueString": "Moyna" + }, + { + "name": "label", + "valueString": "Mr." + }, + { + "name": "result", + "valueUnsignedInt": 1 + }, + { + "name": "drillDown", + "valueString": "((name.given) contains 'Moyna') and ((name.prefix) contains 'Mr.')" + } + ] + }, { "name": "grouping", "part": [ @@ -153,10 +174,11 @@ "part": [ { "name": "label", - "valueString": "Gilberto712" + "valueString": "Guy979" }, { - "name": "label" + "name": "label", + "valueString": "Mr." }, { "name": "result", @@ -164,7 +186,7 @@ }, { "name": "drillDown", - "valueString": "((name.given) contains 'Gilberto712') and ((name.prefix).empty())" + "valueString": "((name.given) contains 'Guy979') and ((name.prefix) contains 'Mr.')" } ] }, @@ -173,11 +195,11 @@ "part": [ { "name": "label", - "valueString": "Guy979" + "valueString": "Su690" }, { "name": "label", - "valueString": "Mr." + "valueString": "Ms." }, { "name": "result", @@ -185,7 +207,7 @@ }, { "name": "drillDown", - "valueString": "((name.given) contains 'Guy979') and ((name.prefix) contains 'Mr.')" + "valueString": "((name.given) contains 'Su690') and ((name.prefix) contains 'Ms.')" } ] }, @@ -194,11 +216,10 @@ "part": [ { "name": "label", - "valueString": "Su690" + "valueString": "Gilberto712" }, { - "name": "label", - "valueString": "Ms." + "name": "label" }, { "name": "result", @@ -206,7 +227,7 @@ }, { "name": "drillDown", - "valueString": "((name.given) contains 'Su690') and ((name.prefix) contains 'Ms.')" + "valueString": "((name.given) contains 'Gilberto712') and ((name.prefix).empty())" } ] } diff --git a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregation.Parameters.json b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregation.Parameters.json index 35f2fc78c7..e30413098d 100644 --- a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregation.Parameters.json +++ b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregation.Parameters.json @@ -27,7 +27,7 @@ }, { "name": "result", - "valueUnsignedInt": 12 + "valueUnsignedInt": 13 }, { "name": "drillDown", diff --git a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregationAndNoGroupings.Parameters.json b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregationAndNoGroupings.Parameters.json index f3e0fc0df7..0ee3836394 100644 --- a/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregationAndNoGroupings.Parameters.json +++ b/fhir-server/src/test/resources/responses/AggregateQueryTest/queryWithNestedAggregationAndNoGroupings.Parameters.json @@ -6,7 +6,7 @@ "part": [ { "name": "result", - "valueUnsignedInt": 22 + "valueUnsignedInt": 23 } ] } diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.csv index c8fc439bc3..c21942b70b 100644 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.csv +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.csv @@ -2,6 +2,7 @@ 2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316,Ulibarri312,official 7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901,Heller342,official 8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882,Krajcik437,official +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Moyna,Krajcik437,official 8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Arianne,Burchard,nickname 9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Oberbrunner298,official 9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Wuckert783,maiden diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv index f4430a36d7..98b86426e4 100644 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv @@ -2,6 +2,7 @@ 2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316,Ulibarri312,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S 7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901,Heller342,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S 8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882,Krajcik437,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Moyna,Krajcik437,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M 8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Arianne,Burchard,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M 9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Oberbrunner298,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M 9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Wuckert783,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.csv index 4845d6b94d..9b6b176ac9 100644 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.csv +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.csv @@ -2,12 +2,14 @@ 08f08f2d-4d77-44f5-ab06-a38545fc050e,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 0a9a0b5a-5be9-4205-984e-94ceef8d6ba6,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 0d8d0441-a4ad-4f15-bc61-5d9694ed3d48,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -0f1004c8-e9e9-477d-8c43-1eddc445664d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 0f1004c8-e9e9-477d-8c43-1eddc445664d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -10321ec0-3613-4741-a9c3-47bafbc3fae4,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard +0f1004c8-e9e9-477d-8c43-1eddc445664d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 +0f1004c8-e9e9-477d-8c43-1eddc445664d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 10321ec0-3613-4741-a9c3-47bafbc3fae4,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -113e8a00-6fd2-489c-867b-6eedf314c774,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +10321ec0-3613-4741-a9c3-47bafbc3fae4,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 +10321ec0-3613-4741-a9c3-47bafbc3fae4,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 113e8a00-6fd2-489c-867b-6eedf314c774,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +113e8a00-6fd2-489c-867b-6eedf314c774,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 13a2e7a0-1cf8-475f-8065-6d03b6538c9a,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 13c95f69-822b-44c6-ada3-d73aa1466290,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 16aa313e-aa66-408b-a819-33fb6d169ce0,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 @@ -17,17 +19,18 @@ 2540bf5e-5bc9-48be-8e66-37a2358ae75d,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 2c1dce4c-aefa-4ef8-8970-52aab627de48,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 2c67dbd4-0609-43d3-be6b-e88220c77b75,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -2d19750d-c270-4be9-a84a-ddcdbb64085a,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 2d19750d-c270-4be9-a84a-ddcdbb64085a,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +2d19750d-c270-4be9-a84a-ddcdbb64085a,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 2d4189ac-8eb3-4f4a-9bb1-0f754d715aee,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 2d9c0f33-155b-42ad-8158-1291e14c6da7,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 2e2cebf2-8c0e-4a89-8139-c2b233343dd7,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -304a8806-48e6-4d35-bb17-121f1190b475,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 304a8806-48e6-4d35-bb17-121f1190b475,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 +304a8806-48e6-4d35-bb17-121f1190b475,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 +304a8806-48e6-4d35-bb17-121f1190b475,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 31b93f2e-caa4-4a7f-82ca-5b59f1fc2fdd,2b36c1e2-bbe1-45ae-8124-4adad2677702,male,Pedro316,Ulibarri312 335f8324-ce20-4ebc-a00c-ae085c4516b7,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -35a7f624-22be-48b3-b82c-45cf2667c527,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 35a7f624-22be-48b3-b82c-45cf2667c527,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +35a7f624-22be-48b3-b82c-45cf2667c527,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 365c1b4a-c9c5-4d28-8578-7460dec01ad1,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 37af6887-b8ba-480d-bb1c-4409711b8e2f,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 3e56e1ac-af2e-4711-8050-e3d7cee66b91,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 @@ -38,40 +41,45 @@ 460deb7a-74bc-43e2-bc60-fc6f97159b59,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 4d79c3df-899d-42a4-b17d-e93770921d6b,2b36c1e2-bbe1-45ae-8124-4adad2677702,male,Pedro316,Ulibarri312 53238300-6e57-4fc7-99d4-f1c4bcc3366a,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 -537f22b5-8139-4bcc-b048-5342139154fd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 537f22b5-8139-4bcc-b048-5342139154fd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -53bf45a8-d83f-429c-9b91-26b086a405ea,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +537f22b5-8139-4bcc-b048-5342139154fd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 53bf45a8-d83f-429c-9b91-26b086a405ea,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +53bf45a8-d83f-429c-9b91-26b086a405ea,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 5514df98-79c6-4818-b444-9417c93a64f4,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -5be1aab8-ecae-4c29-8f55-fe37535fd2c6,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 5be1aab8-ecae-4c29-8f55-fe37535fd2c6,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -5e7b39f8-773d-4ffb-83e1-735923586881,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard +5be1aab8-ecae-4c29-8f55-fe37535fd2c6,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 5e7b39f8-773d-4ffb-83e1-735923586881,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 +5e7b39f8-773d-4ffb-83e1-735923586881,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 +5e7b39f8-773d-4ffb-83e1-735923586881,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 5eaf7eae-ffb9-44ec-8d46-fa2b87bedefc,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -6017f9b9-36a1-4cc2-a0ae-af31dc92b089,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 6017f9b9-36a1-4cc2-a0ae-af31dc92b089,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +6017f9b9-36a1-4cc2-a0ae-af31dc92b089,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 61bb4baa-6233-4444-8dd2-9c4806338faf,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -69ea0b24-7d91-4498-991f-49f4b07914c0,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 69ea0b24-7d91-4498-991f-49f4b07914c0,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -6b9ffc9c-80b0-40c2-8cf7-f4d51b5448ab,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +69ea0b24-7d91-4498-991f-49f4b07914c0,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 +69ea0b24-7d91-4498-991f-49f4b07914c0,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 6b9ffc9c-80b0-40c2-8cf7-f4d51b5448ab,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +6b9ffc9c-80b0-40c2-8cf7-f4d51b5448ab,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 73f52a06-5ac5-4324-a046-7a4caffb6c48,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -764a0e55-a10a-4d19-aa18-d414cb911a8d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 764a0e55-a10a-4d19-aa18-d414cb911a8d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -76b9935f-b44e-42b6-a991-e2e724b42334,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard +764a0e55-a10a-4d19-aa18-d414cb911a8d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 76b9935f-b44e-42b6-a991-e2e724b42334,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 +76b9935f-b44e-42b6-a991-e2e724b42334,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 +76b9935f-b44e-42b6-a991-e2e724b42334,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 773d711a-5f70-4ac1-98c5-41b7c94e8a16,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 791b93c5-dfab-4a85-a810-5b01b3531c09,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 7e490679-3095-4431-95be-d3d9eb3b40f8,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -7ed4db56-6f72-4dd1-b306-85d80f3a3a87,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 7ed4db56-6f72-4dd1-b306-85d80f3a3a87,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -807ac3db-8499-4876-8508-7839776afc9a,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard +7ed4db56-6f72-4dd1-b306-85d80f3a3a87,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 +7ed4db56-6f72-4dd1-b306-85d80f3a3a87,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 807ac3db-8499-4876-8508-7839776afc9a,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 +807ac3db-8499-4876-8508-7839776afc9a,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 +807ac3db-8499-4876-8508-7839776afc9a,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard 80b37d35-ee1e-49ff-82d3-6e10b0798fd6,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -81297f01-422b-4aba-98df-3609ea25e480,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 81297f01-422b-4aba-98df-3609ea25e480,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -84a1a6b4-583f-4525-b048-d676da5d7533,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +81297f01-422b-4aba-98df-3609ea25e480,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 84a1a6b4-583f-4525-b048-d676da5d7533,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +84a1a6b4-583f-4525-b048-d676da5d7533,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 88fd1ebd-ecca-4986-868c-e850eb3f5e5e,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 8b05436f-e4ea-4f8e-85ff-4ce195645c0e,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 8b9a7f2a-1bb1-4b5a-8b20-072ed6ae6ef9,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 @@ -79,59 +87,64 @@ 91faf3db-94ce-4508-87d2-206b19ba0302,beff242e-580b-47c0-9844-c1a68c36c5bf,male,Guy979,Towne435 922d533c-3cc0-43d1-8d62-817da745358b,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 9404f7b4-1c0c-4e87-b71f-ff267d0552e6,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -9868ffc0-762a-46d7-91db-4eee888484ae,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 9868ffc0-762a-46d7-91db-4eee888484ae,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +9868ffc0-762a-46d7-91db-4eee888484ae,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 9cc30d0d-de95-4242-81fc-e4e1910beb8c,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 9ce65331-faf8-477a-b338-25678919973b,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 9f1b1a87-d763-406b-b5b7-bd6f7417db2d,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -a0fcef9d-b5bf-4930-b520-cf2dee3b9341,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard a0fcef9d-b5bf-4930-b520-cf2dee3b9341,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -aae88d89-dc3f-40e0-9866-ae4095549d32,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard +a0fcef9d-b5bf-4930-b520-cf2dee3b9341,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 +a0fcef9d-b5bf-4930-b520-cf2dee3b9341,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard aae88d89-dc3f-40e0-9866-ae4095549d32,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 +aae88d89-dc3f-40e0-9866-ae4095549d32,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 +aae88d89-dc3f-40e0-9866-ae4095549d32,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard ad2dceaf-b8e6-49a0-952c-bcda78fc7b0b,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -ad7d9189-d6b1-4e98-9900-af4bcb9e50b8,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard ad7d9189-d6b1-4e98-9900-af4bcb9e50b8,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 +ad7d9189-d6b1-4e98-9900-af4bcb9e50b8,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 +ad7d9189-d6b1-4e98-9900-af4bcb9e50b8,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard b2216cf0-b3df-44e0-b03a-2acfb42bdb87,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 b2e43429-d6c7-4332-9ed4-b239aa470e72,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -b5129712-51e2-4d6d-a229-e1c45f78aad4,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 b5129712-51e2-4d6d-a229-e1c45f78aad4,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -b566351c-0d0f-49a8-b7f0-063975f0ea43,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +b5129712-51e2-4d6d-a229-e1c45f78aad4,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 b566351c-0d0f-49a8-b7f0-063975f0ea43,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +b566351c-0d0f-49a8-b7f0-063975f0ea43,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 b64a7247-1000-4701-8771-caa6252cc905,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 b9adaf8a-35f6-475e-90f0-89fcd999c457,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 ba21bde9-8111-4cf4-bc55-1e265470a145,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 ba99aa15-c5a4-48e9-9858-df6a8c4a53d2,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 -bc243663-b435-43a0-912f-40a6f3bfcc2b,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard bc243663-b435-43a0-912f-40a6f3bfcc2b,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 +bc243663-b435-43a0-912f-40a6f3bfcc2b,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 +bc243663-b435-43a0-912f-40a6f3bfcc2b,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard c3034cd6-9395-4b50-b14b-e457b4077e23,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 c47a1fdb-3594-4c0f-944a-655e7df848a8,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 c678e476-64fd-4d12-8f66-20a29f1ce9de,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 c6fbf259-f45f-4266-b988-4b568b8c36c2,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,male,Gilberto712,MacGyver246 -c809b71f-844e-4312-8549-401f1cad25bb,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 c809b71f-844e-4312-8549-401f1cad25bb,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -c98f9644-7da9-4fc4-bf4d-5da3df697616,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard +c809b71f-844e-4312-8549-401f1cad25bb,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 c98f9644-7da9-4fc4-bf4d-5da3df697616,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -cdd25034-c562-4788-aa9e-e53d8006bcbd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +c98f9644-7da9-4fc4-bf4d-5da3df697616,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 +c98f9644-7da9-4fc4-bf4d-5da3df697616,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard cdd25034-c562-4788-aa9e-e53d8006bcbd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +cdd25034-c562-4788-aa9e-e53d8006bcbd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 cddcbfdd-4b15-4e81-bb2a-8fa998f2e628,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -d19f556d-976c-4529-a2ae-f465b489503c,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 d19f556d-976c-4529-a2ae-f465b489503c,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +d19f556d-976c-4529-a2ae-f465b489503c,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 d2353195-134f-44f1-835c-d0801a8f31b2,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 d336c1e0-852b-4ba9-bf38-ff6c4698a154,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 d8196d79-824c-4632-aa78-7fae27d05d96,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 dad66b19-6c91-4513-aa29-abd08fa256cf,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -ddefa5f2-0d73-4a62-a938-920ee87f7d66,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 ddefa5f2-0d73-4a62-a938-920ee87f7d66,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -df0e5bbc-9657-4e73-bfbc-0a92ae5da74d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 +ddefa5f2-0d73-4a62-a938-920ee87f7d66,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 df0e5bbc-9657-4e73-bfbc-0a92ae5da74d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +df0e5bbc-9657-4e73-bfbc-0a92ae5da74d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 e744c7f9-9756-4edd-b7a7-3df5487657b9,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 f04628bf-b618-4674-b6eb-1d386eb1d637,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 f4ae2f38-086a-450a-bd38-8460ee704763,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -f51b905a-6a43-47d5-bf78-abf1b94c2ba1,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 f51b905a-6a43-47d5-bf78-abf1b94c2ba1,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +f51b905a-6a43-47d5-bf78-abf1b94c2ba1,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 f5d1f0e1-9e1c-44ee-b31b-e881bd69c1bb,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 f8bb88e4-6301-4eab-83b8-f49c72bb448f,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 f9994712-1f5e-4a3b-9d64-369b1e3e8f4b,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 fa507deb-a262-475c-938d-5666c502ebae,beff242e-580b-47c0-9844-c1a68c36c5bf,male,Guy979,Towne435 -fd73b5ca-079f-47a5-90b1-e49a547d1554,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 fd73b5ca-079f-47a5-90b1-e49a547d1554,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 +fd73b5ca-079f-47a5-90b1-e49a547d1554,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv index 707aa688da..7f9bcdb827 100644 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv @@ -3,6 +3,7 @@ 7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901 8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Arianne 8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Moyna 9360820c-8602-4335-8b50-c88d627a0c20,Karina848 9360820c-8602-4335-8b50-c88d627a0c20,Karina848 a7eb2ce7-1075-426c-addd-957b861b0e55,Shirley182 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv index 437063bafd..deb68ddc68 100644 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv @@ -2,6 +2,7 @@ 2b36c1e2-bbe1-45ae-8124-4adad2677702,Ulibarri312,Pedro316 7001ad9c-34d2-4eb5-8165-5fdc2147f469,Heller342,Cherryl901 8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437,Seymour882 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437,Moyna 8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Burchard,Arianne 9360820c-8602-4335-8b50-c88d627a0c20,Oberbrunner298,Karina848 9360820c-8602-4335-8b50-c88d627a0c20,Wuckert783,Karina848 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv index 49a001cd9b..cae596bd51 100644 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv @@ -2,6 +2,7 @@ Ophelia894,121503c8-9564-4b48-9086-a22df717948e,Gleichner915 Pedro316,2b36c1e2-bbe1-45ae-8124-4adad2677702,Ulibarri312 Cherryl901,7001ad9c-34d2-4eb5-8165-5fdc2147f469,Heller342 Seymour882,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437 +Moyna,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437 Arianne,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Burchard Karina848,9360820c-8602-4335-8b50-c88d627a0c20,Oberbrunner298 Karina848,9360820c-8602-4335-8b50-c88d627a0c20,Wuckert783 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperator.csv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperator.csv index e6e77c3c7c..682c2ede9f 100644 --- a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperator.csv +++ b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperator.csv @@ -1,20 +1,23 @@ -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437 -beff242e-580b-47c0-9844-c1a68c36c5bf,Towne435 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Ebert178 -2b36c1e2-bbe1-45ae-8124-4adad2677702,Ulibarri312 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,MacGyver246 -a7eb2ce7-1075-426c-addd-957b861b0e55,Botsford977 121503c8-9564-4b48-9086-a22df717948e,Gleichner915 -9360820c-8602-4335-8b50-c88d627a0c20,Oberbrunner298 -9360820c-8602-4335-8b50-c88d627a0c20,Wuckert783 +121503c8-9564-4b48-9086-a22df717948e,Ophelia894 +2b36c1e2-bbe1-45ae-8124-4adad2677702,Ulibarri312 +2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316 7001ad9c-34d2-4eb5-8165-5fdc2147f469,Heller342 +7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437 8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882 -beff242e-580b-47c0-9844-c1a68c36c5bf,Guy979 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Su690 -2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Gilberto712 -a7eb2ce7-1075-426c-addd-957b861b0e55,Shirley182 -121503c8-9564-4b48-9086-a22df717948e,Ophelia894 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Moyna +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Burchard +8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Arianne +9360820c-8602-4335-8b50-c88d627a0c20,Oberbrunner298 9360820c-8602-4335-8b50-c88d627a0c20,Karina848 +9360820c-8602-4335-8b50-c88d627a0c20,Wuckert783 9360820c-8602-4335-8b50-c88d627a0c20,Karina848 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901 +a7eb2ce7-1075-426c-addd-957b861b0e55,Botsford977 +a7eb2ce7-1075-426c-addd-957b861b0e55,Shirley182 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,MacGyver246 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Gilberto712 +beff242e-580b-47c0-9844-c1a68c36c5bf,Towne435 +beff242e-580b-47c0-9844-c1a68c36c5bf,Guy979 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Ebert178 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Su690 diff --git a/fhir-server/src/test/resources/responses/SearchExecutorTest/combineResultInSecondFilter.Bundle.json b/fhir-server/src/test/resources/responses/SearchExecutorTest/combineResultInSecondFilter.Bundle.json index 51ed3a0cab..6c96c7b326 100644 --- a/fhir-server/src/test/resources/responses/SearchExecutorTest/combineResultInSecondFilter.Bundle.json +++ b/fhir-server/src/test/resources/responses/SearchExecutorTest/combineResultInSecondFilter.Bundle.json @@ -207,7 +207,8 @@ "use": "official", "family": "Krajcik437", "given": [ - "Seymour882" + "Seymour882", + "Moyna" ], "prefix": [ "Mr." diff --git a/fhir-server/src/test/resources/responses/SearchExecutorTest/searchWithOffset.Bundle.json b/fhir-server/src/test/resources/responses/SearchExecutorTest/searchWithOffset.Bundle.json index aeb4290764..aa8390352b 100644 --- a/fhir-server/src/test/resources/responses/SearchExecutorTest/searchWithOffset.Bundle.json +++ b/fhir-server/src/test/resources/responses/SearchExecutorTest/searchWithOffset.Bundle.json @@ -78,7 +78,8 @@ "use": "official", "family": "Krajcik437", "given": [ - "Seymour882" + "Seymour882", + "Moyna" ], "prefix": [ "Mr." diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java index cef6186efd..b5e45361dc 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java @@ -17,10 +17,12 @@ package au.csiro.pathling.fhirpath.function; +import static au.csiro.pathling.QueryHelpers.createColumn; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.when; +import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.element.BooleanPath; @@ -60,12 +62,14 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Column valueColumn = when(conditionBoolean.getValueColumn().equalTo(true), ifTrue.getValueColumn()) .otherwise(otherwise.getValueColumn()); + final DatasetWithColumn datasetWithColumn = createColumn(otherwise.getDataset(), valueColumn); // Build a new ElementPath based on the type of the literal `ifTrue` and `otherwise` arguments, // and populate it with the dataset and calculated value column. final String expression = expressionFromInput(input, NAME); - return ifTrue.combineWith(otherwise, otherwise.getDataset(), expression, - inputPath.getIdColumn(), valueColumn, inputPath.isSingular(), inputPath.getThisColumn()); + return ifTrue.combineWith(otherwise, datasetWithColumn.getDataset(), expression, + inputPath.getIdColumn(), datasetWithColumn.getColumn(), inputPath.isSingular(), + inputPath.getThisColumn()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index 70e4a731a8..70c40289ab 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -29,6 +29,7 @@ import au.csiro.pathling.fhirpath.operator.PathTraversalInput; import au.csiro.pathling.fhirpath.operator.PathTraversalOperator; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ExpressionContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.FunctionInvocationContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.IndexInvocationContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.MemberInvocationContext; @@ -37,7 +38,6 @@ import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.TotalInvocationContext; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.spark.sql.Column; @@ -199,18 +199,20 @@ public FhirPath visitFunctionInvocation(@Nullable final FunctionInvocationContex // Create a new ParserContext, which includes information about how to evaluate the `$this` // expression. - final ParserContext argumentContext = new ParserContext(context.getInputContext(), + ParserContext argumentContext = new ParserContext(context.getInputContext(), context.getFhirContext(), context.getSparkSession(), context.getDataSource(), context.getTerminologyServiceFactory(), argumentGroupings, context.getUnnestBehaviour(), context.getVariables(), context.getNesting()); argumentContext.setThisContext(thisPath); - // Parse each of the expressions passed as arguments to the function. - arguments.addAll( - paramList.expression().stream() - .map(expression -> new Visitor(argumentContext).visit(expression)) - .collect(Collectors.toList()) - ); + for (final ExpressionContext expression : paramList.expression()) { + // Parse each of the expressions passed as arguments to the function. + final FhirPath argumentResult = new Visitor(argumentContext).visit(expression); + arguments.add(argumentResult); + // Update the argument context with the updated dataset, for use in parsing subsequent + // arguments. + argumentContext = argumentContext.withContextDataset(argumentResult.getDataset()); + } } final NamedFunctionInput functionInput = new NamedFunctionInput(context, nonLiteral, arguments); From e209004266860512037a2d5f6b898f74c2a8c755 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sat, 29 Jul 2023 10:46:56 +1000 Subject: [PATCH 46/95] Start again with FhirViewExecutor and a single test --- .../csiro/pathling/search/SearchExecutor.java | 5 +- .../pathling/views/FhirViewExecutorTest.java | 52 ++ .../java/au/csiro/pathling/QueryExecutor.java | 11 +- .../aggregate/AggregateQueryExecutor.java | 175 +++--- .../extract/ExtractQueryExecutor.java | 115 ++-- .../pathling/views/ConstantDeclaration.java | 29 + .../pathling/views/DatasetWithColumns.java | 19 + .../csiro/pathling/views/DirectSelection.java | 47 ++ .../au/csiro/pathling/views/FhirView.java | 72 ++- .../pathling/views/FhirViewExecutor.java | 184 ++++--- .../pathling/views/ForEachSelection.java | 34 ++ .../csiro/pathling/views/FromSelection.java | 32 ++ .../csiro/pathling/views/NamedExpression.java | 21 - .../au/csiro/pathling/views/SelectClause.java | 17 + .../views/SelectClauseTypeAdapter.java | 52 ++ .../views/SelectClauseTypeAdapterFactory.java | 21 + .../pathling/views/VariableExpression.java | 17 - .../au/csiro/pathling/views/WhenMany.java | 48 -- .../au/csiro/pathling/views/WhereClause.java | 32 ++ .../csiro/pathling/UnitTestDependencies.java | 18 +- .../au/csiro/pathling/views/FhirViewTest.java | 499 ++---------------- .../requests/views/singularNoUnnesting.json | 18 + .../results/views/singularNoUnnesting.csv | 2 + .../library/query/FhirViewBuilder.java | 3 - 24 files changed, 715 insertions(+), 808 deletions(-) create mode 100644 fhir-server/src/test/java/au/csiro/pathling/views/FhirViewExecutorTest.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/ConstantDeclaration.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/DatasetWithColumns.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/FromSelection.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/NamedExpression.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/SelectClause.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/SelectClauseTypeAdapter.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/SelectClauseTypeAdapterFactory.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/VariableExpression.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/WhenMany.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/WhereClause.java create mode 100644 fhirpath/src/test/resources/requests/views/singularNoUnnesting.json create mode 100644 fhirpath/src/test/resources/results/views/singularNoUnnesting.csv diff --git a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java index 3daf0cc85d..9e41d15941 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java @@ -126,8 +126,9 @@ private Dataset initializeDataset() { dataset = subjectDataset; } else { - final ParserContext parserContext = buildParserContext(resourcePath, - Collections.singletonList(resourcePath.getIdColumn())); + final ParserContext parserContext = new ParserContext(resourcePath, fhirContext, sparkSession, + dataSource, + terminologyServiceFactory, Collections.singletonList(resourcePath.getIdColumn())); Dataset currentDataset = subjectDataset; @Nullable Column filterCondition = null; diff --git a/fhir-server/src/test/java/au/csiro/pathling/views/FhirViewExecutorTest.java b/fhir-server/src/test/java/au/csiro/pathling/views/FhirViewExecutorTest.java new file mode 100644 index 0000000000..81d5a83d55 --- /dev/null +++ b/fhir-server/src/test/java/au/csiro/pathling/views/FhirViewExecutorTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2023 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.views; + +import au.csiro.pathling.io.source.DataSource; +import au.csiro.pathling.terminology.TerminologyServiceFactory; +import au.csiro.pathling.test.SpringBootUnitTest; +import ca.uhn.fhir.context.FhirContext; +import java.util.Optional; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; + +@SpringBootUnitTest +class FhirViewExecutorTest { + + @Autowired + FhirContext fhirContext; + + @Autowired + SparkSession sparkSession; + + @Autowired + DataSource dataSource; + + @Autowired + TerminologyServiceFactory terminologyServiceFactory; + + FhirViewExecutor executor; + + @BeforeEach + void setUp() { + executor = new FhirViewExecutor(fhirContext, sparkSession, dataSource, + Optional.of(terminologyServiceFactory)); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index 5d752d6be9..fd29fe908c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -83,12 +83,6 @@ protected QueryExecutor(@Nonnull final QueryConfiguration configuration, this.terminologyServiceFactory = terminologyServiceFactory; } - protected ParserContext buildParserContext(@Nonnull final FhirPath inputContext, - @Nonnull final List groupingColumns) { - return new ParserContext(inputContext, fhirContext, sparkSession, dataSource, - terminologyServiceFactory, groupingColumns); - } - @Nonnull protected List parseExpressions( @Nonnull final ParserContext parserContext, @Nonnull final Collection expressions) { @@ -175,8 +169,9 @@ private DatasetWithColumn getFilteredIds(@Nonnull final Iterable filters for (final String filter : filters) { // Parse the filter expression. - final ParserContext parserContext = buildParserContext(currentContext, - Collections.singletonList(currentContext.getIdColumn())); + final ParserContext parserContext = new ParserContext(currentContext, fhirContext, + sparkSession, dataSource, + terminologyServiceFactory, Collections.singletonList(currentContext.getIdColumn())); final Parser parser = new Parser(parserContext); final FhirPath fhirPath = parser.parse(filter); diff --git a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java index 8596c49cd6..fe77e3544c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java @@ -17,32 +17,23 @@ package au.csiro.pathling.aggregate; -import static au.csiro.pathling.QueryHelpers.createColumns; -import static au.csiro.pathling.query.ExpressionWithLabel.labelsAsStream; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static au.csiro.pathling.utilities.Strings.randomAlias; -import static java.util.stream.Collectors.toList; import static org.apache.spark.sql.functions.col; import static org.apache.spark.sql.functions.first; import au.csiro.pathling.QueryExecutor; -import au.csiro.pathling.QueryHelpers; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.Database; import au.csiro.pathling.io.source.DataSource; -import au.csiro.pathling.sql.SqlExpressions; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Stream; import javax.annotation.Nonnull; import lombok.Value; import lombok.extern.slf4j.Slf4j; @@ -83,88 +74,90 @@ public AggregateQueryExecutor(@Nonnull final QueryConfiguration configuration, @SuppressWarnings("WeakerAccess") @Nonnull public ResultWithExpressions buildQuery(@Nonnull final AggregateRequest query) { - log.info("Executing request: {}", query); - - /// Set up the parser context for the grouping and filtering expressions. - final ResourcePath inputContext = ResourcePath - .build(getFhirContext(), getDataSource(), query.getSubjectResource(), - query.getSubjectResource().toCode(), true); - final ParserContext groupingFilteringContext = buildParserContext(inputContext, - Collections.singletonList(inputContext.getIdColumn())); - - // Parse the filter expressions. - final Optional> filteredDataset; - final List filters; - if (query.getFilters().isEmpty()) { - filteredDataset = Optional.empty(); - filters = Collections.emptyList(); - } else { - filters = parseExpressions(groupingFilteringContext, query.getFilters()); - validateFilters(filters); - final Dataset filterDataset = filters.get(filters.size() - 1).getDataset(); - final Optional filterConstraint = filters.stream() - .map(FhirPath::getValueColumn) - .reduce(Column::and); - filteredDataset = Optional.of(filterConstraint.map(filterDataset::filter) - .orElse(filterDataset)); - } - - // Parse the grouping expressions. - final Optional> groupingFilteringDataset; - final List groupings; - if (query.getGroupings().isEmpty()) { - groupingFilteringDataset = filteredDataset; - groupings = Collections.emptyList(); - } else { - groupings = parseExpressions(groupingFilteringContext, query.getGroupings(), - filteredDataset); - validateGroupings(groupings); - groupingFilteringDataset = Optional.of(groupings.get(groupings.size() - 1).getDataset()); - } - - // Remove synthetic fields from struct values (such as _fid) before grouping. - final Optional> prunedDataset; - final List prunedGroupings; - if (groupingFilteringDataset.isPresent()) { - final QueryHelpers.DatasetWithColumnMap datasetWithNormalizedGroupings = createColumns( - groupingFilteringDataset.get(), groupings.stream().map(FhirPath::getValueColumn) - .map(SqlExpressions::pruneSyntheticFields).toArray(Column[]::new)); - prunedDataset = Optional.of(datasetWithNormalizedGroupings.getDataset()); - prunedGroupings = new ArrayList<>( - datasetWithNormalizedGroupings.getColumnMap().values()); - } else { - prunedDataset = Optional.empty(); - prunedGroupings = Collections.emptyList(); - } - - // Parse the aggregation expressions. - final ParserContext aggregationContext = groupingFilteringContext.withGroupingColumns( - prunedGroupings); - final List aggregations = parseExpressions(aggregationContext, - query.getAggregations(), prunedDataset); - validateAggregations(aggregations); - final List aggregationColumns = aggregations.stream() - .map(FhirPath::getValueColumn) - .collect(toList()); - final FhirPath lastAggregation = aggregations.get(aggregations.size() - 1); - Dataset aggregationDataset = lastAggregation.getDataset(); - // If the result of the aggregation expressions is not actually aggregated, we need to apply - // a final aggregation step that reduces it down to one row per group. - if (!aggregationContext.getNesting().isRootErased()) { - aggregationDataset = applyAggregation(aggregationContext, aggregationColumns, lastAggregation, - aggregationDataset); - } - - // The final column selection will be the grouping columns, followed by the aggregation - // columns. - final Column[] selection = Stream.concat( - labelColumns(prunedGroupings.stream(), labelsAsStream(query.getGroupingsWithLabels())), - labelColumns(aggregationColumns.stream(), labelsAsStream(query.getAggregationsWithLabels())) - ) - .toArray(Column[]::new); - final Dataset finalDataset = aggregationDataset.select(selection); - - return new ResultWithExpressions(finalDataset, aggregations, groupings, filters); + // log.info("Executing request: {}", query); + // + // /// Set up the parser context for the grouping and filtering expressions. + // final ResourcePath inputContext = ResourcePath + // .build(getFhirContext(), getDataSource(), query.getSubjectResource(), + // query.getSubjectResource().toCode(), true); + // final ParserContext groupingFilteringContext = new ParserContext(inputContext, fhirContext, + // sparkSession, dataSource, + // terminologyServiceFactory, Collections.singletonList(inputContext.getIdColumn())); + // + // // Parse the filter expressions. + // final Optional> filteredDataset; + // final List filters; + // if (query.getFilters().isEmpty()) { + // filteredDataset = Optional.empty(); + // filters = Collections.emptyList(); + // } else { + // filters = parseExpressions(groupingFilteringContext, query.getFilters()); + // validateFilters(filters); + // final Dataset filterDataset = filters.get(filters.size() - 1).getDataset(); + // final Optional filterConstraint = filters.stream() + // .map(FhirPath::getValueColumn) + // .reduce(Column::and); + // filteredDataset = Optional.of(filterConstraint.map(filterDataset::filter) + // .orElse(filterDataset)); + // } + // + // // Parse the grouping expressions. + // final Optional> groupingFilteringDataset; + // final List groupings; + // if (query.getGroupings().isEmpty()) { + // groupingFilteringDataset = filteredDataset; + // groupings = Collections.emptyList(); + // } else { + // groupings = parseExpressions(groupingFilteringContext, query.getGroupings(), + // filteredDataset); + // validateGroupings(groupings); + // groupingFilteringDataset = Optional.of(groupings.get(groupings.size() - 1).getDataset()); + // } + // + // // Remove synthetic fields from struct values (such as _fid) before grouping. + // final Optional> prunedDataset; + // final List prunedGroupings; + // if (groupingFilteringDataset.isPresent()) { + // final QueryHelpers.DatasetWithColumnMap datasetWithNormalizedGroupings = createColumns( + // groupingFilteringDataset.get(), groupings.stream().map(FhirPath::getValueColumn) + // .map(SqlExpressions::pruneSyntheticFields).toArray(Column[]::new)); + // prunedDataset = Optional.of(datasetWithNormalizedGroupings.getDataset()); + // prunedGroupings = new ArrayList<>( + // datasetWithNormalizedGroupings.getColumnMap().values()); + // } else { + // prunedDataset = Optional.empty(); + // prunedGroupings = Collections.emptyList(); + // } + // + // // Parse the aggregation expressions. + // final ParserContext aggregationContext = groupingFilteringContext.withGroupingColumns( + // prunedGroupings); + // final List aggregations = parseExpressions(aggregationContext, + // query.getAggregations(), prunedDataset); + // validateAggregations(aggregations); + // final List aggregationColumns = aggregations.stream() + // .map(FhirPath::getValueColumn) + // .collect(toList()); + // final FhirPath lastAggregation = aggregations.get(aggregations.size() - 1); + // Dataset aggregationDataset = lastAggregation.getDataset(); + // // If the result of the aggregation expressions is not actually aggregated, we need to apply + // // a final aggregation step that reduces it down to one row per group. + // if (!aggregationContext.getNesting().isRootErased()) { + // aggregationDataset = applyAggregation(aggregationContext, aggregationColumns, lastAggregation, + // aggregationDataset); + // } + // + // // The final column selection will be the grouping columns, followed by the aggregation + // // columns. + // final Column[] selection = Stream.concat( + // labelColumns(prunedGroupings.stream(), labelsAsStream(query.getGroupingsWithLabels())), + // labelColumns(aggregationColumns.stream(), labelsAsStream(query.getAggregationsWithLabels())) + // ) + // .toArray(Column[]::new); + // final Dataset finalDataset = aggregationDataset.select(selection); + // + // return new ResultWithExpressions(finalDataset, aggregations, groupings, filters); + return null; } private void validateAggregations(@Nonnull final Collection aggregations) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java index f36e2318ca..e1148012a7 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java @@ -1,6 +1,5 @@ package au.csiro.pathling.extract; -import static au.csiro.pathling.query.ExpressionWithLabel.labelsAsStream; import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static java.util.stream.Collectors.toList; @@ -9,18 +8,14 @@ import au.csiro.pathling.fhirpath.AbstractPath; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.Flat; -import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; -import java.util.Collections; import java.util.List; import java.util.Optional; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; -import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; @@ -63,60 +58,62 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query) { @Nonnull public Dataset buildQuery(@Nonnull final ExtractRequest query, @Nonnull final ExtractResultType resultType) { - - // The context of evaluation is a single resource. - final ResourcePath inputContext = ResourcePath - .build(getFhirContext(), getDataSource(), query.getSubjectResource(), - query.getSubjectResource().toCode(), true); - final ParserContext parserContext = buildParserContext(inputContext, - Collections.singletonList(inputContext.getIdColumn())); - - // Parse each of the column expressions. - final List parsedColumns = - parseExpressions(parserContext, query.getColumnsAsStrings()); - - // Validate and coerce the types of the columns where necessary. - final List coercedColumns = - validateAndCoerceColumns(parsedColumns, resultType); - - // Get the dataset from the last column. - final Dataset unfiltered = coercedColumns.get(parsedColumns.size() - 1).getDataset(); - - // Apply the filters. - final Dataset filtered; - if (query.getFilters().isEmpty()) { - filtered = unfiltered; - } else { - final List filters = query.getFilters(); - - // Parse each of the filter expressions, - final List filterPaths = parseExpressions(parserContext, filters, - Optional.of(unfiltered)); - - // Get the dataset from the last filter. - final Dataset withFilters = filterPaths.get(filterPaths.size() - 1).getDataset(); - - // Combine all the filter value columns using the and operator. - final Optional filterConstraint = filterPaths.stream() - .map(FhirPath::getValueColumn) - .reduce(Column::and); - - // Filter the dataset using the constraint. - filtered = filterConstraint.map(withFilters::filter).orElse(withFilters); - } - - // Select the column values from the dataset, applying labelling where requested. - final Column[] columnValues = labelColumns( - coercedColumns.stream() - .map(FhirPath::getValueColumn), - labelsAsStream(query.getColumns()) - ).toArray(Column[]::new); - final Dataset selectedDataset = filtered.select(columnValues); - - // If there is a row limit, apply it. - return query.getLimit().isPresent() - ? selectedDataset.limit(query.getLimit().get()) - : selectedDataset; + // + // // The context of evaluation is a single resource. + // final ResourcePath inputContext = ResourcePath + // .build(getFhirContext(), getDataSource(), query.getSubjectResource(), + // query.getSubjectResource().toCode(), true); + // final ParserContext parserContext = new ParserContext(inputContext, fhirContext, sparkSession, + // dataSource, + // terminologyServiceFactory, Collections.singletonList(inputContext.getIdColumn())); + // + // // Parse each of the column expressions. + // final List parsedColumns = + // parseExpressions(parserContext, query.getColumnsAsStrings()); + // + // // Validate and coerce the types of the columns where necessary. + // final List coercedColumns = + // validateAndCoerceColumns(parsedColumns, resultType); + // + // // Get the dataset from the last column. + // final Dataset unfiltered = coercedColumns.get(parsedColumns.size() - 1).getDataset(); + // + // // Apply the filters. + // final Dataset filtered; + // if (query.getFilters().isEmpty()) { + // filtered = unfiltered; + // } else { + // final List filters = query.getFilters(); + // + // // Parse each of the filter expressions, + // final List filterPaths = parseExpressions(parserContext, filters, + // Optional.of(unfiltered)); + // + // // Get the dataset from the last filter. + // final Dataset withFilters = filterPaths.get(filterPaths.size() - 1).getDataset(); + // + // // Combine all the filter value columns using the and operator. + // final Optional filterConstraint = filterPaths.stream() + // .map(FhirPath::getValueColumn) + // .reduce(Column::and); + // + // // Filter the dataset using the constraint. + // filtered = filterConstraint.map(withFilters::filter).orElse(withFilters); + // } + // + // // Select the column values from the dataset, applying labelling where requested. + // final Column[] columnValues = labelColumns( + // coercedColumns.stream() + // .map(FhirPath::getValueColumn), + // labelsAsStream(query.getColumns()) + // ).toArray(Column[]::new); + // final Dataset selectedDataset = filtered.select(columnValues); + // + // // If there is a row limit, apply it. + // return query.getLimit().isPresent() + // ? selectedDataset.limit(query.getLimit().get()) + // : selectedDataset; + return null; } private List validateAndCoerceColumns( diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ConstantDeclaration.java b/fhirpath/src/main/java/au/csiro/pathling/views/ConstantDeclaration.java new file mode 100644 index 0000000000..20d81bd455 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ConstantDeclaration.java @@ -0,0 +1,29 @@ +package au.csiro.pathling.views; + +import javax.validation.constraints.NotNull; +import lombok.Data; + +/** + * An optional list of constants that can be used in any FHIRPath expression in the view definition. + * These are effectively strings or numbers that can be injected into FHIRPath expressions below by + * having {@code %constantName`} in the expression. + * + * @author John Grimes + */ +@Data +public class ConstantDeclaration { + + /** + * The name of the variable that can be referenced by following variables or columns, where users + * would use the {@code %variable_name} syntax. + */ + @NotNull + String name; + + /** + * The value of the constant name to be used in expressions below. + */ + @NotNull + String value; + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/DatasetWithColumns.java b/fhirpath/src/main/java/au/csiro/pathling/views/DatasetWithColumns.java new file mode 100644 index 0000000000..2ca3b4a34e --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/DatasetWithColumns.java @@ -0,0 +1,19 @@ +package au.csiro.pathling.views; + +import java.util.List; +import javax.annotation.Nonnull; +import lombok.Value; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; + +@Value +public class DatasetWithColumns { + + @Nonnull + Dataset dataset; + + @Nonnull + List columns; + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java new file mode 100644 index 0000000000..e593ec3859 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java @@ -0,0 +1,47 @@ +package au.csiro.pathling.views; + +import com.google.gson.annotations.SerializedName; +import javax.annotation.Nullable; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Describes the selection of a column in the output. + * + * @author John Grimes + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class DirectSelection extends SelectClause { + + /** + * The name of the column produced in the output. + *

+ * The name is limited to letters, numbers, or underscores and cannot start with an underscore -- + * i.e. with a regular expression of {@code ^[^_][A-Za-z0-9_]+$}. This makes it usable as table + * names in a wide variety of databases. + */ + @NotNull + @Size(max = 255) + @Pattern(regexp = "^[^_][A-Za-z0-9_]+$") + String name; + + /** + * The FHIRPath expression for the column's content. This may be from the resource root or from a + * variable defined above, using a {@code %var_name.rest.of.path} structure. + */ + @NotNull + @SerializedName("expr") + String expression; + + /** + * An optional human-readable description of the column. + */ + @SerializedName("desc") + @Nullable + String description; + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java index 616018c48c..a24525a0c7 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java @@ -1,23 +1,75 @@ package au.csiro.pathling.views; +import com.google.gson.annotations.SerializedName; import java.util.List; -import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import lombok.Data; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; +/** + * View definitions are the heart of this proposal. In a nutshell, view is tabular projection of a + * FHIR resource, where the columns and criteria are defined by FHIRPath expressions. This is + * defined in a simple JSON document for ease of use and to iterate quickly, but future iterations + * may shift to defining views as true FHIR resources. + * + * @author John Grimes + */ @Data public class FhirView { - @Nonnull - ResourceType resource; + /** + * Name of the FHIR view to be created. View runners can use this in whatever way is appropriate + * for their use case, such as a database view or table name. + *

+ * The name is limited to letters, numbers, or underscores and cannot start with an underscore -- + * i.e. with a regular expression of {@code }^[^_][A-Za-z0-9_]+$}. This makes it usable as table + * names in a wide variety of databases. + */ + @NotNull + String name; - @Nonnull - List columns; + /** + * An optional human-readable description of the view. + */ + @SerializedName("desc") + @Nullable + String description; - @Nonnull - List variables; + /** + * The FHIR resource the view is based on, e.g. 'Patient' or 'Observation'. + */ + @NotNull + String resource; - @Nonnull - List filters; + /** + * An optional list of constants that can be used in any FHIRPath expression in the view + * definition. These are effectively strings or numbers that can be injected into FHIRPath + * expressions below by having {@code %constantName`} in the expression. + */ + @Nullable + List constants; + + /** + * The select stanza defines the actual content of the view itself. This stanza is a list where + * each item in is one of: + *

    + *
  • a structure with the column name, expression, and optional description,
  • + *
  • a 'from' structure indicating a relative path to pull fields from in a nested select, or
  • + *
  • a 'forEach' structure, unrolling the specified path and creating a new row for each item.
  • + *
+ * See the comments below for details on the semantics. + */ + @NotNull + @Size(min = 1) + List select; + + /** + * 'where' filters are FHIRPath expressions joined with an implicit "and". This enables users to + * select a subset of rows that match a specific need. For example, a user may be interested only + * in a subset of observations based on code value and can filter them here. + */ + @Nullable + List where; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index 9f7e09988e..c8fad2fa55 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -2,123 +2,143 @@ import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; -import static org.apache.spark.sql.functions.flatten; -import au.csiro.pathling.QueryExecutor; -import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirPathAndContext; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.parser.Parser; import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.fhirpath.parser.UnnestBehaviour; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; +import org.hl7.fhir.r4.model.Enumerations.ResourceType; /** * Executes a FHIR view query. * * @author John Grimes */ -public class FhirViewExecutor extends QueryExecutor { +public class FhirViewExecutor { - private static Map WHEN_MANY_UNNEST_MAP = new HashMap<>(); + @Nonnull + private final FhirContext fhirContext; - static { - WHEN_MANY_UNNEST_MAP.put(WhenMany.UNNEST, UnnestBehaviour.UNNEST); - WHEN_MANY_UNNEST_MAP.put(WhenMany.ERROR, UnnestBehaviour.ERROR); - WHEN_MANY_UNNEST_MAP.put(WhenMany.ARRAY, UnnestBehaviour.NOOP); - } + @Nonnull + private final SparkSession sparkSession; + + @Nonnull + private final DataSource dataSource; + @Nonnull + private final Optional terminologyServiceFactory; - public FhirViewExecutor( - @Nonnull final QueryConfiguration configuration, - @Nonnull final FhirContext fhirContext, - @Nonnull final SparkSession sparkSession, - @Nonnull final DataSource dataSource, + public FhirViewExecutor(@Nonnull final FhirContext fhirContext, + @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataSource, @Nonnull final Optional terminologyServiceFactory) { - super(configuration, fhirContext, sparkSession, dataSource, terminologyServiceFactory); + this.fhirContext = fhirContext; + this.sparkSession = sparkSession; + this.dataSource = dataSource; + this.terminologyServiceFactory = terminologyServiceFactory; } @Nonnull public Dataset buildQuery(@Nonnull final FhirView view) { // Build a new expression parser, and parse all the column expressions within the query. - final ResourcePath inputContext = ResourcePath - .build(getFhirContext(), getDataSource(), view.getResource(), - view.getResource().toCode(), true); - final ParserContext parserContext = buildParserContext(inputContext, - singletonList(inputContext.getIdColumn())); - - // Parse the variable expressions. - for (final VariableExpression variable : view.getVariables()) { - final UnnestBehaviour unnestBehaviour = WHEN_MANY_UNNEST_MAP.get(variable.getWhenMany()); - - // Create a copy of the parser context that uses the specified unnest behaviour. - final ParserContext variableContext = parserContext - .withUnnestBehaviour(unnestBehaviour); - - final Parser parser = new Parser(variableContext); - FhirPath result = parser.parse(variable.getExpression()) - .withExpression("%" + variable.getName()); - - // If the variable has a `whenMany` value of `array`, we need to flatten the result to remove - // accumulated array nesting from the underlying structure. - if (variable.getWhenMany() == WhenMany.ARRAY && result instanceof NonLiteralPath - && result instanceof FhirValue) { - final NonLiteralPath nonLiteralResult = (NonLiteralPath) result; - final Column flattenedValue = flatten(nonLiteralResult.getValueColumn()); - result = nonLiteralResult.copy(nonLiteralResult.getExpression(), - nonLiteralResult.getDataset(), nonLiteralResult.getIdColumn(), flattenedValue, - nonLiteralResult.getOrderingColumn(), nonLiteralResult.isSingular(), - nonLiteralResult.getThisColumn()); - } + final ResourceType resourceType = ResourceType.fromCode(view.getResource()); + final ResourcePath inputContext = ResourcePath.build(fhirContext, dataSource, resourceType, + view.getResource(), true); + final ParserContext parserContext = new ParserContext(inputContext, fhirContext, sparkSession, + dataSource, terminologyServiceFactory, singletonList(inputContext.getIdColumn())); + + final DatasetWithColumns select = parseSelect(view.getSelect(), parserContext, + Collections.emptyList()); + final List where = view.getWhere() == null + ? Collections.emptyList() + : view.getWhere().stream() + .map(WhereClause::getExpression) + .collect(toList()); + final Optional filterCondition = parseExpressions(where, parserContext).stream() + .map(FhirPath::getValueColumn) + .reduce(Column::and); + return filterCondition + .map(select.getDataset()::filter) + .orElse(select.getDataset()) + .select(select.getColumns().toArray(new Column[0])); + } - // Add the variable path and its context to the parser context. This ensures that previously - // declared variables can be referenced in later ones. - parserContext.getVariables().put(variable.getName(), - new FhirPathAndContext(result, variableContext)); + @Nonnull + private DatasetWithColumns parseSelect(@Nonnull final List selectGroup, + @Nonnull final ParserContext context, @Nonnull final List columns) { + @Nonnull DatasetWithColumns result = new DatasetWithColumns( + context.getInputContext().getDataset(), columns); + + for (final SelectClause select : selectGroup) { + if (select instanceof DirectSelection) { + final FhirPath path = parseExpression(((DirectSelection) select).getExpression(), + context.withContextDataset(result.getDataset())); + final List newColumns = new ArrayList<>(result.getColumns()); + newColumns.add(path.getValueColumn()); + result = new DatasetWithColumns(path.getDataset(), newColumns); + + } else if (select instanceof FromSelection) { + final FhirPath from = parseExpression(((FromSelection) select).getFrom(), + context.withContextDataset(result.getDataset())); + final DatasetWithColumns nestedResult = parseSelect(((FromSelection) select).getSelect(), + context.withContextDataset(from.getDataset()), result.getColumns()); + final List newColumns = new ArrayList<>(result.getColumns()); + newColumns.addAll(nestedResult.getColumns()); + result = new DatasetWithColumns(nestedResult.getDataset(), newColumns); + + } else if (select instanceof ForEachSelection) { + final FhirPath forEach = parseExpression(((ForEachSelection) select).getForEach(), + context.withContextDataset(result.getDataset())); + final DatasetWithColumns nestedResult = parseSelect(((ForEachSelection) select).getSelect(), + context.withContextDataset(forEach.getDataset()), result.getColumns()); + final List newColumns = new ArrayList<>(result.getColumns()); + newColumns.addAll(nestedResult.getColumns()); + result = new DatasetWithColumns(nestedResult.getDataset(), newColumns); + + } else { + throw new IllegalStateException("Unknown select clause type: " + select.getClass()); + } } - // Parse the column expressions. - final ParserContext columnContext = buildParserContext(inputContext, - singletonList(inputContext.getIdColumn())).withUnnestBehaviour(UnnestBehaviour.NOOP); - columnContext.getVariables().putAll(parserContext.getVariables()); - final List columnExpressions = view.getColumns().stream() - .map(NamedExpression::getExpression) - .collect(toList()); - // final List columnParseResult = - // parseExpressions(columnContext, columnExpressions); - final List columnParseResult = new ArrayList<>(); - final List columnPaths = columnParseResult; - - // Join the column expressions together. - final Dataset unfilteredDataset = columnPaths.get(columnPaths.size() - 1).getDataset(); - - // Filter the dataset. - final Dataset filteredDataset = filterDataset(inputContext, view.getFilters(), - unfilteredDataset, Column::and); - - // Select the column values. - final Column idColumn = inputContext.getIdColumn(); - final Column[] columnValues = labelColumns( - columnPaths.stream().map(FhirPath::getValueColumn), - view.getColumns().stream().map(NamedExpression::getName).map(Optional::of) - ).toArray(Column[]::new); - return filteredDataset.select(columnValues) - .filter(idColumn.isNotNull()); + return result; + } + + @Nonnull + private FhirPath parseExpression(@Nonnull final String expression, + @Nonnull final ParserContext context) { + final Parser parser = new Parser(context); + return parser.parse(expression); + } + + @Nonnull + private List parseExpressions(@Nonnull final List expressions, + @Nonnull final ParserContext context) { + return expressions.stream() + .reduce(new ArrayList<>(), (list, expression) -> { + final ParserContext currentContext = + list.isEmpty() + ? context + : context.withContextDataset(list.get(list.size() - 1).getDataset()); + + final Parser parser = new Parser(currentContext); + list.add(parser.parse(expression)); + return list; + + }, (list1, list2) -> { + list1.addAll(list2); + return list1; + }); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java new file mode 100644 index 0000000000..98dfa1a565 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java @@ -0,0 +1,34 @@ +package au.csiro.pathling.views; + +import java.util.List; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * A 'forEach' expression unnests a new row for each item in the specified FHIRPath expression, and + * users will select columns in the nested select. This differs from the 'from' expression above + * because it creates a new row for each item in the matched collection, unrolling that part of the + * resource. + * + * @author John Grimes + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class ForEachSelection extends SelectClause { + + /** + * The FHIRPath expression to unnest. + */ + @NotNull + String forEach; + + /** + * The nested select clauses. + */ + @NotNull + @Size(min = 1) + List select; + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FromSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/FromSelection.java new file mode 100644 index 0000000000..6e92a714c2 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FromSelection.java @@ -0,0 +1,32 @@ +package au.csiro.pathling.views; + +import java.util.List; +import javax.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * A 'from' expression is a convenience to select values relative to some parent FHIRPath. This does + * not unnest or unroll multiple values. If the 'from' results in a FHIRPath collection, that full + * collection is used in the nested select, so the resulting view would have repeated fields rather + * than a separate row per value. + * + * @author John Grimes + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class FromSelection extends SelectClause { + + /** + * The FHIRPath expression for the parent path to select from. + */ + @NotNull + String from; + + /** + * The nested select clauses. + */ + @NotNull + List select; + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/NamedExpression.java b/fhirpath/src/main/java/au/csiro/pathling/views/NamedExpression.java deleted file mode 100644 index dfbb8acd41..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/views/NamedExpression.java +++ /dev/null @@ -1,21 +0,0 @@ -package au.csiro.pathling.views; - -import javax.annotation.Nonnull; -import lombok.Getter; - -@Getter -public class NamedExpression { - - @Nonnull - private final String expression; - - @Nonnull - private final String name; - - - public NamedExpression(@Nonnull final String expression, @Nonnull final String name) { - this.expression = expression; - this.name = name; - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/SelectClause.java b/fhirpath/src/main/java/au/csiro/pathling/views/SelectClause.java new file mode 100644 index 0000000000..f08e57e4c6 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/SelectClause.java @@ -0,0 +1,17 @@ +package au.csiro.pathling.views; + +/** + * The select stanza defines the actual content of the view itself. This stanza is a list where each + * item in is one of: + *
    + *
  • a structure with the column name, expression, and optional description,
  • + *
  • a 'from' structure indicating a relative path to pull fields from in a nested select, or
  • + *
  • a 'forEach' structure, unrolling the specified path and creating a new row for each item.
  • + *
+ * See the comments below for details on the semantics. + * + * @author John Grimes + */ +public class SelectClause { + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/SelectClauseTypeAdapter.java b/fhirpath/src/main/java/au/csiro/pathling/views/SelectClauseTypeAdapter.java new file mode 100644 index 0000000000..17389ceb9b --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/SelectClauseTypeAdapter.java @@ -0,0 +1,52 @@ +package au.csiro.pathling.views; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapter; +import com.google.gson.internal.Streams; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import javax.annotation.Nonnull; + +/** + * This adapter is used to deserialize a {@link SelectClause} from JSON. It takes a look at the + * structure and decides which type of clause it is, then delegates deserialization to the + * appropriate adapter. + * + * @author John Grimes + */ +public class SelectClauseTypeAdapter extends TypeAdapter { + + @Nonnull + private final Gson gson; + + public SelectClauseTypeAdapter(@Nonnull final Gson gson) { + this.gson = gson; + } + + @Override + public void write(final JsonWriter out, final SelectClause value) throws IOException { + throw new UnsupportedOperationException("This adapter does not support writing"); + } + + @Override + public SelectClause read(final JsonReader in) throws IOException { + final JsonElement jsonElement = Streams.parse(in); + final JsonObject jsonObject = jsonElement.getAsJsonObject(); + + if (jsonObject.has("name")) { + return gson.fromJson(jsonObject, DirectSelection.class); + } else if (jsonObject.has("from")) { + return gson.fromJson(jsonObject, FromSelection.class); + } else if (jsonObject.has("forEach")) { + return gson.fromJson(jsonObject, ForEachSelection.class); + } else { + throw new JsonParseException( + "Select clause must contain either 'name', 'from', or 'forEach'"); + } + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/SelectClauseTypeAdapterFactory.java b/fhirpath/src/main/java/au/csiro/pathling/views/SelectClauseTypeAdapterFactory.java new file mode 100644 index 0000000000..0b4e54d162 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/SelectClauseTypeAdapterFactory.java @@ -0,0 +1,21 @@ +package au.csiro.pathling.views; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import javax.annotation.Nullable; + +public class SelectClauseTypeAdapterFactory implements TypeAdapterFactory { + + @Nullable + @Override + public TypeAdapter create(final Gson gson, final TypeToken type) { + if (SelectClause.class != type.getRawType()) { + return null; + } + //noinspection unchecked + return (TypeAdapter) new SelectClauseTypeAdapter(gson); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/VariableExpression.java b/fhirpath/src/main/java/au/csiro/pathling/views/VariableExpression.java deleted file mode 100644 index fa0ec8f0fc..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/views/VariableExpression.java +++ /dev/null @@ -1,17 +0,0 @@ -package au.csiro.pathling.views; - -import javax.annotation.Nonnull; -import lombok.Getter; - -@Getter -public class VariableExpression extends NamedExpression { - - private final WhenMany whenMany; - - public VariableExpression(@Nonnull final String expression, @Nonnull final String name, - final WhenMany whenMany) { - super(expression, name); - this.whenMany = whenMany; - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/WhenMany.java b/fhirpath/src/main/java/au/csiro/pathling/views/WhenMany.java deleted file mode 100644 index 490ed662e8..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/views/WhenMany.java +++ /dev/null @@ -1,48 +0,0 @@ -package au.csiro.pathling.views; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import lombok.Getter; - -public enum WhenMany { - - /** - * Indicates that a variable should not have repeated values. - */ - ERROR("error"), - - /** - * Indicates that a variable should create an array for repeated values. - */ - ARRAY("array"), - - /** - * Indicates that repeated values should be unnested into separate rows. Does not allow for - * multiple levels of nesting that have the root resource as their nearest common ancestor. - */ - UNNEST("unnest"); - - @Nonnull - @Getter - private final String code; - - WhenMany(@Nonnull final String code) { - this.code = code; - } - - /** - * Returns the {@link WhenMany} value corresponding to the given code. - * - * @param code the code to look up - * @return the corresponding {@link WhenMany} value - */ - public static WhenMany fromCode(@Nullable final String code) { - for (final WhenMany whenMany : values()) { - if (whenMany.code.equals(code)) { - return whenMany; - } - } - throw new IllegalArgumentException("Unknown code: " + code); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/WhereClause.java b/fhirpath/src/main/java/au/csiro/pathling/views/WhereClause.java new file mode 100644 index 0000000000..0f2d1acf4c --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/WhereClause.java @@ -0,0 +1,32 @@ +package au.csiro.pathling.views; + +import com.google.gson.annotations.SerializedName; +import javax.annotation.Nullable; +import javax.validation.constraints.NotNull; +import lombok.Data; + +/** + * 'where' filters are FHIRPath expressions joined with an implicit "and". This enables users to + * select a subset of rows that match a specific need. For example, a user may be interested only in + * a subset of observations based on code value and can filter them here. + * + * @author John Grimes + */ +@Data +public class WhereClause { + + /** + * The FHIRPath expression for the filter. + */ + @NotNull + @SerializedName("expr") + String expression; + + /** + * An optional human-readable description of the filter. + */ + @SerializedName("desc") + @Nullable + String description; + +} diff --git a/fhirpath/src/test/java/au/csiro/pathling/UnitTestDependencies.java b/fhirpath/src/test/java/au/csiro/pathling/UnitTestDependencies.java index 253232625f..fddbd66db1 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/UnitTestDependencies.java +++ b/fhirpath/src/test/java/au/csiro/pathling/UnitTestDependencies.java @@ -26,8 +26,11 @@ import au.csiro.pathling.terminology.TerminologyServiceFactory; import au.csiro.pathling.test.SharedMocks; import au.csiro.pathling.test.stubs.TestTerminologyServiceFactory; +import au.csiro.pathling.views.SelectClauseTypeAdapterFactory; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import javax.annotation.Nonnull; import org.apache.spark.sql.SparkSession; import org.fhir.ucum.UcumException; @@ -72,12 +75,12 @@ static SparkSession sparkSession( .master("local[1]") .appName("pathling-unittest") .config("spark.default.parallelism", 1) - .config("spark.driver.bindAddress","localhost") - .config("spark.driver.host","localhost") + .config("spark.driver.bindAddress", "localhost") + .config("spark.driver.host", "localhost") .config("spark.sql.shuffle.partitions", 1) .config("spark.sql.debug.maxToStringFields", 100) .config("spark.network.timeout", "600s") - .config("spark.ui.enabled",false) + .config("spark.ui.enabled", false) .config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension") .config("spark.sql.catalog.spark_catalog", "org.apache.spark.sql.delta.catalog.DeltaCatalog") @@ -129,4 +132,13 @@ static UcumService ucumService() throws UcumException { return Ucum.service(); } + @Bean + @ConditionalOnMissingBean + @Nonnull + static Gson gson() { + final GsonBuilder builder = new GsonBuilder(); + builder.registerTypeAdapterFactory(new SelectClauseTypeAdapterFactory()); + return builder.create(); + } + } diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java index 9f6a0bbc41..05b38767fb 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -1,29 +1,32 @@ package au.csiro.pathling.views; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; -import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.io.Database; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.assertions.DatasetAssert; import ca.uhn.fhir.context.FhirContext; +import com.google.gson.Gson; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; import java.nio.file.Path; -import java.util.List; import java.util.Optional; +import java.util.stream.Stream; +import javax.annotation.Nonnull; 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.hl7.fhir.r4.model.Enumerations.ResourceType; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; -import scala.collection.mutable.WrappedArray; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; @SpringBootUnitTest class FhirViewTest { @@ -37,6 +40,9 @@ class FhirViewTest { @Autowired FhirEncoders fhirEncoders; + @Autowired + Gson gson; + @MockBean TerminologyServiceFactory terminologyServiceFactory; @@ -47,469 +53,34 @@ class FhirViewTest { @BeforeEach void setUp() { - final QueryConfiguration queryConfiguration = QueryConfiguration.builder().build(); final DataSource dataSource = Database.forFileSystem(spark, fhirEncoders, TEST_DATA_PATH.toUri().toString(), true); - executor = new FhirViewExecutor(queryConfiguration, fhirContext, spark, dataSource, + executor = new FhirViewExecutor(fhirContext, spark, dataSource, Optional.of(terminologyServiceFactory)); } - // Test 1: - // Select ID, gender and birth date for each patient. - // Tests the use of singular elements with no unnesting. - // { - // "resource": "Patient", - // "columns": [ - // { - // "name": "id", - // "expr": "id" - // }, - // { - // "name": "gender", - // "expr": "gender" - // }, - // { - // "name": "birth_date", - // "expr": "birthDate" - // } - // ] - // } - @Test - void test1() { - final FhirView view = new FhirView(ResourceType.PATIENT, - List.of( - new NamedExpression("id", "id"), - new NamedExpression("gender", "gender"), - new NamedExpression("birthDate", "birth_date") - ), List.of(), List.of()); - final Dataset result = executor.buildQuery(view); - - // Expected result: - // | id | gender | birth_date | - // |----|--------|------------| - // | 1 | female | 1959-09-27 | - // | 2 | male | 1983-09-06 | - DatasetAssert.of(result).hasRows( - RowFactory.create("1", "female", "1959-09-27"), - RowFactory.create("2", "male", "1983-09-06") - ); - } - - // Test 2: - // Select ID, name use and family name for each patient. - // Tests the unnesting of two elements that are singular relative to their common repeating - // parent. - // { - // "resource": "Patient", - // "vars": [ - // { - // "name": "name", - // "expr": "name", - // "whenMany": "unnest" - // } - // ], - // "columns": [ - // { - // "name": "id", - // "expr": "id" - // }, - // { - // "name": "name_use", - // "expr": "%name.use" - // }, - // { - // "name": "family_name", - // "expr": "%name.family" - // } - // ] - // } - @Test - void test2() { - final FhirView view = new FhirView(ResourceType.PATIENT, - List.of( - new NamedExpression("id", "id"), - new NamedExpression("%name.use", "name_use"), - new NamedExpression("%name.family", "family_name") - ), - List.of( - new VariableExpression("name", "name", WhenMany.UNNEST) - ), - List.of()); - final Dataset result = executor.buildQuery(view); - - // Expected result: - // | id | name_use | family_name | - // |----|----------|-------------| - // | 1 | maiden | Wuckert | - // | 1 | official | Oberbrunner | - // | 2 | nickname | Cleveland | - // | 2 | official | Towne | - DatasetAssert.of(result).hasRowsUnordered( - RowFactory.create("1", "maiden", "Wuckert"), - RowFactory.create("1", "official", "Oberbrunner"), - RowFactory.create("2", "nickname", "Cleveland"), - RowFactory.create("2", "official", "Towne") - ); - } - - // Test 3: - // Select ID, family name and given name for each patient. - // Tests multiple levels of unnesting that share the same lineage. - // { - // "resource": "Patient", - // "vars": [ - // { - // "name": "name", - // "expr": "name", - // "whenMany": "unnest" - // }, - // { - // "name": "givenName", - // "expr": "name.given", - // "whenMany": "unnest" - // } - // ], - // "columns": [ - // { - // "name": "id", - // "expr": "id" - // }, - // { - // "name": "family_name", - // "expr": "%name.family" - // }, - // { - // "name": "given_name", - // "expr": "%givenName" - // } - // ] - // } - @Test - void test3() { - final FhirView view = new FhirView(ResourceType.PATIENT, - List.of( - new NamedExpression("id", "id"), - new NamedExpression("%name.family", "family_name"), - new NamedExpression("%givenName", "given_name") - ), - List.of( - new VariableExpression("name", "name", WhenMany.UNNEST), - new VariableExpression("name.given", "givenName", WhenMany.UNNEST) - ), - List.of()); - final Dataset result = executor.buildQuery(view); - - // Expected result: - // | id | family_name | given_name | - // |----|-------------|------------| - // | 1 | Wuckert | Karina | - // | 1 | Oberbrunner | Karina | - // | 2 | Towne | Guy | - // | 2 | Cleveland | Maponos | - // | 2 | Cleveland | Wilburg | - DatasetAssert.of(result).hasRowsUnordered( - RowFactory.create("1", "Wuckert", "Karina"), - RowFactory.create("1", "Oberbrunner", "Karina"), - RowFactory.create("2", "Towne", "Guy"), - RowFactory.create("2", "Cleveland", "Maponos"), - RowFactory.create("2", "Cleveland", "Wilburg") - ); + @Nonnull + static Stream requests() throws IOException { + // Get all classpath entries with a prefix of "requests/views", and an extension of ".json". + final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + final Resource[] resources = resolver.getResources("classpath:requests/views/*.json"); + return Stream.of(resources).map(resource -> { + try { + return resource.getFile(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + }).map(File::toPath); } - // Test 4: - // Select ID, name prefix, family name, marital status system and marital status code for each - // patient. - // Tests two sets of multi-level nesting that have the root resource as their nearest common - // ancestor. - // { - // "resource": "Patient", - // "vars": [ - // { - // "name": "name", - // "expr": "name", - // "whenMany": "unnest" - // }, - // { - // "name": "namePrefix", - // "expr": "name.prefix", - // "whenMany": "unnest" - // }, - // { - // "name": "maritalStatus", - // "expr": "maritalStatus.coding", - // "whenMany": "unnest" - // } - // ], - // "columns": [ - // { - // "name": "id", - // "expr": "id" - // }, - // { - // "name": "name_prefix", - // "expr": "%namePrefix" - // }, - // { - // "name": "family_name", - // "expr": "%name.family" - // }, - // { - // "name": "marital_status_system", - // "expr": "%maritalStatus.system" - // }, - // { - // "name": "marital_status_code", - // "expr": "%maritalStatus.code" - // } - // ] - // } - @Test - void test4() { - final FhirView view = new FhirView(ResourceType.PATIENT, - List.of( - new NamedExpression("id", "id"), - new NamedExpression("%namePrefix", "name_prefix"), - new NamedExpression("%name.family", "family_name"), - new NamedExpression("%maritalStatus.system", "marital_status_system"), - new NamedExpression("%maritalStatus.code", "marital_status_code") - ), - List.of( - new VariableExpression("name", "name", WhenMany.UNNEST), - new VariableExpression("name.prefix", "namePrefix", - WhenMany.UNNEST), - new VariableExpression("maritalStatus.coding", "maritalStatus", WhenMany.UNNEST) - ), - List.of()); + @ParameterizedTest + @MethodSource("requests") + void test(@Nonnull final Path request) throws IOException { + final FhirView view = gson.fromJson(new FileReader(request.toFile()), FhirView.class); final Dataset result = executor.buildQuery(view); - - // Expected result: - // | id | name_prefix | family_name | marital_status_system | marital_status_code | - // |----|-------------|-------------|--------------------------------------------------------|---------------------| - // | 1 | Miss. | Wuckert | http://terminology.hl7.org/CodeSystem/v3-MaritalStatus | M | - // | 1 | Miss. | Wuckert | http://snomed.info/sct | 87915002 | - // | 1 | Mrs. | Oberbrunner | http://terminology.hl7.org/CodeSystem/v3-MaritalStatus | M | - // | 1 | Mrs. | Oberbrunner | http://snomed.info/sct | 87915002 | - // | 2 | Mr. | Towne | NULL | NULL | - // | 2 | Prof. | Cleveland | NULL | NULL | - DatasetAssert.of(result).hasRowsUnordered( - RowFactory.create("1", "Miss.", "Wuckert", - "http://terminology.hl7.org/CodeSystem/v3-MaritalStatus", "M"), - RowFactory.create("1", "Miss.", "Wuckert", "http://snomed.info/sct", "87915002"), - RowFactory.create("1", "Mrs.", "Oberbrunner", - "http://terminology.hl7.org/CodeSystem/v3-MaritalStatus", "M"), - RowFactory.create("1", "Mrs.", "Oberbrunner", "http://snomed.info/sct", "87915002"), - RowFactory.create("2", "Mr.", "Towne", null, null), - RowFactory.create("2", "Prof.", "Cleveland", null, null) - ); - } - - // Test case 5: - // Select ID and given name for each patient, but leave the given names in an array. - // Tests the ability to return columns as arrays, where the expressions that define those columns - // return a collection. - // { - // "resource": "Patient", - // "vars": [ - // { - // "name": "givenName", - // "expr": "%name.given", - // "whenMany": "array" - // } - // ], - // "columns": [ - // { - // "name": "id", - // "expr": "id" - // }, - // { - // "name": "given_name", - // "expr": "%givenName" - // } - // ] - // } - @Test - void test5() { - final FhirView view = new FhirView(ResourceType.PATIENT, - List.of( - new NamedExpression("id", "id"), - new NamedExpression("%givenName", "given_name") - ), - List.of( - new VariableExpression("name.given", "givenName", WhenMany.ARRAY) - ), - List.of()); - final Dataset result = executor.buildQuery(view); - - // Expected result: - // | id | given_name | - // |----|-------------------------| - // | 1 | [Karina, Karina] | - // | 2 | [Guy, Maponos, Wilburg] | - DatasetAssert.of(result).hasRowsUnordered( - RowFactory.create("1", WrappedArray.make(List.of("Karina", "Karina").toArray())), - RowFactory.create("2", WrappedArray.make(List.of("Guy", "Maponos", "Wilburg").toArray())) - ); - } - - // Test case 6: - // Select ID and given name for each patient, with a row for each name and the given names for - // each name within an array. - // Tests the ability to use the unnest and array directives together. - // { - // "resource": "Patient", - // "vars": [ - // { - // "name": "name", - // "expr": "name", - // "whenMany": "unnest" - // }, - // { - // "name": "givenName", - // "expr": "%name.given", - // "whenMany": "array" - // } - // ], - // "columns": [ - // { - // "name": "id", - // "expr": "id" - // }, - // { - // "name": "given_name", - // "expr": "%givenName" - // } - // ] - // } - @Test - void test6() { - final FhirView view = new FhirView(ResourceType.PATIENT, - List.of( - new NamedExpression("id", "id"), - new NamedExpression("%name.given", "given_name") - ), - List.of( - new VariableExpression("name", "name", WhenMany.UNNEST), - new VariableExpression("name.given", "givenName", WhenMany.ARRAY) - ), - List.of()); - final Dataset result = executor.buildQuery(view); - - // Expected result: - // | id | given_name | - // |----|--------------------| - // | 1 | [Karina] | - // | 1 | [Karina] | - // | 2 | [Guy] | - // | 2 | [Maponos, Wilburg] | - DatasetAssert.of(result).hasRowsUnordered( - RowFactory.create("1", WrappedArray.make(List.of("Karina").toArray())), - RowFactory.create("1", WrappedArray.make(List.of("Karina").toArray())), - RowFactory.create("2", WrappedArray.make(List.of("Guy").toArray())), - RowFactory.create("2", WrappedArray.make(List.of("Maponos", "Wilburg").toArray())) - ); - } - - // Test case 7: - // Select ID and given name for each patient, with the given names for each name represented - // within a nested array. - // Tests the ability to use the multiple array directives together. - // { - // "resource": "Patient", - // "vars": [ - // { - // - // "name": "name", - // "expr": "name", - // "whenMany": "unnest" - // }, - // { - // "name": "givenName", - // "expr": "%name.given", - // "whenMany": "array" - // } - // ], - // "columns": [ - // { - // "name": "id", - // "expr": "id" - // }, - // { - // "name": "given_name", - // "expr": "%givenName" - // } - // ] - // } - @Test - void test7() { - final FhirView view = new FhirView(ResourceType.PATIENT, - List.of( - new NamedExpression("id", "id"), - new NamedExpression("%name.given", "given_name") - ), - List.of( - new VariableExpression("name", "name", WhenMany.ARRAY), - new VariableExpression("name.given", "givenName", WhenMany.ARRAY) - ), - List.of()); - final Dataset result = executor.buildQuery(view); - - // Expected result: - // | id | given_name | - // |----|-----------------------------| - // | 1 | [[Karina], [Karina]] | - // | 2 | [[Guy], [Maponos, Wilburg]] | - DatasetAssert.of(result).hasRowsUnordered( - RowFactory.create("1", WrappedArray.make(List.of( - WrappedArray.make(List.of("Karina").toArray()), - WrappedArray.make(List.of("Karina").toArray()) - ).toArray())), - RowFactory.create("2", WrappedArray.make(List.of( - WrappedArray.make(List.of("Guy").toArray()), - WrappedArray.make(List.of("Maponos", "Wilburg").toArray()) - ).toArray())) - ); - } - - // Test case 8: - // Select ID and family name for each patient, but force an error through the use of the `error` - // value for `whenMany`. - // This tests the behaviour of the `error` value for `whenMany`. - // { - // "resource": "Patient", - // "vars": [ - // { - // - // "name": "name", - // "expr": "name", - // "whenMany": "error" - // } - // ], - // "columns": [ - // { - // "name": "id", - // "expr": "id" - // }, - // { - // "name": "family_name", - // "expr": "%name.family" - // } - // ] - // } - @Test - void test8() { - final FhirView view = new FhirView(ResourceType.PATIENT, - List.of( - new NamedExpression("id", "id"), - new NamedExpression("%name.family", "family_name") - ), - List.of( - new VariableExpression("name", "name", WhenMany.ERROR) - ), - List.of()); - - assertThrows(InvalidUserInputError.class, () -> { - executor.buildQuery(view); - }); + assertThat(result) + .hasRows(spark, "results/views/" + + request.getFileName().toString().replace(".json", ".csv")); } } diff --git a/fhirpath/src/test/resources/requests/views/singularNoUnnesting.json b/fhirpath/src/test/resources/requests/views/singularNoUnnesting.json new file mode 100644 index 0000000000..26b60412a7 --- /dev/null +++ b/fhirpath/src/test/resources/requests/views/singularNoUnnesting.json @@ -0,0 +1,18 @@ +{ + "name": "Singular elements, no unnesting", + "resource": "Patient", + "select": [ + { + "name": "id", + "expr": "id" + }, + { + "name": "gender", + "expr": "gender" + }, + { + "name": "birth_date", + "expr": "birthDate" + } + ] +} diff --git a/fhirpath/src/test/resources/results/views/singularNoUnnesting.csv b/fhirpath/src/test/resources/results/views/singularNoUnnesting.csv new file mode 100644 index 0000000000..12eede1a92 --- /dev/null +++ b/fhirpath/src/test/resources/results/views/singularNoUnnesting.csv @@ -0,0 +1,2 @@ +1,female,1959-09-27 +2,male,1983-09-06 diff --git a/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java b/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java index dcece81d17..c1f3868e26 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java +++ b/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java @@ -20,9 +20,6 @@ import static au.csiro.pathling.utilities.Preconditions.requireNonBlank; import au.csiro.pathling.views.FhirView; -import au.csiro.pathling.views.NamedExpression; -import au.csiro.pathling.views.VariableExpression; -import au.csiro.pathling.views.WhenMany; import java.util.ArrayList; import java.util.List; import javax.annotation.Nonnull; From 3a0e5ebfd8c37a68843b3a17c2c434bb0100c27e Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sat, 29 Jul 2023 12:02:45 +1000 Subject: [PATCH 47/95] Add new test: nested singular elements --- .../csiro/pathling/search/SearchExecutor.java | 4 +- .../java/au/csiro/pathling/QueryExecutor.java | 8 +-- .../fhirpath/parser/ParserContext.java | 11 ++++ ...hColumns.java => ContextAndSelection.java} | 9 ++-- .../pathling/views/FhirViewExecutor.java | 52 +++++++++---------- .../pathling/test/assertions/Assertions.java | 15 ++++-- .../test/assertions/DatasetAssert.java | 7 ++- .../au/csiro/pathling/views/FhirViewTest.java | 47 ++++++++++++----- .../requests/views/nestedSingular.json | 23 ++++++++ .../results/views/nestedSingular.csv | 5 ++ .../results/views/singularNoUnnesting.csv | 1 + 11 files changed, 129 insertions(+), 53 deletions(-) rename fhirpath/src/main/java/au/csiro/pathling/views/{DatasetWithColumns.java => ContextAndSelection.java} (54%) create mode 100644 fhirpath/src/test/resources/requests/views/nestedSingular.json create mode 100644 fhirpath/src/test/resources/results/views/nestedSingular.csv diff --git a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java index 9e41d15941..b80743b6e4 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java @@ -127,8 +127,8 @@ private Dataset initializeDataset() { } else { final ParserContext parserContext = new ParserContext(resourcePath, fhirContext, sparkSession, - dataSource, - terminologyServiceFactory, Collections.singletonList(resourcePath.getIdColumn())); + dataSource, terminologyServiceFactory, + Collections.singletonList(resourcePath.getIdColumn())); Dataset currentDataset = subjectDataset; @Nullable Column filterCondition = null; diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index fd29fe908c..7837bb8c61 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -61,16 +61,16 @@ public abstract class QueryExecutor { private final QueryConfiguration configuration; @Nonnull - private final FhirContext fhirContext; + protected final FhirContext fhirContext; @Nonnull - private final SparkSession sparkSession; + protected final SparkSession sparkSession; @Nonnull - private final DataSource dataSource; + protected final DataSource dataSource; @Nonnull - private final Optional terminologyServiceFactory; + protected final Optional terminologyServiceFactory; protected QueryExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index b7465675e4..e3d978fd53 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -180,6 +180,17 @@ public ParserContext withUnnestBehaviour(@Nonnull final UnnestBehaviour unnestBe return context; } + /** + * Creates a copy of the current parser context with a different input context. + * + * @return a new {@link ParserContext} + */ + public ParserContext withInputContext(@Nonnull final FhirPath inputContext) { + return new ParserContext(inputContext, fhirContext, + sparkSession, dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, + variables, nesting); + } + /** * Creates a copy of the current parser context with a different input dataset. * diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/DatasetWithColumns.java b/fhirpath/src/main/java/au/csiro/pathling/views/ContextAndSelection.java similarity index 54% rename from fhirpath/src/main/java/au/csiro/pathling/views/DatasetWithColumns.java rename to fhirpath/src/main/java/au/csiro/pathling/views/ContextAndSelection.java index 2ca3b4a34e..4f02166c89 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/DatasetWithColumns.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ContextAndSelection.java @@ -1,19 +1,18 @@ package au.csiro.pathling.views; +import au.csiro.pathling.fhirpath.FhirPath; import java.util.List; import javax.annotation.Nonnull; import lombok.Value; import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; @Value -public class DatasetWithColumns { +public class ContextAndSelection { @Nonnull - Dataset dataset; + FhirPath context; @Nonnull - List columns; + List selection; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index c8fad2fa55..9efc0638b2 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -58,7 +58,7 @@ public Dataset buildQuery(@Nonnull final FhirView view) { final ParserContext parserContext = new ParserContext(inputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, singletonList(inputContext.getIdColumn())); - final DatasetWithColumns select = parseSelect(view.getSelect(), parserContext, + final ContextAndSelection select = parseSelect(view.getSelect(), parserContext, Collections.emptyList()); final List where = view.getWhere() == null ? Collections.emptyList() @@ -69,42 +69,42 @@ public Dataset buildQuery(@Nonnull final FhirView view) { .map(FhirPath::getValueColumn) .reduce(Column::and); return filterCondition - .map(select.getDataset()::filter) - .orElse(select.getDataset()) - .select(select.getColumns().toArray(new Column[0])); + .map(select.getContext().getDataset()::filter) + .orElse(select.getContext().getDataset()) + .select(select.getSelection().toArray(new Column[0])); } @Nonnull - private DatasetWithColumns parseSelect(@Nonnull final List selectGroup, - @Nonnull final ParserContext context, @Nonnull final List columns) { - @Nonnull DatasetWithColumns result = new DatasetWithColumns( - context.getInputContext().getDataset(), columns); + private ContextAndSelection parseSelect(@Nonnull final List selectGroup, + @Nonnull final ParserContext context, @Nonnull final List selection) { + @Nonnull ContextAndSelection result = new ContextAndSelection( + context.getInputContext(), selection); for (final SelectClause select : selectGroup) { if (select instanceof DirectSelection) { - final FhirPath path = parseExpression(((DirectSelection) select).getExpression(), - context.withContextDataset(result.getDataset())); - final List newColumns = new ArrayList<>(result.getColumns()); - newColumns.add(path.getValueColumn()); - result = new DatasetWithColumns(path.getDataset(), newColumns); + final DirectSelection directSelection = (DirectSelection) select; + final FhirPath path = parseExpression(directSelection.getExpression(), + context.withInputContext(result.getContext())); + final List newColumns = new ArrayList<>(result.getSelection()); + newColumns.add(path.getValueColumn().alias(directSelection.getName())); + result = new ContextAndSelection(context.getInputContext().withDataset(path.getDataset()), + newColumns); } else if (select instanceof FromSelection) { - final FhirPath from = parseExpression(((FromSelection) select).getFrom(), - context.withContextDataset(result.getDataset())); - final DatasetWithColumns nestedResult = parseSelect(((FromSelection) select).getSelect(), - context.withContextDataset(from.getDataset()), result.getColumns()); - final List newColumns = new ArrayList<>(result.getColumns()); - newColumns.addAll(nestedResult.getColumns()); - result = new DatasetWithColumns(nestedResult.getDataset(), newColumns); + final FromSelection fromSelection = (FromSelection) select; + final FhirPath from = parseExpression(fromSelection.getFrom(), + context.withInputContext(result.getContext())); + final ContextAndSelection nestedResult = parseSelect(fromSelection.getSelect(), + context.withInputContext(from), result.getSelection()); + result = new ContextAndSelection(nestedResult.getContext(), nestedResult.getSelection()); } else if (select instanceof ForEachSelection) { final FhirPath forEach = parseExpression(((ForEachSelection) select).getForEach(), - context.withContextDataset(result.getDataset())); - final DatasetWithColumns nestedResult = parseSelect(((ForEachSelection) select).getSelect(), - context.withContextDataset(forEach.getDataset()), result.getColumns()); - final List newColumns = new ArrayList<>(result.getColumns()); - newColumns.addAll(nestedResult.getColumns()); - result = new DatasetWithColumns(nestedResult.getDataset(), newColumns); + context.withInputContext(result.getContext())); + final ContextAndSelection nestedResult = parseSelect( + ((ForEachSelection) select).getSelect(), context.withInputContext(forEach), + result.getSelection()); + result = new ContextAndSelection(nestedResult.getContext(), nestedResult.getSelection()); } else { throw new IllegalStateException("Unknown select clause type: " + select.getClass()); diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java index d230882bbe..7ac51fb44e 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java @@ -28,6 +28,7 @@ import java.util.regex.Pattern; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; +import org.apache.spark.sql.DataFrameReader; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; @@ -67,11 +68,19 @@ public static void assertMatches(@Nonnull final String expectedRegex, public static void assertDatasetAgainstCsv(@Nonnull final SparkSession spark, @Nonnull final String expectedCsvPath, @Nonnull final Dataset actualDataset) { + assertDatasetAgainstCsv(spark, expectedCsvPath, actualDataset, false); + } + + public static void assertDatasetAgainstCsv(@Nonnull final SparkSession spark, + @Nonnull final String expectedCsvPath, @Nonnull final Dataset actualDataset, + final boolean header) { final URL url = getResourceAsUrl(expectedCsvPath); final String decodedUrl = URLDecoder.decode(url.toString(), StandardCharsets.UTF_8); - final Dataset expectedDataset = spark.read() - .schema(actualDataset.schema()) - .csv(decodedUrl); + final DataFrameReader reader = spark.read().schema(actualDataset.schema()); + if (header) { + reader.option("header", true); + } + final Dataset expectedDataset = reader.csv(decodedUrl); new DatasetAssert(actualDataset) .hasRowsUnordered(expectedDataset); } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java index 8934dcff61..6bc5f7d928 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java @@ -86,9 +86,14 @@ public DatasetAssert hasRows(@Nonnull final Dataset expected) { @Nonnull public DatasetAssert hasRows(@Nonnull final SparkSession spark, @Nonnull final String expectedCsvPath) { + return hasRows(spark, expectedCsvPath, false); + } + + public DatasetAssert hasRows(@Nonnull final SparkSession spark, + @Nonnull final String expectedCsvPath, final boolean header) { dataset.explain(); dataset.show(1000, false); - assertDatasetAgainstCsv(spark, expectedCsvPath, dataset); + assertDatasetAgainstCsv(spark, expectedCsvPath, dataset, header); return this; } diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java index 05b38767fb..b7428b5951 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -10,16 +10,20 @@ import ca.uhn.fhir.context.FhirContext; import com.google.gson.Gson; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; import java.util.stream.Stream; import javax.annotation.Nonnull; +import lombok.Value; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; @@ -29,6 +33,7 @@ import org.springframework.core.io.support.ResourcePatternResolver; @SpringBootUnitTest +@TestInstance(Lifecycle.PER_CLASS) class FhirViewTest { @Autowired @@ -60,27 +65,45 @@ void setUp() { } @Nonnull - static Stream requests() throws IOException { - // Get all classpath entries with a prefix of "requests/views", and an extension of ".json". + Stream requests() throws IOException { final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); final Resource[] resources = resolver.getResources("classpath:requests/views/*.json"); return Stream.of(resources).map(resource -> { - try { - return resource.getFile(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - }).map(File::toPath); + try { + return resource.getFile(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + }).map(File::toPath) + .map(path -> { + try { + final FhirView view = gson.fromJson(new FileReader(path.toFile()), FhirView.class); + return new TestParameters(view, path); + } catch (final FileNotFoundException e) { + throw new RuntimeException(e); + } + }); } @ParameterizedTest @MethodSource("requests") - void test(@Nonnull final Path request) throws IOException { - final FhirView view = gson.fromJson(new FileReader(request.toFile()), FhirView.class); - final Dataset result = executor.buildQuery(view); + void test(@Nonnull final TestParameters parameters) { + final Dataset result = executor.buildQuery(parameters.getView()); assertThat(result) .hasRows(spark, "results/views/" + - request.getFileName().toString().replace(".json", ".csv")); + parameters.getRequestFile().getFileName().toString().replace(".json", ".csv"), true); + } + + @Value + static class TestParameters { + + FhirView view; + Path requestFile; + + @Override + public String toString() { + return view.getName(); + } } } diff --git a/fhirpath/src/test/resources/requests/views/nestedSingular.json b/fhirpath/src/test/resources/requests/views/nestedSingular.json new file mode 100644 index 0000000000..095854da20 --- /dev/null +++ b/fhirpath/src/test/resources/requests/views/nestedSingular.json @@ -0,0 +1,23 @@ +{ + "name": "Nested singular elements", + "resource": "Patient", + "select": [ + { + "name": "id", + "expr": "id" + }, + { + "forEach": "name", + "select": [ + { + "name": "name_use", + "expr": "use" + }, + { + "name": "family_name", + "expr": "family" + } + ] + } + ] +} diff --git a/fhirpath/src/test/resources/results/views/nestedSingular.csv b/fhirpath/src/test/resources/results/views/nestedSingular.csv new file mode 100644 index 0000000000..c1981c06f9 --- /dev/null +++ b/fhirpath/src/test/resources/results/views/nestedSingular.csv @@ -0,0 +1,5 @@ +id,name_use,family_name +1,maiden,Wuckert +1,official,Oberbrunner +2,nickname,Cleveland +2,official,Towne diff --git a/fhirpath/src/test/resources/results/views/singularNoUnnesting.csv b/fhirpath/src/test/resources/results/views/singularNoUnnesting.csv index 12eede1a92..edd1aa46b7 100644 --- a/fhirpath/src/test/resources/results/views/singularNoUnnesting.csv +++ b/fhirpath/src/test/resources/results/views/singularNoUnnesting.csv @@ -1,2 +1,3 @@ +id,gender,birth_date 1,female,1959-09-27 2,male,1983-09-06 From 9cd1f8e6d87ff5e042f198d8ff385bdeac2ef53c Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sat, 29 Jul 2023 13:49:07 +1000 Subject: [PATCH 48/95] Add explicit unnesting --- .../au/csiro/pathling/fhirpath/FhirPath.java | 3 + .../pathling/fhirpath/NonLiteralPath.java | 12 +++ .../fhirpath/literal/LiteralPath.java | 6 ++ .../operator/PathTraversalOperator.java | 77 ++++--------------- .../pathling/views/FhirViewExecutor.java | 3 +- .../au/csiro/pathling/views/FhirViewTest.java | 2 +- 6 files changed, 38 insertions(+), 65 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java index 023454308e..96c694ae0d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java @@ -127,6 +127,9 @@ NonLiteralPath combineWith(@Nonnull FhirPath target, @Nonnull Dataset datas @Nonnull String expression, @Nonnull Column idColumn, @Nonnull Column valueColumn, boolean singular, @Nonnull Optional thisColumn); + @Nonnull + FhirPath unnest(); + /** * Prints out to stdout all the ids and values of all the elements in this path. For debugging * purposes only. diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java index 439742c9c5..2ee500e4d5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java @@ -17,10 +17,13 @@ package au.csiro.pathling.fhirpath; +import static au.csiro.pathling.QueryHelpers.createColumn; import static au.csiro.pathling.QueryHelpers.getUnionableColumns; import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static au.csiro.pathling.utilities.Preconditions.checkPresent; +import static org.apache.spark.sql.functions.explode_outer; +import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.function.NamedFunction; import au.csiro.pathling.fhirpath.literal.NullLiteralPath; @@ -211,4 +214,13 @@ public Dataset getUnionableDataset(@Nonnull final FhirPath target) { return getDataset().select(getUnionableColumns(this, target).toArray(new Column[]{})); } + @Nonnull + @Override + public FhirPath unnest() { + final DatasetWithColumn datasetWithColumn = createColumn(getDataset(), + explode_outer(getValueColumn())); + return copy(getExpression(), datasetWithColumn.getDataset(), datasetWithColumn.getColumn(), + datasetWithColumn.getColumn(), getOrderingColumn(), isSingular(), getThisColumn()); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java index c20d364466..73fb642e5f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java @@ -232,4 +232,10 @@ public Dataset getOrderedDataset(@Nonnull final Nesting nesting) { return dataset; } + @Nonnull + @Override + public FhirPath unnest() { + return this; + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java index a9131be415..645b14d984 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java @@ -17,23 +17,18 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.QueryHelpers.explodeArray; +import static au.csiro.pathling.QueryHelpers.createColumn; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static org.apache.spark.sql.functions.lit; -import static org.apache.spark.sql.functions.when; +import static org.apache.spark.sql.functions.col; -import au.csiro.pathling.encoders.ExtensionSupport; -import au.csiro.pathling.errors.InvalidUserInputError; +import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Nesting; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.parser.UnnestBehaviour; import java.util.Optional; import javax.annotation.Nonnull; -import org.apache.commons.lang3.tuple.MutablePair; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; @@ -74,64 +69,20 @@ public NonLiteralPath invoke(@Nonnull final PathTraversalInput input) { final ElementDefinition childDefinition = optionalChild.get(); final Dataset leftDataset = left.getDataset(); - final Nesting nesting = input.getContext().getNesting(); - - // If the element has a max cardinality of more than one, it will need to be unnested. - final boolean maxCardinalityOfOne = childDefinition.getMaxCardinality() == 1; - final boolean resultSingular = left.isSingular() && maxCardinalityOfOne; - - final Column field; - if (ExtensionSupport.EXTENSION_ELEMENT_NAME().equals(right)) { - // Lookup the extensions by _fid in the extension container. - field = left.getExtensionContainerColumn() - .apply(getValueField(left, ExtensionSupport.FID_FIELD_NAME())); - } else { - field = getValueField(left, right); - } - - final UnnestBehaviour unnestBehaviour = input.getContext().getUnnestBehaviour(); - - if (!maxCardinalityOfOne && unnestBehaviour == UnnestBehaviour.ERROR) { - throw new InvalidUserInputError("Encountered a repeating element: " + expression); - - } else if (maxCardinalityOfOne || unnestBehaviour == UnnestBehaviour.NOOP) { - return ElementPath.build(expression, leftDataset, left.getIdColumn(), field, - Optional.empty(), resultSingular, left.getCurrentResource(), left.getThisColumn(), - childDefinition); - - } else if (unnestBehaviour == UnnestBehaviour.UNNEST) { - return nesting.updateOrRetrieve(childDefinition, expression, leftDataset, false, - left.getThisColumn(), key -> { - final MutablePair valueAndOrderingColumns = new MutablePair<>(); - final Dataset resultDataset = explodeArray(left.getDataset(), field, - valueAndOrderingColumns); - return ElementPath.build(expression, resultDataset, left.getIdColumn(), - valueAndOrderingColumns.getLeft(), Optional.of(valueAndOrderingColumns.getRight()), - false, left.getCurrentResource(), left.getThisColumn(), childDefinition); - }); - + final Dataset result; + final Column valueColumn; + if (left instanceof ResourcePath) { + result = leftDataset; + valueColumn = ((ResourcePath) left).getElementColumn(right); } else { - throw new UnsupportedOperationException("Unsupported unnest behaviour: " + unnestBehaviour); + final DatasetWithColumn datasetAndColumn = createColumn(leftDataset, + col(left.getValueColumn() + "." + right)); + result = datasetAndColumn.getDataset(); + valueColumn = datasetAndColumn.getColumn(); } - } - - @Nonnull - private static Column getValueField(@Nonnull final NonLiteralPath path, - @Nonnull final String fieldName) { - // If the input path is a ResourcePath, we look for a bare column. Otherwise, we will need to - // extract it from a struct. - final Column field; - if (path instanceof ResourcePath) { - final ResourcePath resourcePath = (ResourcePath) path; - // When the value column of the ResourcePath is null, the path traversal results in null. This - // can happen when attempting to do a path traversal on the result of a function like when. - field = when(resourcePath.getValueColumn().isNull(), lit(null)) - .otherwise(resourcePath.getElementColumn(fieldName)); - } else { - field = path.getValueColumn().getField(fieldName); - } - return field; + return ElementPath.build(expression, result, left.getIdColumn(), valueColumn, Optional.empty(), + false, left.getCurrentResource(), left.getThisColumn(), childDefinition); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index 9efc0638b2..31c6953c90 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -101,8 +101,9 @@ private ContextAndSelection parseSelect(@Nonnull final List select } else if (select instanceof ForEachSelection) { final FhirPath forEach = parseExpression(((ForEachSelection) select).getForEach(), context.withInputContext(result.getContext())); + final FhirPath unnested = forEach.unnest(); final ContextAndSelection nestedResult = parseSelect( - ((ForEachSelection) select).getSelect(), context.withInputContext(forEach), + ((ForEachSelection) select).getSelect(), context.withInputContext(unnested), result.getSelection()); result = new ContextAndSelection(nestedResult.getContext(), nestedResult.getSelection()); diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java index b7428b5951..075c4b227c 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -59,7 +59,7 @@ class FhirViewTest { @BeforeEach void setUp() { final DataSource dataSource = Database.forFileSystem(spark, fhirEncoders, - TEST_DATA_PATH.toUri().toString(), true); + TEST_DATA_PATH.toUri().toString(), false); executor = new FhirViewExecutor(fhirContext, spark, dataSource, Optional.of(terminologyServiceFactory)); } From e6519eab532652f94f4459fd69574899a7785bd5 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sun, 30 Jul 2023 09:10:55 +1000 Subject: [PATCH 49/95] Add new test: related unnesting --- .../fhirpath/element/ElementPath.java | 13 +---- .../fhirpath/parser/ParserContext.java | 4 +- .../pathling/views/ContextAndSelection.java | 6 +- .../pathling/views/FhirViewExecutor.java | 58 ++++++++++++------- .../pathling/views/ForEachSelection.java | 6 +- .../csiro/pathling/views/FromSelection.java | 6 +- .../pathling/views/NestedSelectClause.java | 11 ++++ .../au/csiro/pathling/views/SelectClause.java | 4 +- .../requests/views/relatedUnnesting.json | 28 +++++++++ .../results/views/relatedUnnesting.csv | 6 ++ 10 files changed, 103 insertions(+), 39 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/NestedSelectClause.java create mode 100644 fhirpath/src/test/resources/requests/views/relatedUnnesting.json create mode 100644 fhirpath/src/test/resources/results/views/relatedUnnesting.csv diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java index 1de32e1b46..40d91f1cac 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java @@ -17,9 +17,6 @@ package au.csiro.pathling.fhirpath.element; -import static au.csiro.pathling.QueryHelpers.createColumns; - -import au.csiro.pathling.QueryHelpers.DatasetWithColumnMap; import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; @@ -150,20 +147,14 @@ private static ElementPath getInstance(@Nonnull final String expression, final Class elementPathClass = ElementDefinition .elementClassForType(fhirType).orElse(ElementPath.class); - final DatasetWithColumnMap datasetWithColumns = orderingColumn - .map(column -> createColumns(dataset, column, valueColumn)) - .orElseGet(() -> createColumns(dataset, valueColumn)); - try { // Call its constructor and return. final Constructor constructor = elementPathClass .getDeclaredConstructor(String.class, Dataset.class, Column.class, Column.class, Optional.class, boolean.class, Optional.class, Optional.class, FHIRDefinedType.class); return constructor - .newInstance(expression, datasetWithColumns.getDataset(), idColumn, - datasetWithColumns.getColumn(valueColumn), - orderingColumn.map(datasetWithColumns::getColumn), singular, currentResource, - thisColumn, fhirType); + .newInstance(expression, dataset, idColumn, valueColumn, orderingColumn, singular, + currentResource, thisColumn, fhirType); } catch (final NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Problem building an ElementPath class", e); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index e3d978fd53..16cb1867d5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -186,9 +186,11 @@ public ParserContext withUnnestBehaviour(@Nonnull final UnnestBehaviour unnestBe * @return a new {@link ParserContext} */ public ParserContext withInputContext(@Nonnull final FhirPath inputContext) { - return new ParserContext(inputContext, fhirContext, + final ParserContext parserContext = new ParserContext(inputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, nesting); + thisContext.ifPresent(parserContext::setThisContext); + return parserContext; } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ContextAndSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/ContextAndSelection.java index 4f02166c89..2b5429540a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/ContextAndSelection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ContextAndSelection.java @@ -11,8 +11,12 @@ public class ContextAndSelection { @Nonnull FhirPath context; - + @Nonnull List selection; + public void show() { + context.getDataset().select(selection.toArray(new Column[0])).show(false); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index 31c6953c90..f66e103e15 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -79,42 +79,60 @@ private ContextAndSelection parseSelect(@Nonnull final List select @Nonnull final ParserContext context, @Nonnull final List selection) { @Nonnull ContextAndSelection result = new ContextAndSelection( context.getInputContext(), selection); + @Nonnull ParserContext currentContext = context; for (final SelectClause select : selectGroup) { if (select instanceof DirectSelection) { - final DirectSelection directSelection = (DirectSelection) select; - final FhirPath path = parseExpression(directSelection.getExpression(), - context.withInputContext(result.getContext())); - final List newColumns = new ArrayList<>(result.getSelection()); - newColumns.add(path.getValueColumn().alias(directSelection.getName())); - result = new ContextAndSelection(context.getInputContext().withDataset(path.getDataset()), - newColumns); + result = directSelection(currentContext, (DirectSelection) select, result); } else if (select instanceof FromSelection) { - final FromSelection fromSelection = (FromSelection) select; - final FhirPath from = parseExpression(fromSelection.getFrom(), - context.withInputContext(result.getContext())); - final ContextAndSelection nestedResult = parseSelect(fromSelection.getSelect(), - context.withInputContext(from), result.getSelection()); - result = new ContextAndSelection(nestedResult.getContext(), nestedResult.getSelection()); + result = nestedSelection(currentContext, (FromSelection) select, result, false); } else if (select instanceof ForEachSelection) { - final FhirPath forEach = parseExpression(((ForEachSelection) select).getForEach(), - context.withInputContext(result.getContext())); - final FhirPath unnested = forEach.unnest(); - final ContextAndSelection nestedResult = parseSelect( - ((ForEachSelection) select).getSelect(), context.withInputContext(unnested), - result.getSelection()); - result = new ContextAndSelection(nestedResult.getContext(), nestedResult.getSelection()); + result = nestedSelection(currentContext, (ForEachSelection) select, result, true); } else { throw new IllegalStateException("Unknown select clause type: " + select.getClass()); } + currentContext = context.withContextDataset(result.getContext().getDataset()); } return result; } + @Nonnull + private ContextAndSelection directSelection(final @Nonnull ParserContext context, + @Nonnull final DirectSelection select, @Nonnull ContextAndSelection result) { + final FhirPath path = parseExpression(select.getExpression(), + context.withInputContext(result.getContext())); + final List newColumns = new ArrayList<>(result.getSelection()); + newColumns.add(path.getValueColumn().alias(select.getName())); + result = new ContextAndSelection(context.getInputContext().withDataset(path.getDataset()), + newColumns); + System.out.println("Direct selection: " + select.getName() + " = " + select.getExpression()); + result.show(); + return result; + } + + @Nonnull + private ContextAndSelection nestedSelection(final @Nonnull ParserContext context, + @Nonnull final NestedSelectClause select, @Nonnull ContextAndSelection result, + final boolean unnest) { + final FhirPath from = parseExpression(select.getExpression(), + context.withInputContext(result.getContext())); + final FhirPath nextInputContext = unnest + ? from.unnest() + : from; + final ParserContext nextContext = context.withInputContext(nextInputContext); + nextContext.setThisContext(nextInputContext); + final ContextAndSelection nestedResult = parseSelect(select.getSelect(), nextContext, + result.getSelection()); + result = new ContextAndSelection(nestedResult.getContext(), nestedResult.getSelection()); + System.out.println("Nested selection: " + select.getExpression()); + result.show(); + return result; + } + @Nonnull private FhirPath parseExpression(@Nonnull final String expression, @Nonnull final ParserContext context) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java index 98dfa1a565..49219c897c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java @@ -1,5 +1,6 @@ package au.csiro.pathling.views; +import com.google.gson.annotations.SerializedName; import java.util.List; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @@ -16,13 +17,14 @@ */ @Data @EqualsAndHashCode(callSuper = false) -public class ForEachSelection extends SelectClause { +public class ForEachSelection extends NestedSelectClause { /** * The FHIRPath expression to unnest. */ @NotNull - String forEach; + @SerializedName("forEach") + String expression; /** * The nested select clauses. diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FromSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/FromSelection.java index 6e92a714c2..c1f4a071e1 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FromSelection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FromSelection.java @@ -1,5 +1,6 @@ package au.csiro.pathling.views; +import com.google.gson.annotations.SerializedName; import java.util.List; import javax.validation.constraints.NotNull; import lombok.Data; @@ -15,13 +16,14 @@ */ @Data @EqualsAndHashCode(callSuper = false) -public class FromSelection extends SelectClause { +public class FromSelection extends NestedSelectClause { /** * The FHIRPath expression for the parent path to select from. */ @NotNull - String from; + @SerializedName("from") + String expression; /** * The nested select clauses. diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/NestedSelectClause.java b/fhirpath/src/main/java/au/csiro/pathling/views/NestedSelectClause.java new file mode 100644 index 0000000000..69f21d2f08 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/NestedSelectClause.java @@ -0,0 +1,11 @@ +package au.csiro.pathling.views; + +import java.util.List; + +public abstract class NestedSelectClause extends SelectClause { + + abstract String getExpression(); + + abstract List getSelect(); + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/SelectClause.java b/fhirpath/src/main/java/au/csiro/pathling/views/SelectClause.java index f08e57e4c6..ed9192402f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/SelectClause.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/SelectClause.java @@ -12,6 +12,6 @@ * * @author John Grimes */ -public class SelectClause { - +public abstract class SelectClause { + } diff --git a/fhirpath/src/test/resources/requests/views/relatedUnnesting.json b/fhirpath/src/test/resources/requests/views/relatedUnnesting.json new file mode 100644 index 0000000000..7b1f9e9642 --- /dev/null +++ b/fhirpath/src/test/resources/requests/views/relatedUnnesting.json @@ -0,0 +1,28 @@ +{ + "name": "Related unnesting", + "resource": "Patient", + "select": [ + { + "name": "id", + "expr": "id" + }, + { + "forEach": "name", + "select": [ + { + "name": "family_name", + "expr": "family" + }, + { + "forEach": "given", + "select": [ + { + "name": "given_name", + "expr": "$this" + } + ] + } + ] + } + ] +} diff --git a/fhirpath/src/test/resources/results/views/relatedUnnesting.csv b/fhirpath/src/test/resources/results/views/relatedUnnesting.csv new file mode 100644 index 0000000000..e9f4b71c45 --- /dev/null +++ b/fhirpath/src/test/resources/results/views/relatedUnnesting.csv @@ -0,0 +1,6 @@ +id,family_name,given_name +1,Wuckert,Karina +1,Oberbrunner,Karina +2,Towne,Guy +2,Cleveland,Maponos +2,Cleveland,Wilburg From 0c81e8165f8f72354d31c4496467c0e52a865267 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Mon, 31 Jul 2023 10:20:56 +1000 Subject: [PATCH 50/95] Add new test: resource-related unnesting --- .../au/csiro/pathling/fhirpath/FhirPath.java | 5 +-- .../fhirpath/parser/ParserContext.java | 4 ++ .../pathling/views/FhirViewExecutor.java | 11 ++++- ...ting.json => elementRelatedUnnesting.json} | 2 +- .../views/resourceRelatedUnnesting.json | 41 +++++++++++++++++++ ...esting.csv => elementRelatedUnnesting.csv} | 0 .../views/resourceRelatedUnnesting.csv | 7 ++++ 7 files changed, 64 insertions(+), 6 deletions(-) rename fhirpath/src/test/resources/requests/views/{relatedUnnesting.json => elementRelatedUnnesting.json} (91%) create mode 100644 fhirpath/src/test/resources/requests/views/resourceRelatedUnnesting.json rename fhirpath/src/test/resources/results/views/{relatedUnnesting.csv => elementRelatedUnnesting.csv} (100%) create mode 100644 fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.csv diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java index 96c694ae0d..666a7ac2b2 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java @@ -134,9 +134,8 @@ NonLiteralPath combineWith(@Nonnull FhirPath target, @Nonnull Dataset datas * Prints out to stdout all the ids and values of all the elements in this path. For debugging * purposes only. */ - default void dumpAll() { - getDataset().select(getIdColumn(), getValueColumn()).collectAsList() - .forEach(System.out::println); + default void show() { + getDataset().select(getIdColumn(), getValueColumn()).show(false); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index 16cb1867d5..dbc082b3f3 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -166,6 +166,10 @@ public void setThisContext(@Nonnull final FhirPath thisContext) { this.thisContext = Optional.of(thisContext); } + public void unsetThisContext() { + this.thisContext = Optional.empty(); + } + /** * Creates a copy of the current parser context with the specified unnest behaviour. * diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index f66e103e15..a835094dc9 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -55,6 +55,7 @@ public Dataset buildQuery(@Nonnull final FhirView view) { final ResourceType resourceType = ResourceType.fromCode(view.getResource()); final ResourcePath inputContext = ResourcePath.build(fhirContext, dataSource, resourceType, view.getResource(), true); + inputContext.getDataset().printSchema(); final ParserContext parserContext = new ParserContext(inputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, singletonList(inputContext.getIdColumn())); @@ -82,6 +83,10 @@ private ContextAndSelection parseSelect(@Nonnull final List select @Nonnull ParserContext currentContext = context; for (final SelectClause select : selectGroup) { + System.out.println("Select: " + select); + System.out.println("Current context: " + currentContext.getInputContext() + "(" + + currentContext.getInputContext().getExpression() + ")"); + currentContext.getInputContext().show(); if (select instanceof DirectSelection) { result = directSelection(currentContext, (DirectSelection) select, result); @@ -94,7 +99,8 @@ private ContextAndSelection parseSelect(@Nonnull final List select } else { throw new IllegalStateException("Unknown select clause type: " + select.getClass()); } - currentContext = context.withContextDataset(result.getContext().getDataset()); + currentContext = currentContext.withContextDataset(result.getContext().getDataset()); + currentContext.unsetThisContext(); } return result; @@ -127,7 +133,8 @@ private ContextAndSelection nestedSelection(final @Nonnull ParserContext context nextContext.setThisContext(nextInputContext); final ContextAndSelection nestedResult = parseSelect(select.getSelect(), nextContext, result.getSelection()); - result = new ContextAndSelection(nestedResult.getContext(), nestedResult.getSelection()); + result = new ContextAndSelection(context.withContextDataset(nestedResult.getContext() + .getDataset()).getInputContext(), nestedResult.getSelection()); System.out.println("Nested selection: " + select.getExpression()); result.show(); return result; diff --git a/fhirpath/src/test/resources/requests/views/relatedUnnesting.json b/fhirpath/src/test/resources/requests/views/elementRelatedUnnesting.json similarity index 91% rename from fhirpath/src/test/resources/requests/views/relatedUnnesting.json rename to fhirpath/src/test/resources/requests/views/elementRelatedUnnesting.json index 7b1f9e9642..e5ee6f4cb8 100644 --- a/fhirpath/src/test/resources/requests/views/relatedUnnesting.json +++ b/fhirpath/src/test/resources/requests/views/elementRelatedUnnesting.json @@ -1,5 +1,5 @@ { - "name": "Related unnesting", + "name": "Element-related unnesting", "resource": "Patient", "select": [ { diff --git a/fhirpath/src/test/resources/requests/views/resourceRelatedUnnesting.json b/fhirpath/src/test/resources/requests/views/resourceRelatedUnnesting.json new file mode 100644 index 0000000000..5102d3381d --- /dev/null +++ b/fhirpath/src/test/resources/requests/views/resourceRelatedUnnesting.json @@ -0,0 +1,41 @@ +{ + "name": "Resource-related unnesting", + "resource": "Patient", + "select": [ + { + "name": "id", + "expr": "id" + }, + { + "forEach": "name", + "select": [ + { + "forEach": "prefix", + "select": [ + { + "name": "name_prefix", + "expr": "$this" + } + ] + }, + { + "name": "family_name", + "expr": "family" + } + ] + }, + { + "forEach": "maritalStatus.coding", + "select": [ + { + "name": "marital_status_system", + "expr": "system" + }, + { + "name": "marital_status_code", + "expr": "code" + } + ] + } + ] +} diff --git a/fhirpath/src/test/resources/results/views/relatedUnnesting.csv b/fhirpath/src/test/resources/results/views/elementRelatedUnnesting.csv similarity index 100% rename from fhirpath/src/test/resources/results/views/relatedUnnesting.csv rename to fhirpath/src/test/resources/results/views/elementRelatedUnnesting.csv diff --git a/fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.csv b/fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.csv new file mode 100644 index 0000000000..34a0606d5e --- /dev/null +++ b/fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.csv @@ -0,0 +1,7 @@ +id,name_prefix,family_name,marital_status_system,marital_status_code +1,Miss.,Wuckert,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M +1,Miss.,Wuckert,http://snomed.info/sct,87915002 +1,Mrs.,Oberbrunner,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M +1,Mrs.,Oberbrunner,http://snomed.info/sct,87915002 +2,Mr.,Towne,, +2,Prof.,Cleveland,, From a8b068cf8ee9e691e15e91c86e567f42b273f20c Mon Sep 17 00:00:00 2001 From: John Grimes Date: Mon, 31 Jul 2023 10:40:24 +1000 Subject: [PATCH 51/95] Add new test: forEach within from --- .../pathling/fhirpath/NonLiteralPath.java | 22 +++++++++++-- .../requests/views/forEachWithinFrom.json | 33 +++++++++++++++++++ .../results/views/forEachWithinFrom.csv | 11 +++++++ 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 fhirpath/src/test/resources/requests/views/forEachWithinFrom.json create mode 100644 fhirpath/src/test/resources/results/views/forEachWithinFrom.csv diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java index 2ee500e4d5..2c871b9ad8 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java @@ -22,6 +22,7 @@ import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static au.csiro.pathling.utilities.Preconditions.checkPresent; import static org.apache.spark.sql.functions.explode_outer; +import static org.apache.spark.sql.functions.flatten; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.element.ElementDefinition; @@ -36,6 +37,8 @@ import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; +import org.apache.spark.sql.types.ArrayType; +import org.apache.spark.sql.types.DataType; /** * Represents any FHIRPath expression which is not a literal. @@ -217,10 +220,25 @@ public Dataset getUnionableDataset(@Nonnull final FhirPath target) { @Nonnull @Override public FhirPath unnest() { + final Column flattenedValueColumn = valueIsArrayOfArrays() + ? flatten(getValueColumn()) + : getValueColumn(); final DatasetWithColumn datasetWithColumn = createColumn(getDataset(), - explode_outer(getValueColumn())); + explode_outer(flattenedValueColumn)); return copy(getExpression(), datasetWithColumn.getDataset(), datasetWithColumn.getColumn(), datasetWithColumn.getColumn(), getOrderingColumn(), isSingular(), getThisColumn()); } - + + private boolean valueIsArrayOfArrays() { + final boolean arrayOfArrays; + final DataType dataType = dataset.select(getValueColumn()).schema().fields()[0].dataType(); + if (dataType instanceof ArrayType) { + final ArrayType arrayType = (ArrayType) dataType; + arrayOfArrays = arrayType.elementType() instanceof ArrayType; + } else { + arrayOfArrays = false; + } + return arrayOfArrays; + } + } diff --git a/fhirpath/src/test/resources/requests/views/forEachWithinFrom.json b/fhirpath/src/test/resources/requests/views/forEachWithinFrom.json new file mode 100644 index 0000000000..f63af31e1c --- /dev/null +++ b/fhirpath/src/test/resources/requests/views/forEachWithinFrom.json @@ -0,0 +1,33 @@ +{ + "name": "forEach within from", + "resource": "Patient", + "select": [ + { + "name": "id", + "expr": "id" + }, + { + "from": "name", + "select": [ + { + "forEach": "prefix", + "select": [ + { + "name": "name_prefix", + "expr": "$this" + } + ] + }, + { + "forEach": "given", + "select": [ + { + "name": "given_name", + "expr": "$this" + } + ] + } + ] + } + ] +} diff --git a/fhirpath/src/test/resources/results/views/forEachWithinFrom.csv b/fhirpath/src/test/resources/results/views/forEachWithinFrom.csv new file mode 100644 index 0000000000..8c23df144a --- /dev/null +++ b/fhirpath/src/test/resources/results/views/forEachWithinFrom.csv @@ -0,0 +1,11 @@ +id,name_prefix,given_name +1,Mrs.,Karina +1,Mrs.,Karina +1,Miss.,Karina +1,Miss.,Karina +2,Mr.,Maponos +2,Mr.,Wilburg +2,Mr.,Guy +2,Prof.,Maponos +2,Prof.,Wilburg +2,Prof.,Guy From 80ff55dd268415ebb37222a9b60867736d46418a Mon Sep 17 00:00:00 2001 From: John Grimes Date: Mon, 31 Jul 2023 11:33:12 +1000 Subject: [PATCH 52/95] Add new test: unnest empty collection --- .../requests/views/unnestEmptyCollection.json | 19 ++++++++++++++++++ .../results/views/unnestEmptyCollection.csv | 3 +++ ...-8e8e-3f959160df7e-c000.snappy.parquet.crc | Bin 356 -> 0 bytes .../_delta_log/.00000000000000000000.json.crc | Bin 160 -> 0 bytes .../_delta_log/00000000000000000000.json | 6 +++--- ...c84-8637-5d2b8d3832fc-c000.snappy.parquet} | Bin 6 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 fhirpath/src/test/resources/requests/views/unnestEmptyCollection.json create mode 100644 fhirpath/src/test/resources/results/views/unnestEmptyCollection.csv delete mode 100644 fhirpath/src/test/resources/test-data/views/Patient.parquet/.part-00000-ddd590a4-cb85-4d4f-8e8e-3f959160df7e-c000.snappy.parquet.crc delete mode 100644 fhirpath/src/test/resources/test-data/views/Patient.parquet/_delta_log/.00000000000000000000.json.crc rename fhirpath/src/test/resources/test-data/views/Patient.parquet/{part-00000-ddd590a4-cb85-4d4f-8e8e-3f959160df7e-c000.snappy.parquet => part-00000-15aca56b-393b-4c84-8637-5d2b8d3832fc-c000.snappy.parquet} (100%) diff --git a/fhirpath/src/test/resources/requests/views/unnestEmptyCollection.json b/fhirpath/src/test/resources/requests/views/unnestEmptyCollection.json new file mode 100644 index 0000000000..b0c26a251e --- /dev/null +++ b/fhirpath/src/test/resources/requests/views/unnestEmptyCollection.json @@ -0,0 +1,19 @@ +{ + "name": "Unnest empty collection", + "resource": "Patient", + "select": [ + { + "name": "id", + "expr": "id" + }, + { + "forEach": "photo", + "select": [ + { + "name": "photo_title", + "expr": "title" + } + ] + } + ] +} diff --git a/fhirpath/src/test/resources/results/views/unnestEmptyCollection.csv b/fhirpath/src/test/resources/results/views/unnestEmptyCollection.csv new file mode 100644 index 0000000000..611399a22e --- /dev/null +++ b/fhirpath/src/test/resources/results/views/unnestEmptyCollection.csv @@ -0,0 +1,3 @@ +id,photo_title +1, +2, diff --git a/fhirpath/src/test/resources/test-data/views/Patient.parquet/.part-00000-ddd590a4-cb85-4d4f-8e8e-3f959160df7e-c000.snappy.parquet.crc b/fhirpath/src/test/resources/test-data/views/Patient.parquet/.part-00000-ddd590a4-cb85-4d4f-8e8e-3f959160df7e-c000.snappy.parquet.crc deleted file mode 100644 index dacc426a64082331f62a3ee837797eec0225f7a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 356 zcmV-q0h|6~a$^7h00IEzB?~DGGdU-X>u*LyPScp>V9y^qRZUr=$KLrlh$Nr~gF7qGT0k_UDUo<`7t7k{;U0}yu81(&j_cj z;>{AIadS-WY{yP;mIC^pSjBs$POC;FT%Vh2msk46TpjYECWZQrF9}|KCFCE=D2ccb z=A3>ZkNnzO@17IMTF>p99L!0^@)XdGZRG$NC>30s7iB2{EDv-zc-eaT#7H{UEt|2Vrv34_pU)^vN~aXU0o_1%nQoNOxK4VGxKl_RojQs zgNMpud=w(+$9O>zt=?g*)66cS%#KLV)x*tsaGy&k-85#rF20@~ZOV8HoL#e*c30z5=J3SB}nDi5TG(LSDMiY^cOpqXf@dBxgMcXe4#$py$ OQVi{>{h7P@XwjYaV@w4A diff --git a/fhirpath/src/test/resources/test-data/views/Patient.parquet/_delta_log/00000000000000000000.json b/fhirpath/src/test/resources/test-data/views/Patient.parquet/_delta_log/00000000000000000000.json index 1bf4ab166c..c81d34f0c0 100644 --- a/fhirpath/src/test/resources/test-data/views/Patient.parquet/_delta_log/00000000000000000000.json +++ b/fhirpath/src/test/resources/test-data/views/Patient.parquet/_delta_log/00000000000000000000.json @@ -1,4 +1,4 @@ -{"commitInfo":{"timestamp":1683834324003,"operation":"WRITE","operationParameters":{"mode":"Overwrite","partitionBy":"[]"},"isolationLevel":"Serializable","isBlindAppend":false,"operationMetrics":{"numFiles":"1","numOutputRows":"2","numOutputBytes":"44411"},"engineInfo":"Apache-Spark/3.3.2 Delta-Lake/2.2.0","txnId":"8b858a77-e68a-4b7c-90ec-c92486d23860"}} +{"commitInfo":{"timestamp":1690767027638,"operation":"WRITE","operationParameters":{"mode":"Overwrite","partitionBy":"[]"},"isolationLevel":"Serializable","isBlindAppend":false,"operationMetrics":{"numFiles":"1","numOutputRows":"2","numOutputBytes":"44411"},"engineInfo":"Apache-Spark/3.3.2 Delta-Lake/2.3.0","txnId":"10dcf5fe-8df3-4489-b59e-1dc917964101"}} {"protocol":{"minReaderVersion":1,"minWriterVersion":2}} -{"metaData":{"id":"19044b9f-61df-4780-a133-fa6b426228c8","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"id_versioned\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"meta\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"versionId\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"versionId_versioned\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"lastUpdated\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}},{\"name\":\"source\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"profile\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"security\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"tag\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"implicitRules\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"language\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"status\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"div\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"identifier\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"assigner\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"active\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}},{\"name\":\"name\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"family\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"given\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"prefix\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"suffix\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"telecom\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"rank\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"gender\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"birthDate\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"deceasedBoolean\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}},{\"name\":\"deceasedDateTime\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"address\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"line\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"city\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"district\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"state\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"postalCode\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"country\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"maritalStatus\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"multipleBirthBoolean\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}},{\"name\":\"multipleBirthInteger\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"photo\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"contentType\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"language\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"data\",\"type\":\"binary\",\"nullable\":true,\"metadata\":{}},{\"name\":\"url\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"size\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"hash\",\"type\":\"binary\",\"nullable\":true,\"metadata\":{}},{\"name\":\"title\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"creation\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"contact\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"relationship\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"name\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"family\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"given\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"prefix\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"suffix\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"telecom\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"rank\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"address\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"line\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"city\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"district\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"state\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"postalCode\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"country\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"gender\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"organization\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"communication\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"language\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"preferred\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"generalPractitioner\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"managingOrganization\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"link\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"other\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}}]}","partitionColumns":[],"configuration":{},"createdTime":1683834321904}} -{"add":{"path":"part-00000-ddd590a4-cb85-4d4f-8e8e-3f959160df7e-c000.snappy.parquet","partitionValues":{},"size":44411,"modificationTime":1683834323360,"dataChange":true,"stats":"{\"numRecords\":2,\"minValues\":{\"id\":\"1\",\"id_versioned\":\"Patient/1\",\"meta\":{},\"text\":{},\"gender\":\"female\",\"birthDate\":\"1959-09-27\",\"maritalStatus\":{\"text\":\"Married\"}},\"maxValues\":{\"id\":\"2\",\"id_versioned\":\"Patient/2\",\"meta\":{},\"text\":{},\"gender\":\"male\",\"birthDate\":\"1983-09-06\",\"maritalStatus\":{\"text\":\"Married\"}},\"nullCount\":{\"id\":0,\"id_versioned\":0,\"meta\":{\"id\":2,\"versionId\":2,\"versionId_versioned\":2,\"lastUpdated\":2,\"source\":2,\"profile\":0,\"security\":0,\"tag\":0},\"implicitRules\":2,\"language\":2,\"text\":{\"id\":2,\"status\":2,\"div\":2},\"identifier\":0,\"active\":2,\"name\":0,\"telecom\":0,\"gender\":0,\"birthDate\":0,\"deceasedBoolean\":2,\"deceasedDateTime\":2,\"address\":0,\"maritalStatus\":{\"id\":2,\"coding\":0,\"text\":1},\"multipleBirthBoolean\":2,\"multipleBirthInteger\":2,\"photo\":0,\"contact\":0,\"communication\":0}}"}} +{"metaData":{"id":"cb62c705-df68-4165-ab77-62724aa95024","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"id_versioned\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"meta\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"versionId\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"versionId_versioned\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"lastUpdated\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}},{\"name\":\"source\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"profile\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"security\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"tag\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"implicitRules\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"language\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"status\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"div\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"identifier\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"assigner\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"active\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}},{\"name\":\"name\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"family\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"given\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"prefix\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"suffix\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"telecom\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"rank\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"gender\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"birthDate\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"deceasedBoolean\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}},{\"name\":\"deceasedDateTime\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"address\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"line\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"city\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"district\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"state\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"postalCode\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"country\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"maritalStatus\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"multipleBirthBoolean\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}},{\"name\":\"multipleBirthInteger\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"photo\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"contentType\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"language\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"data\",\"type\":\"binary\",\"nullable\":true,\"metadata\":{}},{\"name\":\"url\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"size\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"hash\",\"type\":\"binary\",\"nullable\":true,\"metadata\":{}},{\"name\":\"title\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"creation\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"contact\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"relationship\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"name\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"family\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"given\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"prefix\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"suffix\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"telecom\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"rank\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"address\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"line\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"city\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"district\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"state\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"postalCode\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"country\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"gender\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"organization\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"communication\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"language\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"preferred\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"generalPractitioner\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"managingOrganization\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"link\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"other\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}}]}","partitionColumns":[],"configuration":{},"createdTime":1690767025495}} +{"add":{"path":"part-00000-15aca56b-393b-4c84-8637-5d2b8d3832fc-c000.snappy.parquet","partitionValues":{},"size":44411,"modificationTime":1690767026983,"dataChange":true,"stats":"{\"numRecords\":2,\"minValues\":{\"id\":\"1\",\"id_versioned\":\"Patient/1\",\"meta\":{},\"text\":{},\"gender\":\"female\",\"birthDate\":\"1959-09-27\",\"maritalStatus\":{\"text\":\"Married\"}},\"maxValues\":{\"id\":\"2\",\"id_versioned\":\"Patient/2\",\"meta\":{},\"text\":{},\"gender\":\"male\",\"birthDate\":\"1983-09-06\",\"maritalStatus\":{\"text\":\"Married\"}},\"nullCount\":{\"id\":0,\"id_versioned\":0,\"meta\":{\"id\":2,\"versionId\":2,\"versionId_versioned\":2,\"lastUpdated\":2,\"source\":2,\"profile\":0,\"security\":0,\"tag\":0},\"implicitRules\":2,\"language\":2,\"text\":{\"id\":2,\"status\":2,\"div\":2},\"identifier\":0,\"active\":2,\"name\":0,\"telecom\":0,\"gender\":0,\"birthDate\":0,\"deceasedBoolean\":2,\"deceasedDateTime\":2,\"address\":0,\"maritalStatus\":{\"id\":2,\"coding\":0,\"text\":1},\"multipleBirthBoolean\":2,\"multipleBirthInteger\":2,\"photo\":0,\"contact\":0,\"communication\":0}}"}} diff --git a/fhirpath/src/test/resources/test-data/views/Patient.parquet/part-00000-ddd590a4-cb85-4d4f-8e8e-3f959160df7e-c000.snappy.parquet b/fhirpath/src/test/resources/test-data/views/Patient.parquet/part-00000-15aca56b-393b-4c84-8637-5d2b8d3832fc-c000.snappy.parquet similarity index 100% rename from fhirpath/src/test/resources/test-data/views/Patient.parquet/part-00000-ddd590a4-cb85-4d4f-8e8e-3f959160df7e-c000.snappy.parquet rename to fhirpath/src/test/resources/test-data/views/Patient.parquet/part-00000-15aca56b-393b-4c84-8637-5d2b8d3832fc-c000.snappy.parquet From def7022e6bca4f668880f2e3038eec274f65d2a4 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Tue, 1 Aug 2023 11:36:00 +1000 Subject: [PATCH 53/95] Add flattened blood pressure test and getId function --- .../au/csiro/pathling/fhirpath/Referrer.java | 14 +++ .../fhirpath/UntypedResourcePath.java | 6 ++ .../fhirpath/element/ReferencePath.java | 6 ++ .../fhirpath/function/GetIdFunction.java | 35 ++++++++ .../fhirpath/function/NamedFunction.java | 1 + .../pathling/views/FhirViewExecutor.java | 19 +--- .../views/flattenedBloodPressure.json | 82 ++++++++++++++++++ .../results/views/flattenedBloodPressure.csv | 0 .../_delta_log/00000000000000000000.json | 4 + ...41b2-9f1d-f94a57684570-c000.snappy.parquet | Bin 0 -> 154746 bytes .../_delta_log/00000000000000000000.json | 6 +- ...aa2-b08a-d8980f55947d-c000.snappy.parquet} | Bin 44411 -> 44411 bytes 12 files changed, 155 insertions(+), 18 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java create mode 100644 fhirpath/src/test/resources/requests/views/flattenedBloodPressure.json create mode 100644 fhirpath/src/test/resources/results/views/flattenedBloodPressure.csv create mode 100644 fhirpath/src/test/resources/test-data/views/Observation.parquet/_delta_log/00000000000000000000.json create mode 100644 fhirpath/src/test/resources/test-data/views/Observation.parquet/part-00000-a35c6b50-884f-41b2-9f1d-f94a57684570-c000.snappy.parquet rename fhirpath/src/test/resources/test-data/views/Patient.parquet/{part-00000-15aca56b-393b-4c84-8637-5d2b8d3832fc-c000.snappy.parquet => part-00000-52417c46-8b68-4aa2-b08a-d8980f55947d-c000.snappy.parquet} (96%) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java index 5f6af789a3..7ecc8795d7 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java @@ -18,7 +18,9 @@ package au.csiro.pathling.fhirpath; import static org.apache.spark.sql.functions.concat; +import static org.apache.spark.sql.functions.element_at; import static org.apache.spark.sql.functions.lit; +import static org.apache.spark.sql.functions.split; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -48,6 +50,11 @@ static Column referenceColumnFor(@Nonnull final Referrer referrer) { return referrer.getValueColumn().getField(REFERENCE_FIELD_NAME); } + @Nonnull + static Column referenceIdColumnFor(@Nonnull final Column referenceColumn) { + return element_at(split(referenceColumn, PATH_SEPARATOR, 2), 2); + } + /** * Constructs an equality column for matching a resource reference to a {@link ResourcePath}. * @@ -94,6 +101,13 @@ static Column resourceEqualityFor(@Nonnull final Referrer referrer, @Nonnull Column getReferenceColumn(); + /** + * @return the ID component of the {@code reference} element from within the Reference struct in + * this path's value column + */ + @Nonnull + Column getReferenceIdColumn(); + /** * Constructs an equality column for matching a resource reference to a {@link ResourcePath}. * diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java index e74902ebf6..614829fc7d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java @@ -66,6 +66,12 @@ public Column getReferenceColumn() { return valueColumn.getField(Referrer.REFERENCE_FIELD_NAME); } + @Nonnull + @Override + public Column getReferenceIdColumn() { + return Referrer.referenceIdColumnFor(getReferenceColumn()); + } + @Nonnull @Override public Optional getChildElement(@Nonnull final String name) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java index 2f1f8b3178..5e6b48abf7 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java @@ -59,6 +59,12 @@ public Column getReferenceColumn() { return Referrer.referenceColumnFor(this); } + @Nonnull + @Override + public Column getReferenceIdColumn() { + return Referrer.referenceIdColumnFor(Referrer.referenceColumnFor(this)); + } + @Nonnull public Column getResourceEquality(@Nonnull final ResourcePath resourcePath) { return Referrer.resourceEqualityFor(this, resourcePath); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java new file mode 100644 index 0000000000..be628e1a8c --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java @@ -0,0 +1,35 @@ +package au.csiro.pathling.fhirpath.function; + +import static au.csiro.pathling.QueryHelpers.createColumn; +import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; +import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; +import static au.csiro.pathling.utilities.Preconditions.checkUserInput; + +import au.csiro.pathling.QueryHelpers.DatasetWithColumn; +import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.element.ReferencePath; +import javax.annotation.Nonnull; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +public class GetIdFunction implements NamedFunction { + + private static final String NAME = "getId"; + + @Nonnull + @Override + public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + checkNoArguments(NAME, input); + checkUserInput(input.getInput() instanceof ReferencePath, "Input to getId must be a Reference"); + final ReferencePath referencePath = (ReferencePath) input.getInput(); + final DatasetWithColumn datasetWithColumn = createColumn(referencePath.getDataset(), + referencePath.getReferenceIdColumn()); + final String expression = expressionFromInput(input, NAME); + + return ElementPath.build(expression, datasetWithColumn.getDataset(), + referencePath.getIdColumn(), datasetWithColumn.getColumn(), + referencePath.getOrderingColumn(), referencePath.isSingular(), + referencePath.getCurrentResource(), referencePath.getThisColumn(), FHIRDefinedType.STRING); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java index 13cf2a7db5..fd6bbebad4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java @@ -72,6 +72,7 @@ public interface NamedFunction { .put("property", new PropertyFunction()) .put("designation", new DesignationFunction()) .put("toString", new ToStringFunction()) + .put("getId", new GetIdFunction()) .build(); /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index a835094dc9..ebd9ec08c4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -55,7 +55,6 @@ public Dataset buildQuery(@Nonnull final FhirView view) { final ResourceType resourceType = ResourceType.fromCode(view.getResource()); final ResourcePath inputContext = ResourcePath.build(fhirContext, dataSource, resourceType, view.getResource(), true); - inputContext.getDataset().printSchema(); final ParserContext parserContext = new ParserContext(inputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, singletonList(inputContext.getIdColumn())); @@ -83,10 +82,6 @@ private ContextAndSelection parseSelect(@Nonnull final List select @Nonnull ParserContext currentContext = context; for (final SelectClause select : selectGroup) { - System.out.println("Select: " + select); - System.out.println("Current context: " + currentContext.getInputContext() + "(" - + currentContext.getInputContext().getExpression() + ")"); - currentContext.getInputContext().show(); if (select instanceof DirectSelection) { result = directSelection(currentContext, (DirectSelection) select, result); @@ -108,21 +103,18 @@ private ContextAndSelection parseSelect(@Nonnull final List select @Nonnull private ContextAndSelection directSelection(final @Nonnull ParserContext context, - @Nonnull final DirectSelection select, @Nonnull ContextAndSelection result) { + @Nonnull final DirectSelection select, @Nonnull final ContextAndSelection result) { final FhirPath path = parseExpression(select.getExpression(), context.withInputContext(result.getContext())); final List newColumns = new ArrayList<>(result.getSelection()); newColumns.add(path.getValueColumn().alias(select.getName())); - result = new ContextAndSelection(context.getInputContext().withDataset(path.getDataset()), + return new ContextAndSelection(context.getInputContext().withDataset(path.getDataset()), newColumns); - System.out.println("Direct selection: " + select.getName() + " = " + select.getExpression()); - result.show(); - return result; } @Nonnull private ContextAndSelection nestedSelection(final @Nonnull ParserContext context, - @Nonnull final NestedSelectClause select, @Nonnull ContextAndSelection result, + @Nonnull final NestedSelectClause select, @Nonnull final ContextAndSelection result, final boolean unnest) { final FhirPath from = parseExpression(select.getExpression(), context.withInputContext(result.getContext())); @@ -133,11 +125,8 @@ private ContextAndSelection nestedSelection(final @Nonnull ParserContext context nextContext.setThisContext(nextInputContext); final ContextAndSelection nestedResult = parseSelect(select.getSelect(), nextContext, result.getSelection()); - result = new ContextAndSelection(context.withContextDataset(nestedResult.getContext() + return new ContextAndSelection(context.withContextDataset(nestedResult.getContext() .getDataset()).getInputContext(), nestedResult.getSelection()); - System.out.println("Nested selection: " + select.getExpression()); - result.show(); - return result; } @Nonnull diff --git a/fhirpath/src/test/resources/requests/views/flattenedBloodPressure.json b/fhirpath/src/test/resources/requests/views/flattenedBloodPressure.json new file mode 100644 index 0000000000..912543178c --- /dev/null +++ b/fhirpath/src/test/resources/requests/views/flattenedBloodPressure.json @@ -0,0 +1,82 @@ +{ + "name": "Flattened blood pressure", + "resource": "Observation", + "constants": [ + { + "name": "sbp_component", + "value": "component.where(code.coding.exists(system='http://loinc.org' and code='8480-6')).first()" + }, + { + "name": "dbp_component", + "value": "component.where(code.coding.exists(system='http://loinc.org' and code='8462-4')).first()" + } + ], + "select": [ + { + "name": "id", + "expr": "id" + }, + { + "name": "patient_id", + "expr": "subject.getId()" + }, + { + "name": "effective_date_time", + "expr": "effective.ofType(dateTime)" + }, + { + "from": "%sbp_component", + "select": [ + { + "name": "sbp_quantity_system", + "expr": "value.ofType(Quantity).system" + }, + { + "name": "sbp_quantity_code", + "expr": "value.ofType(Quantity).code" + }, + { + "name": "sbp_quantity_display", + "expr": "value.ofType(Quantity).unit" + }, + { + "name": "sbp_quantity_value", + "expr": "value.ofType(Quantity).value" + } + ] + }, + { + "from": "%dbp_component", + "select": [ + { + "name": "dbp_quantity_system", + "expr": "value.ofType(Quantity).system" + }, + { + "name": "dbp_quantity_code", + "expr": "value.ofType(Quantity).code" + }, + { + "name": "dbp_quantity_display", + "expr": "value.ofType(Quantity).unit" + }, + { + "name": "dbp_quantity_value", + "expr": "value.ofType(Quantity).value" + } + ] + } + ], + "where": [ + { + "expr": "code.coding.exists(system='http://loinc.org' and code='85354-9')" + }, + { + "expr": "%sbp_component.dataAbsentReason.empty()" + }, + { + "expr": "%dbp_component.dataAbsentReason.empty()" + } + ] +} + diff --git a/fhirpath/src/test/resources/results/views/flattenedBloodPressure.csv b/fhirpath/src/test/resources/results/views/flattenedBloodPressure.csv new file mode 100644 index 0000000000..e69de29bb2 diff --git a/fhirpath/src/test/resources/test-data/views/Observation.parquet/_delta_log/00000000000000000000.json b/fhirpath/src/test/resources/test-data/views/Observation.parquet/_delta_log/00000000000000000000.json new file mode 100644 index 0000000000..a040d53372 --- /dev/null +++ b/fhirpath/src/test/resources/test-data/views/Observation.parquet/_delta_log/00000000000000000000.json @@ -0,0 +1,4 @@ +{"commitInfo":{"timestamp":1690767715961,"operation":"WRITE","operationParameters":{"mode":"Overwrite","partitionBy":"[]"},"isolationLevel":"Serializable","isBlindAppend":false,"operationMetrics":{"numFiles":"1","numOutputRows":"1","numOutputBytes":"154746"},"engineInfo":"Apache-Spark/3.3.2 Delta-Lake/2.3.0","txnId":"5f04ae46-b2c1-456a-8b19-a1a24fc51fb5"}} +{"protocol":{"minReaderVersion":1,"minWriterVersion":2}} +{"metaData":{"id":"f0b52a85-c0e9-48a3-939a-fb0348c124da","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"id_versioned\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"meta\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"versionId\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"versionId_versioned\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"lastUpdated\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}},{\"name\":\"source\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"profile\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"security\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"tag\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"implicitRules\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"language\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"status\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"div\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"identifier\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"assigner\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"basedOn\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"partOf\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"status\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"category\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"subject\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"focus\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"encounter\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"effectiveDateTime\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"effectiveInstant\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}},{\"name\":\"effectivePeriod\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"effectiveTiming\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"event\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"repeat\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"boundsDuration\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"boundsPeriod\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"boundsRange\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"low\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"high\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"count\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"countMax\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"duration\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"duration_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"durationMax\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"durationMax_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"durationUnit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"frequency\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"frequencyMax\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"periodMax\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"periodMax_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"periodUnit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"dayOfWeek\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"timeOfDay\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"when\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"offset\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"issued\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}},{\"name\":\"performer\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueBoolean\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valueCodeableConcept\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueDateTime\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valueInteger\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valuePeriod\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueQuantity\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueRange\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"low\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"high\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueRatio\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"numerator\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"denominator\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueSampledData\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"origin\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"factor\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"factor_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"lowerLimit\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"lowerLimit_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"upperLimit\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"upperLimit_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"dimensions\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"data\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueString\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valueTime\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"dataAbsentReason\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"interpretation\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"note\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"authorReference\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"authorString\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"time\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"bodySite\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"method\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"specimen\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"device\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"referenceRange\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"low\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"high\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"appliesTo\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"age\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"low\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"high\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"hasMember\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"derivedFrom\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"component\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueBoolean\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valueCodeableConcept\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueDateTime\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valueInteger\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valuePeriod\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueQuantity\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueRange\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"low\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"high\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueRatio\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"numerator\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"denominator\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueSampledData\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"origin\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"factor\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"factor_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"lowerLimit\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"lowerLimit_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"upperLimit\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"upperLimit_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"dimensions\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"data\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueString\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valueTime\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"dataAbsentReason\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"interpretation\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"referenceRange\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"low\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"high\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"appliesTo\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"age\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"low\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"high\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}}]}","partitionColumns":[],"configuration":{},"createdTime":1690767712665}} +{"add":{"path":"part-00000-a35c6b50-884f-41b2-9f1d-f94a57684570-c000.snappy.parquet","partitionValues":{},"size":154746,"modificationTime":1690767715371,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"id\":\"blood-pressure\",\"id_versioned\":\"Observation/blood-pressure\",\"meta\":{},\"text\":{\"status\":\"generated\",\"div\":\"
S#5RY{hz;B({7%{@=z+4%#v)KH@PX#)p?eQurxA?t9*?zFp1|`Q;;6f`D`O?X6=|R@G z+-A46_C7P5BoHoMs}d5UfCyGG9|KcZ!1(8aK^EDT*iQeo=l2V>I)(N$?=o7aP?u4D zj?w^@+pe|k{r%7{nZp41Wt*`5Bv}@KfpV1s#q01ZPN3TJ4qL~iwy*!WZ|D+1mlrRc zd|J&=agoK6>2z(iZ_9TV_-(I4V-+1GVE4StO^WDqVXBMu{%v%2* zjlh1l!wXEVtxASnX!4mWv({%i>;;Z|<>qC2hhMhdp1IPoK651iS+`E+`~2J8@Q~Bx zD|9=yXV_f@ZdU=elI+KBvS&H6AItT6iVB?2c-$UuhW$$Cjmj$J-8b0_9ZsjKAUDH) zL*cfY?D-CFuB#x!zLxxvIlK*#Ay4T@!75Osk z*IQT4eSCSI;>ykQ1Intat>bU0UGUID=~*7X-;A2e#O6hnQf$&AMZW{a%Q!R}@(6mpp9AT{^E>=SK4Q@fI|LJl zdqLoe(aW=;dUHMA?F-(1%Y!bz!)@Q<$}RBOGkt}Qg4J)ov0;9OebW{Hb<2A^Zl_YP z+2ee_;&ywUxXFIae`URBwLg~Uc+3SgvD`Lw*T*yciZ|a?0OrlzzJi3aTc6u>{fbp9 zR;|3<<5ae6_xY9lbWfJ=zQ--;Z`qr^ctxGna{DDevDmFQS$tP4OullfwLdxCXYHSV zP3B7L&n@fU{!b{Pvf%I6Y+CKQ#qI+DsDo^~qrho*x*V4*KKoW{({2=cvo@8UW*e-qqyx)xcqr`hudw>&U3k)UZue9 zDZKT*WQY6CFWxc7n(8acdQ{2wUo+2=dEd^p_kuer1^#qpnOP z+dp673n`Nb5qxKd%zTGy(Vg&SJx=zK)I+dAw z%x=5NcKVfnOjxx@1EfnbG0s>lb4la|R??T9R}YeodFE_rqOop%HeA%8dEa4MW}E)% zj{WmAPlhh`(skH>TZtoMAXb~%$aqy}*k%UK504SJAOHG`&#^8T8pfsT)P{jW8c2ZW z>a4Z~tL=-Q`%}k!jTnBo>vHW$>qV2dKzE%18mQg09J|R-l!xt=5Vo~pc)HeQ#Gp^6 zZN2S>uk3FVwRwl2%?42BVo+u)>4MUIqzeLclB=nF@Ndy_2_07qI%NR=nL`A$}pkrcZ*lGk&P;rCp4%^>afA=bz^g!h-q;~8pX~#|`p=q{v*>->StKSu~ymh0R1+rVTE?+Iq%th%a z{zMe}OkcV8O9KnnH#G`A&aJ~!6Kgr zk43XY6hlV&IU2h8`)ewCRcD_6}vva%i0L_3Kne9n^ zX<){k=!S^mPqX^-dC(lqBD@*)UzoKZ)xvqTW6E%047!2Qv5{fk^XfsP)-h;o^D(Ju zng%fqqV3&*%~!L&%Xa4d?cfWQ6|mPP83vd0Y`h3bxZ~#k;g%gjTKnubKN0S!tMDlj zH=qC8#J^wzFauLJ04LK8z)z5kE6KR&>DZ@^KRY)QzBdAz%Ql7CICm91`kTM}WGw+q zrrQ162HfGL0+s=wWe#X=kehtE^j_NwzwUbmcJVPKucDh80n>WX2MHut!LklFZ&QhE zwg!?)tF6E`{jZnFhEJCHJwoPMlKJ1^|H;02-1d&2Kd_y5sqoVVm&14W{X2fJKt*|b;Zo2--HTlWU;6nxIr)l%d?ZOeor(qeGFz&xW!KJ^ zlU3-2SCPm=KL|07W3heEcDL=9Un_ndHUzV(SRqu!4VFu+m%roH-@LNb_x2r^T)N!y zv2`zOum0yftI+D#!l$6moP3ph*U56H+%H)D2D-=jEu@l?ZEu6E?O*!oFHg=)($dt$`jfzd5^PCJ zQ{cP{^61-y!^EWX10(*ZL0Ffj!oe99@@Ent#`Gu9-;(Izeu+c)W}+xDmHUl)80@`( zomeNzngvLOBzi(!;t)leI&YMn&I4Yk$+>uRlJ;L?Hic{%@t=2;Q06cSf<_k$haTzw z((;gpgky;Ge`ya%wHi;JV2CBEwb&6vOH+fA!Ygo*dcM%oR87*J1?^VGc#=@;IW)51 zVqGn9hdxVaX{xd3G#)6_rPA3#!70MS^R-ijmZmaM`&rUC@}EFFUpoj$5H&3Lc_Bai zNB4*JNMwBT*SN<3rs+)6#W#91J;(GM({tjOc9{*wY&d4aF&j>-%Qa@)Fyn?9H_W&( zYjGo4j~wDpn5hRXuY^bnF>5DakbB;`wd4a66v%}E+LQ28D*0L@)R!bnQ~y28Hzrzc zAu_5wGY2MNp9g<}t;tPC>ObV`1YH98ctJBPJWAfM!UOmpEm<-Gd5U~rp#DdD3X+$G z{a_%{ZvwzP`dI+@i~4;4e{({#pcB<_2^>T!>W>S8Q+OZReAS+FtjNy9RR@|p>&p7v%=P=)#vz1mfW_==2r7Ht!8578uq}*8r!4l1Krm# zW0$UF90O}@e(izlS!&G<%oV3@u(fH-d)Bchrq|i3wdBeSTaJoxD#P}WD)!vO9I=$##6ai^Au0zV$H{S>c<&z?I)Nx^a=JrU9pwP zLxNoQw2&^yb#RrM7LSJnx$YSu9b_%y*2BGPnmhusmbc+qE{_OuL#dE1$PLd4>4Mx) zCZr2;!zVfKs314N5iyz$M+Ld*Q$o5RH&qDfg531HkS@qgaP$qs7v!cFgmjR#{;#o$ z%OiqZ|7k00v&RIvy_%<&eum2fg50!|r%!^caqammPoD-^8*TK|aJl|PE*}@U)^zTGiyk%1@|U4Sn?^D@U7 z6Xd{=*ji;CH;qrhWH+`L_PYUt@xZPc|dBr!lJS510y*&N2 zAa{L}r&q%j@fyykAn*AWPoEa#!);bJAg}*6mrn?CXFE@?euc|pg50*B1PDF`yfxR`2m;51bJ^4PcMFz z%e{hJe~70a6XfeK2>e#F1XWZud`>nS7)%41r^Yl@Wx2fra1LQ7P^^eoP;16{lvD#|2 zA4Y%4nfDBGc^YKRBYJ+Vjmv|Ayz4YiKPAWq|Bt6vO>ucdkX!!F)6WQU_ZgmE^A9eM3G&`)o?iSm?&LCDjm(~J|16iwYB=Tq$lA!O`Nbp_e@+W>cP&q^ z`y7{#3-WeE21vUf;;&6N23NMV?*_OOskG z9TViXFY)xUW-bp2a?6)_`dLBl+r`tHzQW~Gf_x~z(`#Sm@-ac~*v-=`zsluNLEhWK z(@Va_{)!*gvm>{&ussNuI$xcXn&fg`ZP|1=ERf@$i(Jk} zwklp9T*j?vKU|*#`w0}k!?q-3mIdnbcWTcg&|6t*5T0!w@LueO-6ancIvsw+Nnz*t zJVoAYMb!pKFDmqUa$If&PHLUEDHAljJ~=hn?eh6;Ni@erid)H73j93Fe4mnCbJL zf})i}c-`Es4tJ4I(s_l7*X3~rmldmFF4*0xwE|3;qZDYZ5v(!a;q$q23&7~f)T#@- zN{-@H3Rr8yOp+Hv#*4BXKE-)YLA1(>5q2KLA^$x&Gl_}?(EA@Lcs7KWT#q+uI0*9R zbI6B4RSZbM&T(!P;v!#B)}s(DV|CKGIiBny_-Z}4lYsKGfFGlaE`Y}2DJt;C*k;fT z-=gH?Ksj7nl?~v2_q+17hKwEG687tz1<-B_{G@NVlqKGqNJHl^z)IrZd^w5&*IZ>Q z1mRHjqER5IQ=R8k3KfUnc5Mi-#aUn_r*A`%*WrgTgYDnsXKagYm)n4p2DF86=<+Ss zevtf|j5w0WKmdP%QHjH?(98?7J^2tJ9Da{i43o*^m}4;^MBEg6W+>`qk5KSzM}eon z1q`ky6({kp#c&k4pp?b736z3=Yf8-()OqV8q`QAahr#=xMbrQZRolfBV>5KNp}Vs= zZqE}Jo@tZwT)BA{Sh2B5%*D_{o5y^V?{aJ-PD@(Se5cyht`5mA8E~@1MnixjM zOWChlE~4Q=nkwiA&3=K?vHhN$_bJL_F+{lqehAd}B0i?M5IbIIPnt8pKsMxDYC%B}L^d)kz3>{!#jtSWfhom>S^6ay zCbl?WolJ2;YDDd}fCURSQ`LYo7r!_rs>d-HM}*<^wjVi3EC{-h1_=-_Z6 zOo0^dCYaXxL*-e*f0ptU6~dq-d_MlOmXGwc3!N|+EFi1kK3dVquu$y~C!e(Q-QxGc zG+RtxfUY7;`oS@lkj%Gc`C!_;8SdzTY4SO~-n7_7Rz?cF@KOM0{ZZCl&hgSF7kK<3 zUqV3Dvh}5fAp*jsjv{}a$GcfuY>jDU3**ZoiV*B($z--7`klC{40_Ga@;J9|ae?0F zxbf$~o2R@`E8isGo+GRLq&|FwN;ZipG5G)~qf^=H%2r~{wwSH()4^?&78>-n1(?l6 zC-LPd?S;d!e*h93DuW z{(?M*?=B@jYi5_6m`iY`_=O-OE;ns zwGX@SV=lHPPyFwwHD%nIR+pTadPkx+;VG~y(W}f94mj7FOe4|p#=zRCDNSJQh9pZ` z@jT7k523l)M{zoPYo7exl)E8^RPWPReVvD}`g#rFsk`yfB?xA(j9L9-;@cw>v;HC@ zI%*%^G0^&pBt-)VI!!^383Lti#>Cr*n#W|s1Bh7nLo9kb#z^A1kxZ$5IK|X#pD(F7 zfS~OZv~&T43~UaOKy}{_GUU<)67n%bK0=WP0GT#4icCH^3|r{~GE{L7si0F7s%c>q zD7n6y7D}4cAY64f8geokTu@a{%6Kh^*GlmYz*Y1SORBHs0~brm9YnBR3RbZw3RqAD zS1gk71`+Q##Tx=Vv4VrDA#~ji$)G0?wERbC!N4U%N!nfOzt}|QAl*se5=pC)pO6|2 zP=Ij(fPL7>@{Y@S>oD&jnztFQd1f5|`2tjn9)z}eu|(U7S;uJBqjFaDD?Ma>Fy-O2 zql+_zdpHFVVlKa*Q%Dyh-yx*haX86#n;;Z|<>qDjn`W1-w`Z<&tj}CoxE@)3{_SpfNc~zk!|p0@y9$(a z$dvuqP4+BD_G7tT@;z00w#V)9X4tQE-l(in-hGq3(BUNCvu4TJleTEtK zwd9X1j~Cv~W!P62ZnOJ5ZkN-3MN*smv#BVnEbU)J~mZPnGw z)@K&lRlO)~dX~rU_vB~TR~2qswtllhKg$+NY)^Z<$m73BUsjq1<%P0FM7)=N)tzAn zqvgRim7ABr_NJn2KlIic6u6|*MYfcs7x};lUbKzJnGRdcusdIo=g)U9TmQfo`|a?f zonFg$ull21MP?>n3DpnEtk29^PrpniRh41Ki$JNKBvHKA11ijKgau z!@h}_?>!zjY>M6NalT)1!|ubV)$&-L<1rU_#Bx^d2v>_RSd>JcOvI|64E?d9po;&Zp9gCvg532f~q%jmsaoeA8`Sa`!x7(hb=W;vY8)v(x zkT{gXO&v7Y1nOs})C#O_)QdUOm2J4|S2eYCU1x?}yONWrr>bS&vhmjUQ8Mmi>~m6; z2CE^%uHC{JR4?p%Q2IQ-juRoFZnTrjEMjVlq?K)e&8aRoguMrDf8Z`Vu>|0JU}Nrw>^IxrNu_t{n8|hD%&MZS zHWYQ6LO2VDyDsGoH!->3MORQPk6_}pYu^Y9V{kURW77Yd-LYGUS!xePo4?r|HVTIvhuwRz`#+>t=BMyI_R$`E)jt&-j2wiFZ??%EmMIyn zyqCQ9Fa8Ol9kOexJrqtg+|;YR(kF+sh}b|8cV3|(mPJHV&u0i}g}YNWr|rB# zVrfH`4$3l~0E@Ueg)oiFOg+dnM48%`#a|xXz7;~%zDz3AQKXupROMI3PsO*i5T5cY zC7vndsp-W^A5MTrx7&qK4a-!OKPCRPk5cWqO55!cyEg=Hgdu!;u9Aw@jC}o+ulQ=s zgpvK3o>Z>w^D4et!rYITlN7U;VTNtKqD$faHW{!F0V{us_ODOVlne_<&yrw`xIRr% za2)YkDBdxK7q-yq2~1)$J|<&MBj#a>+5S#V$7q=JfN`d3`#U97tNVyQOi zPYJIE@#=qu^)wV2FG#x~8Lb7;_EWULwM09*=S|!;wkhS_w7|8QsdI5|r2YpMuXTTm zo%KOP9R$=hmb58E#qG%KfOuW0#U?a$tyJ7WgevdH;vP!Z%(pExRHQDDl;Tjj1b+hI z0~EeyMI7*Apz5)v^jXttR%E7TMA&R}JIt!+HlvlHwizXo7b@iqViv2vq@Rq{4uMfi zVXQX3G7hWi`gu}w<13|_s|S*ll%4@}jJ8#AaPSWP(G+)D+p0`B;??DMxYNn)4L;o; zVArs}i6!5QN*x2G-V2{3td2t|U6UnxHLV7{g!l5gKZsu8eiQZTM!oj^0`;m~qm3Y9 zXhp$82U07QYouBkL87xj1m9$g0FnA3Lg?no#|(KXkHX_KP|{PVK>ZOcY4>$R0Ssu% zv9`5PL-x^jUnl8a{&V6{j?cvJjo z18Xx=m#ATc?avEN0T(dvl;{j?s96(e_F+p}_w~`sscVd+u-(^7h3!Ge;z2BI%?;5Z zdE5I4d;?W;LuTql>TW|OK*562#9|yp&IaJjfKJ8$r><%3v!=9JAzNnZO6gTow2h51 zXMlptI)`YODKJcG${-kK2)?P>vrcR6;T<`+ag$o!vrekzssYkK_6}h!PXn&5ZSlrj zX-%oNrcJNQOubsNZRpo{1|I`E&M2N}vt3ZM)RaCb+Fr0(Wd<*sc29~lbj(W0fyXja zFO~MF1|@*jQJ)btIea?opQ+2~JlA0#=tPfffIP_*y#!n?Nuc z$N5z;l1?P1PslJe2y=wO?7Ep(D~HkPLhQNgX0WHWRhUb}p24Z2JzJ3M6p(>E&vLTV z;45lyszg4=I16M8`F>+j2Es7Eh@=ORbni%#?G8)Y;ClAmVQSFLH^@n>G`LlZf3yvBv>> zF&#)5#1@uY2>iGVU-nB<(}yX1;C8rbB_??B8Y46|Aqm_rm7xJiCMd}zza@HS3A24Y$@WvS_V+|3(_NV*Hu?7VNX6(yu`xEaS-29-=q1@Mu!$J022*zRtB#674fCL6u9$F zrgJ!75`oFtup)Tpos!nO5WJ6q?|5(2#vy}C4PnQ7CHxM=KSA-k0AG!;MwJg4V!LFP zeq^Z_L)(}BPs}C?6=O^PM=Ht$lI*4=-LaGCgKW19e-`1pDSY{+n9CqX+44=2{yT?= zAB|Ijp4bWaY?=6fj|{&X;Yh{mLrs9 zI1Uzl3>cR2hYbCM#4bTW~o&5$l6O;$AC44Fc5)N9|y){*1gDjoU-oP5_@e$3#fTcG|;t-C^PbXx&5yT5nyukfY@z|}D3^H)Pq~a-rJfx}!km^u7oQmvT zO9nY1Lze%BIMkS`Y2dJQTIgv@6ufh7i&Q0uM=gHX~pg1)KoDC~Zo2hbh-n zhTV^_1FD|ykE$oX1(jh2-Y==zhnOc-RRJ@Ks`A~b40%FE9!KP=KT)eb9M!Lc`&Sug z;9*JKX$0LvK_?Itk5md1toZeTAAzvP3_@bOaE(9k6xl%NkFblGvVcRxT!sY&(vSZ3>f8E^mrcTvFV4~4Hl?7X${ zAZZZQACh!ChEQFqZU{xp8q|FwQnc5R{W5y-D53r_Cy>EL9ZE4$P_I|(LgCW9*MmWd_yf|A<5} zh!jJVVh||k$yLVdA@sYtL7C?S@=Q^lU5+S@$=}@(*WBfhlrH%lah{qJSheE-rk-t> zs$NEn?Ss5o7?*R_k(`#a<}A&Y8~LThx;87l%ptaG&XUwY%#9jz} z>ks5eML3N3rIT0%r{lm6a=6nn#VArVQ;M!!t@RtP<j`elG71UO3ps$8ts)!(tr@>jVe1==xx{r_Uc^g@2E znb8G`;1`s=GD0sR?57BokA_ANRY`)HddV3_j zI}y9&6tyB?hpyMKt0}0k40;4XnCj5)GMRyLA1KRp}{M?%r67%CM4(fuKH-6QKaDjf!<4>8YOeY; zwUM9I+(yab(OMIEms07MbgW14Rtnw+;A(Xm1Lw_&fcMGZdl9^sf>#%XwPeKhR9z&g z+>NNmDQbUcR9tEm8&$uIGJ+`O|A$ts-pZ^hdLE)k4WfFhq|PY>3Q(Ya01D9{5GZ7e z=$GNjCrE7{QuTTwq|Fd6RFkNFLQ<{~(Z*EeBB1HpM8AyFhB!N>sI|6**Gg^_)!QVE zdJw6RBK1c=(zl9!8RsbC98fjd&gxHeT(pysW>LLe(sByXhbj61poeG{5zzJRVn9Z( z{3EI3GZek%$?(>U*f46Il$35p*xJ8iB^}|g%e6oOCzXnA;)o2e9|77ZK+Q*k^#Xte z1x5La*WvegMW~vOO1ktR)PSlBKxrcYK^G1swXq{I+Bl+}R2BOes}9y!b&7>-Xf+>` zG@M4vsx#DDfT{NNp)o_Xwj(lR^)XVfdnjbhQ&AxGO|IrCNxuMs^{Dzq1C!g`5gG0P z!W~m}`*<|nbmP04k4w4@AXwQysOV^N>Td>R^_o!=$m2f zC!|so|A|yr7o`{iiijQ%u`LeC*fofKlw#KvTSJ12Hv@{T37(f!Z2`E{l;UYL_z-~$ z(Vj#&sYO8+39$&ahzCmO4ZWw?4bMLftL;q61b!>VGX zyc)yeNv*e`REF$9$O%_Wt|s+OPRb-yuO zMTKr$=)t&TNM>3MpOiHpMxOfOIpmXsQNp9O;n;ZejTnF=tDi!i{gkJv+t1`GYZz3EdjWCud-d>RdXlt6~p zFUOG$<7BKgGP8%LCH)2vY%c|Cs)(o`1$%^75P^g|66A4&>{In4kYV*(2wQd)?>t=@%czPF5!T(p3lpA)+21i4r)gN8(z<%!onaug8=s-;BE@oR2flE z4k+tcDeKvTu-&Sj5n&fP;mv=653YmoaeD>+f$@@Npi&i^HK+e85xtGF(y3lIQ4_@SRWknHfg zE)^;8Z&n;W59=Wb5IiXXbs^9Q1*-ov>VbPKf^ed}3WUrO z_Ap{kQ|x20VvBtSc}WWZ68@4^^m;-CSQ6Dfq$)2(d)L1hKb5Wl5H0|dxO$K) zK)H@5z{T4FAp#_cXbg!CQKF981PZ0z!G)ohZ~>UaHH}CB#-50Fdy95r3HCANo8~)Bt`^jDzeF`YA*|L(zBE zGxUwY+ApHo3tcm@EaZ_OEB;2RtF{cQ>u5yCWU}KBU5!A&vl3J@LbXw-)-OcTPG8^f ztOV78Py-a|EQO-qsn5^yIJa+c`4ue^lHdwYNB{!}aFPOaG%#HrV)-M5JyJf%Bw-#$ z%&JeKsmfn6h$+T6f(scX+>+Cz3ieRkL8G|3fJT5JrvzM&z&#XrSEE6NdGsQvkWE6} zhp5LW>M5hBLEa5HCEy+eE-Od-9{eJxtM=d#>nu1f5w#(^gg%DoyC`~96BzHglrOO7os*$)IhT#J%hXv@=3762-`tnPaA{P`yynLFi#=o5XJ2JvN2V~PLucw zE{5wlX&1iPQIH#cv;cV|$bATT zm_m+m$cG|9id$redqF-4wg+J+DD2+dpy^_|)jt}nuwF}$A*+NuhRCIrXvgBO0&=qmmcT=P34Z4p;sysPe0_^?c)o~BfFY*@+=9TP z6!=&Sz;d*Nyb^R5LZ7A3?O%&QXZBWx=nOd};9&%=e*x>VbPoViFUgB$>u4*xgbwf| zx>HEEpVAEwI_f%c((&~}LI-#fUBy30RS#0Srmq_}TiA78!UI4OPc!nIraY4dd2q{x z)|nGr$SC1&bG31q?2M~HMg|7GptBL5)AzLvZNCd}`ppOy^ z5ds6P7BjUnGSR`z@=2Fpck%zz{j?ffTktR_k{ZIsBry^zfoiNPSFgxi9+ofP+Q8!JFrq)>~; z%|{xulS9A(OycT7t`W*r|Lp|1M7Jh<04VVdBi}UTJCP`#Zk$gD0Z}46g@kpVL2u}6 zPq2V|20+*VPGYM#OI)IzvQ@v5Ae(NONeBT^B5X#&Bb0EA5Yo9pe7+JqYapxuE3tMU z>nY0Gwx1Os~CcVcywe1{=#i{$$Y9EF8$m*TtM6LRg1tPBE_ z#B>~)S}D^2X9~o@6s_F>yu@DeFXAY@lzmqRE9XqH%PkQoBn|3OgX2_#shQUxsBHp= zq{2Q_q5QL0*@wS7<3`Z8Rv?i0dyqds`RfnNIDb(41rAAtF;w9YRpA6tfwte7@`KPV z8i*u4rcsYEsz>Mdm?`3Ecf5_0mIq)G*UsW(@ckV%SnXAvj4NXFnB&ORb(@e+f^9+A zMhZK^Ve=w2XaXxwks+@H-G$HxD0IvBL1P0$CJkB|VMBHaeHhV)Df$_LZeW0biL2MNIp*G}}z?%`gje?KG2rdRg z((OQg3BCj22PpjBt{8RK4Y(n<1U`V^CnetBh889Wv<49Rmi*?aU zD5*;&K&c;g1Exe-vV&Cr9!gn%C^i#D9C-tn#8r=6J(TNM3|u&^5U0|RS;F3j*vBY# z`wv-dMQtgFEj4z?FTwX9eA(x)W=gwR%@~6Z+1dd`A{j%HU6f>ikkBc$F_KWt9Y7?G zY2@gl98Eupkwf3!A-jaW^BLkzM=APbtmtxs2LK7b1@VjPuo?~=j#YbIi-+tIdKaQM zQ1pr)1G>Q$A7R?7pJoH7#5s(d9h7s3a2jm#5jgd29zZ3|Q^+|)IRia0S}|gy2Q-PS zqJ+4_6eT;&$u>sn-}zXC1b)aRfi@#(&F9e%x_-h+uyH}2!*`dGpQU(pa|-EQ1_6a! z5@-j4?xUd9y-`7R<3s`pc_hdIgzTq~qXHysg!j6(D$YB+p8RNsj3cw^=O(WMBaFLzd32Nu zCC<8|VUFzvI9Y!nfkN^ZxydO9tWZ$|jFYsU<)Ytwv={I@MD&=)4u=()fJvtomHL+(y9PNsVWYkMfSafRndKfStMeE)8cG$ z%6@A~tu+nuC0_IqEP6j^0!1JFB`f;IFh!RS^r0aIGD;m2VhQ&QMlE5;<9W0Y zL!uU7NEB7i5w|){DW*9^xV9gXB3j4+T!}V-wB=vKlJ)+Il`LUeF$9rl2iOvICsGF} zb>q;?Q3pj20GGH2k^2zkJ~@NjJerU~1F}RsiNs@+xa-$5SZFbt5N-gMxXa2gay4OH z*9-%9ME=;k94F3UB;vOvvomBMJ_=CGc7VZ=>KRVgpx?L!!`-SAuRu z=m84dF&b5G{gF0WoFzj!$Sh%ZBlby(UG_V`rZcT@os}M&&DN7sc39yyHUJ>K)?y3$ zXWl2Sza>@pJL#oti`YAlB7ar$9NU9%3)fNLCtj4i)EK`solj2rSW1bNz@$w>QBsqM z8uZ^Vh!g&@EDaj0O`#&=pvVeKTJi5=G_rompPWLB4Iq-StICN3?))-3VCk63VYIN? zls_5Dm{MU)1Aw$1P&73;KYwE`{WmEl|0_f>1E|7YPyveB@rM{y5PPvbcsCqCqzytz zQkSZi@D&xf{60@kK779eL$nnhZ*GL2K;5|Vg(z4j7Hkv>wgc`a>lu%+WW1v)v!+ym zVaKg$l>m}DFF*eS8*@LT{TDQrQbOo5h`Jozg=6A|*FY6@xjRf?*Eg!jE0)PA<<^vs zr2#5wX-jBB``Ee{wpai2o>k!OYRWra{mm;|eQ)0Zzb}Q34gQy8v7}6*Vzpnv8A;s) zC`My;UIL0~YaGcb52t)M4L~zfliz~vj;KN$lK&;@QuZm*7*2yO#1sD*V||5L`$$fK zYxQcZ1TSq0%9pwzcSU~s>J`59HG&9Ag-j1om5r!nb6}3`HkiX5Q?;~&jZfM^C9pG5 z3%sOXhMZC?Xh-020EP+MFb57-6#_7S-$Hu~$Sr~QA$ZBl2)_GIQ5{eM7s5JG9snf# z3B+%r_)`QwvL8wKL1CM)0F1;^TtPgfld>E-&TMbcL~=|fB!D83)FR0UCE5AsSV?$L zCh(A7f^S9mX$pTdW_U5+5&}Sw2)dD=ZZ}qS>xr24S65=GCqaG*eiY%`Dg0Riudc*~ zi5OB%M~h{oOn@qpP9f4rwA#aN~BfK6K^|3N$VzKHDuU;MA!gM zVhbQ!^;falCSqa}-oemXgX|J|C!)7f^tS(HwPv8@=-liDnXMj#HX`LZiNiiS7tkj!+>0U?i5ZN>cOXEm#+ge~p<%9|HhD!f!y~jly%qXm~9y`5CEP;SBrE9DBUSe7j6Qi1z0#-M;u^4 zP6@abfrlyZ!T&Q{4&5PKA_ZVb6x~R1hEh~bF}H{o1#}eBU;|(zmQiG>-Ggp0LRgFq zHgqf@0}g;mTvN!^M!8!49-q-dMIFGBsH&=npAAr|GjUSsLl2-xBmpEjNlCiT#8)0U z{s4|d(}^@yU&kt{`A2*-d}X@ zjYT2@)cSA)*b;Twr-_>!qtwO!jL)A1_I8_fWY zMAMBlLzHHc&=?LRqS=gu5_Y9$9Nk94lAc?0Bd73HD z;WFlb;nqaL`Ja)f1@IF41hOBb?6sd{PB}B|`mhD?5_|DyiPwx$_Hn``Za8l7qXubI0St+v6)Eb!h2FE{Q_QZ0DdYnPDG?w^MBPZVpAz*E zA{xF76U}4C5>g7lkSIoxVvtfaRK!sh>99gd1V|Fm6cU}LM8^qHjAhXuVMu8JN1~~! zAzrt$4eMtA^RbsF)WL?74A3O90Fv#cWM!3cl!>2C$X>1kjzrUmG<}rj2%({)55sP- zgwwD?5NVkJO(GjavJ;f7`GwfY^hYC78o-fgCXuG%+gML02~Fe;13}S2J1~(F0D?qN z_9AhF-ISoSiVszlm~q|=miVunbCozBTA zgcpD%-frZrdj;L8{j*Tm$Xl=sxl{1=D?$$F68R{Sw^Q=68fIz3O@(j0B8>$QB!Vd< zI6?^qIKj3sjV02NdSV*^D2b`+bHrIsQKse>Sy2LUFh$!m3Gfno0NJbeV=bPZd3Ld} zlC69|A!*Qw8njXkx@%{={JM5aQ~(Z1g+WxIm#R?rxfxf1H(|mL1QP!w@*k)C$7h~j z*P4k6z#*wnR!6+B{5$9e2kO8C>bJNvVk{*qyM}X>4^?6nm@w7YM#z_zX zFo~-jxeigTQ3G62Pp_h}5}+ieK4coBOs(~-RN=bbNMH4cJ4lEP0Zw9@K(-wnSeHA# z0Bm%O9WR@(>_G?tQ6enBux*!l303LJPPOZ7tFsptQRh61J+m3_@4| zR$^^M)?vyzl{hP34k3I1DDib8-x7AP`b7+v6_!3a3Gx& zssK`AoJ7VR%2?LSYB_Gkkg*DYCEl_x5bruhd4~uuo#@8R8!A`JNNUug8Vyv9LxIH2A2DPBfyCd6 z{2i3P_T|L*`2l703qin>=(>?^h|(S7bnS6;R#DC^qbdN0q{1kwFhy19*v(2jqbkVl z4tOLrrcjNV@1y@!esy-#2%5bCk)%h}OQap_qk4?ak{I%wSu%$F zVH^-idUT>5lT?qAuYn$P9yg=D5qdHQY?3mAs7&QS^pGP&nFKmL@dP#6G5}E`oJ7JF zN*LI~2m^66&WH!AQ3ZfOQlPAn_~K!zz!|Q<;LIryX^rRJl>5_wRZ^-Em71VR^?sdM zWtNo^BM4Cpm?g#9QL)k=pno*AzFvyyVhYg=s3pz%P_t&L+3D9!GahA#TEHu*HGyg! zq-q`h#_MJ08xN2z8FdqFcAcV^kgf+G=85BMeZYEiwjRK1f#JsOE- zFfj1dCNf+D4oQVpRH42LoxSUuOoe!60OWuyab^W@5?eR2?Wb%t`xsko9DztXj!R?& zNQrS283!rj1YwMSRID*d(>XwuNT-nWG$q~tEg+4546KocoY4WcL|xTHJbve^Sf3Sb zGh*RTlRAKwm;=bXmoksc46}Y-2cQyXCvx^t&em_wNNL3mg0vZcl~@On^#o$chwDNt%>>iFnv!ZbKojcRGp~t$jt5kd zCXJ{`H`Qc{XhOqhBGDw`G!Hl=71~jSajL@M{RyiOv^oWt5@jD!mi!Psq5eC}6XGp( zs1+)Jl~^Z`wTZHxAgqanG-M4K(*RuJE^a0s*GaiMJ7&hnp@JGO`(qNP=6_#St!fJYxMQv55to+nno{aX&QF)6j~4@nb3w=ClWA6-`9$R|I!oRG{PgX?SI?}8$?+mYpl-yx%N z?J^B?S;j}7dg@vs?<1;$N&+YZoar#zDy#b5nSj0wVkQdjhEv!cD1-_VEl92x^!Xk6 zg~qGG=PB}LE6j|f#$&3=La!&s1r@IO0;>my*X!60$%jP8~2ZDf@Mnyx9jwQ| zU6eqhDuk25J$%eP+W`Ou&t4v#4O)|j0HuJdYA|yQRpIdYT)73If@Oqo$oAaDAK2m? zAVw`2c_5LYUzWqCIPW1N%$W*;Z>+Hru?5WJ{rBXUroc6e#Kt_?FsIJ-c;76uOS7kc zN?19%@a8zYchSupV(4BHi7PsL&vx%?y1MD=ba6ReS2wsA;48{{6qc)kyUTNPAjLyO zjwf4~?z8q9_pI>T9S|u6*`6Xe_&&ta!nt0_N=^3>>wu%xLQDc@i5B0HGrKNxD%q}lhkN;TYp%WF8b~(S0U-)K z%!6aO=sg|AQOMO~4k*8e2RUAq1~nmf2XGa@XfeJ0OLTQeH@u8l_eZE1vmFJV0#~-f z?Rrvp8EPczgsaST*RG18GI6g0x1qsmjJiC+qx2)hOpk;vra_~{z(yOE8N<2#sYN!! zs~^6SX~ew}>D1tRSmsi*%@wX0m@!S=0~1qBG-H~PEkLhtOv`iS=9$fjE|VMVF?jeh zhX~Ad_;~LE93sRphQPTux0=Br5qCMvf0tuh0uWAlX29z%wth_mN_e=g0qM#W=LAB) znlZf|NN6gM<5gfUX+buBZ?Y`rYG)Wa6U7W8y)=%u;wenR=A|&Y62)2R3~7dRO02@2 zj_vp4yiZXcV~v!JQ)h9Nfvij2ld}Q0mKu5O%X_;-);6BVQ$j2;%wjP;IXON>n3x-E z12KhYVbdt9xmj8@G2iSwWxmA3&jx1Y%}xw%?}BU0g;k<>*0~`J=XkvNp= z*SY(m1s4x)W1GFzIflXA-+aV}Ft@`x7x{eTc27aJQpm%f5ep`=*kXn!Gd#U6FP_oM z6Mt=oFn6-o1!|i!I7X1#+`Z-wOZHtF+V#F7co7=%>!Mj293d+kM8`W@L6n3i2Ieaz zvR^sOyA~teP`Jvx0N0D8Kx4i?iTqKl*??&mY`#A+-=Bz|jKsNEW;Q3YIT`;v^!0ch z8N(_CI`3w(vx$uhjSM~#nFALT!HR}%FVSo>h8rry_Ik!_=Vm*9bG36m?uGB0J+SN3 zY*pq4MRS8<^z)ZZu+} zJ-!NfK`$K99SOJfbR=k_ws}la6jC)ON_Tf+o z+lb#)%8M4~44y+Jtd$dXjTDdr@r1K*Vm3G&w#K^>zON+WhZZsSTG|H~aqiW`t`9yn z;v3ZsD$09LQp{VkeDL|uX2s!q9Y3ux-@-m+ZW5WjO(NXGNz3;Zdg1uU(4UUQy^AA} z^%v9oP47=^4?-dv91A>t-es_bzc-{IIEwsv9`9x)NAbdT;t*Ozm`%O0+o&Rr1bFzN z1>BNgPfs?&@*x)3B&Ls);@yUqh>vA?oZGj!Lit&Q8Z|?zdfu{mN^Alr2FL5}23s_o zM4k6Ko>FHH70jVRVnYRke(ftH#~XzD^dUmTJ}3HMr?S;`K8`Jz@x$!4dP7EZ=C|ze z57f*xac(=dj(9<@iJRZDn{SiNx5){4e>RVQHaO~tp(UHA_$b#dgKtC~c!CU>MDQrb zkKPF}y)sYn(b>v8#Rq)EV1Hnq;zL%v%(t-T?^{^&6rX5|DyH|JgWjLWUOBeQFU(uI zH)PwG`3{ed?96v~%cPx*=1Ygz-YJ+LM3^5$m>)z$o(09QG+}=Itp;R+t3G_;!raaM z=H1P0e*Ik(e#On46@gl{ap6|7*a}fLL6|2{*SYuzFRpfFz z?eBiKa^0%zb*pZ??z(k1DB0Jq$-XW-$8kgUb!)TUt>j#H-I~?ctyz&6xur3vw3anuB%HUH>U=?LyX<+((7+A+~` delta 219 zcmex;i|O|*rVYu~%p7bClhdrt!1R3UKrsE;+8j*l+Sq~VJR1)%z0(G)e)D&m02Z)- zm!mD1?sBvM(?=a48vi>&6xur3vw3anuB%HUH>U=?LyX<+((7+A+ Date: Thu, 3 Aug 2023 17:50:14 +1000 Subject: [PATCH 54/95] Enable use of ofType with choice types --- .../csiro/pathling/search/SearchProvider.java | 2 +- .../pathling/fhirpath/NonLiteralPath.java | 5 +- .../fhirpath/ReferenceNestingKey.java | 3 +- .../csiro/pathling/fhirpath/ResourcePath.java | 8 +- .../pathling/fhirpath/TypeSpecifier.java | 50 ++++ .../fhirpath/UntypedResourcePath.java | 2 +- .../definition/BasicElementDefinition.java | 86 +++++++ .../definition/ChoiceElementDefinition.java | 62 +++++ .../definition/ElementDefinition.java | 134 +++++++++++ .../ReferenceDefinition.java | 20 +- .../ReferenceExtensionDefinition.java | 13 +- .../definition/ResolvedChoiceDefinition.java | 42 ++++ .../{ => definition}/ResourceDefinition.java | 11 +- .../fhirpath/element/ChoiceElementPath.java | 98 ++++++++ .../fhirpath/element/ElementDefinition.java | 215 ------------------ .../fhirpath/element/ElementPath.java | 6 +- .../fhirpath/element/ExtensionPath.java | 1 + .../fhirpath/element/QuantityPath.java | 3 +- .../fhirpath/element/ReferencePath.java | 13 ++ .../fhirpath/function/OfTypeFunction.java | 59 ++++- .../fhirpath/function/ResolveFunction.java | 4 +- .../function/ReverseResolveFunction.java | 4 +- .../terminology/TranslateFunction.java | 2 +- .../fhirpath/literal/LiteralPath.java | 2 +- .../operator/PathTraversalOperator.java | 45 ++-- .../fhirpath/parser/InvocationVisitor.java | 23 +- .../fhirpath/function/CountFunctionTest.java | 2 +- .../function/ExtensionFunctionTest.java | 2 +- .../fhirpath/function/FirstFunctionTest.java | 2 +- .../function/ResolveFunctionTest.java | 2 +- .../function/ReverseResolveFunctionTest.java | 8 +- .../terminology/DesignationFunctionTest.java | 2 +- .../terminology/DisplayFunctionTest.java | 2 +- .../terminology/MemberOfFunctionTest.java | 2 +- .../terminology/PropertyFunctionTest.java | 2 +- .../terminology/TranslateFunctionTest.java | 2 +- .../operator/ComparisonOperatorDateTest.java | 2 +- .../ComparisonOperatorDateTimeTest.java | 2 +- .../ComparisonOperatorInstantTest.java | 2 +- .../operator/ComparisonOperatorTimeTest.java | 2 +- .../operator/PathTraversalOperatorTest.java | 2 +- .../test/assertions/ElementPathAssertion.java | 2 +- .../test/builders/ElementPathBuilder.java | 5 +- .../test/builders/ResourcePathBuilder.java | 2 +- .../pathling/test/helpers/FhirHelpers.java | 4 +- 45 files changed, 654 insertions(+), 308 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/TypeSpecifier.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BasicElementDefinition.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChoiceElementDefinition.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java rename fhirpath/src/main/java/au/csiro/pathling/fhirpath/{element => definition}/ReferenceDefinition.java (83%) rename fhirpath/src/main/java/au/csiro/pathling/fhirpath/{element => definition}/ReferenceExtensionDefinition.java (72%) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResolvedChoiceDefinition.java rename fhirpath/src/main/java/au/csiro/pathling/fhirpath/{ => definition}/ResourceDefinition.java (90%) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ChoiceElementPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementDefinition.java diff --git a/fhir-server/src/main/java/au/csiro/pathling/search/SearchProvider.java b/fhir-server/src/main/java/au/csiro/pathling/search/SearchProvider.java index 44b93884c8..5454931bcf 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/search/SearchProvider.java +++ b/fhir-server/src/main/java/au/csiro/pathling/search/SearchProvider.java @@ -21,7 +21,7 @@ import au.csiro.pathling.config.ServerConfiguration; import au.csiro.pathling.encoders.FhirEncoders; -import au.csiro.pathling.fhirpath.ResourceDefinition; +import au.csiro.pathling.fhirpath.definition.ResourceDefinition; import au.csiro.pathling.io.Database; import au.csiro.pathling.security.OperationAccess; import au.csiro.pathling.terminology.TerminologyServiceFactory; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java index 2c871b9ad8..e0efe04ae1 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java @@ -25,7 +25,8 @@ import static org.apache.spark.sql.functions.flatten; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; -import au.csiro.pathling.fhirpath.element.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.BasicElementDefinition; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.function.NamedFunction; import au.csiro.pathling.fhirpath.literal.NullLiteralPath; import java.util.Arrays; @@ -156,7 +157,7 @@ public Column getExtensionContainerColumn() { * Returns the specified child of this path, if there is one. * * @param name The name of the child element - * @return an {@link ElementDefinition} object + * @return an {@link BasicElementDefinition} object */ @Nonnull public abstract Optional getChildElement(@Nonnull final String name); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ReferenceNestingKey.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ReferenceNestingKey.java index 00976f5d11..b46c98910d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ReferenceNestingKey.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ReferenceNestingKey.java @@ -1,6 +1,7 @@ package au.csiro.pathling.fhirpath; -import au.csiro.pathling.fhirpath.element.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.ResourceDefinition; import javax.annotation.Nonnull; import lombok.Value; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java index 935491ec95..039a9b9a45 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java @@ -19,14 +19,14 @@ import static au.csiro.pathling.QueryHelpers.aliasAllColumns; import static au.csiro.pathling.utilities.Preconditions.checkPresent; -import static java.util.Objects.requireNonNull; import static org.apache.spark.sql.functions.col; import au.csiro.pathling.QueryHelpers.DatasetWithColumnMap; import au.csiro.pathling.encoders.EncoderBuilder; import au.csiro.pathling.encoders.ExtensionSupport; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.element.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.ResourceDefinition; import au.csiro.pathling.io.source.DataSource; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; @@ -151,8 +151,8 @@ public static ResourcePath build(@Nonnull final FhirContext fhirContext, * @return the {@link Column} within the dataset pertaining to this element */ @Nonnull - public Column getElementColumn(@Nonnull final String elementName) { - return requireNonNull(elementsToColumns.get(elementName)); + public Optional getElementColumn(@Nonnull final String elementName) { + return Optional.ofNullable(elementsToColumns.get(elementName)); } @Nonnull diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TypeSpecifier.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TypeSpecifier.java new file mode 100644 index 0000000000..ebd05300af --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TypeSpecifier.java @@ -0,0 +1,50 @@ +package au.csiro.pathling.fhirpath; + +import static org.apache.spark.sql.functions.lit; + +import au.csiro.pathling.fhirpath.literal.LiteralPath; +import javax.annotation.Nonnull; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; +import org.hl7.fhir.r4.model.StringType; + +public class TypeSpecifier extends LiteralPath implements AbstractPath { + + @Nonnull + private final FHIRDefinedType type; + + protected TypeSpecifier(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, + @Nonnull final FHIRDefinedType type) { + super(dataset, idColumn, new StringType(type.toCode()), type.toCode()); + this.type = type; + this.valueColumn = lit(type.toCode()); + } + + @Nonnull + public static TypeSpecifier build(@Nonnull final FhirPath context, + @Nonnull final FHIRDefinedType type) { + return new TypeSpecifier(context.getDataset(), context.getIdColumn(), type); + } + + + @Nonnull + @Override + public FhirPath withDataset(@Nonnull final Dataset dataset) { + return new TypeSpecifier(dataset, idColumn, type); + } + + @Nonnull + @Override + public String getExpression() { + return type.toCode(); + } + + @Nonnull + @Override + public Column buildValueColumn() { + return lit(expression.orElse("[type specifier]")); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java index 614829fc7d..59b2f5d47a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java @@ -18,7 +18,7 @@ package au.csiro.pathling.fhirpath; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.element.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.element.ReferencePath; import java.util.Optional; import javax.annotation.Nonnull; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BasicElementDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BasicElementDefinition.java new file mode 100644 index 0000000000..ec11111ead --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BasicElementDefinition.java @@ -0,0 +1,86 @@ +/* + * Copyright 2023 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.definition; + +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +/** + * Encapsulates the FHIR definitions for an element. + * + * @author John Grimes + */ +public class BasicElementDefinition implements + ElementDefinition { + + @Nonnull + protected final ChildDefinitionType childDefinition; + + @Nonnull + protected final Optional elementDefinition; + + protected BasicElementDefinition(@Nonnull final ChildDefinitionType childDefinition) { + this.childDefinition = childDefinition; + elementDefinition = getElementDefinition(); + } + + @Nonnull + protected Optional getElementDefinition() { + @Nullable BaseRuntimeElementDefinition child; + try { + child = childDefinition.getChildByName(childDefinition.getElementName()); + } catch (final Throwable e) { + child = null; + } + return Optional.ofNullable(child); + } + + @Override + @Nonnull + public String getElementName() { + return childDefinition.getElementName(); + } + + @Override + @Nonnull + public Optional getChildElement(@Nonnull final String name) { + return elementDefinition + .filter(elementDef -> elementDef instanceof BaseRuntimeElementCompositeDefinition) + .map(compElementDef -> (BaseRuntimeElementCompositeDefinition) compElementDef) + .flatMap(compElementDef -> Optional.ofNullable(compElementDef.getChildByName(name)) + .or(() -> Optional.ofNullable(compElementDef.getChildByName(name + "[x]")))) + .map(ElementDefinition::build); + } + + @Override + public Optional getMaxCardinality() { + return Optional.of(childDefinition.getMax()); + } + + @Override + @Nonnull + public Optional getFhirType() { + return elementDefinition.flatMap(ElementDefinition::getFhirTypeFromElementDefinition); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChoiceElementDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChoiceElementDefinition.java new file mode 100644 index 0000000000..903f402c1f --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChoiceElementDefinition.java @@ -0,0 +1,62 @@ +package au.csiro.pathling.fhirpath.definition; + +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nonnull; +import org.apache.commons.lang.WordUtils; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +public class ChoiceElementDefinition implements ElementDefinition { + + @Nonnull + private final RuntimeChildChoiceDefinition childDefinition; + + private final Map> elementNameToDefinition; + + protected ChoiceElementDefinition(@Nonnull final RuntimeChildChoiceDefinition childDefinition) { + this.childDefinition = childDefinition; + elementNameToDefinition = new HashMap<>(); + childDefinition.getValidChildNames() + .forEach(name -> elementNameToDefinition.put(name, childDefinition.getChildByName(name))); + } + + @Nonnull + @Override + public String getElementName() { + return childDefinition.getElementName(); + } + + @Nonnull + @Override + public Optional getChildElement(@Nonnull final String name) { + return Optional.empty(); + } + + @Override + public Optional getMaxCardinality() { + return Optional.of(childDefinition.getMax()); + } + + @Nonnull + @Override + public Optional getFhirType() { + return Optional.empty(); + } + + @Nonnull + public Optional getChildByType(@Nonnull final String type) { + final String key = ChoiceElementDefinition.getColumnName(getElementName(), type); + return Optional.ofNullable(elementNameToDefinition.get(key)) + .map(def -> new ResolvedChoiceDefinition(def, this)); + } + + @Nonnull + public static String getColumnName(@Nonnull final String elementName, + @Nonnull final String type) { + return elementName + WordUtils.capitalize(type); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java new file mode 100644 index 0000000000..48b335ef1b --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java @@ -0,0 +1,134 @@ +package au.csiro.pathling.fhirpath.definition; + +import au.csiro.pathling.fhirpath.element.BooleanPath; +import au.csiro.pathling.fhirpath.element.CodingPath; +import au.csiro.pathling.fhirpath.element.DatePath; +import au.csiro.pathling.fhirpath.element.DateTimePath; +import au.csiro.pathling.fhirpath.element.DecimalPath; +import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.element.ExtensionPath; +import au.csiro.pathling.fhirpath.element.IntegerPath; +import au.csiro.pathling.fhirpath.element.QuantityPath; +import au.csiro.pathling.fhirpath.element.ReferencePath; +import au.csiro.pathling.fhirpath.element.StringPath; +import au.csiro.pathling.fhirpath.element.TimePath; +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.RuntimeChildAny; +import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; +import ca.uhn.fhir.context.RuntimeChildResourceDefinition; +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nonnull; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.r4.model.BackboneElement; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +public interface ElementDefinition { + + // See https://hl7.org/fhir/fhirpath.html#types. + @Nonnull + Map> FHIR_TYPE_TO_ELEMENT_PATH_CLASS = + new ImmutableMap.Builder>() + .put(FHIRDefinedType.BOOLEAN, BooleanPath.class) + .put(FHIRDefinedType.STRING, StringPath.class) + .put(FHIRDefinedType.URI, StringPath.class) + .put(FHIRDefinedType.URL, StringPath.class) + .put(FHIRDefinedType.CANONICAL, StringPath.class) + .put(FHIRDefinedType.CODE, StringPath.class) + .put(FHIRDefinedType.OID, StringPath.class) + .put(FHIRDefinedType.ID, StringPath.class) + .put(FHIRDefinedType.UUID, StringPath.class) + .put(FHIRDefinedType.MARKDOWN, StringPath.class) + .put(FHIRDefinedType.BASE64BINARY, StringPath.class) + .put(FHIRDefinedType.INTEGER, IntegerPath.class) + .put(FHIRDefinedType.UNSIGNEDINT, IntegerPath.class) + .put(FHIRDefinedType.POSITIVEINT, IntegerPath.class) + .put(FHIRDefinedType.DECIMAL, DecimalPath.class) + .put(FHIRDefinedType.DATE, DatePath.class) + .put(FHIRDefinedType.DATETIME, DateTimePath.class) + .put(FHIRDefinedType.INSTANT, DateTimePath.class) + .put(FHIRDefinedType.TIME, TimePath.class) + .put(FHIRDefinedType.CODING, CodingPath.class) + .put(FHIRDefinedType.QUANTITY, QuantityPath.class) + .put(FHIRDefinedType.SIMPLEQUANTITY, QuantityPath.class) + .put(FHIRDefinedType.REFERENCE, ReferencePath.class) + .put(FHIRDefinedType.EXTENSION, ExtensionPath.class) + .build(); + + /** + * @param childDefinition A HAPI {@link BaseRuntimeChildDefinition} that describes this element + * @return A shiny new ElementDefinition + */ + @Nonnull + static ElementDefinition build(@Nonnull final BaseRuntimeChildDefinition childDefinition) { + if (childDefinition instanceof RuntimeChildAny && "valueReference".equals(childDefinition + .getElementName())) { + return new ReferenceExtensionDefinition((RuntimeChildAny) childDefinition); + } else if (childDefinition instanceof RuntimeChildResourceDefinition) { + return new ReferenceDefinition((RuntimeChildResourceDefinition) childDefinition); + } else if (childDefinition instanceof RuntimeChildChoiceDefinition) { + return new ChoiceElementDefinition((RuntimeChildChoiceDefinition) childDefinition); + } else { + return new BasicElementDefinition<>(childDefinition); + } + } + + /** + * @param fhirType A {@link FHIRDefinedType} + * @return The subtype of {@link ElementPath} that represents this type + */ + @Nonnull + static Optional> elementClassForType( + @Nonnull final FHIRDefinedType fhirType) { + return Optional.ofNullable( + BasicElementDefinition.FHIR_TYPE_TO_ELEMENT_PATH_CLASS.get(fhirType)); + } + + @Nonnull + static Optional getFhirTypeFromElementDefinition( + @Nonnull final BaseRuntimeElementDefinition elementDefinition) { + return Optional.ofNullable(elementDefinition.newInstance()) + .flatMap(ElementDefinition::getFhirTypeFromObject); + } + + static Optional getFhirTypeFromObject(final IBase hapiObject) { + // BackboneElements do not seem to correctly report their FHIR type. + if (hapiObject.getClass().getSuperclass() == BackboneElement.class) { + return Optional.of(FHIRDefinedType.BACKBONEELEMENT); + } else { + final FHIRDefinedType fhirType = FHIRDefinedType.fromCode(hapiObject.fhirType()); + return Optional.ofNullable(fhirType); + } + } + + /** + * @return the name of this element + */ + @Nonnull + String getElementName(); + + /** + * Returns the child element of this element with the specified name. + * + * @param name The name of the child element + * @return A new ElementDefinition describing the child + */ + @Nonnull + Optional getChildElement(@Nonnull String name); + + /** + * @return The maximum cardinality for this element + */ + Optional getMaxCardinality(); + + /** + * @return The {@link FHIRDefinedType} that corresponds to the type of this element. Not all + * elements have a type, e.g. polymorphic elements. + */ + @Nonnull + Optional getFhirType(); + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferenceDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ReferenceDefinition.java similarity index 83% rename from fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferenceDefinition.java rename to fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ReferenceDefinition.java index f25a96dd9b..ead29290ec 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferenceDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ReferenceDefinition.java @@ -15,11 +15,10 @@ * limitations under the License. */ -package au.csiro.pathling.fhirpath.element; +package au.csiro.pathling.fhirpath.definition; import static java.util.Objects.requireNonNull; -import au.csiro.pathling.fhirpath.NestingKey; import ca.uhn.fhir.context.RuntimeChildResourceDefinition; import java.lang.reflect.InvocationTargetException; import java.util.List; @@ -35,18 +34,17 @@ * * @author John Grimes */ -public class ReferenceDefinition extends ElementDefinition { +public class ReferenceDefinition extends BasicElementDefinition { - @Nonnull - private final RuntimeChildResourceDefinition childDefinition; - - protected ReferenceDefinition(@Nonnull final RuntimeChildResourceDefinition childDefinition, - @Nonnull final String elementName, final NestingKey parent) { - super(childDefinition, elementName, parent); - this.childDefinition = childDefinition; + protected ReferenceDefinition(@Nonnull final RuntimeChildResourceDefinition childDefinition) { + super(childDefinition); } - @Override + /** + * Returns the set of resources that a reference can refer to. + * + * @return A set of {@link ResourceType} objects, if this element is a reference + */ @Nonnull public Set getReferenceTypes() { final List> resourceTypes = childDefinition.getResourceTypes(); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferenceExtensionDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ReferenceExtensionDefinition.java similarity index 72% rename from fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferenceExtensionDefinition.java rename to fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ReferenceExtensionDefinition.java index 8938603843..c3b0dc1131 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferenceExtensionDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ReferenceExtensionDefinition.java @@ -15,10 +15,9 @@ * limitations under the License. */ -package au.csiro.pathling.fhirpath.element; +package au.csiro.pathling.fhirpath.definition; -import au.csiro.pathling.fhirpath.NestingKey; -import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.RuntimeChildAny; import java.util.Set; import javax.annotation.Nonnull; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -28,14 +27,12 @@ * * @author John Grimes */ -public class ReferenceExtensionDefinition extends ElementDefinition { +public class ReferenceExtensionDefinition extends BasicElementDefinition { - protected ReferenceExtensionDefinition(@Nonnull final BaseRuntimeChildDefinition childDefinition, - @Nonnull final String elementName, final NestingKey parent) { - super(childDefinition, elementName, parent); + protected ReferenceExtensionDefinition(@Nonnull final RuntimeChildAny childDefinition) { + super(childDefinition); } - @Override @Nonnull public Set getReferenceTypes() { // We always treat a reference extension as a reference to any resource, as we don't have enough diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResolvedChoiceDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResolvedChoiceDefinition.java new file mode 100644 index 0000000000..09a16d3969 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResolvedChoiceDefinition.java @@ -0,0 +1,42 @@ +package au.csiro.pathling.fhirpath.definition; + +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; +import java.util.Optional; +import javax.annotation.Nonnull; +import lombok.Getter; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +public class ResolvedChoiceDefinition implements ElementDefinition { + + @Nonnull + private final BaseRuntimeElementDefinition elementDefinition; + + @Nonnull + @Getter + private final Optional maxCardinality; + + public ResolvedChoiceDefinition(@Nonnull final BaseRuntimeElementDefinition definition, @Nonnull final + ChoiceElementDefinition parent) { + this.elementDefinition = definition; + this.maxCardinality = parent.getMaxCardinality(); + } + + @Nonnull + @Override + public String getElementName() { + return elementDefinition.getName(); + } + + @Nonnull + @Override + public Optional getChildElement(@Nonnull final String name) { + return Optional.empty(); + } + + @Nonnull + @Override + public Optional getFhirType() { + return ElementDefinition.getFhirTypeFromElementDefinition(elementDefinition); + } +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourceDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResourceDefinition.java similarity index 90% rename from fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourceDefinition.java rename to fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResourceDefinition.java index 923a54d02c..04d4872a81 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourceDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResourceDefinition.java @@ -15,10 +15,9 @@ * limitations under the License. */ -package au.csiro.pathling.fhirpath; +package au.csiro.pathling.fhirpath.definition; -import au.csiro.pathling.fhirpath.element.ElementDefinition; -import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import au.csiro.pathling.fhirpath.NestingKey; import ca.uhn.fhir.context.RuntimeResourceDefinition; import java.util.Objects; import java.util.Optional; @@ -73,9 +72,9 @@ public ResourceDefinition(@Nonnull final ResourceType resourceType, */ @Nonnull public Optional getChildElement(@Nonnull final String name) { - final Optional childDefinition = Optional - .ofNullable(definition.getChildByName(name)); - return childDefinition.map(definition -> ElementDefinition.build(definition, name, this)); + return Optional.ofNullable(definition.getChildByName(name)) + .or(() -> Optional.ofNullable(definition.getChildByName(name + "[x]"))) + .map(ElementDefinition::build); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ChoiceElementPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ChoiceElementPath.java new file mode 100644 index 0000000000..932f181f38 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ChoiceElementPath.java @@ -0,0 +1,98 @@ +package au.csiro.pathling.fhirpath.element; + +import au.csiro.pathling.errors.InvalidUserInputError; +import au.csiro.pathling.fhirpath.AbstractPath; +import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.NonLiteralPath; +import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.definition.ChoiceElementDefinition; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.operator.PathTraversalOperator; +import java.util.Optional; +import javax.annotation.Nonnull; +import lombok.AccessLevel; +import lombok.Getter; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; + +public class ChoiceElementPath extends NonLiteralPath implements AbstractPath { + + @Getter(AccessLevel.PUBLIC) + @Nonnull + protected final ChoiceElementDefinition definition; + + @Getter + @Nonnull + private final Optional orderingColumn; + + @Nonnull + private final NonLiteralPath from; + + protected ChoiceElementPath(@Nonnull final String expression, @Nonnull final Dataset dataset, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, + @Nonnull final Optional currentResource, + @Nonnull final Optional thisColumn, + @Nonnull final ChoiceElementDefinition definition, @Nonnull final NonLiteralPath from) { + super(expression, dataset, idColumn, valueColumn, singular, currentResource, thisColumn); + this.orderingColumn = orderingColumn; + this.definition = definition; + this.from = from; + } + + @Nonnull + public static ChoiceElementPath build(@Nonnull final String expression, + @Nonnull final NonLiteralPath from, @Nonnull final Dataset dataset, + @Nonnull final Column valueColumn, @Nonnull final Optional orderingColumn, + final boolean singular, final ChoiceElementDefinition definition) { + return new ChoiceElementPath(expression, dataset, from.getIdColumn(), valueColumn, + orderingColumn, singular, from.getCurrentResource(), from.getThisColumn(), definition, + from); + } + + @Nonnull + @Override + public NonLiteralPath combineWith(@Nonnull final FhirPath target, + @Nonnull final Dataset dataset, @Nonnull final String expression, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, final boolean singular, + @Nonnull final Optional thisColumn) { + throw new InvalidUserInputError( + "Paths cannot be merged into a collection together: " + getExpression() + ", " + target + .getExpression()); + } + + @Nonnull + @Override + public Optional getChildElement(@Nonnull final String name) { + return Optional.empty(); + } + + @Nonnull + public Optional resolveChoice(@Nonnull final String type) { + return definition.getChildByType(type); + } + + @Nonnull + public Optional resolveChoiceColumn(@Nonnull final String type) { + if (from instanceof ResourcePath) { + return ((ResourcePath) from).getElementColumn( + ChoiceElementDefinition.getColumnName(definition.getElementName(), type)); + } else if (from instanceof ElementPath) { + return Optional.of(PathTraversalOperator.buildTraversalColumn(from, type)); + } else { + throw new IllegalStateException("Cannot resolve choice column from " + from); + } + } + + @Nonnull + @Override + public NonLiteralPath copy(@Nonnull final String expression, @Nonnull final Dataset dataset, + @Nonnull final Column idColumn, @Nonnull final Column valueColumn, + @Nonnull final Optional orderingColumn, final boolean singular, + @Nonnull final Optional thisColumn) { + return new ChoiceElementPath(expression, dataset, idColumn, valueColumn, orderingColumn, + singular, currentResource, thisColumn, definition, from); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementDefinition.java deleted file mode 100644 index 9523817b25..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementDefinition.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2023 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.element; - -import au.csiro.pathling.fhirpath.NestingKey; -import ca.uhn.fhir.context.BaseRuntimeChildDefinition; -import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; -import ca.uhn.fhir.context.BaseRuntimeElementDefinition; -import ca.uhn.fhir.context.RuntimeChildAny; -import ca.uhn.fhir.context.RuntimeChildResourceDefinition; -import com.google.common.collect.ImmutableMap; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.hl7.fhir.instance.model.api.IBase; -import org.hl7.fhir.r4.model.BackboneElement; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; - -/** - * Encapsulates the FHIR definitions for an element. - * - * @author John Grimes - */ -public class ElementDefinition implements NestingKey { - - // See https://hl7.org/fhir/fhirpath.html#types. - @Nonnull - private static final Map> FHIR_TYPE_TO_ELEMENT_PATH_CLASS = - new ImmutableMap.Builder>() - .put(FHIRDefinedType.BOOLEAN, BooleanPath.class) - .put(FHIRDefinedType.STRING, StringPath.class) - .put(FHIRDefinedType.URI, StringPath.class) - .put(FHIRDefinedType.URL, StringPath.class) - .put(FHIRDefinedType.CANONICAL, StringPath.class) - .put(FHIRDefinedType.CODE, StringPath.class) - .put(FHIRDefinedType.OID, StringPath.class) - .put(FHIRDefinedType.ID, StringPath.class) - .put(FHIRDefinedType.UUID, StringPath.class) - .put(FHIRDefinedType.MARKDOWN, StringPath.class) - .put(FHIRDefinedType.BASE64BINARY, StringPath.class) - .put(FHIRDefinedType.INTEGER, IntegerPath.class) - .put(FHIRDefinedType.UNSIGNEDINT, IntegerPath.class) - .put(FHIRDefinedType.POSITIVEINT, IntegerPath.class) - .put(FHIRDefinedType.DECIMAL, DecimalPath.class) - .put(FHIRDefinedType.DATE, DatePath.class) - .put(FHIRDefinedType.DATETIME, DateTimePath.class) - .put(FHIRDefinedType.INSTANT, DateTimePath.class) - .put(FHIRDefinedType.TIME, TimePath.class) - .put(FHIRDefinedType.CODING, CodingPath.class) - .put(FHIRDefinedType.QUANTITY, QuantityPath.class) - .put(FHIRDefinedType.SIMPLEQUANTITY, QuantityPath.class) - .put(FHIRDefinedType.REFERENCE, ReferencePath.class) - .put(FHIRDefinedType.EXTENSION, ExtensionPath.class) - .build(); - - @Nonnull - private final BaseRuntimeChildDefinition childDefinition; - - @Nonnull - private final Optional elementDefinition; - - /** - * The parent definition for this element. There will always be a parent definition, whether it be - * another {@link org.hl7.fhir.r4.model.ElementDefinition} or a - * {@link au.csiro.pathling.fhirpath.ResourceDefinition}. - */ - @Nonnull - private final NestingKey parent; - - protected ElementDefinition(@Nonnull final BaseRuntimeChildDefinition childDefinition, - @Nonnull final String elementName, @Nonnull final NestingKey parent) { - this.childDefinition = childDefinition; - this.parent = parent; - elementDefinition = Optional.ofNullable(childDefinition.getChildByName(elementName)); - } - - /** - * @param childDefinition A HAPI {@link BaseRuntimeChildDefinition} that describes this element - * @param elementName The name of the element - * @return A shiny new ElementDefinition - */ - @Nonnull - public static ElementDefinition build(@Nonnull final BaseRuntimeChildDefinition childDefinition, - @Nonnull final String elementName, @Nonnull final NestingKey parent) { - if (elementName.equals("valueReference") && childDefinition instanceof RuntimeChildAny) { - return new ReferenceExtensionDefinition(childDefinition, elementName, parent); - } else if (childDefinition instanceof RuntimeChildResourceDefinition) { - return new ReferenceDefinition((RuntimeChildResourceDefinition) childDefinition, elementName, - parent); - } else { - return new ElementDefinition(childDefinition, elementName, parent); - } - } - - /** - * Returns the child element of this element with the specified name. - * - * @param name The name of the child element - * @return A new ElementDefinition describing the child - */ - @Nonnull - public Optional getChildElement(@Nonnull final String name) { - if (elementDefinition.isPresent() - && elementDefinition.get() instanceof BaseRuntimeElementCompositeDefinition) { - final BaseRuntimeElementCompositeDefinition compositeDefinition = - (BaseRuntimeElementCompositeDefinition) elementDefinition.get(); - final BaseRuntimeChildDefinition newChild = compositeDefinition.getChildByName(name); - if (newChild == null) { - return Optional.empty(); - } - // When building a new element definition from a child of this one, specify this element as - // the parent. - return Optional.of(ElementDefinition.build(newChild, name, this)); - } else { - return Optional.empty(); - } - } - - /** - * @return The maximum cardinality for this element - */ - public int getMaxCardinality() { - return childDefinition.getMax(); - } - - /** - * Returns the set of resources that a reference can refer to. - * - * @return A set of {@link ResourceType} objects, if this element is a reference - */ - @Nonnull - public Set getReferenceTypes() { - return Collections.emptySet(); - } - - /** - * @return The {@link FHIRDefinedType} that corresponds to the type of this element. Not all - * elements have a type, e.g. polymorphic elements. - */ - @Nonnull - public Optional getFhirType() { - if (elementDefinition.isEmpty()) { - return Optional.empty(); - } - - @Nullable final IBase exampleObject = elementDefinition.get().newInstance(); - if (exampleObject == null || exampleObject.getClass() == null) { - return Optional.empty(); - } - - return getFhirTypeFromObject(exampleObject); - } - - /** - * @param fhirType A {@link FHIRDefinedType} - * @return The subtype of {@link ElementPath} that represents this type - */ - @Nonnull - public static Optional> elementClassForType( - @Nonnull final FHIRDefinedType fhirType) { - return Optional.ofNullable(FHIR_TYPE_TO_ELEMENT_PATH_CLASS.get(fhirType)); - } - - private static Optional getFhirTypeFromObject(final IBase hapiObject) { - // BackboneElements do not seem to correctly report their FHIR type. - if (hapiObject.getClass().getSuperclass() == BackboneElement.class) { - return Optional.of(FHIRDefinedType.BACKBONEELEMENT); - } else { - final FHIRDefinedType fhirType = FHIRDefinedType.fromCode(hapiObject.fhirType()); - return Optional.ofNullable(fhirType); - } - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final ElementDefinition that = (ElementDefinition) o; - return Objects.equals(childDefinition, that.childDefinition) - // Recursively compare parent definitions. - && Objects.equals(parent, that.parent); - } - - @Override - public int hashCode() { - // Recursively hash parent definitions. - return Objects.hash(childDefinition, parent); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java index 40d91f1cac..63281935ad 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java @@ -21,6 +21,8 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.definition.BasicElementDefinition; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Optional; @@ -53,7 +55,7 @@ public class ElementPath extends NonLiteralPath { @Getter(AccessLevel.PUBLIC) @Nonnull - protected Optional definition = Optional.empty(); + protected Optional definition = Optional.empty(); @Getter @Nonnull @@ -72,7 +74,7 @@ protected ElementPath(@Nonnull final String expression, @Nonnull final Dataset * Use this builder when the path is the child of another path, and will need to be traversable. * diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ExtensionPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ExtensionPath.java index d13439069d..91838d5a3d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ExtensionPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ExtensionPath.java @@ -19,6 +19,7 @@ import au.csiro.pathling.encoders.ExtensionSupport; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import java.util.Arrays; import java.util.List; import java.util.Optional; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/QuantityPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/QuantityPath.java index d2da652794..a78b7c3bd8 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/QuantityPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/QuantityPath.java @@ -26,6 +26,7 @@ import au.csiro.pathling.fhirpath.Numeric; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.comparison.QuantitySqlComparator; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.encoding.QuantityEncoding; import au.csiro.pathling.fhirpath.literal.NullLiteralPath; import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; @@ -156,7 +157,7 @@ private static Column getValidResult( public static Function buildMathOperation(@Nonnull final Numeric source, @Nonnull final MathOperation operation, @Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Optional elementDefinition) { + @Nonnull final Optional elementDefinition) { return target -> { final BiFunction mathOperation = getMathOperation(operation); final Column sourceComparable = source.getNumericValueColumn(); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java index 5e6b48abf7..4780ef1105 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java @@ -17,8 +17,12 @@ package au.csiro.pathling.fhirpath.element; +import static au.csiro.pathling.utilities.Preconditions.check; + import au.csiro.pathling.fhirpath.Referrer; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.ReferenceDefinition; import java.util.Collections; import java.util.Optional; import java.util.Set; @@ -45,6 +49,15 @@ protected ReferencePath(@Nonnull final String expression, @Nonnull final Dataset thisColumn, fhirType); } + @Nonnull + @Override + public Optional getDefinition() { + final Optional definition = super.getDefinition(); + check(definition.isEmpty() || definition.get() instanceof ReferenceDefinition); + //noinspection unchecked + return (Optional) definition; + } + @Nonnull public Set getResourceTypes() { if (getDefinition().isPresent()) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/OfTypeFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/OfTypeFunction.java index 4eeb690cff..97069c223e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/OfTypeFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/OfTypeFunction.java @@ -17,12 +17,22 @@ package au.csiro.pathling.fhirpath.function; +import static au.csiro.pathling.QueryHelpers.createColumn; +import static au.csiro.pathling.utilities.Preconditions.checkPresent; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; +import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.TypeSpecifier; import au.csiro.pathling.fhirpath.UntypedResourcePath; +import au.csiro.pathling.fhirpath.element.ChoiceElementPath; +import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import java.util.Optional; import javax.annotation.Nonnull; +import org.apache.spark.sql.Column; /** * A function filters items in the input collection to only those that are of the given type. @@ -38,11 +48,23 @@ public class OfTypeFunction implements NamedFunction { @Override public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final String expression = NamedFunction.expressionFromInput(input, NAME); - checkUserInput(input.getInput() instanceof UntypedResourcePath, - "Input to ofType function must be a polymorphic resource type: " + input.getInput() - .getExpression()); + final NonLiteralPath inputPath = input.getInput(); + final boolean untypedResource = inputPath instanceof UntypedResourcePath; + final boolean choiceElement = inputPath instanceof ChoiceElementPath; + checkUserInput(untypedResource || choiceElement, + "Input to ofType function must be a polymorphic resource type or choice element path: " + + inputPath.getExpression()); checkUserInput(input.getArguments().size() == 1, "ofType function must have one argument: " + expression); + + return untypedResource + ? resolveUntypedResource(input, expression) + : resolveChoiceElement(input, expression); + } + + @Nonnull + private static FhirPath resolveUntypedResource(final @Nonnull NamedFunctionInput input, + final String expression) { final UntypedResourcePath inputPath = (UntypedResourcePath) input.getInput(); final FhirPath argumentPath = input.getArguments().get(0); @@ -52,9 +74,34 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { "Argument to ofType function must be a resource type: " + argumentPath.getExpression()); final ResourcePath resourcePath = (ResourcePath) argumentPath; - return ResolveFunction.resolveMonomorphicReference(inputPath, input.getContext().getDataSource(), - input.getContext().getFhirContext(), resourcePath.getResourceType(), expression, - input.getContext()); + return ResolveFunction.resolveMonomorphicReference(inputPath, + input.getContext().getDataSource(), input.getContext().getFhirContext(), + resourcePath.getResourceType(), expression, input.getContext()); + } + + @Nonnull + private FhirPath resolveChoiceElement(@Nonnull final NamedFunctionInput input, + @Nonnull final String expression) { + final ChoiceElementPath inputPath = (ChoiceElementPath) input.getInput(); + final FhirPath argumentPath = input.getArguments().get(0); + + // If the input is a choice element, check that the argument is a type specifier. + checkUserInput(argumentPath instanceof TypeSpecifier, + "Argument to ofType function must be a type specifier: " + argumentPath.getExpression()); + final TypeSpecifier typeSpecifier = (TypeSpecifier) argumentPath; + final String type = typeSpecifier.getExpression(); + final Optional maybeDefinition = inputPath.resolveChoice(type); + checkUserInput(maybeDefinition.isPresent(), + "Choice element does not have a child element with name " + type + ": " + + inputPath.getExpression()); + final ElementDefinition definition = maybeDefinition.get(); + final Column valueColumn = checkPresent(inputPath.resolveChoiceColumn(type)); + final DatasetWithColumn datasetWithColumn = createColumn(inputPath.getDataset(), + valueColumn); + + return ElementPath.build(expression, datasetWithColumn.getDataset(), inputPath.getIdColumn(), + datasetWithColumn.getColumn(), inputPath.getOrderingColumn(), inputPath.isSingular(), + inputPath.getCurrentResource(), inputPath.getThisColumn(), definition); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java index 46c2ade5ea..778c0efcee 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java @@ -29,7 +29,7 @@ import au.csiro.pathling.fhirpath.ReferenceNestingKey; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.UntypedResourcePath; -import au.csiro.pathling.fhirpath.element.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.BasicElementDefinition; import au.csiro.pathling.fhirpath.element.ReferencePath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; @@ -97,7 +97,7 @@ public static FhirPath resolveMonomorphicReference(@Nonnull final ReferencePath // create a dataset with the full resources. final ResourcePath resourcePath = ResourcePath.build(fhirContext, dataSource, resourceType, expression, referencePath.isSingular()); - final ElementDefinition referenceDefinition = checkPresent(referencePath.getDefinition()); + final BasicElementDefinition referenceDefinition = checkPresent(referencePath.getDefinition()); final ReferenceNestingKey referenceNestingKey = new ReferenceNestingKey(referenceDefinition, resourcePath.getDefinition()); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java index 4df525c833..c3babf3204 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java @@ -26,7 +26,7 @@ import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.ReferenceNestingKey; import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.BasicElementDefinition; import au.csiro.pathling.fhirpath.element.ReferencePath; import java.util.Set; import javax.annotation.Nonnull; @@ -69,7 +69,7 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final ResourcePath currentResource = nonLiteralArgument.getCurrentResource().get(); final ReferencePath referencePath = (ReferencePath) argument; - final ElementDefinition referenceDefinition = checkPresent(referencePath.getDefinition()); + final BasicElementDefinition referenceDefinition = checkPresent(referencePath.getDefinition()); final ReferenceNestingKey referenceNestingKey = new ReferenceNestingKey(referenceDefinition, currentResource.getDefinition()); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java index 51e847ab5b..44f6061979 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java @@ -27,8 +27,8 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.TerminologyUtils; -import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.function.Arguments; import au.csiro.pathling.fhirpath.function.NamedFunction; import au.csiro.pathling.fhirpath.function.NamedFunctionInput; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java index 73fb642e5f..ba3285ac8a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java @@ -111,8 +111,8 @@ private LiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column i this.idColumn = idColumn; this.value = value; this.dataset = dataset; - this.valueColumn = buildValueColumn().alias(randomAlias()); this.expression = expression; + this.valueColumn = buildValueColumn().alias(randomAlias()); } protected LiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java index 645b14d984..552ac35263 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java @@ -25,8 +25,10 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.ChoiceElementDefinition; +import au.csiro.pathling.fhirpath.element.ChoiceElementPath; import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -55,14 +57,7 @@ public NonLiteralPath invoke(@Nonnull final PathTraversalInput input) { .getExpression()); final NonLiteralPath left = (NonLiteralPath) input.getLeft(); final String right = input.getRight(); - - // If the input expression is the same as the input context, the child will be the start of the - // expression. This is to account for where we omit the expression that represents the input - // expression, e.g. "gender" instead of "Patient.gender". - final String inputContextExpression = input.getContext().getInputContext().getExpression(); - final String expression = left.getExpression().equals(inputContextExpression) - ? right - : left.getExpression() + "." + right; + final String expression = buildExpression(input, left, right); final Optional optionalChild = left.getChildElement(right); checkUserInput(optionalChild.isPresent(), "No such child: " + expression); @@ -73,16 +68,40 @@ public NonLiteralPath invoke(@Nonnull final PathTraversalInput input) { final Column valueColumn; if (left instanceof ResourcePath) { result = leftDataset; - valueColumn = ((ResourcePath) left).getElementColumn(right); + valueColumn = ((ResourcePath) left).getElementColumn(right).orElse(left.getIdColumn()); } else { final DatasetWithColumn datasetAndColumn = createColumn(leftDataset, - col(left.getValueColumn() + "." + right)); + buildTraversalColumn(left, right)); result = datasetAndColumn.getDataset(); valueColumn = datasetAndColumn.getColumn(); } - return ElementPath.build(expression, result, left.getIdColumn(), valueColumn, Optional.empty(), - false, left.getCurrentResource(), left.getThisColumn(), childDefinition); + if (childDefinition instanceof ChoiceElementDefinition) { + return ChoiceElementPath.build(expression, left, result, valueColumn, Optional.empty(), false, + (ChoiceElementDefinition) childDefinition); + } else { + return ElementPath.build(expression, result, left.getIdColumn(), valueColumn, + Optional.empty(), false, left.getCurrentResource(), left.getThisColumn(), + childDefinition); + } + } + + @Nonnull + private static String buildExpression(final @Nonnull PathTraversalInput input, + @Nonnull final NonLiteralPath left, @Nonnull final String right) { + // If the input expression is the same as the input context, the child will be the start of the + // expression. This is to account for where we omit the expression that represents the input + // expression, e.g. "gender" instead of "Patient.gender". + final String inputContextExpression = input.getContext().getInputContext().getExpression(); + return left.getExpression().equals(inputContextExpression) + ? right + : left.getExpression() + "." + right; + } + + @Nonnull + public static Column buildTraversalColumn(@Nonnull final NonLiteralPath left, + @Nonnull final String right) { + return col(left.getValueColumn() + "." + right); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index 70c40289ab..c4edad7311 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -24,6 +24,7 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.TypeSpecifier; import au.csiro.pathling.fhirpath.function.NamedFunction; import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.operator.PathTraversalInput; @@ -42,6 +43,7 @@ import javax.annotation.Nullable; import org.apache.spark.sql.Column; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; import org.hl7.fhir.r4.model.Enumerations.ResourceType; /** @@ -134,12 +136,23 @@ public FhirPath visitMemberInvocation(@Nullable final MemberInvocationContext ct final ResourceType resourceType; try { resourceType = ResourceType.fromCode(fhirPath); + } catch (final FHIRException e) { - // If the expression is not a resource reference, treat it as a path traversal from the - // input context. - final PathTraversalInput pathTraversalInput = new PathTraversalInput(context, - thisContext, fhirPath); - return new PathTraversalOperator().invoke(pathTraversalInput); + // If the expression is not a resource type, see if it is one of the other FHIR types. + final FHIRDefinedType fhirType; + try { + fhirType = FHIRDefinedType.fromCode(fhirPath); + + } catch (final FHIRException e2) { + // If the expression is not a FHIR type, treat it as a path traversal from the + // input context. + final PathTraversalInput pathTraversalInput = new PathTraversalInput(context, + thisContext, fhirPath); + return new PathTraversalOperator().invoke(pathTraversalInput); + } + + // If the expression is a FHIR type, we build a TypeSpecifier for it. + return TypeSpecifier.build(context.getInputContext(), fhirType); } // If the expression is a resource reference, we build a ResourcePath for it - we call this diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/CountFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/CountFunctionTest.java index 2a4bbf0f25..3fea54055d 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/CountFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/CountFunctionTest.java @@ -121,7 +121,7 @@ void countsByGrouping() { .resourceType(ResourceType.PATIENT) .expression("Patient") .build(); - final Column groupingColumn = inputPath.getElementColumn("gender"); + final Column groupingColumn = inputPath.getElementColumn("gender").orElseThrow(); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .groupingColumns(Collections.singletonList(groupingColumn)) diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExtensionFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExtensionFunctionTest.java index 09848bf03d..31f0d21f86 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExtensionFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExtensionFunctionTest.java @@ -36,8 +36,8 @@ import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/FirstFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/FirstFunctionTest.java index d4ff027741..d843dcf962 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/FirstFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/FirstFunctionTest.java @@ -241,7 +241,7 @@ void illegalToCallFirstOnGrouping() { .expression("Patient") .build(); - final Column groupingColumn = inputPath.getElementColumn("gender"); + final Column groupingColumn = inputPath.getElementColumn("gender").orElseThrow(); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .groupingColumns(Collections.singletonList(groupingColumn)) diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ResolveFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ResolveFunctionTest.java index a17b843b7e..dad57a3c15 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ResolveFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ResolveFunctionTest.java @@ -30,8 +30,8 @@ import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.UntypedResourcePath; -import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java index a82df0ab38..50a85a1e3f 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java @@ -29,9 +29,8 @@ import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.ResourceDefinition; import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; @@ -246,12 +245,9 @@ void throwsErrorIfArgumentTypeDoesNotMatchInput() { .build(); final RuntimeResourceDefinition hapiResourceDef = fhirContext.getResourceDefinition( "Encounter"); - final ResourceDefinition resourceDefinition = new ResourceDefinition(ResourceType.ENCOUNTER, - hapiResourceDef, Optional.empty()); final BaseRuntimeChildDefinition childDefinition = hapiResourceDef.getChildByName( "episodeOfCare"); - final ElementDefinition definition = ElementDefinition.build(childDefinition, "episodeOfCare", - resourceDefinition); + final ElementDefinition definition = ElementDefinition.build(childDefinition); final ElementPath argument = new ElementPathBuilder(spark) .expression("Encounter.episodeOfCare") .fhirType(FHIRDefinedType.REFERENCE) diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunctionTest.java index 1ec468babf..50ad1509bc 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunctionTest.java @@ -30,8 +30,8 @@ import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.element.CodingPath; -import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.function.NamedFunction; import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.literal.CodingLiteral; diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunctionTest.java index 4601a513cf..050e122138 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunctionTest.java @@ -31,8 +31,8 @@ import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.element.CodingPath; -import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.function.NamedFunction; import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunctionTest.java index 256663c538..94c1e630f6 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunctionTest.java @@ -40,8 +40,8 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.element.BooleanPath; import au.csiro.pathling.fhirpath.element.CodingPath; -import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunctionTest.java index 9b23dac797..edad385673 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunctionTest.java @@ -30,8 +30,8 @@ import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.element.CodingPath; -import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.function.NamedFunction; import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunctionTest.java index abfca246f5..c4b9cf4fd4 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunctionTest.java @@ -40,8 +40,8 @@ import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.element.CodingPath; -import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTest.java index b4f2ac5756..04e385c8fa 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTest.java @@ -22,8 +22,8 @@ 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.definition.ElementDefinition; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.DatasetBuilder; diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTimeTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTimeTest.java index 318a919b69..f67f772d4e 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTimeTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTimeTest.java @@ -22,8 +22,8 @@ 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.definition.ElementDefinition; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.DatasetBuilder; diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java index 1d7a4cc7bb..87c0a4acdf 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java @@ -22,8 +22,8 @@ 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.definition.ElementDefinition; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.DatasetBuilder; diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTimeTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTimeTest.java index 17b0665825..dd3988202f 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTimeTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTimeTest.java @@ -22,8 +22,8 @@ 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.definition.ElementDefinition; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.DatasetBuilder; diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperatorTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperatorTest.java index 3a2fa7b982..eb1a0ca961 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperatorTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperatorTest.java @@ -34,8 +34,8 @@ import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.element.StringPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java index c7c097ceaf..03d52b74c8 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java @@ -20,8 +20,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import au.csiro.pathling.fhirpath.element.ElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import javax.annotation.Nonnull; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java index 6d20f442c6..898c96b691 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java @@ -23,8 +23,9 @@ import au.csiro.pathling.fhirpath.Nesting; import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.BasicElementDefinition; import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.test.helpers.SparkHelpers.IdAndValueColumns; import java.util.Optional; import javax.annotation.Nonnull; @@ -81,7 +82,7 @@ public ElementPathBuilder(@Nonnull final SparkSession spark) { singular = false; nesting = new Nesting(); fhirType = FHIRDefinedType.NULL; - definition = mock(ElementDefinition.class); + definition = mock(BasicElementDefinition.class); } @Nonnull diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java index ff2c78c9f3..cef0564692 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java @@ -23,7 +23,7 @@ import static org.mockito.Mockito.when; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; -import au.csiro.pathling.fhirpath.ResourceDefinition; +import au.csiro.pathling.fhirpath.definition.ResourceDefinition; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.test.fixtures.PatientResourceRowFixture; diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java index 5fe60018f7..91736906d3 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java @@ -19,8 +19,8 @@ import static java.util.Objects.requireNonNull; -import au.csiro.pathling.fhirpath.ResourceDefinition; -import au.csiro.pathling.fhirpath.element.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.ResourceDefinition; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import java.util.Optional; From fe798f7c4ba1e2abde754c16a9491b00097c6c78 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Thu, 3 Aug 2023 21:27:26 +1000 Subject: [PATCH 55/95] Implement constants --- .../csiro/pathling/search/SearchExecutor.java | 2 +- .../java/au/csiro/pathling/QueryExecutor.java | 3 +- .../java/au/csiro/pathling/QueryHelpers.java | 22 ++++++ .../pathling/fhirpath/NonLiteralPath.java | 23 ++---- .../operator/PathTraversalOperator.java | 17 +++-- .../fhirpath/parser/ConstantReplacer.java | 71 +++++++++++++++++++ .../fhirpath/parser/InvocationVisitor.java | 45 ++++++------ .../fhirpath/parser/ParserContext.java | 21 +++--- .../pathling/views/FhirViewExecutor.java | 19 +++-- .../test/builders/ParserContextBuilder.java | 2 +- 10 files changed, 163 insertions(+), 62 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ConstantReplacer.java diff --git a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java index b80743b6e4..881193080d 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java @@ -128,7 +128,7 @@ private Dataset initializeDataset() { } else { final ParserContext parserContext = new ParserContext(resourcePath, fhirContext, sparkSession, dataSource, terminologyServiceFactory, - Collections.singletonList(resourcePath.getIdColumn())); + Collections.singletonList(resourcePath.getIdColumn()), Optional.empty()); Dataset currentDataset = subjectDataset; @Nullable Column filterCondition = null; diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index 7837bb8c61..54ffa0fd39 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -171,7 +171,8 @@ private DatasetWithColumn getFilteredIds(@Nonnull final Iterable filters // Parse the filter expression. final ParserContext parserContext = new ParserContext(currentContext, fhirContext, sparkSession, dataSource, - terminologyServiceFactory, Collections.singletonList(currentContext.getIdColumn())); + terminologyServiceFactory, Collections.singletonList(currentContext.getIdColumn()), + Optional.empty()); final Parser parser = new Parser(parserContext); final FhirPath fhirPath = parser.parse(filter); diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java b/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java index 8eb351ae62..2bcb3f918a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java @@ -22,6 +22,7 @@ import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static org.apache.spark.sql.functions.col; +import static org.apache.spark.sql.functions.flatten; import static org.apache.spark.sql.functions.lit; import static org.apache.spark.sql.functions.posexplode_outer; @@ -54,6 +55,8 @@ import org.apache.spark.sql.SparkSession; import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder; import org.apache.spark.sql.functions; +import org.apache.spark.sql.types.ArrayType; +import org.apache.spark.sql.types.DataType; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -491,6 +494,25 @@ public static Dataset explodeArray(@Nonnull final Dataset dataset, return resultDataset; } + public static Column flattenValue(final Dataset dataset, @Nonnull final Column value) { + return valueIsArrayOfArrays(dataset, value) + ? flatten(value) + : value; + } + + private static boolean valueIsArrayOfArrays(@Nonnull final Dataset dataset, + final Column value) { + final boolean arrayOfArrays; + final DataType dataType = dataset.select(value).schema().fields()[0].dataType(); + if (dataType instanceof ArrayType) { + final ArrayType arrayType = (ArrayType) dataType; + arrayOfArrays = arrayType.elementType() instanceof ArrayType; + } else { + arrayOfArrays = false; + } + return arrayOfArrays; + } + /** * Represents a type of join that can be made between two {@link Dataset} objects. */ diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java index e0efe04ae1..5ed1cf7a35 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java @@ -18,12 +18,13 @@ package au.csiro.pathling.fhirpath; import static au.csiro.pathling.QueryHelpers.createColumn; +import static au.csiro.pathling.QueryHelpers.flattenValue; import static au.csiro.pathling.QueryHelpers.getUnionableColumns; import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static au.csiro.pathling.utilities.Preconditions.checkPresent; import static org.apache.spark.sql.functions.explode_outer; -import static org.apache.spark.sql.functions.flatten; +import au.csiro.pathling.QueryHelpers; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.definition.BasicElementDefinition; import au.csiro.pathling.fhirpath.definition.ElementDefinition; @@ -38,8 +39,6 @@ import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; -import org.apache.spark.sql.types.ArrayType; -import org.apache.spark.sql.types.DataType; /** * Represents any FHIRPath expression which is not a literal. @@ -221,25 +220,11 @@ public Dataset getUnionableDataset(@Nonnull final FhirPath target) { @Nonnull @Override public FhirPath unnest() { - final Column flattenedValueColumn = valueIsArrayOfArrays() - ? flatten(getValueColumn()) - : getValueColumn(); + final Column flattenedValue = flattenValue(dataset, getValueColumn()); final DatasetWithColumn datasetWithColumn = createColumn(getDataset(), - explode_outer(flattenedValueColumn)); + explode_outer(flattenedValue)); return copy(getExpression(), datasetWithColumn.getDataset(), datasetWithColumn.getColumn(), datasetWithColumn.getColumn(), getOrderingColumn(), isSingular(), getThisColumn()); } - private boolean valueIsArrayOfArrays() { - final boolean arrayOfArrays; - final DataType dataType = dataset.select(getValueColumn()).schema().fields()[0].dataType(); - if (dataType instanceof ArrayType) { - final ArrayType arrayType = (ArrayType) dataType; - arrayOfArrays = arrayType.elementType() instanceof ArrayType; - } else { - arrayOfArrays = false; - } - return arrayOfArrays; - } - } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java index 552ac35263..43fb9a3108 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java @@ -18,17 +18,17 @@ package au.csiro.pathling.fhirpath.operator; import static au.csiro.pathling.QueryHelpers.createColumn; +import static au.csiro.pathling.QueryHelpers.flattenValue; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static org.apache.spark.sql.functions.col; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.definition.ChoiceElementDefinition; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.element.ChoiceElementPath; import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -63,6 +63,10 @@ public NonLiteralPath invoke(@Nonnull final PathTraversalInput input) { checkUserInput(optionalChild.isPresent(), "No such child: " + expression); final ElementDefinition childDefinition = optionalChild.get(); + final boolean maxCardinalityOfOne = childDefinition.getMaxCardinality().isPresent() + && childDefinition.getMaxCardinality().get() == 1; + final boolean resultSingular = left.isSingular() && maxCardinalityOfOne; + final Dataset leftDataset = left.getDataset(); final Dataset result; final Column valueColumn; @@ -77,11 +81,11 @@ public NonLiteralPath invoke(@Nonnull final PathTraversalInput input) { } if (childDefinition instanceof ChoiceElementDefinition) { - return ChoiceElementPath.build(expression, left, result, valueColumn, Optional.empty(), false, - (ChoiceElementDefinition) childDefinition); + return ChoiceElementPath.build(expression, left, result, valueColumn, Optional.empty(), + resultSingular, (ChoiceElementDefinition) childDefinition); } else { return ElementPath.build(expression, result, left.getIdColumn(), valueColumn, - Optional.empty(), false, left.getCurrentResource(), left.getThisColumn(), + Optional.empty(), resultSingular, left.getCurrentResource(), left.getThisColumn(), childDefinition); } } @@ -101,7 +105,8 @@ private static String buildExpression(final @Nonnull PathTraversalInput input, @Nonnull public static Column buildTraversalColumn(@Nonnull final NonLiteralPath left, @Nonnull final String right) { - return col(left.getValueColumn() + "." + right); + final Column flattenedValue = flattenValue(left.getDataset(), left.getValueColumn()); + return flattenedValue.getField(right); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ConstantReplacer.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ConstantReplacer.java new file mode 100644 index 0000000000..d18d4a85f2 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ConstantReplacer.java @@ -0,0 +1,71 @@ +/* + * Copyright 2023 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.parser; + +import static java.util.Objects.requireNonNull; + +import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathLexer; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ExternalConstantContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ExternalConstantTermContext; +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTree; + +public class ConstantReplacer extends FhirPathBaseVisitor { + + private final Map constants; + + public ConstantReplacer(final Map constants) { + this.constants = constants; + } + + @Nonnull + public String execute(@Nonnull final String expression) { + final FhirPathLexer lexer = new FhirPathLexer(CharStreams.fromString(expression)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + final FhirPathParser parser = new FhirPathParser(tokens); + + lexer.removeErrorListeners(); + lexer.addErrorListener(new ParserErrorListener()); + + // Remove the default console error reporter, and add a listener that wraps each parse error in + // an invalid request exception. + parser.removeErrorListeners(); + parser.addErrorListener(new ParserErrorListener()); + + return visit(parser.expression()); + } + + @Override + public String visitExternalConstantTerm(@Nullable final ExternalConstantTermContext ctx) { + final ExternalConstantContext constantContext = requireNonNull( + requireNonNull(ctx).externalConstant()); + final String term = Optional.ofNullable((ParseTree) constantContext.identifier()) + .orElse(constantContext.STRING()).getText(); + + final Optional constant = Optional.ofNullable(constants.get(term)); + return constant.orElse(term); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index c4edad7311..d80a410327 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -39,6 +39,7 @@ import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.TotalInvocationContext; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.spark.sql.Column; @@ -126,39 +127,39 @@ public FhirPath visitMemberInvocation(@Nullable final MemberInvocationContext ct return new PathTraversalOperator().invoke(pathTraversalInput); } } else { - // If we're in the context of a function's arguments, there are two valid things this + // If we're in the context of a function's arguments, there are three valid things this // could be: // (1) a path traversal from the "this" context; - // (2) a reference to a resource type. - final FhirPath thisContext = context.getThisContext().get(); + // (2) a resource type specifier, or; + // (3) a data type specifier. - // Check if the expression is a reference to a known resource type. - final ResourceType resourceType; try { - resourceType = ResourceType.fromCode(fhirPath); + // Check if the expression is a reference to a known resource type. + final ResourceType resourceType = ResourceType.fromCode(fhirPath); + return ResourcePath + .build(context.getFhirContext(), context.getDataSource(), resourceType, fhirPath, + true); } catch (final FHIRException e) { - // If the expression is not a resource type, see if it is one of the other FHIR types. - final FHIRDefinedType fhirType; try { - fhirType = FHIRDefinedType.fromCode(fhirPath); - - } catch (final FHIRException e2) { - // If the expression is not a FHIR type, treat it as a path traversal from the - // input context. + // If the expression is not a resource type, attempt path traversal. final PathTraversalInput pathTraversalInput = new PathTraversalInput(context, - thisContext, fhirPath); + context.getThisContext().get(), fhirPath); return new PathTraversalOperator().invoke(pathTraversalInput); - } - // If the expression is a FHIR type, we build a TypeSpecifier for it. - return TypeSpecifier.build(context.getInputContext(), fhirType); + } catch (final InvalidUserInputError e2) { + try { + // If it is not a valid path traversal, see if it is a valid data type specifier. + final FHIRDefinedType fhirType = FHIRDefinedType.fromCode(fhirPath); + return TypeSpecifier.build(context.getInputContext(), fhirType); + + } catch (final FHIRException e3) { + throw new InvalidUserInputError( + "Invocation is not a valid path or type specifier: " + fhirPath); + } + } } - // If the expression is a resource reference, we build a ResourcePath for it - we call this - // the current resource reference. - return ResourcePath - .build(context.getFhirContext(), context.getDataSource(), resourceType, fhirPath, true); } } } @@ -215,7 +216,7 @@ public FhirPath visitFunctionInvocation(@Nullable final FunctionInvocationContex ParserContext argumentContext = new ParserContext(context.getInputContext(), context.getFhirContext(), context.getSparkSession(), context.getDataSource(), context.getTerminologyServiceFactory(), argumentGroupings, context.getUnnestBehaviour(), - context.getVariables(), context.getNesting()); + context.getVariables(), context.getNesting(), Optional.empty()); argumentContext.setThisContext(thisPath); for (final ExpressionContext expression : paramList.expression()) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index dbc082b3f3..e0381d5a50 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -114,6 +114,9 @@ public class ParserContext { @Nonnull private final Nesting nesting; + @Nonnull + private final Optional constantReplacer; + /** * @param inputContext the input context from which the FHIRPath is to be evaluated * @param fhirContext a {@link FhirContext} that can be used to do FHIR stuff @@ -128,9 +131,10 @@ public class ParserContext { public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataSource, @Nonnull final Optional terminologyServiceFactory, - @Nonnull final List groupingColumns) { + @Nonnull final List groupingColumns, + final Optional constantReplacer) { this(inputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, - groupingColumns, UnnestBehaviour.UNNEST, new HashMap<>(), new Nesting()); + groupingColumns, UnnestBehaviour.UNNEST, new HashMap<>(), new Nesting(), constantReplacer); } /** @@ -150,7 +154,7 @@ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirCo @Nonnull final List groupingColumns, @Nonnull final UnnestBehaviour unnestBehaviour, @Nonnull final Map variables, - @Nonnull final Nesting nesting) { + @Nonnull final Nesting nesting, @Nonnull final Optional constantReplacer) { this.inputContext = inputContext; this.fhirContext = fhirContext; this.sparkSession = sparkSession; @@ -160,6 +164,7 @@ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirCo this.unnestBehaviour = unnestBehaviour; this.variables = variables; this.nesting = nesting; + this.constantReplacer = constantReplacer; } public void setThisContext(@Nonnull final FhirPath thisContext) { @@ -179,7 +184,7 @@ public void unsetThisContext() { public ParserContext withUnnestBehaviour(@Nonnull final UnnestBehaviour unnestBehaviour) { final ParserContext context = new ParserContext(inputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, - nesting); + nesting, constantReplacer); thisContext.ifPresent(context::setThisContext); return context; } @@ -192,7 +197,7 @@ public ParserContext withUnnestBehaviour(@Nonnull final UnnestBehaviour unnestBe public ParserContext withInputContext(@Nonnull final FhirPath inputContext) { final ParserContext parserContext = new ParserContext(inputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, - variables, nesting); + variables, nesting, constantReplacer); thisContext.ifPresent(parserContext::setThisContext); return parserContext; } @@ -206,7 +211,7 @@ public ParserContext withContextDataset(@Nonnull final Dataset contextDatas final FhirPath newInputContext = inputContext.withDataset(contextDataset); final ParserContext parserContext = new ParserContext(newInputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, - variables, nesting); + variables, nesting, constantReplacer); thisContext.ifPresent( thisContext -> parserContext.setThisContext(thisContext.withDataset(contextDataset))); return parserContext; @@ -229,7 +234,7 @@ public ParserContext withGroupingColumns(@Nonnull final List groupingCol nonLiteralInputContext.getThisColumn()); final ParserContext context = new ParserContext(nonSingularInputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, - variables, nesting); + variables, nesting, constantReplacer); thisContext.ifPresent(context::setThisContext); return context; } @@ -260,7 +265,7 @@ public ParserContext disaggregate(@Nonnull final FhirPath aggregatedPath) { final FhirPath newInputContext = inputContext.withDataset(newDataset); final ParserContext parserContext = new ParserContext(newInputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, - variables, new Nesting()); + variables, new Nesting(), constantReplacer); thisContext.ifPresent(parserContext::setThisContext); return parserContext; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index ebd9ec08c4..76738ebacd 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -2,9 +2,11 @@ import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.parser.ConstantReplacer; import au.csiro.pathling.fhirpath.parser.Parser; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; @@ -51,12 +53,19 @@ public FhirViewExecutor(@Nonnull final FhirContext fhirContext, @Nonnull public Dataset buildQuery(@Nonnull final FhirView view) { + // Get the constants from the query, and build a replacer. + final Optional constantReplacer = Optional.ofNullable(view.getConstants()) + .map(c -> c.stream() + .collect(toMap(ConstantDeclaration::getName, ConstantDeclaration::getValue))) + .map(ConstantReplacer::new); + // Build a new expression parser, and parse all the column expressions within the query. final ResourceType resourceType = ResourceType.fromCode(view.getResource()); final ResourcePath inputContext = ResourcePath.build(fhirContext, dataSource, resourceType, view.getResource(), true); final ParserContext parserContext = new ParserContext(inputContext, fhirContext, sparkSession, - dataSource, terminologyServiceFactory, singletonList(inputContext.getIdColumn())); + dataSource, terminologyServiceFactory, singletonList(inputContext.getIdColumn()), + constantReplacer); final ContextAndSelection select = parseSelect(view.getSelect(), parserContext, Collections.emptyList()); @@ -133,7 +142,10 @@ private ContextAndSelection nestedSelection(final @Nonnull ParserContext context private FhirPath parseExpression(@Nonnull final String expression, @Nonnull final ParserContext context) { final Parser parser = new Parser(context); - return parser.parse(expression); + final String updatedExpression = context.getConstantReplacer() + .map(replacer -> replacer.execute(expression)) + .orElse(expression); + return parser.parse(updatedExpression); } @Nonnull @@ -146,8 +158,7 @@ private List parseExpressions(@Nonnull final List expressions, ? context : context.withContextDataset(list.get(list.size() - 1).getDataset()); - final Parser parser = new Parser(currentContext); - list.add(parser.parse(expression)); + list.add(parseExpression(expression, currentContext)); return list; }, (list1, list2) -> { diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java index 6d9c35ff88..d99e12a062 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java @@ -117,7 +117,7 @@ public ParserContextBuilder groupingColumns(@Nonnull final List grouping @Nonnull public ParserContext build() { return new ParserContext(inputContext, fhirContext, spark, dataSource, - Optional.ofNullable(terminologyServiceFactory), groupingColumns); + Optional.ofNullable(terminologyServiceFactory), groupingColumns, Optional.empty()); } } From b8d97b84e98849878a75a8e70e7fa923da6130c4 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sun, 6 Aug 2023 14:28:31 +1000 Subject: [PATCH 56/95] Change all CSV test expectations to TSV --- .../aggregate/AggregateQueryExecutorTest.java | 4 +- .../pathling/extract/ExtractQueryTest.java | 42 +- .../parser/DateTimeArithmeticParserTest.java | 4 +- .../pathling/fhirpath/parser/ParserTest.java | 84 +- .../fhirpath/parser/QuantityParserTest.java | 4 +- .../simpleQuery.csv | 2 - .../simpleQuery.tsv | 2 + .../ExtractQueryTest/codingColumn.csv | 71 - .../ExtractQueryTest/codingColumn.tsv | 71 + .../ExtractQueryTest/codingLiteralColumn.csv | 71 - .../ExtractQueryTest/codingLiteralColumn.tsv | 71 + ...er.csv => combineResultInSecondFilter.tsv} | 0 .../ExtractQueryTest/combineWithLiterals.csv | 18 - .../ExtractQueryTest/combineWithLiterals.tsv | 18 + .../combineWithUnequalCardinalities.csv | 24 - .../combineWithUnequalCardinalities.tsv | 24 + .../eliminatesTrailingNulls.csv | 10 - .../eliminatesTrailingNulls.tsv | 10 + .../responses/ExtractQueryTest/limit.csv | 3 - .../responses/ExtractQueryTest/limit.tsv | 3 + .../ExtractQueryTest/linkedUnnesting.csv | 12 - .../ExtractQueryTest/linkedUnnesting.tsv | 12 + .../ExtractQueryTest/literalColumn.csv | 71 - .../ExtractQueryTest/literalColumn.tsv | 71 + .../ExtractQueryTest/multipleFilters.csv | 2 - .../ExtractQueryTest/multipleFilters.tsv | 2 + .../multipleIndependentUnnestings.csv | 14 - .../multipleIndependentUnnestings.tsv | 14 + ...leNonSingularColumnsWithDifferentTypes.csv | 217 - ...leNonSingularColumnsWithDifferentTypes.tsv | 217 + .../multiplePolymorphicResolves.csv | 150 - .../multiplePolymorphicResolves.tsv | 150 + .../ExtractQueryTest/multipleResolves.csv | 217 - .../ExtractQueryTest/multipleResolves.tsv | 217 + .../multipleReverseResolves.csv | 71 - .../multipleReverseResolves.tsv | 71 + .../multipleReverseResolves2.csv | 71 - .../multipleReverseResolves2.tsv | 71 + .../resolveAndCodingLiteralColumn.csv | 10 - .../resolveAndCodingLiteralColumn.tsv | 10 + .../simpleQueryWithAliases.csv | 4 - .../simpleQueryWithAliases.tsv | 4 + .../ExtractQueryTest/structuredResult.csv | 12 - .../ExtractQueryTest/structuredResult.tsv | 12 + .../toleranceOfColumnOrdering1.csv | 12 - .../toleranceOfColumnOrdering1.tsv | 12 + .../toleranceOfColumnOrdering2.csv | 12 - .../toleranceOfColumnOrdering2.tsv | 12 + .../whereInMultipleColumns.csv | 9 - .../whereInMultipleColumns.tsv | 9 + .../ExtractTest/{extract.csv => extract.tsv} | 0 .../ParserTest/ageAtTimeOfEncounter.csv | 217 - .../ParserTest/ageAtTimeOfEncounter.tsv | 217 + .../lengthObservationComparison.csv | 1502 ------- .../lengthObservationComparison.tsv | 1502 +++++++ .../lengthObservationSubtraction.csv | 1502 ------- .../lengthObservationSubtraction.tsv | 1502 +++++++ .../ParserTest/lengthOfEncounter.csv | 217 - .../ParserTest/lengthOfEncounter.tsv | 217 + .../testAggregationFollowingNestedWhere.csv | 9 - .../testAggregationFollowingNestedWhere.tsv | 9 + .../testBooleanOperatorWithLeftLiteral.csv | 9 - .../testBooleanOperatorWithLeftLiteral.tsv | 9 + .../ParserTest/testCombineOperator.csv | 23 - .../ParserTest/testCombineOperator.tsv | 23 + .../testCombineOperatorWithCodingLiterals.csv | 9 - .../testCombineOperatorWithCodingLiterals.tsv | 9 + ...tCombineOperatorWithComplexTypeAndNull.csv | 19 - ...tCombineOperatorWithComplexTypeAndNull.tsv | 19 + ...peratorWithDifferentlyTypedStringPaths.csv | 142 - ...peratorWithDifferentlyTypedStringPaths.tsv | 142 + .../testCombineOperatorWithResourcePaths.csv | 142 - .../testCombineOperatorWithResourcePaths.tsv | 142 + .../testCombineOperatorWithTwoLiterals.csv | 18 - .../testCombineOperatorWithTwoLiterals.tsv | 18 + ...ineOperatorWithTwoUntypedResourcePaths.csv | 174 - ...ineOperatorWithTwoUntypedResourcePaths.tsv | 174 + .../testCombineOperatorWithWhereFunction.csv | 9 - .../testCombineOperatorWithWhereFunction.tsv | 9 + .../testComplexExtensionsOnComplexPath.csv | 13 - .../testComplexExtensionsOnComplexPath.tsv | 13 + .../testDesignationFunctionWithLanguage.csv | 71 - .../testDesignationFunctionWithLanguage.tsv | 71 + .../testDesignationFunctionWithNoLanguage.csv | 84 - .../testDesignationFunctionWithNoLanguage.tsv | 84 + .../ParserTest/testDisplayFunction.csv | 71 - .../ParserTest/testDisplayFunction.tsv | 71 + .../ParserTest/testExtensionFunction.csv | 63 - .../ParserTest/testExtensionFunction.tsv | 63 + .../testExtensionFunctionInWhere.csv | 9 - .../testExtensionFunctionInWhere.tsv | 9 + ...testExtensionFunctionOnTranslateResult.csv | 96 - ...testExtensionFunctionOnTranslateResult.tsv | 96 + .../testExtensionsCurrentResource.csv | 497 --- .../testExtensionsCurrentResource.tsv | 497 +++ .../ParserTest/testExtensionsOnElements.csv | 9 - .../ParserTest/testExtensionsOnElements.tsv | 9 + .../ParserTest/testExtensionsOnResources.csv | 63 - .../ParserTest/testExtensionsOnResources.tsv | 63 + .../responses/ParserTest/testIfFunction.csv | 9 - .../responses/ParserTest/testIfFunction.tsv | 9 + .../testIfFunctionWithComplexTypeResult.csv | 10 - .../testIfFunctionWithComplexTypeResult.tsv | 10 + .../ParserTest/testIifWithNullLiteral.csv | 9 - .../ParserTest/testIifWithNullLiteral.tsv | 9 + .../ParserTest/testNestedExtensions.csv | 81 - .../ParserTest/testNestedExtensions.tsv | 81 + ...estNestedWhereWithAggregationOnElement.csv | 10 - ...estNestedWhereWithAggregationOnElement.tsv | 10 + .../responses/ParserTest/testNotFunction.csv | 9 - .../responses/ParserTest/testNotFunction.tsv | 9 + .../testPropertyFunctionWithCodingType.csv | 77 - .../testPropertyFunctionWithCodingType.tsv | 77 + ...QuantityAdditionSubtractionAndEquality.csv | 9 - ...QuantityAdditionSubtractionAndEquality.tsv | 9 + .../testQuantityAdditionWithOverflow_code.csv | 9 - .../testQuantityAdditionWithOverflow_code.tsv | 9 + ...testQuantityAdditionWithOverflow_value.csv | 9 - ...testQuantityAdditionWithOverflow_value.tsv | 9 + .../testQuantityMultiplicationAndDivision.csv | 9 - .../testQuantityMultiplicationAndDivision.tsv | 9 + .../testQueryWithExternalConstantInWhere.csv | 10 - .../testQueryWithExternalConstantInWhere.tsv | 10 + .../testResolutionOfExtensionReference.csv | 217 - .../testResolutionOfExtensionReference.tsv | 217 + ...utionOfExtensionReferenceWithWrongType.csv | 217 - ...utionOfExtensionReferenceWithWrongType.tsv | 217 + ...erseResolveFollowingMonomorphicResolve.csv | 3519 ----------------- ...erseResolveFollowingMonomorphicResolve.tsv | 3519 +++++++++++++++++ ...erseResolveFollowingPolymorphicResolve.csv | 217 - ...erseResolveFollowingPolymorphicResolve.tsv | 217 + ...tReverseResolveFollowingReverseResolve.csv | 217 - ...tReverseResolveFollowingReverseResolve.tsv | 217 + ...SubsumesAndSubsumedBy-subsumedBy-empty.csv | 71 - ...SubsumesAndSubsumedBy-subsumedBy-empty.tsv | 71 + .../testSubsumesAndSubsumedBy-subsumedBy.csv | 71 - .../testSubsumesAndSubsumedBy-subsumedBy.tsv | 71 + ...stSubsumesAndSubsumedBy-subsumes-empty.csv | 71 - ...stSubsumesAndSubsumedBy-subsumes-empty.tsv | 71 + ...estSubsumesAndSubsumedBy-subsumes-self.csv | 71 - ...estSubsumesAndSubsumedBy-subsumes-self.tsv | 71 + .../testSubsumesAndSubsumedBy-subsumes.csv | 71 - .../testSubsumesAndSubsumedBy-subsumes.tsv | 71 + .../ParserTest/testTranslateFunction.csv | 96 - .../ParserTest/testTranslateFunction.tsv | 96 + .../testTranslateWithWhereAndTranslate.csv | 4 - .../testTranslateWithWhereAndTranslate.tsv | 4 + .../ParserTest/testUntilFunction.csv | 217 - .../ParserTest/testUntilFunction.tsv | 217 + .../ParserTest/testWhereWithMemberOf.csv | 71 - .../ParserTest/testWhereWithMemberOf.tsv | 71 + .../ParserTest/testWithCodingLiteral.csv | 9 - .../ParserTest/testWithCodingLiteral.tsv | 9 + .../pathling/test/assertions/Assertions.java | 10 +- .../test/assertions/DatasetAssert.java | 3 +- .../au/csiro/pathling/views/FhirViewTest.java | 2 +- .../results/views/elementRelatedUnnesting.csv | 6 - .../results/views/elementRelatedUnnesting.tsv | 6 + .../results/views/flattenedBloodPressure.csv | 0 .../results/views/flattenedBloodPressure.tsv | 2 + .../results/views/forEachWithinFrom.csv | 11 - .../results/views/forEachWithinFrom.tsv | 11 + .../results/views/nestedSingular.csv | 5 - .../results/views/nestedSingular.tsv | 5 + .../views/resourceRelatedUnnesting.csv | 7 - .../views/resourceRelatedUnnesting.tsv | 7 + .../results/views/singularNoUnnesting.csv | 3 - .../results/views/singularNoUnnesting.tsv | 3 + .../results/views/unnestEmptyCollection.csv | 3 - .../results/views/unnestEmptyCollection.tsv | 3 + 170 files changed, 11555 insertions(+), 11552 deletions(-) delete mode 100644 fhir-server/src/test/resources/responses/AggregateQueryExecutorTest/simpleQuery.csv create mode 100644 fhir-server/src/test/resources/responses/AggregateQueryExecutorTest/simpleQuery.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/codingColumn.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/codingColumn.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/codingLiteralColumn.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/codingLiteralColumn.tsv rename fhir-server/src/test/resources/responses/ExtractQueryTest/{combineResultInSecondFilter.csv => combineResultInSecondFilter.tsv} (100%) delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithLiterals.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithLiterals.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithUnequalCardinalities.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithUnequalCardinalities.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/eliminatesTrailingNulls.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/eliminatesTrailingNulls.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/limit.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/limit.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/literalColumn.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/literalColumn.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multipleFilters.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multipleFilters.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multipleNonSingularColumnsWithDifferentTypes.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multipleNonSingularColumnsWithDifferentTypes.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multipleResolves.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multipleResolves.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves2.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves2.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/resolveAndCodingLiteralColumn.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/resolveAndCodingLiteralColumn.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/simpleQueryWithAliases.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/simpleQueryWithAliases.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.tsv delete mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/whereInMultipleColumns.csv create mode 100644 fhir-server/src/test/resources/responses/ExtractQueryTest/whereInMultipleColumns.tsv rename fhir-server/src/test/resources/responses/ExtractTest/{extract.csv => extract.tsv} (100%) delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/ageAtTimeOfEncounter.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/ageAtTimeOfEncounter.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/lengthObservationComparison.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/lengthObservationComparison.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/lengthObservationSubtraction.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/lengthObservationSubtraction.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/lengthOfEncounter.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/lengthOfEncounter.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testAggregationFollowingNestedWhere.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testAggregationFollowingNestedWhere.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testBooleanOperatorWithLeftLiteral.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testBooleanOperatorWithLeftLiteral.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperator.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperator.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithCodingLiterals.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithCodingLiterals.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithComplexTypeAndNull.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithComplexTypeAndNull.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithDifferentlyTypedStringPaths.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithDifferentlyTypedStringPaths.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithResourcePaths.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithResourcePaths.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoLiterals.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoLiterals.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoUntypedResourcePaths.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoUntypedResourcePaths.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithWhereFunction.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithWhereFunction.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testComplexExtensionsOnComplexPath.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testComplexExtensionsOnComplexPath.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithLanguage.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithLanguage.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithNoLanguage.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithNoLanguage.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testDisplayFunction.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testDisplayFunction.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testExtensionFunction.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testExtensionFunction.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionInWhere.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionInWhere.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionOnTranslateResult.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionOnTranslateResult.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testExtensionsCurrentResource.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testExtensionsCurrentResource.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnElements.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnElements.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnResources.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnResources.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testIfFunction.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testIfFunction.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testIfFunctionWithComplexTypeResult.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testIfFunctionWithComplexTypeResult.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testIifWithNullLiteral.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testIifWithNullLiteral.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testNestedExtensions.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testNestedExtensions.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testNestedWhereWithAggregationOnElement.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testNestedWhereWithAggregationOnElement.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testNotFunction.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testNotFunction.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testPropertyFunctionWithCodingType.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testPropertyFunctionWithCodingType.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionSubtractionAndEquality.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionSubtractionAndEquality.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_code.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_code.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_value.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_value.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testQuantityMultiplicationAndDivision.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testQuantityMultiplicationAndDivision.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testQueryWithExternalConstantInWhere.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testQueryWithExternalConstantInWhere.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReference.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReference.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReferenceWithWrongType.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReferenceWithWrongType.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingMonomorphicResolve.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingMonomorphicResolve.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingPolymorphicResolve.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingPolymorphicResolve.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingReverseResolve.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingReverseResolve.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy-empty.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy-empty.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-empty.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-empty.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-self.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-self.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testTranslateFunction.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testTranslateFunction.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testTranslateWithWhereAndTranslate.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testTranslateWithWhereAndTranslate.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testUntilFunction.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testUntilFunction.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testWhereWithMemberOf.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testWhereWithMemberOf.tsv delete mode 100644 fhir-server/src/test/resources/responses/ParserTest/testWithCodingLiteral.csv create mode 100644 fhir-server/src/test/resources/responses/ParserTest/testWithCodingLiteral.tsv delete mode 100644 fhirpath/src/test/resources/results/views/elementRelatedUnnesting.csv create mode 100644 fhirpath/src/test/resources/results/views/elementRelatedUnnesting.tsv delete mode 100644 fhirpath/src/test/resources/results/views/flattenedBloodPressure.csv create mode 100644 fhirpath/src/test/resources/results/views/flattenedBloodPressure.tsv delete mode 100644 fhirpath/src/test/resources/results/views/forEachWithinFrom.csv create mode 100644 fhirpath/src/test/resources/results/views/forEachWithinFrom.tsv delete mode 100644 fhirpath/src/test/resources/results/views/nestedSingular.csv create mode 100644 fhirpath/src/test/resources/results/views/nestedSingular.tsv delete mode 100644 fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.csv create mode 100644 fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.tsv delete mode 100644 fhirpath/src/test/resources/results/views/singularNoUnnesting.csv create mode 100644 fhirpath/src/test/resources/results/views/singularNoUnnesting.tsv delete mode 100644 fhirpath/src/test/resources/results/views/unnestEmptyCollection.csv create mode 100644 fhirpath/src/test/resources/results/views/unnestEmptyCollection.tsv diff --git a/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryExecutorTest.java b/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryExecutorTest.java index 0388553946..d8bd8db44b 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryExecutorTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryExecutorTest.java @@ -103,7 +103,7 @@ void simpleQueryWithLabels() { result.columns()); assertThat(result) .debugAllRows() - .hasRows(spark, "responses/AggregateQueryExecutorTest/simpleQuery.csv"); + .hasRows(spark, "responses/AggregateQueryExecutorTest/simpleQuery.tsv"); } @Test @@ -120,7 +120,7 @@ void simpleQueryWithNoLabels() { assertTrue(Stream.of(result.columns()).allMatch(Strings::looksLikeAlias)); assertThat(result) .debugAllRows() - .hasRows(spark, "responses/AggregateQueryExecutorTest/simpleQuery.csv"); + .hasRows(spark, "responses/AggregateQueryExecutorTest/simpleQuery.tsv"); } void mockResource(final ResourceType... resourceTypes) { diff --git a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java index d93b0247a8..8c5d5c20aa 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java @@ -115,7 +115,7 @@ void simpleQueryWithAliases() { assertArrayEquals(new String[]{"id", "gender", "given_name", "condition_count"}, result.columns()); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/simpleQueryWithAliases.csv"); + .hasRows(spark, "responses/ExtractQueryTest/simpleQueryWithAliases.tsv"); } @Test @@ -134,7 +134,7 @@ void multipleResolves() { assertTrue(Stream.of(result.columns()).allMatch(Strings::looksLikeAlias)); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/multipleResolves.csv"); + .hasRows(spark, "responses/ExtractQueryTest/multipleResolves.tsv"); } @Test @@ -151,7 +151,7 @@ void multipleReverseResolves() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/multipleReverseResolves.csv"); + .hasRows(spark, "responses/ExtractQueryTest/multipleReverseResolves.tsv"); } @Test @@ -170,7 +170,7 @@ void multiplePolymorphicResolves() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) .debugAllRows() - .hasRows(spark, "responses/ExtractQueryTest/multiplePolymorphicResolves.csv"); + .hasRows(spark, "responses/ExtractQueryTest/multiplePolymorphicResolves.tsv"); } @Test @@ -185,7 +185,7 @@ void literalColumn() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/literalColumn.csv"); + .hasRows(spark, "responses/ExtractQueryTest/literalColumn.tsv"); } @Test @@ -201,7 +201,7 @@ void resolveAndCodingLiteralColumn() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/resolveAndCodingLiteralColumn.csv"); + .hasRows(spark, "responses/ExtractQueryTest/resolveAndCodingLiteralColumn.tsv"); } @Test @@ -216,7 +216,7 @@ void codingColumn() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/codingColumn.csv"); + .hasRows(spark, "responses/ExtractQueryTest/codingColumn.tsv"); } @Test @@ -232,7 +232,7 @@ void codingLiteralColumn() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/codingLiteralColumn.csv"); + .hasRows(spark, "responses/ExtractQueryTest/codingLiteralColumn.tsv"); } @Test @@ -251,7 +251,7 @@ void multipleFilters() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/multipleFilters.csv"); + .hasRows(spark, "responses/ExtractQueryTest/multipleFilters.tsv"); } @Test @@ -268,7 +268,7 @@ void limit() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/limit.csv"); + .hasRows(spark, "responses/ExtractQueryTest/limit.tsv"); } @Test @@ -283,7 +283,7 @@ void eliminatesTrailingNulls() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/eliminatesTrailingNulls.csv"); + .hasRows(spark, "responses/ExtractQueryTest/eliminatesTrailingNulls.tsv"); } @Test @@ -299,7 +299,7 @@ void combineResultInSecondFilter() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/combineResultInSecondFilter.csv"); + .hasRows(spark, "responses/ExtractQueryTest/combineResultInSecondFilter.tsv"); } @Test @@ -315,7 +315,7 @@ void whereInMultipleColumns() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/whereInMultipleColumns.csv"); + .hasRows(spark, "responses/ExtractQueryTest/whereInMultipleColumns.tsv"); } @Test @@ -332,7 +332,7 @@ void multipleNonSingularColumnsWithDifferentTypes() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) .hasRows(spark, - "responses/ExtractQueryTest/multipleNonSingularColumnsWithDifferentTypes.csv"); + "responses/ExtractQueryTest/multipleNonSingularColumnsWithDifferentTypes.tsv"); } @Test @@ -349,7 +349,7 @@ void linkedUnnesting() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/linkedUnnesting.csv"); + .hasRows(spark, "responses/ExtractQueryTest/linkedUnnesting.tsv"); } @Test @@ -367,7 +367,7 @@ void multipleIndependentUnnestings() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/multipleIndependentUnnestings.csv"); + .hasRows(spark, "responses/ExtractQueryTest/multipleIndependentUnnestings.tsv"); } @Test @@ -383,7 +383,7 @@ void toleranceOfColumnOrdering() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv"); + .hasRows(spark, "responses/ExtractQueryTest/toleranceOfColumnOrdering1.tsv"); final ExtractRequest request2 = new ExtractRequestBuilder(subjectResource) .withColumn("name.given") @@ -392,7 +392,7 @@ void toleranceOfColumnOrdering() { final Dataset result2 = executor.buildQuery(request2); assertThat(result2) - .hasRows(spark, "responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv"); + .hasRows(spark, "responses/ExtractQueryTest/toleranceOfColumnOrdering2.tsv"); } @Test @@ -470,7 +470,7 @@ void structuredResult() { assertThat(result) .debugAllRows() - .hasRows(spark, "responses/ExtractQueryTest/structuredResult.csv"); + .hasRows(spark, "responses/ExtractQueryTest/structuredResult.tsv"); } @Test @@ -485,7 +485,7 @@ void combineWithLiterals() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/combineWithLiterals.csv"); + .hasRows(spark, "responses/ExtractQueryTest/combineWithLiterals.tsv"); } @Test @@ -502,7 +502,7 @@ void combineWithUnequalCardinalities() { final Dataset result = executor.buildQuery(request, ExtractResultType.FLAT); assertThat(result) - .hasRows(spark, "responses/ExtractQueryTest/combineWithUnequalCardinalities.csv"); + .hasRows(spark, "responses/ExtractQueryTest/combineWithUnequalCardinalities.tsv"); } void mockResource(final ResourceType... resourceTypes) { diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java index aa017f00b7..bda7525690 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java @@ -42,7 +42,7 @@ void lengthOfEncounter() { assertThatResultOf("(period.start + 20 minutes) > period.end") .isElementPath(BooleanPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/lengthOfEncounter.csv"); + .hasRows(spark, "responses/ParserTest/lengthOfEncounter.tsv"); } @Test @@ -61,7 +61,7 @@ void ageAtTimeOfEncounter() { assertThatResultOf("period.start > (subject.resolve().ofType(Patient).birthDate + 60 years)") .isElementPath(BooleanPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/ageAtTimeOfEncounter.csv"); + .hasRows(spark, "responses/ParserTest/ageAtTimeOfEncounter.tsv"); } } diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java index 8058e03509..1deac4b1a8 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java @@ -302,31 +302,31 @@ void testSubsumesAndSubsumedBy() { "reverseResolve(Condition.subject).code.subsumes(http://snomed.info/sct|40055000)") .isElementPath(BooleanPath.class) .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-empty.csv"); + .hasRows(spark, "responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-empty.tsv"); assertThatResultOf( "reverseResolve(Condition.subject).code.subsumedBy(http://snomed.info/sct|40055000)") .isElementPath(BooleanPath.class) .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy-empty.csv"); + .hasRows(spark, "responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy-empty.tsv"); // on the same collection should return all True (even though one is CodeableConcept) assertThatResultOf( "reverseResolve(Condition.subject).code.coding.subsumes(%resource.reverseResolve(Condition.subject).code)") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-self.csv"); + .hasRows(spark, "responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-self.tsv"); setupSubsumes(terminologyService).withSubsumes( CD_SNOMED_444814009, CD_SNOMED_40055000); assertThatResultOf( "reverseResolve(Condition.subject).code.subsumes(http://snomed.info/sct|40055000)") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testSubsumesAndSubsumedBy-subsumes.csv"); + .hasRows(spark, "responses/ParserTest/testSubsumesAndSubsumedBy-subsumes.tsv"); assertThatResultOf("reverseResolve(Condition.subject).code.subsumedBy" + "(http://snomed.info/sct|40055000|http://snomed.info/sct/32506021000036107/version/20200229)") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy.csv"); + .hasRows(spark, "responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy.tsv"); } @Test @@ -375,7 +375,7 @@ void testWhereWithMemberOf() { + "$this.code.memberOf('http://snomed.info/sct?fhir_vs=refset/32570521000036109'))" + ".recordedDate") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testWhereWithMemberOf.csv"); + .hasRows(spark, "responses/ParserTest/testWhereWithMemberOf.tsv"); } /** @@ -389,7 +389,7 @@ void testAggregationFollowingNestedWhere() { "where(name.where(use = 'official').first().given.first() in " + "name.where(use = 'maiden').first().given).gender") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testAggregationFollowingNestedWhere.csv"); + .hasRows(spark, "responses/ParserTest/testAggregationFollowingNestedWhere.tsv"); } @Test @@ -397,7 +397,7 @@ void testNestedWhereWithAggregationOnElement() { assertThatResultOf( "name.where('Karina848' in where(use contains 'maiden').given).family") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testNestedWhereWithAggregationOnElement.csv"); + .hasRows(spark, "responses/ParserTest/testNestedWhereWithAggregationOnElement.tsv"); } @Test @@ -411,17 +411,17 @@ void testQueryWithExternalConstantInWhere() { assertThatResultOf( "name.family.where($this = %resource.name.family.first())") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testQueryWithExternalConstantInWhere.csv"); + .hasRows(spark, "responses/ParserTest/testQueryWithExternalConstantInWhere.tsv"); assertThatResultOf( "name.family.where($this = %context.name.family.first())") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testQueryWithExternalConstantInWhere.csv"); + .hasRows(spark, "responses/ParserTest/testQueryWithExternalConstantInWhere.tsv"); assertThatResultOf( "name.family.where(%resource.name.family.first() = $this)") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testQueryWithExternalConstantInWhere.csv"); + .hasRows(spark, "responses/ParserTest/testQueryWithExternalConstantInWhere.tsv"); } @Test @@ -438,7 +438,7 @@ void testNotFunction() { assertThatResultOf( "(name.given contains 'Su690').not()") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testNotFunction.csv"); + .hasRows(spark, "responses/ParserTest/testNotFunction.tsv"); } @Test @@ -446,7 +446,7 @@ void testIfFunction() { assertThatResultOf( "gender.iif($this = 'male', 'Male', 'Not male')") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testIfFunction.csv"); + .hasRows(spark, "responses/ParserTest/testIfFunction.tsv"); } @Test @@ -454,7 +454,7 @@ void testIfFunctionWithComplexTypeResult() { assertThatResultOf( "iif(gender = 'male', contact.name, name).given") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testIfFunctionWithComplexTypeResult.csv"); + .hasRows(spark, "responses/ParserTest/testIfFunctionWithComplexTypeResult.tsv"); } @Test @@ -483,7 +483,7 @@ void testTranslateFunction() { assertThatResultOf(ResourceType.CONDITION, "code.coding.translate('http://snomed.info/sct?fhir_cm=900000000000526001', false, 'equivalent').code") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testTranslateFunction.csv"); + .hasRows(spark, "responses/ParserTest/testTranslateFunction.tsv"); } @Test @@ -492,7 +492,7 @@ void testDisplayFunction() { assertThatResultOf(ResourceType.CONDITION, "code.coding.display()") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testDisplayFunction.csv"); + .hasRows(spark, "responses/ParserTest/testDisplayFunction.tsv"); } @@ -502,7 +502,7 @@ void testPropertyFunctionWithDefaultType() { assertThatResultOf(ResourceType.CONDITION, "code.coding.property('display')") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testDisplayFunction.csv"); + .hasRows(spark, "responses/ParserTest/testDisplayFunction.tsv"); } @Test @@ -511,7 +511,7 @@ void testPropertyFunctionWithCodingType() { assertThatResultOf(ResourceType.CONDITION, "code.coding.property('child', 'Coding').code") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testPropertyFunctionWithCodingType.csv"); + .hasRows(spark, "responses/ParserTest/testPropertyFunctionWithCodingType.tsv"); } @@ -522,7 +522,7 @@ void testDesignationFunctionWithLanguage() { assertThatResultOf(ResourceType.CONDITION, "code.coding.designation(http://snomed.info/sct|900000000000003001, 'en')") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testDesignationFunctionWithLanguage.csv"); + .hasRows(spark, "responses/ParserTest/testDesignationFunctionWithLanguage.tsv"); } @Test @@ -533,7 +533,7 @@ void testDesignationFunctionWithNoLanguage() { "code.coding.designation(http://terminology.hl7.org/CodeSystem/designation-usage|display)") .selectOrderedResult() .debugAllRows() - .hasRows(spark, "responses/ParserTest/testDesignationFunctionWithNoLanguage.csv"); + .hasRows(spark, "responses/ParserTest/testDesignationFunctionWithNoLanguage.tsv"); } @Test @@ -548,7 +548,7 @@ void testTranslateWithWhereAndTranslate() { assertThatResultOf(ResourceType.CONDITION, "code.translate('uuid:cm=1', false, 'equivalent').where($this.translate('uuid:cm=2', false, 'equivalent').code.count()=13).code") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testTranslateWithWhereAndTranslate.csv"); + .hasRows(spark, "responses/ParserTest/testTranslateWithWhereAndTranslate.tsv"); } @Test @@ -556,7 +556,7 @@ void testWithCodingLiteral() { assertThatResultOf( "maritalStatus.coding contains http://terminology.hl7.org/CodeSystem/v3-MaritalStatus|S||S") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testWithCodingLiteral.csv"); + .hasRows(spark, "responses/ParserTest/testWithCodingLiteral.tsv"); } @Test @@ -564,7 +564,7 @@ void testCombineOperator() { assertThatResultOf("name.family combine name.given") .isElementPath(StringPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/testCombineOperator.csv"); + .hasRows(spark, "responses/ParserTest/testCombineOperator.tsv"); } @Test @@ -572,7 +572,7 @@ void testCombineOperatorWithWhereFunction() { assertThatResultOf("where((name.family combine name.given) contains 'Gleichner915').birthDate") .isElementPath(DatePath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/testCombineOperatorWithWhereFunction.csv"); + .hasRows(spark, "responses/ParserTest/testCombineOperatorWithWhereFunction.tsv"); } @Test @@ -581,7 +581,7 @@ void testCombineOperatorWithResourcePaths() { "reverseResolve(Condition.subject).where(clinicalStatus.coding.code contains 'active') combine reverseResolve(Condition.subject).where(clinicalStatus.coding.code contains 'resolved')") .isResourcePath() .selectResult() - .hasRows(spark, "responses/ParserTest/testCombineOperatorWithResourcePaths.csv"); + .hasRows(spark, "responses/ParserTest/testCombineOperatorWithResourcePaths.tsv"); } @Test @@ -592,7 +592,7 @@ void testCombineOperatorWithDifferentlyTypedStringPaths() { .isElementPath(StringPath.class) .selectResult() .hasRows(spark, - "responses/ParserTest/testCombineOperatorWithDifferentlyTypedStringPaths.csv"); + "responses/ParserTest/testCombineOperatorWithDifferentlyTypedStringPaths.tsv"); } @Test @@ -601,7 +601,7 @@ void testCombineOperatorWithComplexTypeAndNull() { .isElementPath(StringPath.class) .selectResult() .hasRows(spark, - "responses/ParserTest/testCombineOperatorWithComplexTypeAndNull.csv"); + "responses/ParserTest/testCombineOperatorWithComplexTypeAndNull.tsv"); } @Test @@ -609,7 +609,7 @@ void testCombineOperatorWithTwoLiterals() { assertThatResultOf("1 combine 2") .isElementPath(IntegerPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/testCombineOperatorWithTwoLiterals.csv"); + .hasRows(spark, "responses/ParserTest/testCombineOperatorWithTwoLiterals.tsv"); } @Test @@ -620,7 +620,7 @@ void testCombineOperatorWithTwoUntypedResourcePaths() { .isResourcePath() .hasResourceType(ResourceType.PATIENT) .selectResult() - .hasRows(spark, "responses/ParserTest/testCombineOperatorWithTwoUntypedResourcePaths.csv"); + .hasRows(spark, "responses/ParserTest/testCombineOperatorWithTwoUntypedResourcePaths.tsv"); } @Test @@ -630,7 +630,7 @@ void testCombineOperatorWithCodingLiterals() { + "http://snomed.info/sct|230690007||'Stroke').empty()") .isElementPath(BooleanPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/testCombineOperatorWithCodingLiterals.csv"); + .hasRows(spark, "responses/ParserTest/testCombineOperatorWithCodingLiterals.tsv"); } @Test @@ -638,7 +638,7 @@ void testBooleanOperatorWithLeftLiteral() { assertThatResultOf("@1970-11-22 = birthDate") .isElementPath(BooleanPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/testBooleanOperatorWithLeftLiteral.csv"); + .hasRows(spark, "responses/ParserTest/testBooleanOperatorWithLeftLiteral.tsv"); } @Test @@ -657,7 +657,7 @@ void testExtensionsOnResources() { "extension.url") .isElementPath(StringPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/testExtensionsOnResources.csv"); + .hasRows(spark, "responses/ParserTest/testExtensionsOnResources.tsv"); } @Test @@ -667,7 +667,7 @@ void testExtensionFunction() { "extension('http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName').valueString") .isElementPath(StringPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/testExtensionFunction.csv"); + .hasRows(spark, "responses/ParserTest/testExtensionFunction.tsv"); } @Test @@ -676,7 +676,7 @@ void testExtensionsOnElements() { "address.extension.url") .isElementPath(StringPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/testExtensionsOnElements.csv"); + .hasRows(spark, "responses/ParserTest/testExtensionsOnElements.tsv"); } @Test @@ -685,7 +685,7 @@ void testNestedExtensions() { "extension.extension.url") .isElementPath(StringPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/testNestedExtensions.csv"); + .hasRows(spark, "responses/ParserTest/testNestedExtensions.tsv"); } @Test @@ -694,7 +694,7 @@ void testExtensionsCurrentResource() { "subject.resolve().ofType(Patient).extension.url") .isElementPath(StringPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/testExtensionsCurrentResource.csv"); + .hasRows(spark, "responses/ParserTest/testExtensionsCurrentResource.tsv"); } @Test @@ -705,7 +705,7 @@ void testComplexExtensionsOnComplexPath() { + ".extension('latitude').valueDecimal") .isElementPath(DecimalPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/testComplexExtensionsOnComplexPath.csv"); + .hasRows(spark, "responses/ParserTest/testComplexExtensionsOnComplexPath.tsv"); } @Test @@ -714,7 +714,7 @@ void testExtensionFunctionInWhere() { "address.where($this.extension('http://hl7.org/fhir/StructureDefinition/geolocation').extension('latitude').valueDecimal contains 42.391383).city") .isElementPath(StringPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/testExtensionFunctionInWhere.csv"); + .hasRows(spark, "responses/ParserTest/testExtensionFunctionInWhere.tsv"); } @@ -729,7 +729,7 @@ void testExtensionFunctionOnTranslateResult() { assertThatResultOf(ResourceType.CONDITION, "code.coding.translate('http://snomed.info/sct?fhir_cm=900000000000526001', false, 'equivalent').extension('uuid:any').url") .selectOrderedResult() - .hasRows(spark, "responses/ParserTest/testExtensionFunctionOnTranslateResult.csv"); + .hasRows(spark, "responses/ParserTest/testExtensionFunctionOnTranslateResult.tsv"); } @Test @@ -747,7 +747,7 @@ void testReverseResolveFollowingMonomorphicResolve() { "serviceProvider.resolve().reverseResolve(Encounter.serviceProvider).id") .isElementPath(StringPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/testReverseResolveFollowingMonomorphicResolve.csv"); + .hasRows(spark, "responses/ParserTest/testReverseResolveFollowingMonomorphicResolve.tsv"); } @Test @@ -758,7 +758,7 @@ void testReverseResolveFollowingPolymorphicResolve() { + "contains '2aff9edd-def2-487a-b435-a162e11a303c'") .isElementPath(BooleanPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/testReverseResolveFollowingPolymorphicResolve.csv"); + .hasRows(spark, "responses/ParserTest/testReverseResolveFollowingPolymorphicResolve.tsv"); } @Test @@ -767,7 +767,7 @@ void testReverseResolveFollowingReverseResolve() { "reverseResolve(Encounter.subject).reverseResolve(CarePlan.encounter).id") .isElementPath(StringPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/testReverseResolveFollowingReverseResolve.csv"); + .hasRows(spark, "responses/ParserTest/testReverseResolveFollowingReverseResolve.tsv"); } @Test diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java index 209c3ce8ff..108a215406 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java @@ -42,7 +42,7 @@ void lengthObservationComparison() { assertThatResultOf("valueQuantity < 1.5 'm'") .isElementPath(BooleanPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/lengthObservationComparison.csv"); + .hasRows(spark, "responses/ParserTest/lengthObservationComparison.tsv"); } @Test @@ -61,7 +61,7 @@ void lengthObservationSubtraction() { assertThatResultOf("valueQuantity > (valueQuantity - 2 'g/dL')") .isElementPath(BooleanPath.class) .selectResult() - .hasRows(spark, "responses/ParserTest/lengthObservationSubtraction.csv"); + .hasRows(spark, "responses/ParserTest/lengthObservationSubtraction.tsv"); } } diff --git a/fhir-server/src/test/resources/responses/AggregateQueryExecutorTest/simpleQuery.csv b/fhir-server/src/test/resources/responses/AggregateQueryExecutorTest/simpleQuery.csv deleted file mode 100644 index 198d520b93..0000000000 --- a/fhir-server/src/test/resources/responses/AggregateQueryExecutorTest/simpleQuery.csv +++ /dev/null @@ -1,2 +0,0 @@ -female,4 -male,5 diff --git a/fhir-server/src/test/resources/responses/AggregateQueryExecutorTest/simpleQuery.tsv b/fhir-server/src/test/resources/responses/AggregateQueryExecutorTest/simpleQuery.tsv new file mode 100644 index 0000000000..21f0d95a10 --- /dev/null +++ b/fhir-server/src/test/resources/responses/AggregateQueryExecutorTest/simpleQuery.tsv @@ -0,0 +1,2 @@ +female 4 +male 5 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/codingColumn.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/codingColumn.csv deleted file mode 100644 index dbf9aac52d..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/codingColumn.csv +++ /dev/null @@ -1,71 +0,0 @@ -01fac610-c309-46f8-9a0f-6b0f55c9915a,http://snomed.info/sct|53741008||'Coronary Heart Disease' -031f1c86-16ec-42e1-a155-81456e778fed,http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' -0447de38-1f2a-411c-9fd2-a9c62ab1f221,http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' -061521f0-7f7e-41a5-ad35-b30fe958dea7,http://snomed.info/sct|195662009||'Acute viral pharyngitis (disorder)' -06e446da-6d66-4d97-a457-4abf2a0d2e24,http://snomed.info/sct|363406005||'Malignant tumor of colon' -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,http://snomed.info/sct|195662009||'Acute viral pharyngitis (disorder)' -1146cf52-573f-4667-97c3-abf235f9a83b,http://snomed.info/sct|33737001||'Fracture of rib' -1452c50c-b0b9-472c-afb7-2f9e5a3c4717,http://snomed.info/sct|65363002||'Otitis media' -171ad058-c082-46ab-b9cc-d7bba6e4e3cd,http://snomed.info/sct|40055000||'Chronic sinusitis (disorder)' -1cea8432-f8a0-4746-ab5c-8f195fc07c55,http://snomed.info/sct|94260004||'Secondary malignant neoplasm of colon' -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' -21372b39-2be0-4d0d-85c4-5d7e250d8f78,http://snomed.info/sct|65363002||'Otitis media' -248e951c-e0ce-4e4b-afae-6273fd109c9d,http://snomed.info/sct|15777000||Prediabetes -251ddff9-1588-4790-8f40-cd6ab1d9b7fc,http://snomed.info/sct|72892002||'Normal pregnancy' -258191b8-2b42-4473-9ba2-be0ba3561767,http://snomed.info/sct|162864005||'Body mass index 30+ - obesity (finding)' -29386b12-9af9-4c21-9f82-858998b388bb,http://snomed.info/sct|38341003||Hypertension -31393667-dd7f-4c94-90c3-6cde75c67d3e,http://snomed.info/sct|403190006||'First degree burn' -31925a68-7a48-412c-b4f8-aef92498dda1,http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' -38171e22-b35d-4161-be15-80243be37b99,http://snomed.info/sct|195662009||'Acute viral pharyngitis (disorder)' -39170d67-8205-4636-84d7-d8575115d14c,http://snomed.info/sct|10509002||'Acute bronchitis (disorder)' -4025ceb3-04a6-41fb-8569-16a8dcce7ccc,http://snomed.info/sct|15777000||Prediabetes -46d69851-eca3-42e2-b142-88c5578f6cff,http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' -475461a2-3bd2-43fd-a5aa-7ce424203ae8,http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' -4dd7824b-4b41-4402-bc42-de016e1bfcd9,http://snomed.info/sct|19169002||'Miscarriage in first trimester' -5300158c-92cb-40fe-bc14-a449d7b3c1c5,http://snomed.info/sct|70704007||'Sprain of wrist' -5378726d-7f2b-4c83-9762-eaf385915fa7,http://snomed.info/sct|271737000||'Anemia (disorder)' -549d3ef5-a05e-4b8e-acba-3d70f26a82f6,http://snomed.info/sct|15777000||Prediabetes -55cde383-2eb1-42d9-b5c6-698d6eade389,http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325,http://snomed.info/sct|399211009||'History of myocardial infarction (situation)' -5ef84858-0480-4a34-8f43-fc962fe627b2,http://snomed.info/sct|19169002||'Miscarriage in first trimester' -6464e20b-55ec-4685-be3e-55bc3b9602d4,http://snomed.info/sct|195662009||'Acute viral pharyngitis (disorder)' -66b38727-96ea-43ad-bff7-5f4df5d779a9,http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' -6794fbaf-e715-4e22-bafa-b7e594550ff7,http://snomed.info/sct|236077008||'Protracted diarrhea' -77fc5f45-e51d-41e2-8356-daa27ebd8268,http://snomed.info/sct|55822004||Hyperlipidemia -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,http://snomed.info/sct|195662009||'Acute viral pharyngitis (disorder)' -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' -80787532-559e-4999-ac25-75c462ce4ef1,http://snomed.info/sct|271737000||'Anemia (disorder)' -8922ff1b-4bc5-41b4-8512-5aad1517e2eb,http://snomed.info/sct|162864005||'Body mass index 30+ - obesity (finding)' -8ddb74fc-46d5-4e94-a0ee-6761b292ae95,http://snomed.info/sct|271737000||'Anemia (disorder)' -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6,http://snomed.info/sct|302870006||'Hypertriglyceridemia (disorder)' -8f3ad6ad-a457-484e-a455-f0711e77b2ba,http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' -9e598086-27bb-4e50-8988-9a40eb3c178f,http://snomed.info/sct|195662009||'Acute viral pharyngitis (disorder)' -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc,http://snomed.info/sct|284551006||'Laceration of foot' -a16f311d-d6dc-486e-8eb0-7cd53a5333ee,http://snomed.info/sct|368581000119106||'Neuropathy due to type 2 diabetes mellitus (disorder)' -aa5a32b3-3993-4eb5-afce-489d0e7413cf,http://snomed.info/sct|162864005||'Body mass index 30+ - obesity (finding)' -abecd476-3911-4a6b-a1c6-b8ecea2fe63e,http://snomed.info/sct|22298006||'Myocardial Infarction' -ad679e19-3e17-4c67-8b0d-dd4f7d862207,http://snomed.info/sct|87433001||'Pulmonary emphysema (disorder)' -b77b04ef-5eda-418c-a6fb-528a2d0a171a,http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' -b8eccdce-7261-4402-9aa0-7360ae6bf53b,http://snomed.info/sct|10509002||'Acute bronchitis (disorder)' -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc,http://snomed.info/sct|237602007||'Metabolic syndrome X (disorder)' -badbd940-9839-4472-aa66-3382d8823c80,http://snomed.info/sct|10509002||'Acute bronchitis (disorder)' -bb9c4fc1-795a-4492-b065-1f497fe18bb2,http://snomed.info/sct|44054006||Diabetes -be4b757d-70f1-464c-bb29-6f9a0f6cb05e,http://snomed.info/sct|6072007||'Bleeding from anus' -c39927f7-d23a-475c-b325-c1de4b51e280,http://snomed.info/sct|156073000||'Fetus with unknown complication' -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' -c892bf2f-a7bc-407e-9508-c778a9b8761c,http://snomed.info/sct|72892002||'Normal pregnancy' -d12274a5-9e03-4c78-ae3e-6cc27e91d737,http://snomed.info/sct|22298006||'Myocardial Infarction' -d554ba19-e081-4979-9c0c-a72cd6e5e8ea,http://snomed.info/sct|444470001||'Injury of anterior cruciate ligament' -da8db730-f051-42d0-a2db-a3577d96e9bd,http://snomed.info/sct|10509002||'Acute bronchitis (disorder)' -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82,http://snomed.info/sct|65363002||'Otitis media' -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d,http://snomed.info/sct|403190006||'First degree burn' -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76,http://snomed.info/sct|38341003||Hypertension -e35f5823-4533-49e5-9652-79d733be6bef,http://snomed.info/sct|15777000||Prediabetes -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d,http://snomed.info/sct|271737000||'Anemia (disorder)' -e87fbe4b-74ef-44e4-8e36-70b2abb48895,http://snomed.info/sct|68496003||'Polyp of colon' -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' -eaf99c09-419d-4162-8417-5a9d7e042cd4,http://snomed.info/sct|367498001||'Seasonal allergic rhinitis' -eb373264-da60-4e98-af7c-e3021fdd8d4b,http://snomed.info/sct|38822007||Cystitis -f1dd0d5f-c410-484b-984c-2ba38cee79ba,http://snomed.info/sct|10509002||'Acute bronchitis (disorder)' -f1fd855c-802c-417a-ab7c-14a3c9daafc6,http://snomed.info/sct|38341003||Hypertension -f41b6458-2358-43e7-ae34-d2a0fbe083d8,http://snomed.info/sct|713197008||'Recurrent rectal polyp' diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/codingColumn.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/codingColumn.tsv new file mode 100644 index 0000000000..a96b61798a --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/codingColumn.tsv @@ -0,0 +1,71 @@ +01fac610-c309-46f8-9a0f-6b0f55c9915a http://snomed.info/sct|53741008||'Coronary Heart Disease' +031f1c86-16ec-42e1-a155-81456e778fed http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' +0447de38-1f2a-411c-9fd2-a9c62ab1f221 http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' +061521f0-7f7e-41a5-ad35-b30fe958dea7 http://snomed.info/sct|195662009||'Acute viral pharyngitis (disorder)' +06e446da-6d66-4d97-a457-4abf2a0d2e24 http://snomed.info/sct|363406005||'Malignant tumor of colon' +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 http://snomed.info/sct|195662009||'Acute viral pharyngitis (disorder)' +1146cf52-573f-4667-97c3-abf235f9a83b http://snomed.info/sct|33737001||'Fracture of rib' +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 http://snomed.info/sct|65363002||'Otitis media' +171ad058-c082-46ab-b9cc-d7bba6e4e3cd http://snomed.info/sct|40055000||'Chronic sinusitis (disorder)' +1cea8432-f8a0-4746-ab5c-8f195fc07c55 http://snomed.info/sct|94260004||'Secondary malignant neoplasm of colon' +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' +21372b39-2be0-4d0d-85c4-5d7e250d8f78 http://snomed.info/sct|65363002||'Otitis media' +248e951c-e0ce-4e4b-afae-6273fd109c9d http://snomed.info/sct|15777000||Prediabetes +251ddff9-1588-4790-8f40-cd6ab1d9b7fc http://snomed.info/sct|72892002||'Normal pregnancy' +258191b8-2b42-4473-9ba2-be0ba3561767 http://snomed.info/sct|162864005||'Body mass index 30+ - obesity (finding)' +29386b12-9af9-4c21-9f82-858998b388bb http://snomed.info/sct|38341003||Hypertension +31393667-dd7f-4c94-90c3-6cde75c67d3e http://snomed.info/sct|403190006||'First degree burn' +31925a68-7a48-412c-b4f8-aef92498dda1 http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' +38171e22-b35d-4161-be15-80243be37b99 http://snomed.info/sct|195662009||'Acute viral pharyngitis (disorder)' +39170d67-8205-4636-84d7-d8575115d14c http://snomed.info/sct|10509002||'Acute bronchitis (disorder)' +4025ceb3-04a6-41fb-8569-16a8dcce7ccc http://snomed.info/sct|15777000||Prediabetes +46d69851-eca3-42e2-b142-88c5578f6cff http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' +475461a2-3bd2-43fd-a5aa-7ce424203ae8 http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' +4dd7824b-4b41-4402-bc42-de016e1bfcd9 http://snomed.info/sct|19169002||'Miscarriage in first trimester' +5300158c-92cb-40fe-bc14-a449d7b3c1c5 http://snomed.info/sct|70704007||'Sprain of wrist' +5378726d-7f2b-4c83-9762-eaf385915fa7 http://snomed.info/sct|271737000||'Anemia (disorder)' +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 http://snomed.info/sct|15777000||Prediabetes +55cde383-2eb1-42d9-b5c6-698d6eade389 http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 http://snomed.info/sct|399211009||'History of myocardial infarction (situation)' +5ef84858-0480-4a34-8f43-fc962fe627b2 http://snomed.info/sct|19169002||'Miscarriage in first trimester' +6464e20b-55ec-4685-be3e-55bc3b9602d4 http://snomed.info/sct|195662009||'Acute viral pharyngitis (disorder)' +66b38727-96ea-43ad-bff7-5f4df5d779a9 http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' +6794fbaf-e715-4e22-bafa-b7e594550ff7 http://snomed.info/sct|236077008||'Protracted diarrhea' +77fc5f45-e51d-41e2-8356-daa27ebd8268 http://snomed.info/sct|55822004||Hyperlipidemia +7d18555a-54c9-4c0a-bf3b-6305d0392a2a http://snomed.info/sct|195662009||'Acute viral pharyngitis (disorder)' +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' +80787532-559e-4999-ac25-75c462ce4ef1 http://snomed.info/sct|271737000||'Anemia (disorder)' +8922ff1b-4bc5-41b4-8512-5aad1517e2eb http://snomed.info/sct|162864005||'Body mass index 30+ - obesity (finding)' +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 http://snomed.info/sct|271737000||'Anemia (disorder)' +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 http://snomed.info/sct|302870006||'Hypertriglyceridemia (disorder)' +8f3ad6ad-a457-484e-a455-f0711e77b2ba http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' +9e598086-27bb-4e50-8988-9a40eb3c178f http://snomed.info/sct|195662009||'Acute viral pharyngitis (disorder)' +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc http://snomed.info/sct|284551006||'Laceration of foot' +a16f311d-d6dc-486e-8eb0-7cd53a5333ee http://snomed.info/sct|368581000119106||'Neuropathy due to type 2 diabetes mellitus (disorder)' +aa5a32b3-3993-4eb5-afce-489d0e7413cf http://snomed.info/sct|162864005||'Body mass index 30+ - obesity (finding)' +abecd476-3911-4a6b-a1c6-b8ecea2fe63e http://snomed.info/sct|22298006||'Myocardial Infarction' +ad679e19-3e17-4c67-8b0d-dd4f7d862207 http://snomed.info/sct|87433001||'Pulmonary emphysema (disorder)' +b77b04ef-5eda-418c-a6fb-528a2d0a171a http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' +b8eccdce-7261-4402-9aa0-7360ae6bf53b http://snomed.info/sct|10509002||'Acute bronchitis (disorder)' +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc http://snomed.info/sct|237602007||'Metabolic syndrome X (disorder)' +badbd940-9839-4472-aa66-3382d8823c80 http://snomed.info/sct|10509002||'Acute bronchitis (disorder)' +bb9c4fc1-795a-4492-b065-1f497fe18bb2 http://snomed.info/sct|44054006||Diabetes +be4b757d-70f1-464c-bb29-6f9a0f6cb05e http://snomed.info/sct|6072007||'Bleeding from anus' +c39927f7-d23a-475c-b325-c1de4b51e280 http://snomed.info/sct|156073000||'Fetus with unknown complication' +c879c300-7fdf-4b53-aa6a-a2b4a266b30c http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' +c892bf2f-a7bc-407e-9508-c778a9b8761c http://snomed.info/sct|72892002||'Normal pregnancy' +d12274a5-9e03-4c78-ae3e-6cc27e91d737 http://snomed.info/sct|22298006||'Myocardial Infarction' +d554ba19-e081-4979-9c0c-a72cd6e5e8ea http://snomed.info/sct|444470001||'Injury of anterior cruciate ligament' +da8db730-f051-42d0-a2db-a3577d96e9bd http://snomed.info/sct|10509002||'Acute bronchitis (disorder)' +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 http://snomed.info/sct|65363002||'Otitis media' +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d http://snomed.info/sct|403190006||'First degree burn' +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 http://snomed.info/sct|38341003||Hypertension +e35f5823-4533-49e5-9652-79d733be6bef http://snomed.info/sct|15777000||Prediabetes +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d http://snomed.info/sct|271737000||'Anemia (disorder)' +e87fbe4b-74ef-44e4-8e36-70b2abb48895 http://snomed.info/sct|68496003||'Polyp of colon' +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 http://snomed.info/sct|444814009||'Viral sinusitis (disorder)' +eaf99c09-419d-4162-8417-5a9d7e042cd4 http://snomed.info/sct|367498001||'Seasonal allergic rhinitis' +eb373264-da60-4e98-af7c-e3021fdd8d4b http://snomed.info/sct|38822007||Cystitis +f1dd0d5f-c410-484b-984c-2ba38cee79ba http://snomed.info/sct|10509002||'Acute bronchitis (disorder)' +f1fd855c-802c-417a-ab7c-14a3c9daafc6 http://snomed.info/sct|38341003||Hypertension +f41b6458-2358-43e7-ae34-d2a0fbe083d8 http://snomed.info/sct|713197008||'Recurrent rectal polyp' diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/codingLiteralColumn.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/codingLiteralColumn.csv deleted file mode 100644 index e98269e36c..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/codingLiteralColumn.csv +++ /dev/null @@ -1,71 +0,0 @@ -01fac610-c309-46f8-9a0f-6b0f55c9915a,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -031f1c86-16ec-42e1-a155-81456e778fed,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -0447de38-1f2a-411c-9fd2-a9c62ab1f221,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -061521f0-7f7e-41a5-ad35-b30fe958dea7,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -06e446da-6d66-4d97-a457-4abf2a0d2e24,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -1146cf52-573f-4667-97c3-abf235f9a83b,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -1452c50c-b0b9-472c-afb7-2f9e5a3c4717,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -171ad058-c082-46ab-b9cc-d7bba6e4e3cd,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -1cea8432-f8a0-4746-ab5c-8f195fc07c55,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -21372b39-2be0-4d0d-85c4-5d7e250d8f78,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -248e951c-e0ce-4e4b-afae-6273fd109c9d,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -251ddff9-1588-4790-8f40-cd6ab1d9b7fc,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -258191b8-2b42-4473-9ba2-be0ba3561767,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -29386b12-9af9-4c21-9f82-858998b388bb,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -31393667-dd7f-4c94-90c3-6cde75c67d3e,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -31925a68-7a48-412c-b4f8-aef92498dda1,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -38171e22-b35d-4161-be15-80243be37b99,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -39170d67-8205-4636-84d7-d8575115d14c,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -4025ceb3-04a6-41fb-8569-16a8dcce7ccc,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -46d69851-eca3-42e2-b142-88c5578f6cff,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -475461a2-3bd2-43fd-a5aa-7ce424203ae8,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -4dd7824b-4b41-4402-bc42-de016e1bfcd9,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -5300158c-92cb-40fe-bc14-a449d7b3c1c5,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -5378726d-7f2b-4c83-9762-eaf385915fa7,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -549d3ef5-a05e-4b8e-acba-3d70f26a82f6,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -55cde383-2eb1-42d9-b5c6-698d6eade389,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -5ef84858-0480-4a34-8f43-fc962fe627b2,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -6464e20b-55ec-4685-be3e-55bc3b9602d4,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -66b38727-96ea-43ad-bff7-5f4df5d779a9,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -6794fbaf-e715-4e22-bafa-b7e594550ff7,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -77fc5f45-e51d-41e2-8356-daa27ebd8268,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -80787532-559e-4999-ac25-75c462ce4ef1,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -8922ff1b-4bc5-41b4-8512-5aad1517e2eb,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -8ddb74fc-46d5-4e94-a0ee-6761b292ae95,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -8f3ad6ad-a457-484e-a455-f0711e77b2ba,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -9e598086-27bb-4e50-8988-9a40eb3c178f,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -a16f311d-d6dc-486e-8eb0-7cd53a5333ee,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -aa5a32b3-3993-4eb5-afce-489d0e7413cf,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -abecd476-3911-4a6b-a1c6-b8ecea2fe63e,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -ad679e19-3e17-4c67-8b0d-dd4f7d862207,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -b77b04ef-5eda-418c-a6fb-528a2d0a171a,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -b8eccdce-7261-4402-9aa0-7360ae6bf53b,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -badbd940-9839-4472-aa66-3382d8823c80,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -bb9c4fc1-795a-4492-b065-1f497fe18bb2,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -be4b757d-70f1-464c-bb29-6f9a0f6cb05e,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -c39927f7-d23a-475c-b325-c1de4b51e280,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -c892bf2f-a7bc-407e-9508-c778a9b8761c,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -d12274a5-9e03-4c78-ae3e-6cc27e91d737,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -d554ba19-e081-4979-9c0c-a72cd6e5e8ea,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -da8db730-f051-42d0-a2db-a3577d96e9bd,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -e35f5823-4533-49e5-9652-79d733be6bef,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -e87fbe4b-74ef-44e4-8e36-70b2abb48895,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -eaf99c09-419d-4162-8417-5a9d7e042cd4,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -eb373264-da60-4e98-af7c-e3021fdd8d4b,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -f1dd0d5f-c410-484b-984c-2ba38cee79ba,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -f1fd855c-802c-417a-ab7c-14a3c9daafc6,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" -f41b6458-2358-43e7-ae34-d2a0fbe083d8,"http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231" diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/codingLiteralColumn.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/codingLiteralColumn.tsv new file mode 100644 index 0000000000..48466ca4f5 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/codingLiteralColumn.tsv @@ -0,0 +1,71 @@ +01fac610-c309-46f8-9a0f-6b0f55c9915a http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +031f1c86-16ec-42e1-a155-81456e778fed http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +0447de38-1f2a-411c-9fd2-a9c62ab1f221 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +061521f0-7f7e-41a5-ad35-b30fe958dea7 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +06e446da-6d66-4d97-a457-4abf2a0d2e24 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +1146cf52-573f-4667-97c3-abf235f9a83b http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +171ad058-c082-46ab-b9cc-d7bba6e4e3cd http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +1cea8432-f8a0-4746-ab5c-8f195fc07c55 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +21372b39-2be0-4d0d-85c4-5d7e250d8f78 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +248e951c-e0ce-4e4b-afae-6273fd109c9d http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +251ddff9-1588-4790-8f40-cd6ab1d9b7fc http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +258191b8-2b42-4473-9ba2-be0ba3561767 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +29386b12-9af9-4c21-9f82-858998b388bb http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +31393667-dd7f-4c94-90c3-6cde75c67d3e http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +31925a68-7a48-412c-b4f8-aef92498dda1 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +38171e22-b35d-4161-be15-80243be37b99 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +39170d67-8205-4636-84d7-d8575115d14c http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +4025ceb3-04a6-41fb-8569-16a8dcce7ccc http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +46d69851-eca3-42e2-b142-88c5578f6cff http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +475461a2-3bd2-43fd-a5aa-7ce424203ae8 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +4dd7824b-4b41-4402-bc42-de016e1bfcd9 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +5300158c-92cb-40fe-bc14-a449d7b3c1c5 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +5378726d-7f2b-4c83-9762-eaf385915fa7 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +55cde383-2eb1-42d9-b5c6-698d6eade389 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +5ef84858-0480-4a34-8f43-fc962fe627b2 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +6464e20b-55ec-4685-be3e-55bc3b9602d4 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +66b38727-96ea-43ad-bff7-5f4df5d779a9 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +6794fbaf-e715-4e22-bafa-b7e594550ff7 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +77fc5f45-e51d-41e2-8356-daa27ebd8268 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +7d18555a-54c9-4c0a-bf3b-6305d0392a2a http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +80787532-559e-4999-ac25-75c462ce4ef1 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +8922ff1b-4bc5-41b4-8512-5aad1517e2eb http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +8f3ad6ad-a457-484e-a455-f0711e77b2ba http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +9e598086-27bb-4e50-8988-9a40eb3c178f http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +a16f311d-d6dc-486e-8eb0-7cd53a5333ee http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +aa5a32b3-3993-4eb5-afce-489d0e7413cf http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +abecd476-3911-4a6b-a1c6-b8ecea2fe63e http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +ad679e19-3e17-4c67-8b0d-dd4f7d862207 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +b77b04ef-5eda-418c-a6fb-528a2d0a171a http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +b8eccdce-7261-4402-9aa0-7360ae6bf53b http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +badbd940-9839-4472-aa66-3382d8823c80 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +bb9c4fc1-795a-4492-b065-1f497fe18bb2 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +be4b757d-70f1-464c-bb29-6f9a0f6cb05e http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +c39927f7-d23a-475c-b325-c1de4b51e280 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +c879c300-7fdf-4b53-aa6a-a2b4a266b30c http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +c892bf2f-a7bc-407e-9508-c778a9b8761c http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +d12274a5-9e03-4c78-ae3e-6cc27e91d737 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +d554ba19-e081-4979-9c0c-a72cd6e5e8ea http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +da8db730-f051-42d0-a2db-a3577d96e9bd http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +e35f5823-4533-49e5-9652-79d733be6bef http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +e87fbe4b-74ef-44e4-8e36-70b2abb48895 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +eaf99c09-419d-4162-8417-5a9d7e042cd4 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +eb373264-da60-4e98-af7c-e3021fdd8d4b http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +f1dd0d5f-c410-484b-984c-2ba38cee79ba http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +f1fd855c-802c-417a-ab7c-14a3c9daafc6 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 +f41b6458-2358-43e7-ae34-d2a0fbe083d8 http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/combineResultInSecondFilter.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/combineResultInSecondFilter.tsv similarity index 100% rename from fhir-server/src/test/resources/responses/ExtractQueryTest/combineResultInSecondFilter.csv rename to fhir-server/src/test/resources/responses/ExtractQueryTest/combineResultInSecondFilter.tsv diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithLiterals.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithLiterals.csv deleted file mode 100644 index 655d985142..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithLiterals.csv +++ /dev/null @@ -1,18 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,foo -121503c8-9564-4b48-9086-a22df717948e,bar -2b36c1e2-bbe1-45ae-8124-4adad2677702,foo -2b36c1e2-bbe1-45ae-8124-4adad2677702,bar -7001ad9c-34d2-4eb5-8165-5fdc2147f469,foo -7001ad9c-34d2-4eb5-8165-5fdc2147f469,bar -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,foo -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,bar -9360820c-8602-4335-8b50-c88d627a0c20,foo -9360820c-8602-4335-8b50-c88d627a0c20,bar -a7eb2ce7-1075-426c-addd-957b861b0e55,foo -a7eb2ce7-1075-426c-addd-957b861b0e55,bar -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,foo -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,bar -beff242e-580b-47c0-9844-c1a68c36c5bf,foo -beff242e-580b-47c0-9844-c1a68c36c5bf,bar -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,foo -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,bar diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithLiterals.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithLiterals.tsv new file mode 100644 index 0000000000..393719a47b --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithLiterals.tsv @@ -0,0 +1,18 @@ +121503c8-9564-4b48-9086-a22df717948e foo +121503c8-9564-4b48-9086-a22df717948e bar +2b36c1e2-bbe1-45ae-8124-4adad2677702 foo +2b36c1e2-bbe1-45ae-8124-4adad2677702 bar +7001ad9c-34d2-4eb5-8165-5fdc2147f469 foo +7001ad9c-34d2-4eb5-8165-5fdc2147f469 bar +8ee183e2-b3c0-4151-be94-b945d6aa8c6d foo +8ee183e2-b3c0-4151-be94-b945d6aa8c6d bar +9360820c-8602-4335-8b50-c88d627a0c20 foo +9360820c-8602-4335-8b50-c88d627a0c20 bar +a7eb2ce7-1075-426c-addd-957b861b0e55 foo +a7eb2ce7-1075-426c-addd-957b861b0e55 bar +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 foo +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 bar +beff242e-580b-47c0-9844-c1a68c36c5bf foo +beff242e-580b-47c0-9844-c1a68c36c5bf bar +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 foo +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 bar diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithUnequalCardinalities.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithUnequalCardinalities.csv deleted file mode 100644 index fbca6c0545..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithUnequalCardinalities.csv +++ /dev/null @@ -1,24 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,Ophelia894,Gleichner915,Ophelia894 -121503c8-9564-4b48-9086-a22df717948e,Ophelia894,Gleichner915,Gleichner915 -2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316,Ulibarri312,Pedro316 -2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316,Ulibarri312,Ulibarri312 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901,Heller342,Cherryl901 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901,Heller342,Heller342 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882,Krajcik437,Seymour882 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882,Krajcik437,Krajcik437 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Moyna,Krajcik437,Moyna -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Moyna,Krajcik437,Krajcik437 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Arianne,Burchard,Arianne -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Arianne,Burchard,Burchard -9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Oberbrunner298,Karina848 -9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Oberbrunner298,Oberbrunner298 -9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Wuckert783,Karina848 -9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Wuckert783,Wuckert783 -a7eb2ce7-1075-426c-addd-957b861b0e55,Shirley182,Botsford977,Shirley182 -a7eb2ce7-1075-426c-addd-957b861b0e55,Shirley182,Botsford977,Botsford977 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Gilberto712,MacGyver246,Gilberto712 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Gilberto712,MacGyver246,MacGyver246 -beff242e-580b-47c0-9844-c1a68c36c5bf,Guy979,Towne435,Guy979 -beff242e-580b-47c0-9844-c1a68c36c5bf,Guy979,Towne435,Towne435 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Su690,Ebert178,Su690 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Su690,Ebert178,Ebert178 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithUnequalCardinalities.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithUnequalCardinalities.tsv new file mode 100644 index 0000000000..ceb0044f3b --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/combineWithUnequalCardinalities.tsv @@ -0,0 +1,24 @@ +121503c8-9564-4b48-9086-a22df717948e Ophelia894 Gleichner915 Ophelia894 +121503c8-9564-4b48-9086-a22df717948e Ophelia894 Gleichner915 Gleichner915 +2b36c1e2-bbe1-45ae-8124-4adad2677702 Pedro316 Ulibarri312 Pedro316 +2b36c1e2-bbe1-45ae-8124-4adad2677702 Pedro316 Ulibarri312 Ulibarri312 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 Cherryl901 Heller342 Cherryl901 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 Cherryl901 Heller342 Heller342 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Seymour882 Krajcik437 Seymour882 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Seymour882 Krajcik437 Krajcik437 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Moyna Krajcik437 Moyna +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Moyna Krajcik437 Krajcik437 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Arianne Burchard Arianne +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Arianne Burchard Burchard +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 Oberbrunner298 Karina848 +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 Oberbrunner298 Oberbrunner298 +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 Wuckert783 Karina848 +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 Wuckert783 Wuckert783 +a7eb2ce7-1075-426c-addd-957b861b0e55 Shirley182 Botsford977 Shirley182 +a7eb2ce7-1075-426c-addd-957b861b0e55 Shirley182 Botsford977 Botsford977 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 Gilberto712 MacGyver246 Gilberto712 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 Gilberto712 MacGyver246 MacGyver246 +beff242e-580b-47c0-9844-c1a68c36c5bf Guy979 Towne435 Guy979 +beff242e-580b-47c0-9844-c1a68c36c5bf Guy979 Towne435 Towne435 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 Su690 Ebert178 Su690 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 Su690 Ebert178 Ebert178 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/eliminatesTrailingNulls.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/eliminatesTrailingNulls.csv deleted file mode 100644 index a8f68d36fe..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/eliminatesTrailingNulls.csv +++ /dev/null @@ -1,10 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -9360820c-8602-4335-8b50-c88d627a0c20,72892002 -9360820c-8602-4335-8b50-c88d627a0c20,72892002 -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/eliminatesTrailingNulls.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/eliminatesTrailingNulls.tsv new file mode 100644 index 0000000000..3cb54eb066 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/eliminatesTrailingNulls.tsv @@ -0,0 +1,10 @@ +121503c8-9564-4b48-9086-a22df717948e +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 72892002 +9360820c-8602-4335-8b50-c88d627a0c20 72892002 +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/limit.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/limit.csv deleted file mode 100644 index a8bfa8c02d..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/limit.csv +++ /dev/null @@ -1,3 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,female -7001ad9c-34d2-4eb5-8165-5fdc2147f469,female -9360820c-8602-4335-8b50-c88d627a0c20,female diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/limit.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/limit.tsv new file mode 100644 index 0000000000..69be0eec4e --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/limit.tsv @@ -0,0 +1,3 @@ +121503c8-9564-4b48-9086-a22df717948e female +7001ad9c-34d2-4eb5-8165-5fdc2147f469 female +9360820c-8602-4335-8b50-c88d627a0c20 female diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.csv deleted file mode 100644 index c21942b70b..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.csv +++ /dev/null @@ -1,12 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,Ophelia894,Gleichner915,official -2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316,Ulibarri312,official -7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901,Heller342,official -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882,Krajcik437,official -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Moyna,Krajcik437,official -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Arianne,Burchard,nickname -9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Oberbrunner298,official -9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Wuckert783,maiden -a7eb2ce7-1075-426c-addd-957b861b0e55,Shirley182,Botsford977,official -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Gilberto712,MacGyver246,official -beff242e-580b-47c0-9844-c1a68c36c5bf,Guy979,Towne435,official -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Su690,Ebert178,official diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.tsv new file mode 100644 index 0000000000..880b12232a --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/linkedUnnesting.tsv @@ -0,0 +1,12 @@ +121503c8-9564-4b48-9086-a22df717948e Ophelia894 Gleichner915 official +2b36c1e2-bbe1-45ae-8124-4adad2677702 Pedro316 Ulibarri312 official +7001ad9c-34d2-4eb5-8165-5fdc2147f469 Cherryl901 Heller342 official +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Seymour882 Krajcik437 official +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Moyna Krajcik437 official +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Arianne Burchard nickname +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 Oberbrunner298 official +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 Wuckert783 maiden +a7eb2ce7-1075-426c-addd-957b861b0e55 Shirley182 Botsford977 official +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 Gilberto712 MacGyver246 official +beff242e-580b-47c0-9844-c1a68c36c5bf Guy979 Towne435 official +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 Su690 Ebert178 official diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/literalColumn.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/literalColumn.csv deleted file mode 100644 index 262e45911e..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/literalColumn.csv +++ /dev/null @@ -1,71 +0,0 @@ -01fac610-c309-46f8-9a0f-6b0f55c9915a,19 -031f1c86-16ec-42e1-a155-81456e778fed,19 -0447de38-1f2a-411c-9fd2-a9c62ab1f221,19 -061521f0-7f7e-41a5-ad35-b30fe958dea7,19 -06e446da-6d66-4d97-a457-4abf2a0d2e24,19 -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,19 -1146cf52-573f-4667-97c3-abf235f9a83b,19 -1452c50c-b0b9-472c-afb7-2f9e5a3c4717,19 -171ad058-c082-46ab-b9cc-d7bba6e4e3cd,19 -1cea8432-f8a0-4746-ab5c-8f195fc07c55,19 -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,19 -21372b39-2be0-4d0d-85c4-5d7e250d8f78,19 -248e951c-e0ce-4e4b-afae-6273fd109c9d,19 -251ddff9-1588-4790-8f40-cd6ab1d9b7fc,19 -258191b8-2b42-4473-9ba2-be0ba3561767,19 -29386b12-9af9-4c21-9f82-858998b388bb,19 -31393667-dd7f-4c94-90c3-6cde75c67d3e,19 -31925a68-7a48-412c-b4f8-aef92498dda1,19 -38171e22-b35d-4161-be15-80243be37b99,19 -39170d67-8205-4636-84d7-d8575115d14c,19 -4025ceb3-04a6-41fb-8569-16a8dcce7ccc,19 -46d69851-eca3-42e2-b142-88c5578f6cff,19 -475461a2-3bd2-43fd-a5aa-7ce424203ae8,19 -4dd7824b-4b41-4402-bc42-de016e1bfcd9,19 -5300158c-92cb-40fe-bc14-a449d7b3c1c5,19 -5378726d-7f2b-4c83-9762-eaf385915fa7,19 -549d3ef5-a05e-4b8e-acba-3d70f26a82f6,19 -55cde383-2eb1-42d9-b5c6-698d6eade389,19 -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325,19 -5ef84858-0480-4a34-8f43-fc962fe627b2,19 -6464e20b-55ec-4685-be3e-55bc3b9602d4,19 -66b38727-96ea-43ad-bff7-5f4df5d779a9,19 -6794fbaf-e715-4e22-bafa-b7e594550ff7,19 -77fc5f45-e51d-41e2-8356-daa27ebd8268,19 -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,19 -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,19 -80787532-559e-4999-ac25-75c462ce4ef1,19 -8922ff1b-4bc5-41b4-8512-5aad1517e2eb,19 -8ddb74fc-46d5-4e94-a0ee-6761b292ae95,19 -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6,19 -8f3ad6ad-a457-484e-a455-f0711e77b2ba,19 -9e598086-27bb-4e50-8988-9a40eb3c178f,19 -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc,19 -a16f311d-d6dc-486e-8eb0-7cd53a5333ee,19 -aa5a32b3-3993-4eb5-afce-489d0e7413cf,19 -abecd476-3911-4a6b-a1c6-b8ecea2fe63e,19 -ad679e19-3e17-4c67-8b0d-dd4f7d862207,19 -b77b04ef-5eda-418c-a6fb-528a2d0a171a,19 -b8eccdce-7261-4402-9aa0-7360ae6bf53b,19 -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc,19 -badbd940-9839-4472-aa66-3382d8823c80,19 -bb9c4fc1-795a-4492-b065-1f497fe18bb2,19 -be4b757d-70f1-464c-bb29-6f9a0f6cb05e,19 -c39927f7-d23a-475c-b325-c1de4b51e280,19 -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,19 -c892bf2f-a7bc-407e-9508-c778a9b8761c,19 -d12274a5-9e03-4c78-ae3e-6cc27e91d737,19 -d554ba19-e081-4979-9c0c-a72cd6e5e8ea,19 -da8db730-f051-42d0-a2db-a3577d96e9bd,19 -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82,19 -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d,19 -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76,19 -e35f5823-4533-49e5-9652-79d733be6bef,19 -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d,19 -e87fbe4b-74ef-44e4-8e36-70b2abb48895,19 -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,19 -eaf99c09-419d-4162-8417-5a9d7e042cd4,19 -eb373264-da60-4e98-af7c-e3021fdd8d4b,19 -f1dd0d5f-c410-484b-984c-2ba38cee79ba,19 -f1fd855c-802c-417a-ab7c-14a3c9daafc6,19 -f41b6458-2358-43e7-ae34-d2a0fbe083d8,19 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/literalColumn.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/literalColumn.tsv new file mode 100644 index 0000000000..ac57228566 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/literalColumn.tsv @@ -0,0 +1,71 @@ +01fac610-c309-46f8-9a0f-6b0f55c9915a 19 +031f1c86-16ec-42e1-a155-81456e778fed 19 +0447de38-1f2a-411c-9fd2-a9c62ab1f221 19 +061521f0-7f7e-41a5-ad35-b30fe958dea7 19 +06e446da-6d66-4d97-a457-4abf2a0d2e24 19 +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 19 +1146cf52-573f-4667-97c3-abf235f9a83b 19 +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 19 +171ad058-c082-46ab-b9cc-d7bba6e4e3cd 19 +1cea8432-f8a0-4746-ab5c-8f195fc07c55 19 +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 19 +21372b39-2be0-4d0d-85c4-5d7e250d8f78 19 +248e951c-e0ce-4e4b-afae-6273fd109c9d 19 +251ddff9-1588-4790-8f40-cd6ab1d9b7fc 19 +258191b8-2b42-4473-9ba2-be0ba3561767 19 +29386b12-9af9-4c21-9f82-858998b388bb 19 +31393667-dd7f-4c94-90c3-6cde75c67d3e 19 +31925a68-7a48-412c-b4f8-aef92498dda1 19 +38171e22-b35d-4161-be15-80243be37b99 19 +39170d67-8205-4636-84d7-d8575115d14c 19 +4025ceb3-04a6-41fb-8569-16a8dcce7ccc 19 +46d69851-eca3-42e2-b142-88c5578f6cff 19 +475461a2-3bd2-43fd-a5aa-7ce424203ae8 19 +4dd7824b-4b41-4402-bc42-de016e1bfcd9 19 +5300158c-92cb-40fe-bc14-a449d7b3c1c5 19 +5378726d-7f2b-4c83-9762-eaf385915fa7 19 +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 19 +55cde383-2eb1-42d9-b5c6-698d6eade389 19 +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 19 +5ef84858-0480-4a34-8f43-fc962fe627b2 19 +6464e20b-55ec-4685-be3e-55bc3b9602d4 19 +66b38727-96ea-43ad-bff7-5f4df5d779a9 19 +6794fbaf-e715-4e22-bafa-b7e594550ff7 19 +77fc5f45-e51d-41e2-8356-daa27ebd8268 19 +7d18555a-54c9-4c0a-bf3b-6305d0392a2a 19 +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 19 +80787532-559e-4999-ac25-75c462ce4ef1 19 +8922ff1b-4bc5-41b4-8512-5aad1517e2eb 19 +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 19 +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 19 +8f3ad6ad-a457-484e-a455-f0711e77b2ba 19 +9e598086-27bb-4e50-8988-9a40eb3c178f 19 +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc 19 +a16f311d-d6dc-486e-8eb0-7cd53a5333ee 19 +aa5a32b3-3993-4eb5-afce-489d0e7413cf 19 +abecd476-3911-4a6b-a1c6-b8ecea2fe63e 19 +ad679e19-3e17-4c67-8b0d-dd4f7d862207 19 +b77b04ef-5eda-418c-a6fb-528a2d0a171a 19 +b8eccdce-7261-4402-9aa0-7360ae6bf53b 19 +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc 19 +badbd940-9839-4472-aa66-3382d8823c80 19 +bb9c4fc1-795a-4492-b065-1f497fe18bb2 19 +be4b757d-70f1-464c-bb29-6f9a0f6cb05e 19 +c39927f7-d23a-475c-b325-c1de4b51e280 19 +c879c300-7fdf-4b53-aa6a-a2b4a266b30c 19 +c892bf2f-a7bc-407e-9508-c778a9b8761c 19 +d12274a5-9e03-4c78-ae3e-6cc27e91d737 19 +d554ba19-e081-4979-9c0c-a72cd6e5e8ea 19 +da8db730-f051-42d0-a2db-a3577d96e9bd 19 +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 19 +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d 19 +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 19 +e35f5823-4533-49e5-9652-79d733be6bef 19 +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d 19 +e87fbe4b-74ef-44e4-8e36-70b2abb48895 19 +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 19 +eaf99c09-419d-4162-8417-5a9d7e042cd4 19 +eb373264-da60-4e98-af7c-e3021fdd8d4b 19 +f1dd0d5f-c410-484b-984c-2ba38cee79ba 19 +f1fd855c-802c-417a-ab7c-14a3c9daafc6 19 +f41b6458-2358-43e7-ae34-d2a0fbe083d8 19 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleFilters.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleFilters.csv deleted file mode 100644 index dd0ce5c532..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleFilters.csv +++ /dev/null @@ -1,2 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,10 -9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,16 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleFilters.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleFilters.tsv new file mode 100644 index 0000000000..bed1d5b7f9 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleFilters.tsv @@ -0,0 +1,2 @@ +121503c8-9564-4b48-9086-a22df717948e female Ophelia894 10 +9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 16 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv deleted file mode 100644 index 98b86426e4..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.csv +++ /dev/null @@ -1,14 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,Ophelia894,Gleichner915,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S -2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316,Ulibarri312,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S -7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901,Heller342,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882,Krajcik437,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Moyna,Krajcik437,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Arianne,Burchard,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M -9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Oberbrunner298,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M -9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Wuckert783,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M -9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Oberbrunner298,http://snomed.info/sct,87915002 -9360820c-8602-4335-8b50-c88d627a0c20,Karina848,Wuckert783,http://snomed.info/sct,87915002 -a7eb2ce7-1075-426c-addd-957b861b0e55,Shirley182,Botsford977,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Gilberto712,MacGyver246,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S -beff242e-580b-47c0-9844-c1a68c36c5bf,Guy979,Towne435,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Su690,Ebert178,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,S diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.tsv new file mode 100644 index 0000000000..ca7aeb214b --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleIndependentUnnestings.tsv @@ -0,0 +1,14 @@ +121503c8-9564-4b48-9086-a22df717948e Ophelia894 Gleichner915 http://terminology.hl7.org/CodeSystem/v3-MaritalStatus S +2b36c1e2-bbe1-45ae-8124-4adad2677702 Pedro316 Ulibarri312 http://terminology.hl7.org/CodeSystem/v3-MaritalStatus S +7001ad9c-34d2-4eb5-8165-5fdc2147f469 Cherryl901 Heller342 http://terminology.hl7.org/CodeSystem/v3-MaritalStatus S +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Seymour882 Krajcik437 http://terminology.hl7.org/CodeSystem/v3-MaritalStatus M +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Moyna Krajcik437 http://terminology.hl7.org/CodeSystem/v3-MaritalStatus M +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Arianne Burchard http://terminology.hl7.org/CodeSystem/v3-MaritalStatus M +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 Oberbrunner298 http://terminology.hl7.org/CodeSystem/v3-MaritalStatus M +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 Wuckert783 http://terminology.hl7.org/CodeSystem/v3-MaritalStatus M +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 Oberbrunner298 http://snomed.info/sct 87915002 +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 Wuckert783 http://snomed.info/sct 87915002 +a7eb2ce7-1075-426c-addd-957b861b0e55 Shirley182 Botsford977 http://terminology.hl7.org/CodeSystem/v3-MaritalStatus S +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 Gilberto712 MacGyver246 http://terminology.hl7.org/CodeSystem/v3-MaritalStatus S +beff242e-580b-47c0-9844-c1a68c36c5bf Guy979 Towne435 http://terminology.hl7.org/CodeSystem/v3-MaritalStatus M +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 Su690 Ebert178 http://terminology.hl7.org/CodeSystem/v3-MaritalStatus S diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleNonSingularColumnsWithDifferentTypes.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleNonSingularColumnsWithDifferentTypes.csv deleted file mode 100644 index 4eb29bfeb9..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleNonSingularColumnsWithDifferentTypes.csv +++ /dev/null @@ -1,217 +0,0 @@ -0134901a-4a69-4b39-92ea-d6f48ec83c6e,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -0250a217-a663-403b-a708-5d14eadf0c40,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -02b993c0-b358-481c-b0d0-0767387feb9f,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -0364073f-91d8-47a1-b8b0-107c6318a691,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -04945715-8ad5-4d8f-bfda-ae26662a3610,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,Consultation for treatment,http://snomed.info/sct|698314001||'Consultation for treatment' -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -0af4a77e-892e-4b3f-9110-4c5224782250,Follow-up encounter,http://snomed.info/sct|390906007||'Follow-up encounter' -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -102e16c5-4920-4dad-b142-0b280b2aacad,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -11dafd5c-85e7-4275-a147-89a63641e35e,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -12b212d8-bc6e-415a-93b0-594381726668,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -12df1bed-5472-4c48-8e88-2cbb3e5eab33,Encounter for 'check-up',http://snomed.info/sct|185349003||'Encounter for \'check-up\'' -151e3048-66ad-4411-8753-677877e3bf0a,Prenatal visit,http://snomed.info/sct|424619006||'Prenatal visit' -16a3408d-c927-4d02-a1ae-cad32419caab,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -16d280a2-c61f-487f-9034-22e884158969,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -17bcf2ea-d921-4715-91c5-6b15226b33d3,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -18ebf5ea-b0a2-4333-9e9c-40217de809ff,Postnatal visit,http://snomed.info/sct|169762003||'Postnatal visit' -19502b5a-2031-487d-9d9c-2001f959408b,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -1db1c71b-9eeb-4a98-abc1-eb699b38510c,Prenatal initial visit,http://snomed.info/sct|424441002||'Prenatal initial visit' -1f3443ee-d15e-4894-b18e-185cfadfcdea,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -1fd714a6-48b4-425a-99b3-ba5c1a995cce,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -202131ae-78bb-4104-89b0-fb90645760f6,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -20fbcb6e-d793-4b05-8e29-8ff82572b0db,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -21995497-0eb8-4c0b-8c23-e6a829f3d596,Follow-up encounter,http://snomed.info/sct|390906007||'Follow-up encounter' -21c6e0fc-bf47-4f80-b1ff-85b940445bdf,Consultation for treatment,http://snomed.info/sct|698314001||'Consultation for treatment' -224e538d-3622-4f5e-b772-211ed358d8de,Patient encounter procedure,http://snomed.info/sct|308335008||'Patient encounter procedure' -23780032-f3bb-4b40-af2e-3fa6b1677376,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -24c4dd5b-748b-4165-a0cc-2867b76c2dc2,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -2514cdf0-1216-4c5e-bfa8-0c11c20c6b93,Patient encounter procedure,http://snomed.info/sct|308335008||'Patient encounter procedure' -274a2e49-9170-4945-bca2-ab6ef4bded75,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -2830e99a-e6b0-411b-a051-7ea1e9f815d1,Asthma follow-up,http://snomed.info/sct|394701000||'Asthma follow-up' -29d4e919-2bd2-44e6-bb8a-380a780f60ff,Asthma follow-up,http://snomed.info/sct|394701000||'Asthma follow-up' -2aff9edd-def2-487a-b435-a162e11a303c,Encounter for check up (procedure),http://snomed.info/sct|185349003||'Encounter for check up (procedure)' -2bd766e5-60e4-4469-bb97-54f0503a1eb0,Prenatal visit,http://snomed.info/sct|424619006||'Prenatal visit' -2bddd98c-86e3-4ae0-9dc6-87da16ab6267,Emergency hospital admission for asthma,http://snomed.info/sct|183478001||'Emergency hospital admission for asthma' -2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5,Urgent care clinic (procedure),http://snomed.info/sct|702927004||'Urgent care clinic (procedure)' -2d2e07ec-e697-4384-a679-867e93740921,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -2de1f829-c9d2-4f22-bf39-536dcb82fc3e,Consultation for treatment,http://snomed.info/sct|698314001||'Consultation for treatment' -2e8eb256-84c9-499a-8bf6-bb39504373e1,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -2ee2ab26-09fb-4e12-bf21-9fed3b4c38de,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -2fc75f11-2097-4e1e-8390-dbff3e844b7a,Emergency room admission (procedure),http://snomed.info/sct|50849002||'Emergency room admission (procedure)' -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,Consultation for treatment,http://snomed.info/sct|698314001||'Consultation for treatment' -308eba53-29fa-489a-97dc-741b077841a7,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -30ae4a13-0cc6-4d12-a5dd-fa9288efcc09,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -31d4c712-75b1-4ecb-9d1a-ccc4b8453826,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -3397a796-894d-409c-97de-a9e6f3f88198,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -359cb842-c25b-429f-ba9b-d52f171e5631,Consultation for treatment,http://snomed.info/sct|698314001||'Consultation for treatment' -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -374fd699-6b74-4500-9d60-2473c7e0364f,Encounter for 'check-up',http://snomed.info/sct|185349003||'Encounter for \'check-up\'' -395019b6-84e8-4919-8b8b-ac64e4a6eade,Obstetric emergency hospital admission,http://snomed.info/sct|183460006||'Obstetric emergency hospital admission' -39efc9b8-a75f-43a3-98a2-5bdc1c8207d2,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -3ddf46fe-b5be-456d-810f-ea6a7402236d,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -4148ac36-1dcb-4e96-89cd-355e7e8f919b,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -41d26b1c-57b9-408c-b955-bf0ee1db4809,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -4215b5d0-bdd9-4222-a04a-81bb637d60af,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -42a9a333-d09d-4b9e-af96-1f02af26bac3,Encounter for check up (procedure),http://snomed.info/sct|185349003||'Encounter for check up (procedure)' -42aa48c7-68cc-4bee-9730-cff29f0e36c5,Prenatal visit,http://snomed.info/sct|424619006||'Prenatal visit' -4506aa76-9d0d-40e4-85bc-5735f74522a0,Encounter for 'check-up',http://snomed.info/sct|185349003||'Encounter for \'check-up\'' -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,Encounter for 'check-up',http://snomed.info/sct|185349003||'Encounter for \'check-up\'' -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -48cc2275-a478-4ade-b076-cf38e1d16017,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -498f4b18-bd4c-470d-9cde-2fca70ec8964,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -4a464bb5-9373-4f1b-9c03-053c64797114,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -4adcaf72-5241-4877-b61c-f34060576c50,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,Encounter for check up (procedure),http://snomed.info/sct|185349003||'Encounter for check up (procedure)' -4cd95073-6db3-4eb2-ac4b-0aacb182b352,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -4cfe72fe-21a0-4201-87b6-ef93695d2495,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -4db144d3-a596-4718-a50a-8ac9175ad386,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,Prenatal visit,http://snomed.info/sct|424619006||'Prenatal visit' -4f14ca7f-5332-4263-a7b2-f0a838380309,Encounter for check up (procedure),http://snomed.info/sct|185349003||'Encounter for check up (procedure)' -4f342a71-dec3-403e-970b-e4c21b9eb98b,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -4f3d1e93-a28f-446e-91af-2baf0168b82b,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -50eac25a-3761-4de3-8a69-aae52e658300,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -51241857-a7a4-4517-96e3-21f797581f89,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -522e2abe-ad96-4d1a-b5a5-748faa997531,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -54c750aa-570a-4e8e-9a02-418781923e39,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -55f17f44-287f-41aa-a1bc-d1c0104af36e,Urgent care clinic (procedure),http://snomed.info/sct|702927004||'Urgent care clinic (procedure)' -56999896-e36b-4e63-a6b6-dbb85dfe1936,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -578cc35c-0982-4d72-a729-b304c026f075,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -5abe533b-ae5f-47ad-8746-f60caf7483c0,Consultation for treatment,http://snomed.info/sct|698314001||'Consultation for treatment' -5cedf18a-fc44-4c39-a80f-2106a0d76934,Asthma follow-up,http://snomed.info/sct|394701000||'Asthma follow-up' -5e1fe0f2-4517-462b-8287-7824f9a60f4f,Urgent care clinic (procedure),http://snomed.info/sct|702927004||'Urgent care clinic (procedure)' -5f64131b-d410-449a-9de6-35415919fec5,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -62059efc-7607-41c9-a3ba-70948f6a8a1d,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -634f0889-3a83-484a-bcc8-86f48e333c7b,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -67e5cf95-236b-4f75-abc5-034ca2966448,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -67fc12fc-bb9f-40f1-9051-127a788b3081,Prenatal initial visit,http://snomed.info/sct|424441002||'Prenatal initial visit' -683714ad-5194-49e7-9b8f-4e306cf68ae1,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -6954c5c2-dd77-490a-9152-f1d78eff913d,Urgent care clinic (procedure),http://snomed.info/sct|702927004||'Urgent care clinic (procedure)' -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,Myocardial Infarction,http://snomed.info/sct|22298006||'Myocardial Infarction' -6af73cae-2e4e-485f-a74d-6f4307eb3af3,Asthma follow-up,http://snomed.info/sct|394701000||'Asthma follow-up' -6c84348c-5065-4e89-9412-bfda023683f2,Consultation for treatment,http://snomed.info/sct|698314001||'Consultation for treatment' -6d47a2fe-3645-4c5c-bebe-1b822eff197f,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -6e187f47-91a1-4217-a185-31b6f01db225,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,Encounter for check up (procedure),http://snomed.info/sct|185349003||'Encounter for check up (procedure)' -721e4027-a080-40d8-bc4a-43cd33477611,Encounter for check up (procedure),http://snomed.info/sct|185349003||'Encounter for check up (procedure)' -72788e7f-1bf1-40b5-a1f1-27c669dc7278,Consultation for treatment,http://snomed.info/sct|698314001||'Consultation for treatment' -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -749babeb-6883-4bd4-92a5-bec52769071c,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -75a09d4f-eef2-4404-a386-dfcb408ec9ed,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -777aa5f2-2a09-4aed-92ea-912694e28b48,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -79be3b69-23d8-4408-8f01-68d993e63b6c,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -7ac56d5a-32a7-4b40-a956-356e3006faed,Encounter for check up (procedure),http://snomed.info/sct|185349003||'Encounter for check up (procedure)' -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,Encounter for 'check-up',http://snomed.info/sct|185349003||'Encounter for \'check-up\'' -7e7322a6-7912-4101-b3be-d134245a0626,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -7ea43fc6-b1f7-46be-a308-5ecb275a1081,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -7f410064-638a-4477-85c4-97fc2bb14a49,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -8089a9ac-9e71-4487-a231-f720e4bc8d3e,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -836b2e59-fb97-4014-ab0c-d5a5dc750969,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -85481b3f-c75e-40cd-bacd-b0afb79e893c,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -85c997bf-63d9-4ebb-8e0b-320de3dddc6c,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,Patient encounter procedure,http://snomed.info/sct|308335008||'Patient encounter procedure' -86415df5-7e47-4637-9e09-aaad9e7628d1,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -8743e69b-17aa-44ff-b8fa-4f582665efc6,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -87b04a97-1d33-4548-aec9-3d2856070702,Emergency room admission (procedure),http://snomed.info/sct|50849002||'Emergency room admission (procedure)' -88b16960-bfff-41d0-81e4-9a4630834a00,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -89f260bb-68b4-4dec-91f4-d52e673e0ff7,Prenatal visit,http://snomed.info/sct|424619006||'Prenatal visit' -8a1908f7-47cc-4f82-859c-781a193e9901,Encounter for 'check-up',http://snomed.info/sct|185349003||'Encounter for \'check-up\'' -8bc7af32-e5ad-4849-ba4d-b446db833ab4,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -8be81657-8ba6-4ec5-8ff9-4809367096ea,Encounter for 'check-up',http://snomed.info/sct|185349003||'Encounter for \'check-up\'' -8dc2ce26-aec7-43c0-9771-350af4257ad8,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -8f76d729-9f74-4cfd-be2a-8b033b568e18,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -900ce9fe-02d3-476b-902a-9467767ecdcf,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -922e2443-9cc5-421f-8310-9cd23f6e9f2d,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -96b2282d-1384-4cfb-9958-f009fb501626,Encounter for check up (procedure),http://snomed.info/sct|185349003||'Encounter for check up (procedure)' -97b42d67-8691-433d-8a31-dada076162ae,Consultation for treatment,http://snomed.info/sct|698314001||'Consultation for treatment' -984067fc-3dfc-47b7-8ee0-4d9b27d43860,Urgent care clinic (procedure),http://snomed.info/sct|702927004||'Urgent care clinic (procedure)' -990c21ab-433b-4920-873e-f9dfc7103d9d,Prenatal visit,http://snomed.info/sct|424619006||'Prenatal visit' -99fc3558-79f3-4d14-9aa1-ff63f621ecd9,Encounter for check up (procedure),http://snomed.info/sct|185349003||'Encounter for check up (procedure)' -9b2eb632-6f1a-4e97-912b-3c8378a1b11c,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -9bc516d4-7c82-4340-a90c-bb493f96fbe4,Emergency room admission (procedure),http://snomed.info/sct|50849002||'Emergency room admission (procedure)' -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,Encounter for check up (procedure),http://snomed.info/sct|185349003||'Encounter for check up (procedure)' -9ed8703e-3b40-424c-9e98-b3b404051f99,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -a1228f20-7c50-4d3a-b88c-2d277ca79d79,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -a221198d-000e-497b-8b70-bb4d37f7bbe1,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -a2c3ee1d-ec35-4b26-9bab-12de3f47d604,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -a6790cbd-8349-432e-a976-5dfc07451203,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -a6cb423a-7571-46df-83e2-ccc6820adb36,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -acb2329d-b1c3-45c3-ad28-91a09aa521e1,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -add6d754-a075-4693-a33a-c061c9a368ff,Encounter for check up (procedure),http://snomed.info/sct|185349003||'Encounter for check up (procedure)' -ae7ddaef-4911-418c-8401-d978617e145b,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -b00df815-7528-4967-bfe2-311130d91c21,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -b07fb98d-2da4-423a-83b4-7a673de93096,Consultation for treatment,http://snomed.info/sct|698314001||'Consultation for treatment' -b0d337c5-3555-4817-ba59-4ceea5ad8a91,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -b1ccd55a-6b88-4240-b20c-ca893f36e23b,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -b6900c21-d310-4fb4-8b8f-176a09c91989,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -b7772635-1801-48e4-a442-f4aaa6860544,Consultation for treatment,http://snomed.info/sct|698314001||'Consultation for treatment' -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -bac374c0-50e8-4a5c-947b-82a8e9a747a9,Patient encounter procedure,http://snomed.info/sct|308335008||'Patient encounter procedure' -baf48725-f0f9-4357-a71d-b1104910deae,Asthma follow-up,http://snomed.info/sct|394701000||'Asthma follow-up' -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -bf895c48-1d52-4780-8e9a-532d69356d28,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -bfc93cb7-e53a-49f7-b86e-81d6a13cfdea,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -c3ec1f2e-1411-41e4-8370-acd5a8f68cf4,Emergency room admission (procedure),http://snomed.info/sct|50849002||'Emergency room admission (procedure)' -c57d51a7-45de-41b9-92fc-efd0634effaf,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -c5c6eed5-aec6-4b8a-b689-adbb219c2267,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -c892b1d7-7485-407d-8883-54fb7fecebc1,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -c90aae7e-5704-4e13-8794-41ab377193bd,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -ca3e0486-fd6b-4a13-a741-3a47760b412d,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -ca7f269d-3794-4994-8c46-d8e9f2aa6378,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -cc56af6d-25c6-480e-9767-da02a55551da,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -cdb8493c-e3b0-447f-b16b-e58dd64660f3,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -cfcbe34a-edc5-4442-bc05-5927fa00336b,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -d05461d6-13bf-402c-890d-db6404933f7c,Patient encounter procedure,http://snomed.info/sct|308335008||'Patient encounter procedure' -d080c116-681a-494a-a928-45924109b49d,Emergency room admission (procedure),http://snomed.info/sct|50849002||'Emergency room admission (procedure)' -d535b213-2abc-438f-aa6a-c06476d60b09,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -d6988116-3c63-44f8-9f94-9e9d51cc5a37,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -d79728e1-8834-4f4a-8edc-ce18aca18be6,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -d7a48a26-7731-4046-bf22-78f0b8084eb9,Prenatal visit,http://snomed.info/sct|424619006||'Prenatal visit' -d90676d2-ce74-4867-a00f-6dbffa7c00be,Encounter for 'check-up',http://snomed.info/sct|185349003||'Encounter for \'check-up\'' -d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2,Emergency Encounter,http://snomed.info/sct|50849002||'Emergency Encounter' -db79f75d-af3c-4f82-b4e5-9b5d26598eef,Prenatal initial visit,http://snomed.info/sct|424441002||'Prenatal initial visit' -def5fd23-2b74-4c64-85e9-fac27e626bb7,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,Prenatal visit,http://snomed.info/sct|424619006||'Prenatal visit' -e0dfbc08-45ad-4a81-b648-7e65c066f673,Encounter for 'check-up',http://snomed.info/sct|185349003||'Encounter for \'check-up\'' -e1040d58-53bc-4467-8101-ca53b772cede,Consultation for treatment,http://snomed.info/sct|698314001||'Consultation for treatment' -e1c2ad9f-6f61-4f16-805a-2f405b63659a,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -e2387a81-5ff5-4daf-b2e8-881998d0d917,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -e26ae29a-213a-4f79-98c2-cc81cf2f451d,Emergency room admission (procedure),http://snomed.info/sct|50849002||'Emergency room admission (procedure)' -e363eb67-64c7-440f-93fd-5c8787c69a85,Prenatal initial visit,http://snomed.info/sct|424441002||'Prenatal initial visit' -e4358f28-62a8-4e06-b2bd-cb00d3310349,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,Encounter for check up (procedure),http://snomed.info/sct|185349003||'Encounter for check up (procedure)' -e8c41168-a093-40eb-8baa-6802d24f554a,Encounter for 'check-up',http://snomed.info/sct|185349003||'Encounter for \'check-up\'' -e8eba267-fecb-477e-9625-771fbcfc3cba,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -ede7745a-8f3e-4e20-9f16-33756be7ab6c,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -ee24bf89-81a3-4259-a382-86917f3829d4,Encounter for problem,http://snomed.info/sct|185347001||'Encounter for problem' -f1293c5e-c997-4ba9-8bfd-438e7a840dc8,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -f18b08bd-40da-43c1-87ec-4ba23542635f,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -f19eefca-c1ad-4860-bdda-4674a631d464,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -f28931e5-1f35-4f9f-a02e-78398735ea67,Patient encounter procedure,http://snomed.info/sct|308335008||'Patient encounter procedure' -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,Encounter for 'check-up',http://snomed.info/sct|185349003||'Encounter for \'check-up\'' -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,Encounter for symptom,http://snomed.info/sct|185345009||'Encounter for symptom' -f3aa491a-4dbd-4436-b674-18b2177dcf18,Non-urgent orthopedic admission,http://snomed.info/sct|183495009||'Non-urgent orthopedic admission' -f3c2f842-6aa3-42c9-a0f3-895c40456868,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -f3eb0c38-2571-44e7-be39-732eb52cf1df,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,Consultation for treatment,http://snomed.info/sct|698314001||'Consultation for treatment' -f77a8561-5adc-452a-ac9a-76273b6a6678,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -f883f2ca-d523-4c9f-86b2-0add137901de,Encounter for check up (procedure),http://snomed.info/sct|185349003||'Encounter for check up (procedure)' -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,Prenatal visit,http://snomed.info/sct|424619006||'Prenatal visit' -fc771883-3bd6-46d9-9626-a130e1b902de,General examination of patient (procedure),http://snomed.info/sct|162673000||'General examination of patient (procedure)' -fcab9efe-fb05-42d6-b366-ce6db1cc4093,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,Encounter for 'check-up',http://snomed.info/sct|185349003||'Encounter for \'check-up\'' -fce20464-ff98-4051-8714-6b9584e52740,Well child visit (procedure),http://snomed.info/sct|410620009||'Well child visit (procedure)' diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleNonSingularColumnsWithDifferentTypes.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleNonSingularColumnsWithDifferentTypes.tsv new file mode 100644 index 0000000000..5ac6a8e452 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleNonSingularColumnsWithDifferentTypes.tsv @@ -0,0 +1,217 @@ +0134901a-4a69-4b39-92ea-d6f48ec83c6e General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +01ac1476-0c9c-4cd7-82a6-4ff526018e9a Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +0250a217-a663-403b-a708-5d14eadf0c40 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +02b993c0-b358-481c-b0d0-0767387feb9f Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +0364073f-91d8-47a1-b8b0-107c6318a691 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +04945715-8ad5-4d8f-bfda-ae26662a3610 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 Consultation for treatment http://snomed.info/sct|698314001||'Consultation for treatment' +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +0af4a77e-892e-4b3f-9110-4c5224782250 Follow-up encounter http://snomed.info/sct|390906007||'Follow-up encounter' +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +102e16c5-4920-4dad-b142-0b280b2aacad Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +11dafd5c-85e7-4275-a147-89a63641e35e Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +12b212d8-bc6e-415a-93b0-594381726668 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +12df1bed-5472-4c48-8e88-2cbb3e5eab33 Encounter for 'check-up' http://snomed.info/sct|185349003||'Encounter for \'check-up\'' +151e3048-66ad-4411-8753-677877e3bf0a Prenatal visit http://snomed.info/sct|424619006||'Prenatal visit' +16a3408d-c927-4d02-a1ae-cad32419caab Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +16d280a2-c61f-487f-9034-22e884158969 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +17bcf2ea-d921-4715-91c5-6b15226b33d3 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +18ebf5ea-b0a2-4333-9e9c-40217de809ff Postnatal visit http://snomed.info/sct|169762003||'Postnatal visit' +19502b5a-2031-487d-9d9c-2001f959408b General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +1cb88e7a-3db6-4a88-9b68-b0e1492114bc Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +1db1c71b-9eeb-4a98-abc1-eb699b38510c Prenatal initial visit http://snomed.info/sct|424441002||'Prenatal initial visit' +1f3443ee-d15e-4894-b18e-185cfadfcdea General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +1fd714a6-48b4-425a-99b3-ba5c1a995cce General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +202131ae-78bb-4104-89b0-fb90645760f6 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +20fbcb6e-d793-4b05-8e29-8ff82572b0db General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +21995497-0eb8-4c0b-8c23-e6a829f3d596 Follow-up encounter http://snomed.info/sct|390906007||'Follow-up encounter' +21c6e0fc-bf47-4f80-b1ff-85b940445bdf Consultation for treatment http://snomed.info/sct|698314001||'Consultation for treatment' +224e538d-3622-4f5e-b772-211ed358d8de Patient encounter procedure http://snomed.info/sct|308335008||'Patient encounter procedure' +23780032-f3bb-4b40-af2e-3fa6b1677376 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +24c4dd5b-748b-4165-a0cc-2867b76c2dc2 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 Patient encounter procedure http://snomed.info/sct|308335008||'Patient encounter procedure' +274a2e49-9170-4945-bca2-ab6ef4bded75 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +2830e99a-e6b0-411b-a051-7ea1e9f815d1 Asthma follow-up http://snomed.info/sct|394701000||'Asthma follow-up' +29d4e919-2bd2-44e6-bb8a-380a780f60ff Asthma follow-up http://snomed.info/sct|394701000||'Asthma follow-up' +2aff9edd-def2-487a-b435-a162e11a303c Encounter for check up (procedure) http://snomed.info/sct|185349003||'Encounter for check up (procedure)' +2bd766e5-60e4-4469-bb97-54f0503a1eb0 Prenatal visit http://snomed.info/sct|424619006||'Prenatal visit' +2bddd98c-86e3-4ae0-9dc6-87da16ab6267 Emergency hospital admission for asthma http://snomed.info/sct|183478001||'Emergency hospital admission for asthma' +2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5 Urgent care clinic (procedure) http://snomed.info/sct|702927004||'Urgent care clinic (procedure)' +2d2e07ec-e697-4384-a679-867e93740921 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +2de1f829-c9d2-4f22-bf39-536dcb82fc3e Consultation for treatment http://snomed.info/sct|698314001||'Consultation for treatment' +2e8eb256-84c9-499a-8bf6-bb39504373e1 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +2ee2ab26-09fb-4e12-bf21-9fed3b4c38de General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +2fc75f11-2097-4e1e-8390-dbff3e844b7a Emergency room admission (procedure) http://snomed.info/sct|50849002||'Emergency room admission (procedure)' +2ffd6142-b75b-406f-bc06-cdb1c02eaefc Consultation for treatment http://snomed.info/sct|698314001||'Consultation for treatment' +308eba53-29fa-489a-97dc-741b077841a7 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +31d4c712-75b1-4ecb-9d1a-ccc4b8453826 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +3397a796-894d-409c-97de-a9e6f3f88198 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +359cb842-c25b-429f-ba9b-d52f171e5631 Consultation for treatment http://snomed.info/sct|698314001||'Consultation for treatment' +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +374fd699-6b74-4500-9d60-2473c7e0364f Encounter for 'check-up' http://snomed.info/sct|185349003||'Encounter for \'check-up\'' +395019b6-84e8-4919-8b8b-ac64e4a6eade Obstetric emergency hospital admission http://snomed.info/sct|183460006||'Obstetric emergency hospital admission' +39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +3ddf46fe-b5be-456d-810f-ea6a7402236d General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +4148ac36-1dcb-4e96-89cd-355e7e8f919b General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +41d26b1c-57b9-408c-b955-bf0ee1db4809 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +4215b5d0-bdd9-4222-a04a-81bb637d60af Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +42a9a333-d09d-4b9e-af96-1f02af26bac3 Encounter for check up (procedure) http://snomed.info/sct|185349003||'Encounter for check up (procedure)' +42aa48c7-68cc-4bee-9730-cff29f0e36c5 Prenatal visit http://snomed.info/sct|424619006||'Prenatal visit' +4506aa76-9d0d-40e4-85bc-5735f74522a0 Encounter for 'check-up' http://snomed.info/sct|185349003||'Encounter for \'check-up\'' +45b25ced-6eed-4fca-8ec1-b7d32ea13efe Encounter for 'check-up' http://snomed.info/sct|185349003||'Encounter for \'check-up\'' +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +48cc2275-a478-4ade-b076-cf38e1d16017 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +498f4b18-bd4c-470d-9cde-2fca70ec8964 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +4a464bb5-9373-4f1b-9c03-053c64797114 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +4adcaf72-5241-4877-b61c-f34060576c50 Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 Encounter for check up (procedure) http://snomed.info/sct|185349003||'Encounter for check up (procedure)' +4cd95073-6db3-4eb2-ac4b-0aacb182b352 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +4cfe72fe-21a0-4201-87b6-ef93695d2495 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +4db144d3-a596-4718-a50a-8ac9175ad386 Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 Prenatal visit http://snomed.info/sct|424619006||'Prenatal visit' +4f14ca7f-5332-4263-a7b2-f0a838380309 Encounter for check up (procedure) http://snomed.info/sct|185349003||'Encounter for check up (procedure)' +4f342a71-dec3-403e-970b-e4c21b9eb98b Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +4f3d1e93-a28f-446e-91af-2baf0168b82b Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +50eac25a-3761-4de3-8a69-aae52e658300 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +51241857-a7a4-4517-96e3-21f797581f89 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +522e2abe-ad96-4d1a-b5a5-748faa997531 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +54c750aa-570a-4e8e-9a02-418781923e39 Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +55f17f44-287f-41aa-a1bc-d1c0104af36e Urgent care clinic (procedure) http://snomed.info/sct|702927004||'Urgent care clinic (procedure)' +56999896-e36b-4e63-a6b6-dbb85dfe1936 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +578cc35c-0982-4d72-a729-b304c026f075 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +5abe533b-ae5f-47ad-8746-f60caf7483c0 Consultation for treatment http://snomed.info/sct|698314001||'Consultation for treatment' +5cedf18a-fc44-4c39-a80f-2106a0d76934 Asthma follow-up http://snomed.info/sct|394701000||'Asthma follow-up' +5e1fe0f2-4517-462b-8287-7824f9a60f4f Urgent care clinic (procedure) http://snomed.info/sct|702927004||'Urgent care clinic (procedure)' +5f64131b-d410-449a-9de6-35415919fec5 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +62059efc-7607-41c9-a3ba-70948f6a8a1d General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +634f0889-3a83-484a-bcc8-86f48e333c7b Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +67e5cf95-236b-4f75-abc5-034ca2966448 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +67fc12fc-bb9f-40f1-9051-127a788b3081 Prenatal initial visit http://snomed.info/sct|424441002||'Prenatal initial visit' +683714ad-5194-49e7-9b8f-4e306cf68ae1 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +6954c5c2-dd77-490a-9152-f1d78eff913d Urgent care clinic (procedure) http://snomed.info/sct|702927004||'Urgent care clinic (procedure)' +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 Myocardial Infarction http://snomed.info/sct|22298006||'Myocardial Infarction' +6af73cae-2e4e-485f-a74d-6f4307eb3af3 Asthma follow-up http://snomed.info/sct|394701000||'Asthma follow-up' +6c84348c-5065-4e89-9412-bfda023683f2 Consultation for treatment http://snomed.info/sct|698314001||'Consultation for treatment' +6d47a2fe-3645-4c5c-bebe-1b822eff197f Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +6e187f47-91a1-4217-a185-31b6f01db225 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 Encounter for check up (procedure) http://snomed.info/sct|185349003||'Encounter for check up (procedure)' +721e4027-a080-40d8-bc4a-43cd33477611 Encounter for check up (procedure) http://snomed.info/sct|185349003||'Encounter for check up (procedure)' +72788e7f-1bf1-40b5-a1f1-27c669dc7278 Consultation for treatment http://snomed.info/sct|698314001||'Consultation for treatment' +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +749babeb-6883-4bd4-92a5-bec52769071c General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +75a09d4f-eef2-4404-a386-dfcb408ec9ed General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +777aa5f2-2a09-4aed-92ea-912694e28b48 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +79be3b69-23d8-4408-8f01-68d993e63b6c General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +7ac56d5a-32a7-4b40-a956-356e3006faed Encounter for check up (procedure) http://snomed.info/sct|185349003||'Encounter for check up (procedure)' +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 Encounter for 'check-up' http://snomed.info/sct|185349003||'Encounter for \'check-up\'' +7e7322a6-7912-4101-b3be-d134245a0626 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +7ea43fc6-b1f7-46be-a308-5ecb275a1081 Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +7f410064-638a-4477-85c4-97fc2bb14a49 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +8089a9ac-9e71-4487-a231-f720e4bc8d3e Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +836b2e59-fb97-4014-ab0c-d5a5dc750969 Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +85481b3f-c75e-40cd-bacd-b0afb79e893c General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +85c997bf-63d9-4ebb-8e0b-320de3dddc6c General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b Patient encounter procedure http://snomed.info/sct|308335008||'Patient encounter procedure' +86415df5-7e47-4637-9e09-aaad9e7628d1 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +8743e69b-17aa-44ff-b8fa-4f582665efc6 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +87b04a97-1d33-4548-aec9-3d2856070702 Emergency room admission (procedure) http://snomed.info/sct|50849002||'Emergency room admission (procedure)' +88b16960-bfff-41d0-81e4-9a4630834a00 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +89f260bb-68b4-4dec-91f4-d52e673e0ff7 Prenatal visit http://snomed.info/sct|424619006||'Prenatal visit' +8a1908f7-47cc-4f82-859c-781a193e9901 Encounter for 'check-up' http://snomed.info/sct|185349003||'Encounter for \'check-up\'' +8bc7af32-e5ad-4849-ba4d-b446db833ab4 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +8be81657-8ba6-4ec5-8ff9-4809367096ea Encounter for 'check-up' http://snomed.info/sct|185349003||'Encounter for \'check-up\'' +8dc2ce26-aec7-43c0-9771-350af4257ad8 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +8f76d729-9f74-4cfd-be2a-8b033b568e18 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +900ce9fe-02d3-476b-902a-9467767ecdcf General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +922e2443-9cc5-421f-8310-9cd23f6e9f2d Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +96b2282d-1384-4cfb-9958-f009fb501626 Encounter for check up (procedure) http://snomed.info/sct|185349003||'Encounter for check up (procedure)' +97b42d67-8691-433d-8a31-dada076162ae Consultation for treatment http://snomed.info/sct|698314001||'Consultation for treatment' +984067fc-3dfc-47b7-8ee0-4d9b27d43860 Urgent care clinic (procedure) http://snomed.info/sct|702927004||'Urgent care clinic (procedure)' +990c21ab-433b-4920-873e-f9dfc7103d9d Prenatal visit http://snomed.info/sct|424619006||'Prenatal visit' +99fc3558-79f3-4d14-9aa1-ff63f621ecd9 Encounter for check up (procedure) http://snomed.info/sct|185349003||'Encounter for check up (procedure)' +9b2eb632-6f1a-4e97-912b-3c8378a1b11c General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +9bc516d4-7c82-4340-a90c-bb493f96fbe4 Emergency room admission (procedure) http://snomed.info/sct|50849002||'Emergency room admission (procedure)' +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 Encounter for check up (procedure) http://snomed.info/sct|185349003||'Encounter for check up (procedure)' +9ed8703e-3b40-424c-9e98-b3b404051f99 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +a1228f20-7c50-4d3a-b88c-2d277ca79d79 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +a221198d-000e-497b-8b70-bb4d37f7bbe1 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +a2c3ee1d-ec35-4b26-9bab-12de3f47d604 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +a6790cbd-8349-432e-a976-5dfc07451203 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +a6b084fb-ada7-480a-9adc-f180d4eb1e2a Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +a6cb423a-7571-46df-83e2-ccc6820adb36 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +acb2329d-b1c3-45c3-ad28-91a09aa521e1 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +add6d754-a075-4693-a33a-c061c9a368ff Encounter for check up (procedure) http://snomed.info/sct|185349003||'Encounter for check up (procedure)' +ae7ddaef-4911-418c-8401-d978617e145b Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +b00df815-7528-4967-bfe2-311130d91c21 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +b07fb98d-2da4-423a-83b4-7a673de93096 Consultation for treatment http://snomed.info/sct|698314001||'Consultation for treatment' +b0d337c5-3555-4817-ba59-4ceea5ad8a91 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +b1ccd55a-6b88-4240-b20c-ca893f36e23b General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +b1d4dc41-07ae-44db-ae17-1df86c5a62cf General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +b6900c21-d310-4fb4-8b8f-176a09c91989 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +b7772635-1801-48e4-a442-f4aaa6860544 Consultation for treatment http://snomed.info/sct|698314001||'Consultation for treatment' +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +bac374c0-50e8-4a5c-947b-82a8e9a747a9 Patient encounter procedure http://snomed.info/sct|308335008||'Patient encounter procedure' +baf48725-f0f9-4357-a71d-b1104910deae Asthma follow-up http://snomed.info/sct|394701000||'Asthma follow-up' +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +bf895c48-1d52-4780-8e9a-532d69356d28 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +bfc93cb7-e53a-49f7-b86e-81d6a13cfdea General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +c3ec1f2e-1411-41e4-8370-acd5a8f68cf4 Emergency room admission (procedure) http://snomed.info/sct|50849002||'Emergency room admission (procedure)' +c57d51a7-45de-41b9-92fc-efd0634effaf Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +c5c6eed5-aec6-4b8a-b689-adbb219c2267 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +c892b1d7-7485-407d-8883-54fb7fecebc1 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +c90aae7e-5704-4e13-8794-41ab377193bd General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +ca3e0486-fd6b-4a13-a741-3a47760b412d Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +ca7f269d-3794-4994-8c46-d8e9f2aa6378 Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +cc56af6d-25c6-480e-9767-da02a55551da Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +cdb8493c-e3b0-447f-b16b-e58dd64660f3 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +cf67a8d4-48e2-4dc9-a521-6aabcff0330b General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +cfcbe34a-edc5-4442-bc05-5927fa00336b General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +d05461d6-13bf-402c-890d-db6404933f7c Patient encounter procedure http://snomed.info/sct|308335008||'Patient encounter procedure' +d080c116-681a-494a-a928-45924109b49d Emergency room admission (procedure) http://snomed.info/sct|50849002||'Emergency room admission (procedure)' +d535b213-2abc-438f-aa6a-c06476d60b09 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +d6988116-3c63-44f8-9f94-9e9d51cc5a37 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +d79728e1-8834-4f4a-8edc-ce18aca18be6 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +d7a48a26-7731-4046-bf22-78f0b8084eb9 Prenatal visit http://snomed.info/sct|424619006||'Prenatal visit' +d90676d2-ce74-4867-a00f-6dbffa7c00be Encounter for 'check-up' http://snomed.info/sct|185349003||'Encounter for \'check-up\'' +d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2 Emergency Encounter http://snomed.info/sct|50849002||'Emergency Encounter' +db79f75d-af3c-4f82-b4e5-9b5d26598eef Prenatal initial visit http://snomed.info/sct|424441002||'Prenatal initial visit' +def5fd23-2b74-4c64-85e9-fac27e626bb7 Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 Prenatal visit http://snomed.info/sct|424619006||'Prenatal visit' +e0dfbc08-45ad-4a81-b648-7e65c066f673 Encounter for 'check-up' http://snomed.info/sct|185349003||'Encounter for \'check-up\'' +e1040d58-53bc-4467-8101-ca53b772cede Consultation for treatment http://snomed.info/sct|698314001||'Consultation for treatment' +e1c2ad9f-6f61-4f16-805a-2f405b63659a General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +e2387a81-5ff5-4daf-b2e8-881998d0d917 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +e26ae29a-213a-4f79-98c2-cc81cf2f451d Emergency room admission (procedure) http://snomed.info/sct|50849002||'Emergency room admission (procedure)' +e363eb67-64c7-440f-93fd-5c8787c69a85 Prenatal initial visit http://snomed.info/sct|424441002||'Prenatal initial visit' +e4358f28-62a8-4e06-b2bd-cb00d3310349 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +e7f68d35-ae55-4fa4-b0be-0672a5a7186a Encounter for check up (procedure) http://snomed.info/sct|185349003||'Encounter for check up (procedure)' +e8c41168-a093-40eb-8baa-6802d24f554a Encounter for 'check-up' http://snomed.info/sct|185349003||'Encounter for \'check-up\'' +e8eba267-fecb-477e-9625-771fbcfc3cba Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +ede7745a-8f3e-4e20-9f16-33756be7ab6c Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +ee24bf89-81a3-4259-a382-86917f3829d4 Encounter for problem http://snomed.info/sct|185347001||'Encounter for problem' +f1293c5e-c997-4ba9-8bfd-438e7a840dc8 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +f18b08bd-40da-43c1-87ec-4ba23542635f General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +f19eefca-c1ad-4860-bdda-4674a631d464 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +f28931e5-1f35-4f9f-a02e-78398735ea67 Patient encounter procedure http://snomed.info/sct|308335008||'Patient encounter procedure' +f29330d0-5b32-4f75-b558-a1c7f05c0b4a Encounter for 'check-up' http://snomed.info/sct|185349003||'Encounter for \'check-up\'' +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c Encounter for symptom http://snomed.info/sct|185345009||'Encounter for symptom' +f3aa491a-4dbd-4436-b674-18b2177dcf18 Non-urgent orthopedic admission http://snomed.info/sct|183495009||'Non-urgent orthopedic admission' +f3c2f842-6aa3-42c9-a0f3-895c40456868 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +f3eb0c38-2571-44e7-be39-732eb52cf1df Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 Consultation for treatment http://snomed.info/sct|698314001||'Consultation for treatment' +f77a8561-5adc-452a-ac9a-76273b6a6678 General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +f883f2ca-d523-4c9f-86b2-0add137901de Encounter for check up (procedure) http://snomed.info/sct|185349003||'Encounter for check up (procedure)' +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c Prenatal visit http://snomed.info/sct|424619006||'Prenatal visit' +fc771883-3bd6-46d9-9626-a130e1b902de General examination of patient (procedure) http://snomed.info/sct|162673000||'General examination of patient (procedure)' +fcab9efe-fb05-42d6-b366-ce6db1cc4093 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 Encounter for 'check-up' http://snomed.info/sct|185349003||'Encounter for \'check-up\'' +fce20464-ff98-4051-8714-6b9584e52740 Well child visit (procedure) http://snomed.info/sct|410620009||'Well child visit (procedure)' diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.csv deleted file mode 100644 index 9b6b176ac9..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.csv +++ /dev/null @@ -1,150 +0,0 @@ -020acef9-0421-46b1-9289-92cdc59c30ca,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -08f08f2d-4d77-44f5-ab06-a38545fc050e,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -0a9a0b5a-5be9-4205-984e-94ceef8d6ba6,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -0d8d0441-a4ad-4f15-bc61-5d9694ed3d48,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -0f1004c8-e9e9-477d-8c43-1eddc445664d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -0f1004c8-e9e9-477d-8c43-1eddc445664d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 -0f1004c8-e9e9-477d-8c43-1eddc445664d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard -10321ec0-3613-4741-a9c3-47bafbc3fae4,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -10321ec0-3613-4741-a9c3-47bafbc3fae4,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 -10321ec0-3613-4741-a9c3-47bafbc3fae4,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard -113e8a00-6fd2-489c-867b-6eedf314c774,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -113e8a00-6fd2-489c-867b-6eedf314c774,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -13a2e7a0-1cf8-475f-8065-6d03b6538c9a,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -13c95f69-822b-44c6-ada3-d73aa1466290,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -16aa313e-aa66-408b-a819-33fb6d169ce0,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 -19113926-d487-42fe-82f6-5c9a75ee4d26,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -1ec37957-06f7-4de5-aa2c-1dc9d02e1ddd,beff242e-580b-47c0-9844-c1a68c36c5bf,male,Guy979,Towne435 -22d20864-b7f9-44f6-88e3-6c3db215eb9a,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -2540bf5e-5bc9-48be-8e66-37a2358ae75d,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -2c1dce4c-aefa-4ef8-8970-52aab627de48,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -2c67dbd4-0609-43d3-be6b-e88220c77b75,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -2d19750d-c270-4be9-a84a-ddcdbb64085a,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -2d19750d-c270-4be9-a84a-ddcdbb64085a,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -2d4189ac-8eb3-4f4a-9bb1-0f754d715aee,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -2d9c0f33-155b-42ad-8158-1291e14c6da7,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -2e2cebf2-8c0e-4a89-8139-c2b233343dd7,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -304a8806-48e6-4d35-bb17-121f1190b475,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -304a8806-48e6-4d35-bb17-121f1190b475,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 -304a8806-48e6-4d35-bb17-121f1190b475,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard -31b93f2e-caa4-4a7f-82ca-5b59f1fc2fdd,2b36c1e2-bbe1-45ae-8124-4adad2677702,male,Pedro316,Ulibarri312 -335f8324-ce20-4ebc-a00c-ae085c4516b7,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -35a7f624-22be-48b3-b82c-45cf2667c527,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -35a7f624-22be-48b3-b82c-45cf2667c527,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -365c1b4a-c9c5-4d28-8578-7460dec01ad1,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -37af6887-b8ba-480d-bb1c-4409711b8e2f,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -3e56e1ac-af2e-4711-8050-e3d7cee66b91,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -3f505329-402b-4c36-a625-637425462d96,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -432d6af2-0c59-420e-9434-9b416fb36a85,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -44d81658-d1f8-4eb7-9a1f-2fdca5c4fa67,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -45407705-2b60-4788-981b-8090026f3b0b,beff242e-580b-47c0-9844-c1a68c36c5bf,male,Guy979,Towne435 -460deb7a-74bc-43e2-bc60-fc6f97159b59,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -4d79c3df-899d-42a4-b17d-e93770921d6b,2b36c1e2-bbe1-45ae-8124-4adad2677702,male,Pedro316,Ulibarri312 -53238300-6e57-4fc7-99d4-f1c4bcc3366a,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 -537f22b5-8139-4bcc-b048-5342139154fd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -537f22b5-8139-4bcc-b048-5342139154fd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -53bf45a8-d83f-429c-9b91-26b086a405ea,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -53bf45a8-d83f-429c-9b91-26b086a405ea,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -5514df98-79c6-4818-b444-9417c93a64f4,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -5be1aab8-ecae-4c29-8f55-fe37535fd2c6,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -5be1aab8-ecae-4c29-8f55-fe37535fd2c6,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -5e7b39f8-773d-4ffb-83e1-735923586881,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -5e7b39f8-773d-4ffb-83e1-735923586881,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 -5e7b39f8-773d-4ffb-83e1-735923586881,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard -5eaf7eae-ffb9-44ec-8d46-fa2b87bedefc,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -6017f9b9-36a1-4cc2-a0ae-af31dc92b089,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -6017f9b9-36a1-4cc2-a0ae-af31dc92b089,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -61bb4baa-6233-4444-8dd2-9c4806338faf,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -69ea0b24-7d91-4498-991f-49f4b07914c0,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -69ea0b24-7d91-4498-991f-49f4b07914c0,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 -69ea0b24-7d91-4498-991f-49f4b07914c0,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard -6b9ffc9c-80b0-40c2-8cf7-f4d51b5448ab,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -6b9ffc9c-80b0-40c2-8cf7-f4d51b5448ab,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -73f52a06-5ac5-4324-a046-7a4caffb6c48,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -764a0e55-a10a-4d19-aa18-d414cb911a8d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -764a0e55-a10a-4d19-aa18-d414cb911a8d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -76b9935f-b44e-42b6-a991-e2e724b42334,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -76b9935f-b44e-42b6-a991-e2e724b42334,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 -76b9935f-b44e-42b6-a991-e2e724b42334,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard -773d711a-5f70-4ac1-98c5-41b7c94e8a16,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -791b93c5-dfab-4a85-a810-5b01b3531c09,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 -7e490679-3095-4431-95be-d3d9eb3b40f8,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -7ed4db56-6f72-4dd1-b306-85d80f3a3a87,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -7ed4db56-6f72-4dd1-b306-85d80f3a3a87,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 -7ed4db56-6f72-4dd1-b306-85d80f3a3a87,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard -807ac3db-8499-4876-8508-7839776afc9a,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -807ac3db-8499-4876-8508-7839776afc9a,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 -807ac3db-8499-4876-8508-7839776afc9a,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard -80b37d35-ee1e-49ff-82d3-6e10b0798fd6,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -81297f01-422b-4aba-98df-3609ea25e480,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -81297f01-422b-4aba-98df-3609ea25e480,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -84a1a6b4-583f-4525-b048-d676da5d7533,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -84a1a6b4-583f-4525-b048-d676da5d7533,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -88fd1ebd-ecca-4986-868c-e850eb3f5e5e,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -8b05436f-e4ea-4f8e-85ff-4ce195645c0e,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -8b9a7f2a-1bb1-4b5a-8b20-072ed6ae6ef9,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -8e20380f-411d-4517-9bc6-609c0ec89355,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -91faf3db-94ce-4508-87d2-206b19ba0302,beff242e-580b-47c0-9844-c1a68c36c5bf,male,Guy979,Towne435 -922d533c-3cc0-43d1-8d62-817da745358b,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -9404f7b4-1c0c-4e87-b71f-ff267d0552e6,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -9868ffc0-762a-46d7-91db-4eee888484ae,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -9868ffc0-762a-46d7-91db-4eee888484ae,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -9cc30d0d-de95-4242-81fc-e4e1910beb8c,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 -9ce65331-faf8-477a-b338-25678919973b,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -9f1b1a87-d763-406b-b5b7-bd6f7417db2d,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -a0fcef9d-b5bf-4930-b520-cf2dee3b9341,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -a0fcef9d-b5bf-4930-b520-cf2dee3b9341,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 -a0fcef9d-b5bf-4930-b520-cf2dee3b9341,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard -aae88d89-dc3f-40e0-9866-ae4095549d32,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -aae88d89-dc3f-40e0-9866-ae4095549d32,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 -aae88d89-dc3f-40e0-9866-ae4095549d32,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard -ad2dceaf-b8e6-49a0-952c-bcda78fc7b0b,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -ad7d9189-d6b1-4e98-9900-af4bcb9e50b8,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -ad7d9189-d6b1-4e98-9900-af4bcb9e50b8,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 -ad7d9189-d6b1-4e98-9900-af4bcb9e50b8,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard -b2216cf0-b3df-44e0-b03a-2acfb42bdb87,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -b2e43429-d6c7-4332-9ed4-b239aa470e72,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -b5129712-51e2-4d6d-a229-e1c45f78aad4,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -b5129712-51e2-4d6d-a229-e1c45f78aad4,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -b566351c-0d0f-49a8-b7f0-063975f0ea43,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -b566351c-0d0f-49a8-b7f0-063975f0ea43,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -b64a7247-1000-4701-8771-caa6252cc905,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -b9adaf8a-35f6-475e-90f0-89fcd999c457,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -ba21bde9-8111-4cf4-bc55-1e265470a145,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -ba99aa15-c5a4-48e9-9858-df6a8c4a53d2,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 -bc243663-b435-43a0-912f-40a6f3bfcc2b,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -bc243663-b435-43a0-912f-40a6f3bfcc2b,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 -bc243663-b435-43a0-912f-40a6f3bfcc2b,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard -c3034cd6-9395-4b50-b14b-e457b4077e23,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -c47a1fdb-3594-4c0f-944a-655e7df848a8,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -c678e476-64fd-4d12-8f66-20a29f1ce9de,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -c6fbf259-f45f-4266-b988-4b568b8c36c2,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,male,Gilberto712,MacGyver246 -c809b71f-844e-4312-8549-401f1cad25bb,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -c809b71f-844e-4312-8549-401f1cad25bb,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -c98f9644-7da9-4fc4-bf4d-5da3df697616,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Seymour882,Krajcik437 -c98f9644-7da9-4fc4-bf4d-5da3df697616,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Moyna,Krajcik437 -c98f9644-7da9-4fc4-bf4d-5da3df697616,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,male,Arianne,Burchard -cdd25034-c562-4788-aa9e-e53d8006bcbd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -cdd25034-c562-4788-aa9e-e53d8006bcbd,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -cddcbfdd-4b15-4e81-bb2a-8fa998f2e628,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -d19f556d-976c-4529-a2ae-f465b489503c,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -d19f556d-976c-4529-a2ae-f465b489503c,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -d2353195-134f-44f1-835c-d0801a8f31b2,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -d336c1e0-852b-4ba9-bf38-ff6c4698a154,7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,Heller342 -d8196d79-824c-4632-aa78-7fae27d05d96,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -dad66b19-6c91-4513-aa29-abd08fa256cf,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -ddefa5f2-0d73-4a62-a938-920ee87f7d66,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -ddefa5f2-0d73-4a62-a938-920ee87f7d66,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -df0e5bbc-9657-4e73-bfbc-0a92ae5da74d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -df0e5bbc-9657-4e73-bfbc-0a92ae5da74d,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -e744c7f9-9756-4edd-b7a7-3df5487657b9,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -f04628bf-b618-4674-b6eb-1d386eb1d637,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -f4ae2f38-086a-450a-bd38-8460ee704763,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -f51b905a-6a43-47d5-bf78-abf1b94c2ba1,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -f51b905a-6a43-47d5-bf78-abf1b94c2ba1,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 -f5d1f0e1-9e1c-44ee-b31b-e881bd69c1bb,a7eb2ce7-1075-426c-addd-957b861b0e55,male,Shirley182,Botsford977 -f8bb88e4-6301-4eab-83b8-f49c72bb448f,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,Ebert178 -f9994712-1f5e-4a3b-9d64-369b1e3e8f4b,121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,Gleichner915 -fa507deb-a262-475c-938d-5666c502ebae,beff242e-580b-47c0-9844-c1a68c36c5bf,male,Guy979,Towne435 -fd73b5ca-079f-47a5-90b1-e49a547d1554,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Oberbrunner298 -fd73b5ca-079f-47a5-90b1-e49a547d1554,9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,Wuckert783 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.tsv new file mode 100644 index 0000000000..55a1fe11a6 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/multiplePolymorphicResolves.tsv @@ -0,0 +1,150 @@ +020acef9-0421-46b1-9289-92cdc59c30ca 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +08f08f2d-4d77-44f5-ab06-a38545fc050e a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +0a9a0b5a-5be9-4205-984e-94ceef8d6ba6 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +0d8d0441-a4ad-4f15-bc61-5d9694ed3d48 a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +0f1004c8-e9e9-477d-8c43-1eddc445664d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Seymour882 Krajcik437 +0f1004c8-e9e9-477d-8c43-1eddc445664d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Moyna Krajcik437 +0f1004c8-e9e9-477d-8c43-1eddc445664d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Arianne Burchard +10321ec0-3613-4741-a9c3-47bafbc3fae4 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Seymour882 Krajcik437 +10321ec0-3613-4741-a9c3-47bafbc3fae4 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Moyna Krajcik437 +10321ec0-3613-4741-a9c3-47bafbc3fae4 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Arianne Burchard +113e8a00-6fd2-489c-867b-6eedf314c774 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +113e8a00-6fd2-489c-867b-6eedf314c774 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +13a2e7a0-1cf8-475f-8065-6d03b6538c9a a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +13c95f69-822b-44c6-ada3-d73aa1466290 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +16aa313e-aa66-408b-a819-33fb6d169ce0 7001ad9c-34d2-4eb5-8165-5fdc2147f469 female Cherryl901 Heller342 +19113926-d487-42fe-82f6-5c9a75ee4d26 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +1ec37957-06f7-4de5-aa2c-1dc9d02e1ddd beff242e-580b-47c0-9844-c1a68c36c5bf male Guy979 Towne435 +22d20864-b7f9-44f6-88e3-6c3db215eb9a e62e52ae-2d75-4070-a0ae-3cc78d35ed08 female Su690 Ebert178 +2540bf5e-5bc9-48be-8e66-37a2358ae75d a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +2c1dce4c-aefa-4ef8-8970-52aab627de48 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +2c67dbd4-0609-43d3-be6b-e88220c77b75 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +2d19750d-c270-4be9-a84a-ddcdbb64085a 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +2d19750d-c270-4be9-a84a-ddcdbb64085a 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +2d4189ac-8eb3-4f4a-9bb1-0f754d715aee e62e52ae-2d75-4070-a0ae-3cc78d35ed08 female Su690 Ebert178 +2d9c0f33-155b-42ad-8158-1291e14c6da7 a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +2e2cebf2-8c0e-4a89-8139-c2b233343dd7 a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +304a8806-48e6-4d35-bb17-121f1190b475 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Seymour882 Krajcik437 +304a8806-48e6-4d35-bb17-121f1190b475 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Moyna Krajcik437 +304a8806-48e6-4d35-bb17-121f1190b475 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Arianne Burchard +31b93f2e-caa4-4a7f-82ca-5b59f1fc2fdd 2b36c1e2-bbe1-45ae-8124-4adad2677702 male Pedro316 Ulibarri312 +335f8324-ce20-4ebc-a00c-ae085c4516b7 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +35a7f624-22be-48b3-b82c-45cf2667c527 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +35a7f624-22be-48b3-b82c-45cf2667c527 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +365c1b4a-c9c5-4d28-8578-7460dec01ad1 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 female Su690 Ebert178 +37af6887-b8ba-480d-bb1c-4409711b8e2f e62e52ae-2d75-4070-a0ae-3cc78d35ed08 female Su690 Ebert178 +3e56e1ac-af2e-4711-8050-e3d7cee66b91 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +3f505329-402b-4c36-a625-637425462d96 a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +432d6af2-0c59-420e-9434-9b416fb36a85 a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +44d81658-d1f8-4eb7-9a1f-2fdca5c4fa67 a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +45407705-2b60-4788-981b-8090026f3b0b beff242e-580b-47c0-9844-c1a68c36c5bf male Guy979 Towne435 +460deb7a-74bc-43e2-bc60-fc6f97159b59 a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +4d79c3df-899d-42a4-b17d-e93770921d6b 2b36c1e2-bbe1-45ae-8124-4adad2677702 male Pedro316 Ulibarri312 +53238300-6e57-4fc7-99d4-f1c4bcc3366a 7001ad9c-34d2-4eb5-8165-5fdc2147f469 female Cherryl901 Heller342 +537f22b5-8139-4bcc-b048-5342139154fd 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +537f22b5-8139-4bcc-b048-5342139154fd 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +53bf45a8-d83f-429c-9b91-26b086a405ea 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +53bf45a8-d83f-429c-9b91-26b086a405ea 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +5514df98-79c6-4818-b444-9417c93a64f4 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 female Su690 Ebert178 +5be1aab8-ecae-4c29-8f55-fe37535fd2c6 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +5be1aab8-ecae-4c29-8f55-fe37535fd2c6 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +5e7b39f8-773d-4ffb-83e1-735923586881 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Seymour882 Krajcik437 +5e7b39f8-773d-4ffb-83e1-735923586881 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Moyna Krajcik437 +5e7b39f8-773d-4ffb-83e1-735923586881 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Arianne Burchard +5eaf7eae-ffb9-44ec-8d46-fa2b87bedefc 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +6017f9b9-36a1-4cc2-a0ae-af31dc92b089 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +6017f9b9-36a1-4cc2-a0ae-af31dc92b089 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +61bb4baa-6233-4444-8dd2-9c4806338faf 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +69ea0b24-7d91-4498-991f-49f4b07914c0 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Seymour882 Krajcik437 +69ea0b24-7d91-4498-991f-49f4b07914c0 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Moyna Krajcik437 +69ea0b24-7d91-4498-991f-49f4b07914c0 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Arianne Burchard +6b9ffc9c-80b0-40c2-8cf7-f4d51b5448ab 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +6b9ffc9c-80b0-40c2-8cf7-f4d51b5448ab 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +73f52a06-5ac5-4324-a046-7a4caffb6c48 a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +764a0e55-a10a-4d19-aa18-d414cb911a8d 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +764a0e55-a10a-4d19-aa18-d414cb911a8d 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +76b9935f-b44e-42b6-a991-e2e724b42334 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Seymour882 Krajcik437 +76b9935f-b44e-42b6-a991-e2e724b42334 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Moyna Krajcik437 +76b9935f-b44e-42b6-a991-e2e724b42334 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Arianne Burchard +773d711a-5f70-4ac1-98c5-41b7c94e8a16 a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +791b93c5-dfab-4a85-a810-5b01b3531c09 7001ad9c-34d2-4eb5-8165-5fdc2147f469 female Cherryl901 Heller342 +7e490679-3095-4431-95be-d3d9eb3b40f8 a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +7ed4db56-6f72-4dd1-b306-85d80f3a3a87 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Seymour882 Krajcik437 +7ed4db56-6f72-4dd1-b306-85d80f3a3a87 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Moyna Krajcik437 +7ed4db56-6f72-4dd1-b306-85d80f3a3a87 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Arianne Burchard +807ac3db-8499-4876-8508-7839776afc9a 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Seymour882 Krajcik437 +807ac3db-8499-4876-8508-7839776afc9a 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Moyna Krajcik437 +807ac3db-8499-4876-8508-7839776afc9a 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Arianne Burchard +80b37d35-ee1e-49ff-82d3-6e10b0798fd6 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +81297f01-422b-4aba-98df-3609ea25e480 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +81297f01-422b-4aba-98df-3609ea25e480 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +84a1a6b4-583f-4525-b048-d676da5d7533 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +84a1a6b4-583f-4525-b048-d676da5d7533 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +88fd1ebd-ecca-4986-868c-e850eb3f5e5e e62e52ae-2d75-4070-a0ae-3cc78d35ed08 female Su690 Ebert178 +8b05436f-e4ea-4f8e-85ff-4ce195645c0e 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +8b9a7f2a-1bb1-4b5a-8b20-072ed6ae6ef9 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +8e20380f-411d-4517-9bc6-609c0ec89355 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +91faf3db-94ce-4508-87d2-206b19ba0302 beff242e-580b-47c0-9844-c1a68c36c5bf male Guy979 Towne435 +922d533c-3cc0-43d1-8d62-817da745358b e62e52ae-2d75-4070-a0ae-3cc78d35ed08 female Su690 Ebert178 +9404f7b4-1c0c-4e87-b71f-ff267d0552e6 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 female Su690 Ebert178 +9868ffc0-762a-46d7-91db-4eee888484ae 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +9868ffc0-762a-46d7-91db-4eee888484ae 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +9cc30d0d-de95-4242-81fc-e4e1910beb8c 7001ad9c-34d2-4eb5-8165-5fdc2147f469 female Cherryl901 Heller342 +9ce65331-faf8-477a-b338-25678919973b 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +9f1b1a87-d763-406b-b5b7-bd6f7417db2d a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +a0fcef9d-b5bf-4930-b520-cf2dee3b9341 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Seymour882 Krajcik437 +a0fcef9d-b5bf-4930-b520-cf2dee3b9341 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Moyna Krajcik437 +a0fcef9d-b5bf-4930-b520-cf2dee3b9341 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Arianne Burchard +aae88d89-dc3f-40e0-9866-ae4095549d32 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Seymour882 Krajcik437 +aae88d89-dc3f-40e0-9866-ae4095549d32 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Moyna Krajcik437 +aae88d89-dc3f-40e0-9866-ae4095549d32 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Arianne Burchard +ad2dceaf-b8e6-49a0-952c-bcda78fc7b0b e62e52ae-2d75-4070-a0ae-3cc78d35ed08 female Su690 Ebert178 +ad7d9189-d6b1-4e98-9900-af4bcb9e50b8 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Seymour882 Krajcik437 +ad7d9189-d6b1-4e98-9900-af4bcb9e50b8 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Moyna Krajcik437 +ad7d9189-d6b1-4e98-9900-af4bcb9e50b8 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Arianne Burchard +b2216cf0-b3df-44e0-b03a-2acfb42bdb87 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +b2e43429-d6c7-4332-9ed4-b239aa470e72 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 female Su690 Ebert178 +b5129712-51e2-4d6d-a229-e1c45f78aad4 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +b5129712-51e2-4d6d-a229-e1c45f78aad4 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +b566351c-0d0f-49a8-b7f0-063975f0ea43 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +b566351c-0d0f-49a8-b7f0-063975f0ea43 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +b64a7247-1000-4701-8771-caa6252cc905 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +b9adaf8a-35f6-475e-90f0-89fcd999c457 a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +ba21bde9-8111-4cf4-bc55-1e265470a145 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +ba99aa15-c5a4-48e9-9858-df6a8c4a53d2 7001ad9c-34d2-4eb5-8165-5fdc2147f469 female Cherryl901 Heller342 +bc243663-b435-43a0-912f-40a6f3bfcc2b 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Seymour882 Krajcik437 +bc243663-b435-43a0-912f-40a6f3bfcc2b 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Moyna Krajcik437 +bc243663-b435-43a0-912f-40a6f3bfcc2b 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Arianne Burchard +c3034cd6-9395-4b50-b14b-e457b4077e23 a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +c47a1fdb-3594-4c0f-944a-655e7df848a8 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +c678e476-64fd-4d12-8f66-20a29f1ce9de 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +c6fbf259-f45f-4266-b988-4b568b8c36c2 bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 male Gilberto712 MacGyver246 +c809b71f-844e-4312-8549-401f1cad25bb 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +c809b71f-844e-4312-8549-401f1cad25bb 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +c98f9644-7da9-4fc4-bf4d-5da3df697616 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Seymour882 Krajcik437 +c98f9644-7da9-4fc4-bf4d-5da3df697616 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Moyna Krajcik437 +c98f9644-7da9-4fc4-bf4d-5da3df697616 8ee183e2-b3c0-4151-be94-b945d6aa8c6d male Arianne Burchard +cdd25034-c562-4788-aa9e-e53d8006bcbd 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +cdd25034-c562-4788-aa9e-e53d8006bcbd 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +cddcbfdd-4b15-4e81-bb2a-8fa998f2e628 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +d19f556d-976c-4529-a2ae-f465b489503c 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +d19f556d-976c-4529-a2ae-f465b489503c 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +d2353195-134f-44f1-835c-d0801a8f31b2 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +d336c1e0-852b-4ba9-bf38-ff6c4698a154 7001ad9c-34d2-4eb5-8165-5fdc2147f469 female Cherryl901 Heller342 +d8196d79-824c-4632-aa78-7fae27d05d96 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +dad66b19-6c91-4513-aa29-abd08fa256cf 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +ddefa5f2-0d73-4a62-a938-920ee87f7d66 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +ddefa5f2-0d73-4a62-a938-920ee87f7d66 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +df0e5bbc-9657-4e73-bfbc-0a92ae5da74d 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +df0e5bbc-9657-4e73-bfbc-0a92ae5da74d 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +e744c7f9-9756-4edd-b7a7-3df5487657b9 a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +f04628bf-b618-4674-b6eb-1d386eb1d637 a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +f4ae2f38-086a-450a-bd38-8460ee704763 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +f51b905a-6a43-47d5-bf78-abf1b94c2ba1 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +f51b905a-6a43-47d5-bf78-abf1b94c2ba1 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 +f5d1f0e1-9e1c-44ee-b31b-e881bd69c1bb a7eb2ce7-1075-426c-addd-957b861b0e55 male Shirley182 Botsford977 +f8bb88e4-6301-4eab-83b8-f49c72bb448f e62e52ae-2d75-4070-a0ae-3cc78d35ed08 female Su690 Ebert178 +f9994712-1f5e-4a3b-9d64-369b1e3e8f4b 121503c8-9564-4b48-9086-a22df717948e female Ophelia894 Gleichner915 +fa507deb-a262-475c-938d-5666c502ebae beff242e-580b-47c0-9844-c1a68c36c5bf male Guy979 Towne435 +fd73b5ca-079f-47a5-90b1-e49a547d1554 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Oberbrunner298 +fd73b5ca-079f-47a5-90b1-e49a547d1554 9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 Wuckert783 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleResolves.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleResolves.csv deleted file mode 100644 index 847301ed02..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleResolves.csv +++ /dev/null @@ -1,217 +0,0 @@ -0134901a-4a69-4b39-92ea-d6f48ec83c6e,4afe828b-d23e-3b89-afa4-f2fa00720415,PCP131717,01237-9524 -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -0250a217-a663-403b-a708-5d14eadf0c40,235b2155-9bbb-3632-9bf7-858ca62a6f25,PCP11768,02043-1743 -02b993c0-b358-481c-b0d0-0767387feb9f,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -0364073f-91d8-47a1-b8b0-107c6318a691,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -04945715-8ad5-4d8f-bfda-ae26662a3610,36c06fcd-daf1-3e5f-869c-f0af0809c8c4,PCP149432,02148-7028 -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,44bef9d3-91c2-3005-93e0-ccf436348ff0,MASSACHUSETTS EYE AND EAR INFIRMARY -,02114 -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0,PCP43736,02128-1134 -0af4a77e-892e-4b3f-9110-4c5224782250,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -102e16c5-4920-4dad-b142-0b280b2aacad,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -11dafd5c-85e7-4275-a147-89a63641e35e,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -12b212d8-bc6e-415a-93b0-594381726668,cb6a50e0-af76-3758-99ad-3200ede03fff,BETH ISRAEL DEACONESS MEDICAL CENTER,02215 -12df1bed-5472-4c48-8e88-2cbb3e5eab33,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -151e3048-66ad-4411-8753-677877e3bf0a,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -16a3408d-c927-4d02-a1ae-cad32419caab,44bef9d3-91c2-3005-93e0-ccf436348ff0,MASSACHUSETTS EYE AND EAR INFIRMARY -,02114 -16d280a2-c61f-487f-9034-22e884158969,4afe828b-d23e-3b89-afa4-f2fa00720415,PCP131717,01237-9524 -17bcf2ea-d921-4715-91c5-6b15226b33d3,4f3a530e-a2f7-3de0-9a09-c0a70a9ab894,BERKSHIRE MEDICAL CENTER INC - 1,01201 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -19502b5a-2031-487d-9d9c-2001f959408b,75bce424-82dd-3c8e-9a24-8bb2b2939174,PCP81055,02151-3257 -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -1db1c71b-9eeb-4a98-abc1-eb699b38510c,d7b11827-25f2-350b-bcd8-939fc59851b0,BOSTON CHILDREN'S HOSPITAL,02115 -1f3443ee-d15e-4894-b18e-185cfadfcdea,c1c433e1-9ed4-3730-bd86-d25de4fa96bf,"MASSACHUSETTS GENERAL PHYSICIANS ORGANIZATION, INC",02150-2339 -1fd714a6-48b4-425a-99b3-ba5c1a995cce,b7dc82e4-3595-391b-a897-495dd7e5cf38,PCP103,01906-1980 -202131ae-78bb-4104-89b0-fb90645760f6,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -20fbcb6e-d793-4b05-8e29-8ff82572b0db,b7dc82e4-3595-391b-a897-495dd7e5cf38,PCP103,01906-1980 -21995497-0eb8-4c0b-8c23-e6a829f3d596,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -21c6e0fc-bf47-4f80-b1ff-85b940445bdf,cb6a50e0-af76-3758-99ad-3200ede03fff,BETH ISRAEL DEACONESS MEDICAL CENTER,02215 -224e538d-3622-4f5e-b772-211ed358d8de,44bef9d3-91c2-3005-93e0-ccf436348ff0,MASSACHUSETTS EYE AND EAR INFIRMARY -,02114 -23780032-f3bb-4b40-af2e-3fa6b1677376,cb6a50e0-af76-3758-99ad-3200ede03fff,BETH ISRAEL DEACONESS MEDICAL CENTER,02215 -24c4dd5b-748b-4165-a0cc-2867b76c2dc2,4afe828b-d23e-3b89-afa4-f2fa00720415,PCP131717,01237-9524 -2514cdf0-1216-4c5e-bfa8-0c11c20c6b93,cb6a50e0-af76-3758-99ad-3200ede03fff,BETH ISRAEL DEACONESS MEDICAL CENTER,02215 -274a2e49-9170-4945-bca2-ab6ef4bded75,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -2830e99a-e6b0-411b-a051-7ea1e9f815d1,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -29d4e919-2bd2-44e6-bb8a-380a780f60ff,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -2aff9edd-def2-487a-b435-a162e11a303c,a0b6ec0c-e587-3b2a-bf9f-248849f29ee5,ST ELIZABETH'S MEDICAL CENTER,02135 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -2bddd98c-86e3-4ae0-9dc6-87da16ab6267,8b58cdd1-3d79-3126-8fe0-da2c54d6805c,CARNEY HOSPITAL,02124 -2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5,0f7f97f6-693c-3de9-8190-85feaf4632e2,CONCENTRA URGENT CARE - EAST BOSTON,2128 -2d2e07ec-e697-4384-a679-867e93740921,b7dc82e4-3595-391b-a897-495dd7e5cf38,PCP103,01906-1980 -2de1f829-c9d2-4f22-bf39-536dcb82fc3e,cb6a50e0-af76-3758-99ad-3200ede03fff,BETH ISRAEL DEACONESS MEDICAL CENTER,02215 -2e8eb256-84c9-499a-8bf6-bb39504373e1,2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0,PCP43736,02128-1134 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -2ee2ab26-09fb-4e12-bf21-9fed3b4c38de,e78cfad6-cc68-393f-bc00-ba462d78ad71,"NORTH SUFFOLK MENTAL HEALTH ASSOCIATION, INC.",02128-2116 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,44bef9d3-91c2-3005-93e0-ccf436348ff0,MASSACHUSETTS EYE AND EAR INFIRMARY -,02114 -308eba53-29fa-489a-97dc-741b077841a7,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -30ae4a13-0cc6-4d12-a5dd-fa9288efcc09,e78cfad6-cc68-393f-bc00-ba462d78ad71,"NORTH SUFFOLK MENTAL HEALTH ASSOCIATION, INC.",02128-2116 -31d4c712-75b1-4ecb-9d1a-ccc4b8453826,4afe828b-d23e-3b89-afa4-f2fa00720415,PCP131717,01237-9524 -3397a796-894d-409c-97de-a9e6f3f88198,75bce424-82dd-3c8e-9a24-8bb2b2939174,PCP81055,02151-3257 -359cb842-c25b-429f-ba9b-d52f171e5631,ff9863d3-3fa3-3861-900e-f00148f5d9c2,"SHRINERS' HOSPITAL FOR CHILDREN - BOSTON, THE",02114 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -374fd699-6b74-4500-9d60-2473c7e0364f,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -395019b6-84e8-4919-8b8b-ac64e4a6eade,08bcda9c-f8c8-3244-82d4-fc306a7a55d3,BOSTON MEDICAL CENTER CORPORATION-,02118 -39efc9b8-a75f-43a3-98a2-5bdc1c8207d2,4afe828b-d23e-3b89-afa4-f2fa00720415,PCP131717,01237-9524 -3ddf46fe-b5be-456d-810f-ea6a7402236d,75bce424-82dd-3c8e-9a24-8bb2b2939174,PCP81055,02151-3257 -4148ac36-1dcb-4e96-89cd-355e7e8f919b,2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0,PCP43736,02128-1134 -41d26b1c-57b9-408c-b955-bf0ee1db4809,75bce424-82dd-3c8e-9a24-8bb2b2939174,PCP81055,02151-3257 -4215b5d0-bdd9-4222-a04a-81bb637d60af,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -42a9a333-d09d-4b9e-af96-1f02af26bac3,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -4506aa76-9d0d-40e4-85bc-5735f74522a0,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -48cc2275-a478-4ade-b076-cf38e1d16017,75bce424-82dd-3c8e-9a24-8bb2b2939174,PCP81055,02151-3257 -498f4b18-bd4c-470d-9cde-2fca70ec8964,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -4a464bb5-9373-4f1b-9c03-053c64797114,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -4adcaf72-5241-4877-b61c-f34060576c50,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,44bef9d3-91c2-3005-93e0-ccf436348ff0,MASSACHUSETTS EYE AND EAR INFIRMARY -,02114 -4cd95073-6db3-4eb2-ac4b-0aacb182b352,2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0,PCP43736,02128-1134 -4cfe72fe-21a0-4201-87b6-ef93695d2495,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -4db144d3-a596-4718-a50a-8ac9175ad386,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -4f14ca7f-5332-4263-a7b2-f0a838380309,ff9863d3-3fa3-3861-900e-f00148f5d9c2,"SHRINERS' HOSPITAL FOR CHILDREN - BOSTON, THE",02114 -4f342a71-dec3-403e-970b-e4c21b9eb98b,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -4f3d1e93-a28f-446e-91af-2baf0168b82b,d7b11827-25f2-350b-bcd8-939fc59851b0,BOSTON CHILDREN'S HOSPITAL,02115 -50eac25a-3761-4de3-8a69-aae52e658300,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -51241857-a7a4-4517-96e3-21f797581f89,2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0,PCP43736,02128-1134 -522e2abe-ad96-4d1a-b5a5-748faa997531,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -54c750aa-570a-4e8e-9a02-418781923e39,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -55f17f44-287f-41aa-a1bc-d1c0104af36e,0f7f97f6-693c-3de9-8190-85feaf4632e2,CONCENTRA URGENT CARE - EAST BOSTON,2128 -56999896-e36b-4e63-a6b6-dbb85dfe1936,2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0,PCP43736,02128-1134 -578cc35c-0982-4d72-a729-b304c026f075,235b2155-9bbb-3632-9bf7-858ca62a6f25,PCP11768,02043-1743 -5abe533b-ae5f-47ad-8746-f60caf7483c0,ff9863d3-3fa3-3861-900e-f00148f5d9c2,"SHRINERS' HOSPITAL FOR CHILDREN - BOSTON, THE",02114 -5cedf18a-fc44-4c39-a80f-2106a0d76934,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -5e1fe0f2-4517-462b-8287-7824f9a60f4f,62fa9127-485f-3032-87cc-6576d91fec94,LAHEY CLINIC NORTHSHORE,1960 -5f64131b-d410-449a-9de6-35415919fec5,d7b11827-25f2-350b-bcd8-939fc59851b0,BOSTON CHILDREN'S HOSPITAL,02115 -62059efc-7607-41c9-a3ba-70948f6a8a1d,b7dc82e4-3595-391b-a897-495dd7e5cf38,PCP103,01906-1980 -634f0889-3a83-484a-bcc8-86f48e333c7b,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -67e5cf95-236b-4f75-abc5-034ca2966448,75bce424-82dd-3c8e-9a24-8bb2b2939174,PCP81055,02151-3257 -67fc12fc-bb9f-40f1-9051-127a788b3081,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -683714ad-5194-49e7-9b8f-4e306cf68ae1,d7b11827-25f2-350b-bcd8-939fc59851b0,BOSTON CHILDREN'S HOSPITAL,02115 -6954c5c2-dd77-490a-9152-f1d78eff913d,3265f387-6c51-32ee-8f6d-b2a89caa34d5,CHELSEA MGH HEALTH CENTER MEDICAL WALK IN,2150 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -6af73cae-2e4e-485f-a74d-6f4307eb3af3,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -6c84348c-5065-4e89-9412-bfda023683f2,ff9863d3-3fa3-3861-900e-f00148f5d9c2,"SHRINERS' HOSPITAL FOR CHILDREN - BOSTON, THE",02114 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -6e187f47-91a1-4217-a185-31b6f01db225,b7dc82e4-3595-391b-a897-495dd7e5cf38,PCP103,01906-1980 -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,44bef9d3-91c2-3005-93e0-ccf436348ff0,MASSACHUSETTS EYE AND EAR INFIRMARY -,02114 -721e4027-a080-40d8-bc4a-43cd33477611,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -72788e7f-1bf1-40b5-a1f1-27c669dc7278,ff9863d3-3fa3-3861-900e-f00148f5d9c2,"SHRINERS' HOSPITAL FOR CHILDREN - BOSTON, THE",02114 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -749babeb-6883-4bd4-92a5-bec52769071c,235b2155-9bbb-3632-9bf7-858ca62a6f25,PCP11768,02043-1743 -75a09d4f-eef2-4404-a386-dfcb408ec9ed,e78cfad6-cc68-393f-bc00-ba462d78ad71,"NORTH SUFFOLK MENTAL HEALTH ASSOCIATION, INC.",02128-2116 -777aa5f2-2a09-4aed-92ea-912694e28b48,b7dc82e4-3595-391b-a897-495dd7e5cf38,PCP103,01906-1980 -79be3b69-23d8-4408-8f01-68d993e63b6c,36c06fcd-daf1-3e5f-869c-f0af0809c8c4,PCP149432,02148-7028 -7ac56d5a-32a7-4b40-a956-356e3006faed,ff9863d3-3fa3-3861-900e-f00148f5d9c2,"SHRINERS' HOSPITAL FOR CHILDREN - BOSTON, THE",02114 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -7e7322a6-7912-4101-b3be-d134245a0626,b7dc82e4-3595-391b-a897-495dd7e5cf38,PCP103,01906-1980 -7ea43fc6-b1f7-46be-a308-5ecb275a1081,cb6a50e0-af76-3758-99ad-3200ede03fff,BETH ISRAEL DEACONESS MEDICAL CENTER,02215 -7f410064-638a-4477-85c4-97fc2bb14a49,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -836b2e59-fb97-4014-ab0c-d5a5dc750969,d7b11827-25f2-350b-bcd8-939fc59851b0,BOSTON CHILDREN'S HOSPITAL,02115 -85481b3f-c75e-40cd-bacd-b0afb79e893c,e78cfad6-cc68-393f-bc00-ba462d78ad71,"NORTH SUFFOLK MENTAL HEALTH ASSOCIATION, INC.",02128-2116 -85c997bf-63d9-4ebb-8e0b-320de3dddc6c,e78cfad6-cc68-393f-bc00-ba462d78ad71,"NORTH SUFFOLK MENTAL HEALTH ASSOCIATION, INC.",02128-2116 -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,ff9863d3-3fa3-3861-900e-f00148f5d9c2,"SHRINERS' HOSPITAL FOR CHILDREN - BOSTON, THE",02114 -86415df5-7e47-4637-9e09-aaad9e7628d1,235b2155-9bbb-3632-9bf7-858ca62a6f25,PCP11768,02043-1743 -8743e69b-17aa-44ff-b8fa-4f582665efc6,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -87b04a97-1d33-4548-aec9-3d2856070702,8b58cdd1-3d79-3126-8fe0-da2c54d6805c,CARNEY HOSPITAL,02124 -88b16960-bfff-41d0-81e4-9a4630834a00,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -8a1908f7-47cc-4f82-859c-781a193e9901,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -8bc7af32-e5ad-4849-ba4d-b446db833ab4,cb6a50e0-af76-3758-99ad-3200ede03fff,BETH ISRAEL DEACONESS MEDICAL CENTER,02215 -8be81657-8ba6-4ec5-8ff9-4809367096ea,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -8dc2ce26-aec7-43c0-9771-350af4257ad8,c1c433e1-9ed4-3730-bd86-d25de4fa96bf,"MASSACHUSETTS GENERAL PHYSICIANS ORGANIZATION, INC",02150-2339 -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -8f76d729-9f74-4cfd-be2a-8b033b568e18,4afe828b-d23e-3b89-afa4-f2fa00720415,PCP131717,01237-9524 -900ce9fe-02d3-476b-902a-9467767ecdcf,c1c433e1-9ed4-3730-bd86-d25de4fa96bf,"MASSACHUSETTS GENERAL PHYSICIANS ORGANIZATION, INC",02150-2339 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -96b2282d-1384-4cfb-9958-f009fb501626,4f3a530e-a2f7-3de0-9a09-c0a70a9ab894,BERKSHIRE MEDICAL CENTER INC - 1,01201 -97b42d67-8691-433d-8a31-dada076162ae,44bef9d3-91c2-3005-93e0-ccf436348ff0,MASSACHUSETTS EYE AND EAR INFIRMARY -,02114 -984067fc-3dfc-47b7-8ee0-4d9b27d43860,0f7f97f6-693c-3de9-8190-85feaf4632e2,CONCENTRA URGENT CARE - EAST BOSTON,2128 -990c21ab-433b-4920-873e-f9dfc7103d9d,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -99fc3558-79f3-4d14-9aa1-ff63f621ecd9,a0b6ec0c-e587-3b2a-bf9f-248849f29ee5,ST ELIZABETH'S MEDICAL CENTER,02135 -9b2eb632-6f1a-4e97-912b-3c8378a1b11c,e78cfad6-cc68-393f-bc00-ba462d78ad71,"NORTH SUFFOLK MENTAL HEALTH ASSOCIATION, INC.",02128-2116 -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -9ed8703e-3b40-424c-9e98-b3b404051f99,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -a1228f20-7c50-4d3a-b88c-2d277ca79d79,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -a221198d-000e-497b-8b70-bb4d37f7bbe1,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -a2c3ee1d-ec35-4b26-9bab-12de3f47d604,235b2155-9bbb-3632-9bf7-858ca62a6f25,PCP11768,02043-1743 -a6790cbd-8349-432e-a976-5dfc07451203,e78cfad6-cc68-393f-bc00-ba462d78ad71,"NORTH SUFFOLK MENTAL HEALTH ASSOCIATION, INC.",02128-2116 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -a6cb423a-7571-46df-83e2-ccc6820adb36,b7dc82e4-3595-391b-a897-495dd7e5cf38,PCP103,01906-1980 -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,d7b11827-25f2-350b-bcd8-939fc59851b0,BOSTON CHILDREN'S HOSPITAL,02115 -acb2329d-b1c3-45c3-ad28-91a09aa521e1,d7b11827-25f2-350b-bcd8-939fc59851b0,BOSTON CHILDREN'S HOSPITAL,02115 -add6d754-a075-4693-a33a-c061c9a368ff,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -ae7ddaef-4911-418c-8401-d978617e145b,d7b11827-25f2-350b-bcd8-939fc59851b0,BOSTON CHILDREN'S HOSPITAL,02115 -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0,PCP43736,02128-1134 -b00df815-7528-4967-bfe2-311130d91c21,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -b07fb98d-2da4-423a-83b4-7a673de93096,44bef9d3-91c2-3005-93e0-ccf436348ff0,MASSACHUSETTS EYE AND EAR INFIRMARY -,02114 -b0d337c5-3555-4817-ba59-4ceea5ad8a91,36c06fcd-daf1-3e5f-869c-f0af0809c8c4,PCP149432,02148-7028 -b1ccd55a-6b88-4240-b20c-ca893f36e23b,c1c433e1-9ed4-3730-bd86-d25de4fa96bf,"MASSACHUSETTS GENERAL PHYSICIANS ORGANIZATION, INC",02150-2339 -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,75bce424-82dd-3c8e-9a24-8bb2b2939174,PCP81055,02151-3257 -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,75bce424-82dd-3c8e-9a24-8bb2b2939174,PCP81055,02151-3257 -b6900c21-d310-4fb4-8b8f-176a09c91989,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -b7772635-1801-48e4-a442-f4aaa6860544,44bef9d3-91c2-3005-93e0-ccf436348ff0,MASSACHUSETTS EYE AND EAR INFIRMARY -,02114 -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,b7dc82e4-3595-391b-a897-495dd7e5cf38,PCP103,01906-1980 -bac374c0-50e8-4a5c-947b-82a8e9a747a9,cb6a50e0-af76-3758-99ad-3200ede03fff,BETH ISRAEL DEACONESS MEDICAL CENTER,02215 -baf48725-f0f9-4357-a71d-b1104910deae,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,b7dc82e4-3595-391b-a897-495dd7e5cf38,PCP103,01906-1980 -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -bf895c48-1d52-4780-8e9a-532d69356d28,2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0,PCP43736,02128-1134 -bfc93cb7-e53a-49f7-b86e-81d6a13cfdea,e78cfad6-cc68-393f-bc00-ba462d78ad71,"NORTH SUFFOLK MENTAL HEALTH ASSOCIATION, INC.",02128-2116 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -c3ec1f2e-1411-41e4-8370-acd5a8f68cf4,8b58cdd1-3d79-3126-8fe0-da2c54d6805c,CARNEY HOSPITAL,02124 -c57d51a7-45de-41b9-92fc-efd0634effaf,d7b11827-25f2-350b-bcd8-939fc59851b0,BOSTON CHILDREN'S HOSPITAL,02115 -c5c6eed5-aec6-4b8a-b689-adbb219c2267,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -c892b1d7-7485-407d-8883-54fb7fecebc1,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -c90aae7e-5704-4e13-8794-41ab377193bd,2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0,PCP43736,02128-1134 -ca3e0486-fd6b-4a13-a741-3a47760b412d,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -ca7f269d-3794-4994-8c46-d8e9f2aa6378,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -cc56af6d-25c6-480e-9767-da02a55551da,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,b7dc82e4-3595-391b-a897-495dd7e5cf38,PCP103,01906-1980 -cfcbe34a-edc5-4442-bc05-5927fa00336b,2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0,PCP43736,02128-1134 -d05461d6-13bf-402c-890d-db6404933f7c,44bef9d3-91c2-3005-93e0-ccf436348ff0,MASSACHUSETTS EYE AND EAR INFIRMARY -,02114 -d080c116-681a-494a-a928-45924109b49d,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -d535b213-2abc-438f-aa6a-c06476d60b09,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -d6988116-3c63-44f8-9f94-9e9d51cc5a37,2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0,PCP43736,02128-1134 -d79728e1-8834-4f4a-8edc-ce18aca18be6,b7dc82e4-3595-391b-a897-495dd7e5cf38,PCP103,01906-1980 -d7a48a26-7731-4046-bf22-78f0b8084eb9,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -d90676d2-ce74-4867-a00f-6dbffa7c00be,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2,08bcda9c-f8c8-3244-82d4-fc306a7a55d3,BOSTON MEDICAL CENTER CORPORATION-,02118 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -def5fd23-2b74-4c64-85e9-fac27e626bb7,4f3a530e-a2f7-3de0-9a09-c0a70a9ab894,BERKSHIRE MEDICAL CENTER INC - 1,01201 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -e0dfbc08-45ad-4a81-b648-7e65c066f673,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -e1040d58-53bc-4467-8101-ca53b772cede,44bef9d3-91c2-3005-93e0-ccf436348ff0,MASSACHUSETTS EYE AND EAR INFIRMARY -,02114 -e1c2ad9f-6f61-4f16-805a-2f405b63659a,2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0,PCP43736,02128-1134 -e2387a81-5ff5-4daf-b2e8-881998d0d917,75bce424-82dd-3c8e-9a24-8bb2b2939174,PCP81055,02151-3257 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -e363eb67-64c7-440f-93fd-5c8787c69a85,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -e4358f28-62a8-4e06-b2bd-cb00d3310349,4afe828b-d23e-3b89-afa4-f2fa00720415,PCP131717,01237-9524 -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,ff9863d3-3fa3-3861-900e-f00148f5d9c2,"SHRINERS' HOSPITAL FOR CHILDREN - BOSTON, THE",02114 -e8c41168-a093-40eb-8baa-6802d24f554a,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -e8eba267-fecb-477e-9625-771fbcfc3cba,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -ede7745a-8f3e-4e20-9f16-33756be7ab6c,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -ee24bf89-81a3-4259-a382-86917f3829d4,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -f1293c5e-c997-4ba9-8bfd-438e7a840dc8,4afe828b-d23e-3b89-afa4-f2fa00720415,PCP131717,01237-9524 -f18b08bd-40da-43c1-87ec-4ba23542635f,c1c433e1-9ed4-3730-bd86-d25de4fa96bf,"MASSACHUSETTS GENERAL PHYSICIANS ORGANIZATION, INC",02150-2339 -f19eefca-c1ad-4860-bdda-4674a631d464,c1c433e1-9ed4-3730-bd86-d25de4fa96bf,"MASSACHUSETTS GENERAL PHYSICIANS ORGANIZATION, INC",02150-2339 -f28931e5-1f35-4f9f-a02e-78398735ea67,ff9863d3-3fa3-3861-900e-f00148f5d9c2,"SHRINERS' HOSPITAL FOR CHILDREN - BOSTON, THE",02114 -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,d733d4a9-080d-3593-b910-2366e652b7ea,BRIGHAM AND WOMEN'S FAULKNER HOSPITAL,02130 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,d692e283-0833-3201-8e55-4f868a9c0736,HALLMARK HEALTH SYSTEM,02176 -f3aa491a-4dbd-4436-b674-18b2177dcf18,44bef9d3-91c2-3005-93e0-ccf436348ff0,MASSACHUSETTS EYE AND EAR INFIRMARY -,02114 -f3c2f842-6aa3-42c9-a0f3-895c40456868,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -f3eb0c38-2571-44e7-be39-732eb52cf1df,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,44bef9d3-91c2-3005-93e0-ccf436348ff0,MASSACHUSETTS EYE AND EAR INFIRMARY -,02114 -f77a8561-5adc-452a-ac9a-76273b6a6678,b7dc82e4-3595-391b-a897-495dd7e5cf38,PCP103,01906-1980 -f883f2ca-d523-4c9f-86b2-0add137901de,ff9863d3-3fa3-3861-900e-f00148f5d9c2,"SHRINERS' HOSPITAL FOR CHILDREN - BOSTON, THE",02114 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,d78e84ec-30aa-3bba-a33a-f29a3a454662,MASSACHUSETTS GENERAL HOSPITAL,02114 -fc771883-3bd6-46d9-9626-a130e1b902de,75bce424-82dd-3c8e-9a24-8bb2b2939174,PCP81055,02151-3257 -fcab9efe-fb05-42d6-b366-ce6db1cc4093,4afe828b-d23e-3b89-afa4-f2fa00720415,PCP131717,01237-9524 -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,d7b11827-25f2-350b-bcd8-939fc59851b0,BOSTON CHILDREN'S HOSPITAL,02115 -fce20464-ff98-4051-8714-6b9584e52740,f5c2ed46-d994-33c7-bc9e-c723b8ad24a0,PCP65005,02151-3200 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleResolves.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleResolves.tsv new file mode 100644 index 0000000000..a0a1f40ffc --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleResolves.tsv @@ -0,0 +1,217 @@ +0134901a-4a69-4b39-92ea-d6f48ec83c6e 4afe828b-d23e-3b89-afa4-f2fa00720415 PCP131717 01237-9524 +01ac1476-0c9c-4cd7-82a6-4ff526018e9a d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +0250a217-a663-403b-a708-5d14eadf0c40 235b2155-9bbb-3632-9bf7-858ca62a6f25 PCP11768 02043-1743 +02b993c0-b358-481c-b0d0-0767387feb9f f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +0364073f-91d8-47a1-b8b0-107c6318a691 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +04945715-8ad5-4d8f-bfda-ae26662a3610 36c06fcd-daf1-3e5f-869c-f0af0809c8c4 PCP149432 02148-7028 +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 44bef9d3-91c2-3005-93e0-ccf436348ff0 MASSACHUSETTS EYE AND EAR INFIRMARY - 02114 +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0 PCP43736 02128-1134 +0af4a77e-892e-4b3f-9110-4c5224782250 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +102e16c5-4920-4dad-b142-0b280b2aacad d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +11dafd5c-85e7-4275-a147-89a63641e35e f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +12b212d8-bc6e-415a-93b0-594381726668 cb6a50e0-af76-3758-99ad-3200ede03fff BETH ISRAEL DEACONESS MEDICAL CENTER 02215 +12df1bed-5472-4c48-8e88-2cbb3e5eab33 d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +151e3048-66ad-4411-8753-677877e3bf0a d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +16a3408d-c927-4d02-a1ae-cad32419caab 44bef9d3-91c2-3005-93e0-ccf436348ff0 MASSACHUSETTS EYE AND EAR INFIRMARY - 02114 +16d280a2-c61f-487f-9034-22e884158969 4afe828b-d23e-3b89-afa4-f2fa00720415 PCP131717 01237-9524 +17bcf2ea-d921-4715-91c5-6b15226b33d3 4f3a530e-a2f7-3de0-9a09-c0a70a9ab894 BERKSHIRE MEDICAL CENTER INC - 1 01201 +18ebf5ea-b0a2-4333-9e9c-40217de809ff d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +19502b5a-2031-487d-9d9c-2001f959408b 75bce424-82dd-3c8e-9a24-8bb2b2939174 PCP81055 02151-3257 +1cb88e7a-3db6-4a88-9b68-b0e1492114bc d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +1db1c71b-9eeb-4a98-abc1-eb699b38510c d7b11827-25f2-350b-bcd8-939fc59851b0 BOSTON CHILDREN'S HOSPITAL 02115 +1f3443ee-d15e-4894-b18e-185cfadfcdea c1c433e1-9ed4-3730-bd86-d25de4fa96bf MASSACHUSETTS GENERAL PHYSICIANS ORGANIZATION INC 02150-2339 +1fd714a6-48b4-425a-99b3-ba5c1a995cce b7dc82e4-3595-391b-a897-495dd7e5cf38 PCP103 01906-1980 +202131ae-78bb-4104-89b0-fb90645760f6 f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +20fbcb6e-d793-4b05-8e29-8ff82572b0db b7dc82e4-3595-391b-a897-495dd7e5cf38 PCP103 01906-1980 +21995497-0eb8-4c0b-8c23-e6a829f3d596 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +21c6e0fc-bf47-4f80-b1ff-85b940445bdf cb6a50e0-af76-3758-99ad-3200ede03fff BETH ISRAEL DEACONESS MEDICAL CENTER 02215 +224e538d-3622-4f5e-b772-211ed358d8de 44bef9d3-91c2-3005-93e0-ccf436348ff0 MASSACHUSETTS EYE AND EAR INFIRMARY - 02114 +23780032-f3bb-4b40-af2e-3fa6b1677376 cb6a50e0-af76-3758-99ad-3200ede03fff BETH ISRAEL DEACONESS MEDICAL CENTER 02215 +24c4dd5b-748b-4165-a0cc-2867b76c2dc2 4afe828b-d23e-3b89-afa4-f2fa00720415 PCP131717 01237-9524 +2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 cb6a50e0-af76-3758-99ad-3200ede03fff BETH ISRAEL DEACONESS MEDICAL CENTER 02215 +274a2e49-9170-4945-bca2-ab6ef4bded75 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +2830e99a-e6b0-411b-a051-7ea1e9f815d1 d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +29d4e919-2bd2-44e6-bb8a-380a780f60ff d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +2aff9edd-def2-487a-b435-a162e11a303c a0b6ec0c-e587-3b2a-bf9f-248849f29ee5 ST ELIZABETH'S MEDICAL CENTER 02135 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +2bddd98c-86e3-4ae0-9dc6-87da16ab6267 8b58cdd1-3d79-3126-8fe0-da2c54d6805c CARNEY HOSPITAL 02124 +2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5 0f7f97f6-693c-3de9-8190-85feaf4632e2 CONCENTRA URGENT CARE - EAST BOSTON 2128 +2d2e07ec-e697-4384-a679-867e93740921 b7dc82e4-3595-391b-a897-495dd7e5cf38 PCP103 01906-1980 +2de1f829-c9d2-4f22-bf39-536dcb82fc3e cb6a50e0-af76-3758-99ad-3200ede03fff BETH ISRAEL DEACONESS MEDICAL CENTER 02215 +2e8eb256-84c9-499a-8bf6-bb39504373e1 2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0 PCP43736 02128-1134 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +2ee2ab26-09fb-4e12-bf21-9fed3b4c38de e78cfad6-cc68-393f-bc00-ba462d78ad71 NORTH SUFFOLK MENTAL HEALTH ASSOCIATION INC. 02128-2116 +2fc75f11-2097-4e1e-8390-dbff3e844b7a d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +2ffd6142-b75b-406f-bc06-cdb1c02eaefc 44bef9d3-91c2-3005-93e0-ccf436348ff0 MASSACHUSETTS EYE AND EAR INFIRMARY - 02114 +308eba53-29fa-489a-97dc-741b077841a7 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 e78cfad6-cc68-393f-bc00-ba462d78ad71 NORTH SUFFOLK MENTAL HEALTH ASSOCIATION INC. 02128-2116 +31d4c712-75b1-4ecb-9d1a-ccc4b8453826 4afe828b-d23e-3b89-afa4-f2fa00720415 PCP131717 01237-9524 +3397a796-894d-409c-97de-a9e6f3f88198 75bce424-82dd-3c8e-9a24-8bb2b2939174 PCP81055 02151-3257 +359cb842-c25b-429f-ba9b-d52f171e5631 ff9863d3-3fa3-3861-900e-f00148f5d9c2 SHRINERS' HOSPITAL FOR CHILDREN - BOSTON THE 02114 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +374fd699-6b74-4500-9d60-2473c7e0364f d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +395019b6-84e8-4919-8b8b-ac64e4a6eade 08bcda9c-f8c8-3244-82d4-fc306a7a55d3 BOSTON MEDICAL CENTER CORPORATION- 02118 +39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 4afe828b-d23e-3b89-afa4-f2fa00720415 PCP131717 01237-9524 +3ddf46fe-b5be-456d-810f-ea6a7402236d 75bce424-82dd-3c8e-9a24-8bb2b2939174 PCP81055 02151-3257 +4148ac36-1dcb-4e96-89cd-355e7e8f919b 2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0 PCP43736 02128-1134 +41d26b1c-57b9-408c-b955-bf0ee1db4809 75bce424-82dd-3c8e-9a24-8bb2b2939174 PCP81055 02151-3257 +4215b5d0-bdd9-4222-a04a-81bb637d60af d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +42a9a333-d09d-4b9e-af96-1f02af26bac3 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +4506aa76-9d0d-40e4-85bc-5735f74522a0 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +48cc2275-a478-4ade-b076-cf38e1d16017 75bce424-82dd-3c8e-9a24-8bb2b2939174 PCP81055 02151-3257 +498f4b18-bd4c-470d-9cde-2fca70ec8964 f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +4a464bb5-9373-4f1b-9c03-053c64797114 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +4adcaf72-5241-4877-b61c-f34060576c50 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 44bef9d3-91c2-3005-93e0-ccf436348ff0 MASSACHUSETTS EYE AND EAR INFIRMARY - 02114 +4cd95073-6db3-4eb2-ac4b-0aacb182b352 2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0 PCP43736 02128-1134 +4cfe72fe-21a0-4201-87b6-ef93695d2495 f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +4db144d3-a596-4718-a50a-8ac9175ad386 d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +4f14ca7f-5332-4263-a7b2-f0a838380309 ff9863d3-3fa3-3861-900e-f00148f5d9c2 SHRINERS' HOSPITAL FOR CHILDREN - BOSTON THE 02114 +4f342a71-dec3-403e-970b-e4c21b9eb98b d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +4f3d1e93-a28f-446e-91af-2baf0168b82b d7b11827-25f2-350b-bcd8-939fc59851b0 BOSTON CHILDREN'S HOSPITAL 02115 +50eac25a-3761-4de3-8a69-aae52e658300 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +51241857-a7a4-4517-96e3-21f797581f89 2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0 PCP43736 02128-1134 +522e2abe-ad96-4d1a-b5a5-748faa997531 f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +54c750aa-570a-4e8e-9a02-418781923e39 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +55f17f44-287f-41aa-a1bc-d1c0104af36e 0f7f97f6-693c-3de9-8190-85feaf4632e2 CONCENTRA URGENT CARE - EAST BOSTON 2128 +56999896-e36b-4e63-a6b6-dbb85dfe1936 2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0 PCP43736 02128-1134 +578cc35c-0982-4d72-a729-b304c026f075 235b2155-9bbb-3632-9bf7-858ca62a6f25 PCP11768 02043-1743 +5abe533b-ae5f-47ad-8746-f60caf7483c0 ff9863d3-3fa3-3861-900e-f00148f5d9c2 SHRINERS' HOSPITAL FOR CHILDREN - BOSTON THE 02114 +5cedf18a-fc44-4c39-a80f-2106a0d76934 d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +5e1fe0f2-4517-462b-8287-7824f9a60f4f 62fa9127-485f-3032-87cc-6576d91fec94 LAHEY CLINIC NORTHSHORE 1960 +5f64131b-d410-449a-9de6-35415919fec5 d7b11827-25f2-350b-bcd8-939fc59851b0 BOSTON CHILDREN'S HOSPITAL 02115 +62059efc-7607-41c9-a3ba-70948f6a8a1d b7dc82e4-3595-391b-a897-495dd7e5cf38 PCP103 01906-1980 +634f0889-3a83-484a-bcc8-86f48e333c7b d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +67e5cf95-236b-4f75-abc5-034ca2966448 75bce424-82dd-3c8e-9a24-8bb2b2939174 PCP81055 02151-3257 +67fc12fc-bb9f-40f1-9051-127a788b3081 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +683714ad-5194-49e7-9b8f-4e306cf68ae1 d7b11827-25f2-350b-bcd8-939fc59851b0 BOSTON CHILDREN'S HOSPITAL 02115 +6954c5c2-dd77-490a-9152-f1d78eff913d 3265f387-6c51-32ee-8f6d-b2a89caa34d5 CHELSEA MGH HEALTH CENTER MEDICAL WALK IN 2150 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +6af73cae-2e4e-485f-a74d-6f4307eb3af3 d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +6c84348c-5065-4e89-9412-bfda023683f2 ff9863d3-3fa3-3861-900e-f00148f5d9c2 SHRINERS' HOSPITAL FOR CHILDREN - BOSTON THE 02114 +6d47a2fe-3645-4c5c-bebe-1b822eff197f d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +6e187f47-91a1-4217-a185-31b6f01db225 b7dc82e4-3595-391b-a897-495dd7e5cf38 PCP103 01906-1980 +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 44bef9d3-91c2-3005-93e0-ccf436348ff0 MASSACHUSETTS EYE AND EAR INFIRMARY - 02114 +721e4027-a080-40d8-bc4a-43cd33477611 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +72788e7f-1bf1-40b5-a1f1-27c669dc7278 ff9863d3-3fa3-3861-900e-f00148f5d9c2 SHRINERS' HOSPITAL FOR CHILDREN - BOSTON THE 02114 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +749babeb-6883-4bd4-92a5-bec52769071c 235b2155-9bbb-3632-9bf7-858ca62a6f25 PCP11768 02043-1743 +75a09d4f-eef2-4404-a386-dfcb408ec9ed e78cfad6-cc68-393f-bc00-ba462d78ad71 NORTH SUFFOLK MENTAL HEALTH ASSOCIATION INC. 02128-2116 +777aa5f2-2a09-4aed-92ea-912694e28b48 b7dc82e4-3595-391b-a897-495dd7e5cf38 PCP103 01906-1980 +79be3b69-23d8-4408-8f01-68d993e63b6c 36c06fcd-daf1-3e5f-869c-f0af0809c8c4 PCP149432 02148-7028 +7ac56d5a-32a7-4b40-a956-356e3006faed ff9863d3-3fa3-3861-900e-f00148f5d9c2 SHRINERS' HOSPITAL FOR CHILDREN - BOSTON THE 02114 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +7e7322a6-7912-4101-b3be-d134245a0626 b7dc82e4-3595-391b-a897-495dd7e5cf38 PCP103 01906-1980 +7ea43fc6-b1f7-46be-a308-5ecb275a1081 cb6a50e0-af76-3758-99ad-3200ede03fff BETH ISRAEL DEACONESS MEDICAL CENTER 02215 +7f410064-638a-4477-85c4-97fc2bb14a49 d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +8089a9ac-9e71-4487-a231-f720e4bc8d3e d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +836b2e59-fb97-4014-ab0c-d5a5dc750969 d7b11827-25f2-350b-bcd8-939fc59851b0 BOSTON CHILDREN'S HOSPITAL 02115 +85481b3f-c75e-40cd-bacd-b0afb79e893c e78cfad6-cc68-393f-bc00-ba462d78ad71 NORTH SUFFOLK MENTAL HEALTH ASSOCIATION INC. 02128-2116 +85c997bf-63d9-4ebb-8e0b-320de3dddc6c e78cfad6-cc68-393f-bc00-ba462d78ad71 NORTH SUFFOLK MENTAL HEALTH ASSOCIATION INC. 02128-2116 +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b ff9863d3-3fa3-3861-900e-f00148f5d9c2 SHRINERS' HOSPITAL FOR CHILDREN - BOSTON THE 02114 +86415df5-7e47-4637-9e09-aaad9e7628d1 235b2155-9bbb-3632-9bf7-858ca62a6f25 PCP11768 02043-1743 +8743e69b-17aa-44ff-b8fa-4f582665efc6 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +87b04a97-1d33-4548-aec9-3d2856070702 8b58cdd1-3d79-3126-8fe0-da2c54d6805c CARNEY HOSPITAL 02124 +88b16960-bfff-41d0-81e4-9a4630834a00 f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +8a1908f7-47cc-4f82-859c-781a193e9901 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +8bc7af32-e5ad-4849-ba4d-b446db833ab4 cb6a50e0-af76-3758-99ad-3200ede03fff BETH ISRAEL DEACONESS MEDICAL CENTER 02215 +8be81657-8ba6-4ec5-8ff9-4809367096ea d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +8dc2ce26-aec7-43c0-9771-350af4257ad8 c1c433e1-9ed4-3730-bd86-d25de4fa96bf MASSACHUSETTS GENERAL PHYSICIANS ORGANIZATION INC 02150-2339 +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +8f76d729-9f74-4cfd-be2a-8b033b568e18 4afe828b-d23e-3b89-afa4-f2fa00720415 PCP131717 01237-9524 +900ce9fe-02d3-476b-902a-9467767ecdcf c1c433e1-9ed4-3730-bd86-d25de4fa96bf MASSACHUSETTS GENERAL PHYSICIANS ORGANIZATION INC 02150-2339 +922e2443-9cc5-421f-8310-9cd23f6e9f2d d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +96b2282d-1384-4cfb-9958-f009fb501626 4f3a530e-a2f7-3de0-9a09-c0a70a9ab894 BERKSHIRE MEDICAL CENTER INC - 1 01201 +97b42d67-8691-433d-8a31-dada076162ae 44bef9d3-91c2-3005-93e0-ccf436348ff0 MASSACHUSETTS EYE AND EAR INFIRMARY - 02114 +984067fc-3dfc-47b7-8ee0-4d9b27d43860 0f7f97f6-693c-3de9-8190-85feaf4632e2 CONCENTRA URGENT CARE - EAST BOSTON 2128 +990c21ab-433b-4920-873e-f9dfc7103d9d d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +99fc3558-79f3-4d14-9aa1-ff63f621ecd9 a0b6ec0c-e587-3b2a-bf9f-248849f29ee5 ST ELIZABETH'S MEDICAL CENTER 02135 +9b2eb632-6f1a-4e97-912b-3c8378a1b11c e78cfad6-cc68-393f-bc00-ba462d78ad71 NORTH SUFFOLK MENTAL HEALTH ASSOCIATION INC. 02128-2116 +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +9ed8703e-3b40-424c-9e98-b3b404051f99 d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +a1228f20-7c50-4d3a-b88c-2d277ca79d79 f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +a221198d-000e-497b-8b70-bb4d37f7bbe1 f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +a2c3ee1d-ec35-4b26-9bab-12de3f47d604 235b2155-9bbb-3632-9bf7-858ca62a6f25 PCP11768 02043-1743 +a6790cbd-8349-432e-a976-5dfc07451203 e78cfad6-cc68-393f-bc00-ba462d78ad71 NORTH SUFFOLK MENTAL HEALTH ASSOCIATION INC. 02128-2116 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +a6cb423a-7571-46df-83e2-ccc6820adb36 b7dc82e4-3595-391b-a897-495dd7e5cf38 PCP103 01906-1980 +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 d7b11827-25f2-350b-bcd8-939fc59851b0 BOSTON CHILDREN'S HOSPITAL 02115 +acb2329d-b1c3-45c3-ad28-91a09aa521e1 d7b11827-25f2-350b-bcd8-939fc59851b0 BOSTON CHILDREN'S HOSPITAL 02115 +add6d754-a075-4693-a33a-c061c9a368ff d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +ae7ddaef-4911-418c-8401-d978617e145b d7b11827-25f2-350b-bcd8-939fc59851b0 BOSTON CHILDREN'S HOSPITAL 02115 +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0 PCP43736 02128-1134 +b00df815-7528-4967-bfe2-311130d91c21 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +b07fb98d-2da4-423a-83b4-7a673de93096 44bef9d3-91c2-3005-93e0-ccf436348ff0 MASSACHUSETTS EYE AND EAR INFIRMARY - 02114 +b0d337c5-3555-4817-ba59-4ceea5ad8a91 36c06fcd-daf1-3e5f-869c-f0af0809c8c4 PCP149432 02148-7028 +b1ccd55a-6b88-4240-b20c-ca893f36e23b c1c433e1-9ed4-3730-bd86-d25de4fa96bf MASSACHUSETTS GENERAL PHYSICIANS ORGANIZATION INC 02150-2339 +b1d4dc41-07ae-44db-ae17-1df86c5a62cf 75bce424-82dd-3c8e-9a24-8bb2b2939174 PCP81055 02151-3257 +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa 75bce424-82dd-3c8e-9a24-8bb2b2939174 PCP81055 02151-3257 +b6900c21-d310-4fb4-8b8f-176a09c91989 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +b7772635-1801-48e4-a442-f4aaa6860544 44bef9d3-91c2-3005-93e0-ccf436348ff0 MASSACHUSETTS EYE AND EAR INFIRMARY - 02114 +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 b7dc82e4-3595-391b-a897-495dd7e5cf38 PCP103 01906-1980 +bac374c0-50e8-4a5c-947b-82a8e9a747a9 cb6a50e0-af76-3758-99ad-3200ede03fff BETH ISRAEL DEACONESS MEDICAL CENTER 02215 +baf48725-f0f9-4357-a71d-b1104910deae d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 b7dc82e4-3595-391b-a897-495dd7e5cf38 PCP103 01906-1980 +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +bf895c48-1d52-4780-8e9a-532d69356d28 2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0 PCP43736 02128-1134 +bfc93cb7-e53a-49f7-b86e-81d6a13cfdea e78cfad6-cc68-393f-bc00-ba462d78ad71 NORTH SUFFOLK MENTAL HEALTH ASSOCIATION INC. 02128-2116 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +c3ec1f2e-1411-41e4-8370-acd5a8f68cf4 8b58cdd1-3d79-3126-8fe0-da2c54d6805c CARNEY HOSPITAL 02124 +c57d51a7-45de-41b9-92fc-efd0634effaf d7b11827-25f2-350b-bcd8-939fc59851b0 BOSTON CHILDREN'S HOSPITAL 02115 +c5c6eed5-aec6-4b8a-b689-adbb219c2267 f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +c892b1d7-7485-407d-8883-54fb7fecebc1 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +c90aae7e-5704-4e13-8794-41ab377193bd 2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0 PCP43736 02128-1134 +ca3e0486-fd6b-4a13-a741-3a47760b412d f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +ca7f269d-3794-4994-8c46-d8e9f2aa6378 d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +cc56af6d-25c6-480e-9767-da02a55551da d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +cf67a8d4-48e2-4dc9-a521-6aabcff0330b b7dc82e4-3595-391b-a897-495dd7e5cf38 PCP103 01906-1980 +cfcbe34a-edc5-4442-bc05-5927fa00336b 2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0 PCP43736 02128-1134 +d05461d6-13bf-402c-890d-db6404933f7c 44bef9d3-91c2-3005-93e0-ccf436348ff0 MASSACHUSETTS EYE AND EAR INFIRMARY - 02114 +d080c116-681a-494a-a928-45924109b49d d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +d535b213-2abc-438f-aa6a-c06476d60b09 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +d6988116-3c63-44f8-9f94-9e9d51cc5a37 2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0 PCP43736 02128-1134 +d79728e1-8834-4f4a-8edc-ce18aca18be6 b7dc82e4-3595-391b-a897-495dd7e5cf38 PCP103 01906-1980 +d7a48a26-7731-4046-bf22-78f0b8084eb9 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +d90676d2-ce74-4867-a00f-6dbffa7c00be d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2 08bcda9c-f8c8-3244-82d4-fc306a7a55d3 BOSTON MEDICAL CENTER CORPORATION- 02118 +db79f75d-af3c-4f82-b4e5-9b5d26598eef d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +def5fd23-2b74-4c64-85e9-fac27e626bb7 4f3a530e-a2f7-3de0-9a09-c0a70a9ab894 BERKSHIRE MEDICAL CENTER INC - 1 01201 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +e0dfbc08-45ad-4a81-b648-7e65c066f673 d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +e1040d58-53bc-4467-8101-ca53b772cede 44bef9d3-91c2-3005-93e0-ccf436348ff0 MASSACHUSETTS EYE AND EAR INFIRMARY - 02114 +e1c2ad9f-6f61-4f16-805a-2f405b63659a 2269bc9e-cade-3aaa-a2e5-fbd322cd2ad0 PCP43736 02128-1134 +e2387a81-5ff5-4daf-b2e8-881998d0d917 75bce424-82dd-3c8e-9a24-8bb2b2939174 PCP81055 02151-3257 +e26ae29a-213a-4f79-98c2-cc81cf2f451d d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +e363eb67-64c7-440f-93fd-5c8787c69a85 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +e4358f28-62a8-4e06-b2bd-cb00d3310349 4afe828b-d23e-3b89-afa4-f2fa00720415 PCP131717 01237-9524 +e7f68d35-ae55-4fa4-b0be-0672a5a7186a ff9863d3-3fa3-3861-900e-f00148f5d9c2 SHRINERS' HOSPITAL FOR CHILDREN - BOSTON THE 02114 +e8c41168-a093-40eb-8baa-6802d24f554a d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +e8eba267-fecb-477e-9625-771fbcfc3cba d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +ede7745a-8f3e-4e20-9f16-33756be7ab6c f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +ee24bf89-81a3-4259-a382-86917f3829d4 d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +f1293c5e-c997-4ba9-8bfd-438e7a840dc8 4afe828b-d23e-3b89-afa4-f2fa00720415 PCP131717 01237-9524 +f18b08bd-40da-43c1-87ec-4ba23542635f c1c433e1-9ed4-3730-bd86-d25de4fa96bf MASSACHUSETTS GENERAL PHYSICIANS ORGANIZATION INC 02150-2339 +f19eefca-c1ad-4860-bdda-4674a631d464 c1c433e1-9ed4-3730-bd86-d25de4fa96bf MASSACHUSETTS GENERAL PHYSICIANS ORGANIZATION INC 02150-2339 +f28931e5-1f35-4f9f-a02e-78398735ea67 ff9863d3-3fa3-3861-900e-f00148f5d9c2 SHRINERS' HOSPITAL FOR CHILDREN - BOSTON THE 02114 +f29330d0-5b32-4f75-b558-a1c7f05c0b4a d733d4a9-080d-3593-b910-2366e652b7ea BRIGHAM AND WOMEN'S FAULKNER HOSPITAL 02130 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c d692e283-0833-3201-8e55-4f868a9c0736 HALLMARK HEALTH SYSTEM 02176 +f3aa491a-4dbd-4436-b674-18b2177dcf18 44bef9d3-91c2-3005-93e0-ccf436348ff0 MASSACHUSETTS EYE AND EAR INFIRMARY - 02114 +f3c2f842-6aa3-42c9-a0f3-895c40456868 f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +f3eb0c38-2571-44e7-be39-732eb52cf1df f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 44bef9d3-91c2-3005-93e0-ccf436348ff0 MASSACHUSETTS EYE AND EAR INFIRMARY - 02114 +f77a8561-5adc-452a-ac9a-76273b6a6678 b7dc82e4-3595-391b-a897-495dd7e5cf38 PCP103 01906-1980 +f883f2ca-d523-4c9f-86b2-0add137901de ff9863d3-3fa3-3861-900e-f00148f5d9c2 SHRINERS' HOSPITAL FOR CHILDREN - BOSTON THE 02114 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c d78e84ec-30aa-3bba-a33a-f29a3a454662 MASSACHUSETTS GENERAL HOSPITAL 02114 +fc771883-3bd6-46d9-9626-a130e1b902de 75bce424-82dd-3c8e-9a24-8bb2b2939174 PCP81055 02151-3257 +fcab9efe-fb05-42d6-b366-ce6db1cc4093 4afe828b-d23e-3b89-afa4-f2fa00720415 PCP131717 01237-9524 +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 d7b11827-25f2-350b-bcd8-939fc59851b0 BOSTON CHILDREN'S HOSPITAL 02115 +fce20464-ff98-4051-8714-6b9584e52740 f5c2ed46-d994-33c7-bc9e-c723b8ad24a0 PCP65005 02151-3200 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves.csv deleted file mode 100644 index 72a18b5c6d..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves.csv +++ /dev/null @@ -1,71 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,06e446da-6d66-4d97-a457-4abf2a0d2e24,http://snomed.info/sct,363406005 -121503c8-9564-4b48-9086-a22df717948e,248e951c-e0ce-4e4b-afae-6273fd109c9d,http://snomed.info/sct,15777000 -121503c8-9564-4b48-9086-a22df717948e,46d69851-eca3-42e2-b142-88c5578f6cff,http://snomed.info/sct,444814009 -121503c8-9564-4b48-9086-a22df717948e,80787532-559e-4999-ac25-75c462ce4ef1,http://snomed.info/sct,271737000 -121503c8-9564-4b48-9086-a22df717948e,9e598086-27bb-4e50-8988-9a40eb3c178f,http://snomed.info/sct,195662009 -121503c8-9564-4b48-9086-a22df717948e,b8eccdce-7261-4402-9aa0-7360ae6bf53b,http://snomed.info/sct,10509002 -121503c8-9564-4b48-9086-a22df717948e,d554ba19-e081-4979-9c0c-a72cd6e5e8ea,http://snomed.info/sct,444470001 -121503c8-9564-4b48-9086-a22df717948e,df98a2ea-8129-4d1a-9a9f-13a0292f6d1d,http://snomed.info/sct,403190006 -121503c8-9564-4b48-9086-a22df717948e,e87fbe4b-74ef-44e4-8e36-70b2abb48895,http://snomed.info/sct,68496003 -121503c8-9564-4b48-9086-a22df717948e,f41b6458-2358-43e7-ae34-d2a0fbe083d8,http://snomed.info/sct,713197008 -2b36c1e2-bbe1-45ae-8124-4adad2677702,badbd940-9839-4472-aa66-3382d8823c80,http://snomed.info/sct,10509002 -2b36c1e2-bbe1-45ae-8124-4adad2677702,db8c0b3e-48f0-43ac-aa03-9ae9a251cb82,http://snomed.info/sct,65363002 -2b36c1e2-bbe1-45ae-8124-4adad2677702,e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76,http://snomed.info/sct,38341003 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,171ad058-c082-46ab-b9cc-d7bba6e4e3cd,http://snomed.info/sct,40055000 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,5300158c-92cb-40fe-bc14-a449d7b3c1c5,http://snomed.info/sct,70704007 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,aa5a32b3-3993-4eb5-afce-489d0e7413cf,http://snomed.info/sct,162864005 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,eb373264-da60-4e98-af7c-e3021fdd8d4b,http://snomed.info/sct,38822007 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,f1fd855c-802c-417a-ab7c-14a3c9daafc6,http://snomed.info/sct,38341003 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,031f1c86-16ec-42e1-a155-81456e778fed,http://snomed.info/sct,444814009 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,061521f0-7f7e-41a5-ad35-b30fe958dea7,http://snomed.info/sct,195662009 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8dfd419b-0b83-4ba7-8fe6-324e2f382bd6,http://snomed.info/sct,302870006 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8f3ad6ad-a457-484e-a455-f0711e77b2ba,http://snomed.info/sct,444814009 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,a16f311d-d6dc-486e-8eb0-7cd53a5333ee,http://snomed.info/sct,368581000119106 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc,http://snomed.info/sct,237602007 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,bb9c4fc1-795a-4492-b065-1f497fe18bb2,http://snomed.info/sct,44054006 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,e620d7ee-6cfe-4f04-ba06-1d0b39f7624d,http://snomed.info/sct,271737000 -9360820c-8602-4335-8b50-c88d627a0c20,1cea8432-f8a0-4746-ab5c-8f195fc07c55,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,1dcedab1-cf3c-46dc-859b-cc3fb63e8375,http://snomed.info/sct,444814009 -9360820c-8602-4335-8b50-c88d627a0c20,251ddff9-1588-4790-8f40-cd6ab1d9b7fc,http://snomed.info/sct,72892002 -9360820c-8602-4335-8b50-c88d627a0c20,29386b12-9af9-4c21-9f82-858998b388bb,http://snomed.info/sct,38341003 -9360820c-8602-4335-8b50-c88d627a0c20,4dd7824b-4b41-4402-bc42-de016e1bfcd9,http://snomed.info/sct,19169002 -9360820c-8602-4335-8b50-c88d627a0c20,549d3ef5-a05e-4b8e-acba-3d70f26a82f6,http://snomed.info/sct,15777000 -9360820c-8602-4335-8b50-c88d627a0c20,55cde383-2eb1-42d9-b5c6-698d6eade389,http://snomed.info/sct,444814009 -9360820c-8602-4335-8b50-c88d627a0c20,66b38727-96ea-43ad-bff7-5f4df5d779a9,http://snomed.info/sct,444814009 -9360820c-8602-4335-8b50-c88d627a0c20,6794fbaf-e715-4e22-bafa-b7e594550ff7,http://snomed.info/sct,236077008 -9360820c-8602-4335-8b50-c88d627a0c20,77fc5f45-e51d-41e2-8356-daa27ebd8268,http://snomed.info/sct,55822004 -9360820c-8602-4335-8b50-c88d627a0c20,8922ff1b-4bc5-41b4-8512-5aad1517e2eb,http://snomed.info/sct,162864005 -9360820c-8602-4335-8b50-c88d627a0c20,be4b757d-70f1-464c-bb29-6f9a0f6cb05e,http://snomed.info/sct,6072007 -9360820c-8602-4335-8b50-c88d627a0c20,c39927f7-d23a-475c-b325-c1de4b51e280,http://snomed.info/sct,156073000 -9360820c-8602-4335-8b50-c88d627a0c20,c892bf2f-a7bc-407e-9508-c778a9b8761c,http://snomed.info/sct,72892002 -9360820c-8602-4335-8b50-c88d627a0c20,eaee8845-e4ed-42e1-9098-99b3f7e14dc3,http://snomed.info/sct,444814009 -9360820c-8602-4335-8b50-c88d627a0c20,f1dd0d5f-c410-484b-984c-2ba38cee79ba,http://snomed.info/sct,10509002 -a7eb2ce7-1075-426c-addd-957b861b0e55,1146cf52-573f-4667-97c3-abf235f9a83b,http://snomed.info/sct,33737001 -a7eb2ce7-1075-426c-addd-957b861b0e55,258191b8-2b42-4473-9ba2-be0ba3561767,http://snomed.info/sct,162864005 -a7eb2ce7-1075-426c-addd-957b861b0e55,31925a68-7a48-412c-b4f8-aef92498dda1,http://snomed.info/sct,444814009 -a7eb2ce7-1075-426c-addd-957b861b0e55,38171e22-b35d-4161-be15-80243be37b99,http://snomed.info/sct,195662009 -a7eb2ce7-1075-426c-addd-957b861b0e55,8ddb74fc-46d5-4e94-a0ee-6761b292ae95,http://snomed.info/sct,271737000 -a7eb2ce7-1075-426c-addd-957b861b0e55,b77b04ef-5eda-418c-a6fb-528a2d0a171a,http://snomed.info/sct,444814009 -a7eb2ce7-1075-426c-addd-957b861b0e55,e35f5823-4533-49e5-9652-79d733be6bef,http://snomed.info/sct,15777000 -a7eb2ce7-1075-426c-addd-957b861b0e55,eaf99c09-419d-4162-8417-5a9d7e042cd4,http://snomed.info/sct,367498001 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,01fac610-c309-46f8-9a0f-6b0f55c9915a,http://snomed.info/sct,53741008 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,1452c50c-b0b9-472c-afb7-2f9e5a3c4717,http://snomed.info/sct,65363002 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,21372b39-2be0-4d0d-85c4-5d7e250d8f78,http://snomed.info/sct,65363002 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,31393667-dd7f-4c94-90c3-6cde75c67d3e,http://snomed.info/sct,403190006 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,39170d67-8205-4636-84d7-d8575115d14c,http://snomed.info/sct,10509002 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325,http://snomed.info/sct,399211009 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc,http://snomed.info/sct,284551006 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,abecd476-3911-4a6b-a1c6-b8ecea2fe63e,http://snomed.info/sct,22298006 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,d12274a5-9e03-4c78-ae3e-6cc27e91d737,http://snomed.info/sct,22298006 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,da8db730-f051-42d0-a2db-a3577d96e9bd,http://snomed.info/sct,10509002 -beff242e-580b-47c0-9844-c1a68c36c5bf,0447de38-1f2a-411c-9fd2-a9c62ab1f221,http://snomed.info/sct,444814009 -beff242e-580b-47c0-9844-c1a68c36c5bf,7ed34fb1-0bbf-4c41-a101-1316ec483aa7,http://snomed.info/sct,444814009 -beff242e-580b-47c0-9844-c1a68c36c5bf,c879c300-7fdf-4b53-aa6a-a2b4a266b30c,http://snomed.info/sct,444814009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,http://snomed.info/sct,195662009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,4025ceb3-04a6-41fb-8569-16a8dcce7ccc,http://snomed.info/sct,15777000 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,475461a2-3bd2-43fd-a5aa-7ce424203ae8,http://snomed.info/sct,444814009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,5378726d-7f2b-4c83-9762-eaf385915fa7,http://snomed.info/sct,271737000 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,5ef84858-0480-4a34-8f43-fc962fe627b2,http://snomed.info/sct,19169002 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,6464e20b-55ec-4685-be3e-55bc3b9602d4,http://snomed.info/sct,195662009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,7d18555a-54c9-4c0a-bf3b-6305d0392a2a,http://snomed.info/sct,195662009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,ad679e19-3e17-4c67-8b0d-dd4f7d862207,http://snomed.info/sct,87433001 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves.tsv new file mode 100644 index 0000000000..783141ffe2 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves.tsv @@ -0,0 +1,71 @@ +121503c8-9564-4b48-9086-a22df717948e 06e446da-6d66-4d97-a457-4abf2a0d2e24 http://snomed.info/sct 363406005 +121503c8-9564-4b48-9086-a22df717948e 248e951c-e0ce-4e4b-afae-6273fd109c9d http://snomed.info/sct 15777000 +121503c8-9564-4b48-9086-a22df717948e 46d69851-eca3-42e2-b142-88c5578f6cff http://snomed.info/sct 444814009 +121503c8-9564-4b48-9086-a22df717948e 80787532-559e-4999-ac25-75c462ce4ef1 http://snomed.info/sct 271737000 +121503c8-9564-4b48-9086-a22df717948e 9e598086-27bb-4e50-8988-9a40eb3c178f http://snomed.info/sct 195662009 +121503c8-9564-4b48-9086-a22df717948e b8eccdce-7261-4402-9aa0-7360ae6bf53b http://snomed.info/sct 10509002 +121503c8-9564-4b48-9086-a22df717948e d554ba19-e081-4979-9c0c-a72cd6e5e8ea http://snomed.info/sct 444470001 +121503c8-9564-4b48-9086-a22df717948e df98a2ea-8129-4d1a-9a9f-13a0292f6d1d http://snomed.info/sct 403190006 +121503c8-9564-4b48-9086-a22df717948e e87fbe4b-74ef-44e4-8e36-70b2abb48895 http://snomed.info/sct 68496003 +121503c8-9564-4b48-9086-a22df717948e f41b6458-2358-43e7-ae34-d2a0fbe083d8 http://snomed.info/sct 713197008 +2b36c1e2-bbe1-45ae-8124-4adad2677702 badbd940-9839-4472-aa66-3382d8823c80 http://snomed.info/sct 10509002 +2b36c1e2-bbe1-45ae-8124-4adad2677702 db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 http://snomed.info/sct 65363002 +2b36c1e2-bbe1-45ae-8124-4adad2677702 e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 http://snomed.info/sct 38341003 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 171ad058-c082-46ab-b9cc-d7bba6e4e3cd http://snomed.info/sct 40055000 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 5300158c-92cb-40fe-bc14-a449d7b3c1c5 http://snomed.info/sct 70704007 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 aa5a32b3-3993-4eb5-afce-489d0e7413cf http://snomed.info/sct 162864005 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 eb373264-da60-4e98-af7c-e3021fdd8d4b http://snomed.info/sct 38822007 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 f1fd855c-802c-417a-ab7c-14a3c9daafc6 http://snomed.info/sct 38341003 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 031f1c86-16ec-42e1-a155-81456e778fed http://snomed.info/sct 444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 061521f0-7f7e-41a5-ad35-b30fe958dea7 http://snomed.info/sct 195662009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 http://snomed.info/sct 302870006 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8f3ad6ad-a457-484e-a455-f0711e77b2ba http://snomed.info/sct 444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d a16f311d-d6dc-486e-8eb0-7cd53a5333ee http://snomed.info/sct 368581000119106 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc http://snomed.info/sct 237602007 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d bb9c4fc1-795a-4492-b065-1f497fe18bb2 http://snomed.info/sct 44054006 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d e620d7ee-6cfe-4f04-ba06-1d0b39f7624d http://snomed.info/sct 271737000 +9360820c-8602-4335-8b50-c88d627a0c20 1cea8432-f8a0-4746-ab5c-8f195fc07c55 http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 1dcedab1-cf3c-46dc-859b-cc3fb63e8375 http://snomed.info/sct 444814009 +9360820c-8602-4335-8b50-c88d627a0c20 251ddff9-1588-4790-8f40-cd6ab1d9b7fc http://snomed.info/sct 72892002 +9360820c-8602-4335-8b50-c88d627a0c20 29386b12-9af9-4c21-9f82-858998b388bb http://snomed.info/sct 38341003 +9360820c-8602-4335-8b50-c88d627a0c20 4dd7824b-4b41-4402-bc42-de016e1bfcd9 http://snomed.info/sct 19169002 +9360820c-8602-4335-8b50-c88d627a0c20 549d3ef5-a05e-4b8e-acba-3d70f26a82f6 http://snomed.info/sct 15777000 +9360820c-8602-4335-8b50-c88d627a0c20 55cde383-2eb1-42d9-b5c6-698d6eade389 http://snomed.info/sct 444814009 +9360820c-8602-4335-8b50-c88d627a0c20 66b38727-96ea-43ad-bff7-5f4df5d779a9 http://snomed.info/sct 444814009 +9360820c-8602-4335-8b50-c88d627a0c20 6794fbaf-e715-4e22-bafa-b7e594550ff7 http://snomed.info/sct 236077008 +9360820c-8602-4335-8b50-c88d627a0c20 77fc5f45-e51d-41e2-8356-daa27ebd8268 http://snomed.info/sct 55822004 +9360820c-8602-4335-8b50-c88d627a0c20 8922ff1b-4bc5-41b4-8512-5aad1517e2eb http://snomed.info/sct 162864005 +9360820c-8602-4335-8b50-c88d627a0c20 be4b757d-70f1-464c-bb29-6f9a0f6cb05e http://snomed.info/sct 6072007 +9360820c-8602-4335-8b50-c88d627a0c20 c39927f7-d23a-475c-b325-c1de4b51e280 http://snomed.info/sct 156073000 +9360820c-8602-4335-8b50-c88d627a0c20 c892bf2f-a7bc-407e-9508-c778a9b8761c http://snomed.info/sct 72892002 +9360820c-8602-4335-8b50-c88d627a0c20 eaee8845-e4ed-42e1-9098-99b3f7e14dc3 http://snomed.info/sct 444814009 +9360820c-8602-4335-8b50-c88d627a0c20 f1dd0d5f-c410-484b-984c-2ba38cee79ba http://snomed.info/sct 10509002 +a7eb2ce7-1075-426c-addd-957b861b0e55 1146cf52-573f-4667-97c3-abf235f9a83b http://snomed.info/sct 33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55 258191b8-2b42-4473-9ba2-be0ba3561767 http://snomed.info/sct 162864005 +a7eb2ce7-1075-426c-addd-957b861b0e55 31925a68-7a48-412c-b4f8-aef92498dda1 http://snomed.info/sct 444814009 +a7eb2ce7-1075-426c-addd-957b861b0e55 38171e22-b35d-4161-be15-80243be37b99 http://snomed.info/sct 195662009 +a7eb2ce7-1075-426c-addd-957b861b0e55 8ddb74fc-46d5-4e94-a0ee-6761b292ae95 http://snomed.info/sct 271737000 +a7eb2ce7-1075-426c-addd-957b861b0e55 b77b04ef-5eda-418c-a6fb-528a2d0a171a http://snomed.info/sct 444814009 +a7eb2ce7-1075-426c-addd-957b861b0e55 e35f5823-4533-49e5-9652-79d733be6bef http://snomed.info/sct 15777000 +a7eb2ce7-1075-426c-addd-957b861b0e55 eaf99c09-419d-4162-8417-5a9d7e042cd4 http://snomed.info/sct 367498001 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 01fac610-c309-46f8-9a0f-6b0f55c9915a http://snomed.info/sct 53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 1452c50c-b0b9-472c-afb7-2f9e5a3c4717 http://snomed.info/sct 65363002 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 21372b39-2be0-4d0d-85c4-5d7e250d8f78 http://snomed.info/sct 65363002 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 31393667-dd7f-4c94-90c3-6cde75c67d3e http://snomed.info/sct 403190006 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 39170d67-8205-4636-84d7-d8575115d14c http://snomed.info/sct 10509002 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 http://snomed.info/sct 399211009 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc http://snomed.info/sct 284551006 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 abecd476-3911-4a6b-a1c6-b8ecea2fe63e http://snomed.info/sct 22298006 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 d12274a5-9e03-4c78-ae3e-6cc27e91d737 http://snomed.info/sct 22298006 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 da8db730-f051-42d0-a2db-a3577d96e9bd http://snomed.info/sct 10509002 +beff242e-580b-47c0-9844-c1a68c36c5bf 0447de38-1f2a-411c-9fd2-a9c62ab1f221 http://snomed.info/sct 444814009 +beff242e-580b-47c0-9844-c1a68c36c5bf 7ed34fb1-0bbf-4c41-a101-1316ec483aa7 http://snomed.info/sct 444814009 +beff242e-580b-47c0-9844-c1a68c36c5bf c879c300-7fdf-4b53-aa6a-a2b4a266b30c http://snomed.info/sct 444814009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 http://snomed.info/sct 195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 4025ceb3-04a6-41fb-8569-16a8dcce7ccc http://snomed.info/sct 15777000 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 475461a2-3bd2-43fd-a5aa-7ce424203ae8 http://snomed.info/sct 444814009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 5378726d-7f2b-4c83-9762-eaf385915fa7 http://snomed.info/sct 271737000 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 5ef84858-0480-4a34-8f43-fc962fe627b2 http://snomed.info/sct 19169002 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 6464e20b-55ec-4685-be3e-55bc3b9602d4 http://snomed.info/sct 195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 7d18555a-54c9-4c0a-bf3b-6305d0392a2a http://snomed.info/sct 195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 ad679e19-3e17-4c67-8b0d-dd4f7d862207 http://snomed.info/sct 87433001 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves2.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves2.csv deleted file mode 100644 index 6b022aa8c9..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves2.csv +++ /dev/null @@ -1,71 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,f41b6458-2358-43e7-ae34-d2a0fbe083d8,http://snomed.info/sct,363406005 -121503c8-9564-4b48-9086-a22df717948e,e87fbe4b-74ef-44e4-8e36-70b2abb48895,http://snomed.info/sct,363406005 -121503c8-9564-4b48-9086-a22df717948e,df98a2ea-8129-4d1a-9a9f-13a0292f6d1d,http://snomed.info/sct,363406005 -121503c8-9564-4b48-9086-a22df717948e,d554ba19-e081-4979-9c0c-a72cd6e5e8ea,http://snomed.info/sct,363406005 -121503c8-9564-4b48-9086-a22df717948e,b8eccdce-7261-4402-9aa0-7360ae6bf53b,http://snomed.info/sct,363406005 -121503c8-9564-4b48-9086-a22df717948e,9e598086-27bb-4e50-8988-9a40eb3c178f,http://snomed.info/sct,363406005 -121503c8-9564-4b48-9086-a22df717948e,80787532-559e-4999-ac25-75c462ce4ef1,http://snomed.info/sct,363406005 -121503c8-9564-4b48-9086-a22df717948e,46d69851-eca3-42e2-b142-88c5578f6cff,http://snomed.info/sct,363406005 -121503c8-9564-4b48-9086-a22df717948e,248e951c-e0ce-4e4b-afae-6273fd109c9d,http://snomed.info/sct,363406005 -121503c8-9564-4b48-9086-a22df717948e,06e446da-6d66-4d97-a457-4abf2a0d2e24,http://snomed.info/sct,363406005 -2b36c1e2-bbe1-45ae-8124-4adad2677702,e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76,http://snomed.info/sct,10509002 -2b36c1e2-bbe1-45ae-8124-4adad2677702,db8c0b3e-48f0-43ac-aa03-9ae9a251cb82,http://snomed.info/sct,10509002 -2b36c1e2-bbe1-45ae-8124-4adad2677702,badbd940-9839-4472-aa66-3382d8823c80,http://snomed.info/sct,10509002 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,f1fd855c-802c-417a-ab7c-14a3c9daafc6,http://snomed.info/sct,40055000 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,eb373264-da60-4e98-af7c-e3021fdd8d4b,http://snomed.info/sct,40055000 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,aa5a32b3-3993-4eb5-afce-489d0e7413cf,http://snomed.info/sct,40055000 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,5300158c-92cb-40fe-bc14-a449d7b3c1c5,http://snomed.info/sct,40055000 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,171ad058-c082-46ab-b9cc-d7bba6e4e3cd,http://snomed.info/sct,40055000 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,e620d7ee-6cfe-4f04-ba06-1d0b39f7624d,http://snomed.info/sct,444814009 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,bb9c4fc1-795a-4492-b065-1f497fe18bb2,http://snomed.info/sct,444814009 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc,http://snomed.info/sct,444814009 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,a16f311d-d6dc-486e-8eb0-7cd53a5333ee,http://snomed.info/sct,444814009 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8f3ad6ad-a457-484e-a455-f0711e77b2ba,http://snomed.info/sct,444814009 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8dfd419b-0b83-4ba7-8fe6-324e2f382bd6,http://snomed.info/sct,444814009 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,061521f0-7f7e-41a5-ad35-b30fe958dea7,http://snomed.info/sct,444814009 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,031f1c86-16ec-42e1-a155-81456e778fed,http://snomed.info/sct,444814009 -9360820c-8602-4335-8b50-c88d627a0c20,f1dd0d5f-c410-484b-984c-2ba38cee79ba,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,eaee8845-e4ed-42e1-9098-99b3f7e14dc3,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,c892bf2f-a7bc-407e-9508-c778a9b8761c,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,c39927f7-d23a-475c-b325-c1de4b51e280,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,be4b757d-70f1-464c-bb29-6f9a0f6cb05e,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,8922ff1b-4bc5-41b4-8512-5aad1517e2eb,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,77fc5f45-e51d-41e2-8356-daa27ebd8268,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,6794fbaf-e715-4e22-bafa-b7e594550ff7,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,66b38727-96ea-43ad-bff7-5f4df5d779a9,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,55cde383-2eb1-42d9-b5c6-698d6eade389,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,549d3ef5-a05e-4b8e-acba-3d70f26a82f6,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,4dd7824b-4b41-4402-bc42-de016e1bfcd9,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,29386b12-9af9-4c21-9f82-858998b388bb,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,251ddff9-1588-4790-8f40-cd6ab1d9b7fc,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,1dcedab1-cf3c-46dc-859b-cc3fb63e8375,http://snomed.info/sct,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,1cea8432-f8a0-4746-ab5c-8f195fc07c55,http://snomed.info/sct,94260004 -a7eb2ce7-1075-426c-addd-957b861b0e55,eaf99c09-419d-4162-8417-5a9d7e042cd4,http://snomed.info/sct,33737001 -a7eb2ce7-1075-426c-addd-957b861b0e55,e35f5823-4533-49e5-9652-79d733be6bef,http://snomed.info/sct,33737001 -a7eb2ce7-1075-426c-addd-957b861b0e55,b77b04ef-5eda-418c-a6fb-528a2d0a171a,http://snomed.info/sct,33737001 -a7eb2ce7-1075-426c-addd-957b861b0e55,8ddb74fc-46d5-4e94-a0ee-6761b292ae95,http://snomed.info/sct,33737001 -a7eb2ce7-1075-426c-addd-957b861b0e55,38171e22-b35d-4161-be15-80243be37b99,http://snomed.info/sct,33737001 -a7eb2ce7-1075-426c-addd-957b861b0e55,31925a68-7a48-412c-b4f8-aef92498dda1,http://snomed.info/sct,33737001 -a7eb2ce7-1075-426c-addd-957b861b0e55,258191b8-2b42-4473-9ba2-be0ba3561767,http://snomed.info/sct,33737001 -a7eb2ce7-1075-426c-addd-957b861b0e55,1146cf52-573f-4667-97c3-abf235f9a83b,http://snomed.info/sct,33737001 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,da8db730-f051-42d0-a2db-a3577d96e9bd,http://snomed.info/sct,53741008 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,d12274a5-9e03-4c78-ae3e-6cc27e91d737,http://snomed.info/sct,53741008 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,abecd476-3911-4a6b-a1c6-b8ecea2fe63e,http://snomed.info/sct,53741008 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc,http://snomed.info/sct,53741008 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325,http://snomed.info/sct,53741008 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,39170d67-8205-4636-84d7-d8575115d14c,http://snomed.info/sct,53741008 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,31393667-dd7f-4c94-90c3-6cde75c67d3e,http://snomed.info/sct,53741008 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,21372b39-2be0-4d0d-85c4-5d7e250d8f78,http://snomed.info/sct,53741008 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,1452c50c-b0b9-472c-afb7-2f9e5a3c4717,http://snomed.info/sct,53741008 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,01fac610-c309-46f8-9a0f-6b0f55c9915a,http://snomed.info/sct,53741008 -beff242e-580b-47c0-9844-c1a68c36c5bf,c879c300-7fdf-4b53-aa6a-a2b4a266b30c,http://snomed.info/sct,444814009 -beff242e-580b-47c0-9844-c1a68c36c5bf,7ed34fb1-0bbf-4c41-a101-1316ec483aa7,http://snomed.info/sct,444814009 -beff242e-580b-47c0-9844-c1a68c36c5bf,0447de38-1f2a-411c-9fd2-a9c62ab1f221,http://snomed.info/sct,444814009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,ad679e19-3e17-4c67-8b0d-dd4f7d862207,http://snomed.info/sct,195662009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,7d18555a-54c9-4c0a-bf3b-6305d0392a2a,http://snomed.info/sct,195662009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,6464e20b-55ec-4685-be3e-55bc3b9602d4,http://snomed.info/sct,195662009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,5ef84858-0480-4a34-8f43-fc962fe627b2,http://snomed.info/sct,195662009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,5378726d-7f2b-4c83-9762-eaf385915fa7,http://snomed.info/sct,195662009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,475461a2-3bd2-43fd-a5aa-7ce424203ae8,http://snomed.info/sct,195662009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,4025ceb3-04a6-41fb-8569-16a8dcce7ccc,http://snomed.info/sct,195662009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,http://snomed.info/sct,195662009 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves2.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves2.tsv new file mode 100644 index 0000000000..f9934df4a0 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/multipleReverseResolves2.tsv @@ -0,0 +1,71 @@ +121503c8-9564-4b48-9086-a22df717948e f41b6458-2358-43e7-ae34-d2a0fbe083d8 http://snomed.info/sct 363406005 +121503c8-9564-4b48-9086-a22df717948e e87fbe4b-74ef-44e4-8e36-70b2abb48895 http://snomed.info/sct 363406005 +121503c8-9564-4b48-9086-a22df717948e df98a2ea-8129-4d1a-9a9f-13a0292f6d1d http://snomed.info/sct 363406005 +121503c8-9564-4b48-9086-a22df717948e d554ba19-e081-4979-9c0c-a72cd6e5e8ea http://snomed.info/sct 363406005 +121503c8-9564-4b48-9086-a22df717948e b8eccdce-7261-4402-9aa0-7360ae6bf53b http://snomed.info/sct 363406005 +121503c8-9564-4b48-9086-a22df717948e 9e598086-27bb-4e50-8988-9a40eb3c178f http://snomed.info/sct 363406005 +121503c8-9564-4b48-9086-a22df717948e 80787532-559e-4999-ac25-75c462ce4ef1 http://snomed.info/sct 363406005 +121503c8-9564-4b48-9086-a22df717948e 46d69851-eca3-42e2-b142-88c5578f6cff http://snomed.info/sct 363406005 +121503c8-9564-4b48-9086-a22df717948e 248e951c-e0ce-4e4b-afae-6273fd109c9d http://snomed.info/sct 363406005 +121503c8-9564-4b48-9086-a22df717948e 06e446da-6d66-4d97-a457-4abf2a0d2e24 http://snomed.info/sct 363406005 +2b36c1e2-bbe1-45ae-8124-4adad2677702 e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 http://snomed.info/sct 10509002 +2b36c1e2-bbe1-45ae-8124-4adad2677702 db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 http://snomed.info/sct 10509002 +2b36c1e2-bbe1-45ae-8124-4adad2677702 badbd940-9839-4472-aa66-3382d8823c80 http://snomed.info/sct 10509002 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 f1fd855c-802c-417a-ab7c-14a3c9daafc6 http://snomed.info/sct 40055000 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 eb373264-da60-4e98-af7c-e3021fdd8d4b http://snomed.info/sct 40055000 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 aa5a32b3-3993-4eb5-afce-489d0e7413cf http://snomed.info/sct 40055000 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 5300158c-92cb-40fe-bc14-a449d7b3c1c5 http://snomed.info/sct 40055000 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 171ad058-c082-46ab-b9cc-d7bba6e4e3cd http://snomed.info/sct 40055000 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d e620d7ee-6cfe-4f04-ba06-1d0b39f7624d http://snomed.info/sct 444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d bb9c4fc1-795a-4492-b065-1f497fe18bb2 http://snomed.info/sct 444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc http://snomed.info/sct 444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d a16f311d-d6dc-486e-8eb0-7cd53a5333ee http://snomed.info/sct 444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8f3ad6ad-a457-484e-a455-f0711e77b2ba http://snomed.info/sct 444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 http://snomed.info/sct 444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 061521f0-7f7e-41a5-ad35-b30fe958dea7 http://snomed.info/sct 444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 031f1c86-16ec-42e1-a155-81456e778fed http://snomed.info/sct 444814009 +9360820c-8602-4335-8b50-c88d627a0c20 f1dd0d5f-c410-484b-984c-2ba38cee79ba http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 eaee8845-e4ed-42e1-9098-99b3f7e14dc3 http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 c892bf2f-a7bc-407e-9508-c778a9b8761c http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 c39927f7-d23a-475c-b325-c1de4b51e280 http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 be4b757d-70f1-464c-bb29-6f9a0f6cb05e http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 8922ff1b-4bc5-41b4-8512-5aad1517e2eb http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 77fc5f45-e51d-41e2-8356-daa27ebd8268 http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 6794fbaf-e715-4e22-bafa-b7e594550ff7 http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 66b38727-96ea-43ad-bff7-5f4df5d779a9 http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 55cde383-2eb1-42d9-b5c6-698d6eade389 http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 549d3ef5-a05e-4b8e-acba-3d70f26a82f6 http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 4dd7824b-4b41-4402-bc42-de016e1bfcd9 http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 29386b12-9af9-4c21-9f82-858998b388bb http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 251ddff9-1588-4790-8f40-cd6ab1d9b7fc http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 1dcedab1-cf3c-46dc-859b-cc3fb63e8375 http://snomed.info/sct 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 1cea8432-f8a0-4746-ab5c-8f195fc07c55 http://snomed.info/sct 94260004 +a7eb2ce7-1075-426c-addd-957b861b0e55 eaf99c09-419d-4162-8417-5a9d7e042cd4 http://snomed.info/sct 33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55 e35f5823-4533-49e5-9652-79d733be6bef http://snomed.info/sct 33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55 b77b04ef-5eda-418c-a6fb-528a2d0a171a http://snomed.info/sct 33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55 8ddb74fc-46d5-4e94-a0ee-6761b292ae95 http://snomed.info/sct 33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55 38171e22-b35d-4161-be15-80243be37b99 http://snomed.info/sct 33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55 31925a68-7a48-412c-b4f8-aef92498dda1 http://snomed.info/sct 33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55 258191b8-2b42-4473-9ba2-be0ba3561767 http://snomed.info/sct 33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55 1146cf52-573f-4667-97c3-abf235f9a83b http://snomed.info/sct 33737001 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 da8db730-f051-42d0-a2db-a3577d96e9bd http://snomed.info/sct 53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 d12274a5-9e03-4c78-ae3e-6cc27e91d737 http://snomed.info/sct 53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 abecd476-3911-4a6b-a1c6-b8ecea2fe63e http://snomed.info/sct 53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc http://snomed.info/sct 53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 http://snomed.info/sct 53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 39170d67-8205-4636-84d7-d8575115d14c http://snomed.info/sct 53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 31393667-dd7f-4c94-90c3-6cde75c67d3e http://snomed.info/sct 53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 21372b39-2be0-4d0d-85c4-5d7e250d8f78 http://snomed.info/sct 53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 1452c50c-b0b9-472c-afb7-2f9e5a3c4717 http://snomed.info/sct 53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 01fac610-c309-46f8-9a0f-6b0f55c9915a http://snomed.info/sct 53741008 +beff242e-580b-47c0-9844-c1a68c36c5bf c879c300-7fdf-4b53-aa6a-a2b4a266b30c http://snomed.info/sct 444814009 +beff242e-580b-47c0-9844-c1a68c36c5bf 7ed34fb1-0bbf-4c41-a101-1316ec483aa7 http://snomed.info/sct 444814009 +beff242e-580b-47c0-9844-c1a68c36c5bf 0447de38-1f2a-411c-9fd2-a9c62ab1f221 http://snomed.info/sct 444814009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 ad679e19-3e17-4c67-8b0d-dd4f7d862207 http://snomed.info/sct 195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 7d18555a-54c9-4c0a-bf3b-6305d0392a2a http://snomed.info/sct 195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 6464e20b-55ec-4685-be3e-55bc3b9602d4 http://snomed.info/sct 195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 5ef84858-0480-4a34-8f43-fc962fe627b2 http://snomed.info/sct 195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 5378726d-7f2b-4c83-9762-eaf385915fa7 http://snomed.info/sct 195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 475461a2-3bd2-43fd-a5aa-7ce424203ae8 http://snomed.info/sct 195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 4025ceb3-04a6-41fb-8569-16a8dcce7ccc http://snomed.info/sct 195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 http://snomed.info/sct 195662009 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/resolveAndCodingLiteralColumn.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/resolveAndCodingLiteralColumn.csv deleted file mode 100644 index 2638c04650..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/resolveAndCodingLiteralColumn.csv +++ /dev/null @@ -1,10 +0,0 @@ -http://loinc.org,http://snomed.info/sct|372823004 -http://loinc.org,http://snomed.info/sct|372823004 -http://loinc.org,http://snomed.info/sct|372823004 -http://loinc.org,http://snomed.info/sct|372823004 -http://loinc.org,http://snomed.info/sct|372823004 -http://loinc.org,http://snomed.info/sct|372823004 -http://loinc.org,http://snomed.info/sct|372823004 -http://loinc.org,http://snomed.info/sct|372823004 -http://loinc.org,http://snomed.info/sct|372823004 -http://loinc.org,http://snomed.info/sct|372823004 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/resolveAndCodingLiteralColumn.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/resolveAndCodingLiteralColumn.tsv new file mode 100644 index 0000000000..8accc1491d --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/resolveAndCodingLiteralColumn.tsv @@ -0,0 +1,10 @@ +http://loinc.org http://snomed.info/sct|372823004 +http://loinc.org http://snomed.info/sct|372823004 +http://loinc.org http://snomed.info/sct|372823004 +http://loinc.org http://snomed.info/sct|372823004 +http://loinc.org http://snomed.info/sct|372823004 +http://loinc.org http://snomed.info/sct|372823004 +http://loinc.org http://snomed.info/sct|372823004 +http://loinc.org http://snomed.info/sct|372823004 +http://loinc.org http://snomed.info/sct|372823004 +http://loinc.org http://snomed.info/sct|372823004 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/simpleQueryWithAliases.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/simpleQueryWithAliases.csv deleted file mode 100644 index 6e61211011..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/simpleQueryWithAliases.csv +++ /dev/null @@ -1,4 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,female,Ophelia894,10 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,female,Cherryl901,5 -9360820c-8602-4335-8b50-c88d627a0c20,female,Karina848,16 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,female,Su690,8 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/simpleQueryWithAliases.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/simpleQueryWithAliases.tsv new file mode 100644 index 0000000000..203a1f4b2a --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/simpleQueryWithAliases.tsv @@ -0,0 +1,4 @@ +121503c8-9564-4b48-9086-a22df717948e female Ophelia894 10 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 female Cherryl901 5 +9360820c-8602-4335-8b50-c88d627a0c20 female Karina848 16 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 female Su690 8 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv deleted file mode 100644 index 7f9bcdb827..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.csv +++ /dev/null @@ -1,12 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,Ophelia894 -2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Arianne -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Moyna -9360820c-8602-4335-8b50-c88d627a0c20,Karina848 -9360820c-8602-4335-8b50-c88d627a0c20,Karina848 -a7eb2ce7-1075-426c-addd-957b861b0e55,Shirley182 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Gilberto712 -beff242e-580b-47c0-9844-c1a68c36c5bf,Guy979 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Su690 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.tsv new file mode 100644 index 0000000000..5b63e62ccc --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/structuredResult.tsv @@ -0,0 +1,12 @@ +121503c8-9564-4b48-9086-a22df717948e Ophelia894 +2b36c1e2-bbe1-45ae-8124-4adad2677702 Pedro316 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 Cherryl901 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Arianne +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Seymour882 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Moyna +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 +a7eb2ce7-1075-426c-addd-957b861b0e55 Shirley182 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 Gilberto712 +beff242e-580b-47c0-9844-c1a68c36c5bf Guy979 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 Su690 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv deleted file mode 100644 index deb68ddc68..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.csv +++ /dev/null @@ -1,12 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,Gleichner915,Ophelia894 -2b36c1e2-bbe1-45ae-8124-4adad2677702,Ulibarri312,Pedro316 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,Heller342,Cherryl901 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437,Seymour882 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437,Moyna -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Burchard,Arianne -9360820c-8602-4335-8b50-c88d627a0c20,Oberbrunner298,Karina848 -9360820c-8602-4335-8b50-c88d627a0c20,Wuckert783,Karina848 -a7eb2ce7-1075-426c-addd-957b861b0e55,Botsford977,Shirley182 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,MacGyver246,Gilberto712 -beff242e-580b-47c0-9844-c1a68c36c5bf,Towne435,Guy979 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Ebert178,Su690 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.tsv new file mode 100644 index 0000000000..940c4cf0a2 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering1.tsv @@ -0,0 +1,12 @@ +121503c8-9564-4b48-9086-a22df717948e Gleichner915 Ophelia894 +2b36c1e2-bbe1-45ae-8124-4adad2677702 Ulibarri312 Pedro316 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 Heller342 Cherryl901 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Krajcik437 Seymour882 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Krajcik437 Moyna +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Burchard Arianne +9360820c-8602-4335-8b50-c88d627a0c20 Oberbrunner298 Karina848 +9360820c-8602-4335-8b50-c88d627a0c20 Wuckert783 Karina848 +a7eb2ce7-1075-426c-addd-957b861b0e55 Botsford977 Shirley182 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 MacGyver246 Gilberto712 +beff242e-580b-47c0-9844-c1a68c36c5bf Towne435 Guy979 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 Ebert178 Su690 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv deleted file mode 100644 index cae596bd51..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.csv +++ /dev/null @@ -1,12 +0,0 @@ -Ophelia894,121503c8-9564-4b48-9086-a22df717948e,Gleichner915 -Pedro316,2b36c1e2-bbe1-45ae-8124-4adad2677702,Ulibarri312 -Cherryl901,7001ad9c-34d2-4eb5-8165-5fdc2147f469,Heller342 -Seymour882,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437 -Moyna,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437 -Arianne,8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Burchard -Karina848,9360820c-8602-4335-8b50-c88d627a0c20,Oberbrunner298 -Karina848,9360820c-8602-4335-8b50-c88d627a0c20,Wuckert783 -Shirley182,a7eb2ce7-1075-426c-addd-957b861b0e55,Botsford977 -Gilberto712,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,MacGyver246 -Guy979,beff242e-580b-47c0-9844-c1a68c36c5bf,Towne435 -Su690,e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Ebert178 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.tsv new file mode 100644 index 0000000000..d0904c1e96 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/toleranceOfColumnOrdering2.tsv @@ -0,0 +1,12 @@ +Ophelia894 121503c8-9564-4b48-9086-a22df717948e Gleichner915 +Pedro316 2b36c1e2-bbe1-45ae-8124-4adad2677702 Ulibarri312 +Cherryl901 7001ad9c-34d2-4eb5-8165-5fdc2147f469 Heller342 +Seymour882 8ee183e2-b3c0-4151-be94-b945d6aa8c6d Krajcik437 +Moyna 8ee183e2-b3c0-4151-be94-b945d6aa8c6d Krajcik437 +Arianne 8ee183e2-b3c0-4151-be94-b945d6aa8c6d Burchard +Karina848 9360820c-8602-4335-8b50-c88d627a0c20 Oberbrunner298 +Karina848 9360820c-8602-4335-8b50-c88d627a0c20 Wuckert783 +Shirley182 a7eb2ce7-1075-426c-addd-957b861b0e55 Botsford977 +Gilberto712 bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 MacGyver246 +Guy979 beff242e-580b-47c0-9844-c1a68c36c5bf Towne435 +Su690 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 Ebert178 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/whereInMultipleColumns.csv b/fhir-server/src/test/resources/responses/ExtractQueryTest/whereInMultipleColumns.csv deleted file mode 100644 index de173153d3..0000000000 --- a/fhir-server/src/test/resources/responses/ExtractQueryTest/whereInMultipleColumns.csv +++ /dev/null @@ -1,9 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,f34e77c9-df31-49c4-92e2-e871fa76026e,999-77-4115 -2b36c1e2-bbe1-45ae-8124-4adad2677702,2f9fd91d-9096-40e8-8307-825f68780599,999-42-8004 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,11fd8dbd-dd20-479f-b82b-558fed60bae7,999-14-8633 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,0dc85075-4f59-4e4f-b75d-a2f601d0cf24,999-21-1297 -9360820c-8602-4335-8b50-c88d627a0c20,eb4458b4-cfda-463e-ba15-eaad8d291d1d,999-23-9134 -a7eb2ce7-1075-426c-addd-957b861b0e55,3d67a22e-4e26-4d7a-a586-ea79114faa50,999-36-4411 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,e993ce4e-4cb9-4e7c-b082-a9144e9efb03,999-38-1164 -beff242e-580b-47c0-9844-c1a68c36c5bf,1f276fc3-7e91-4fc9-a287-be19228e8807,999-56-3056 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,2cc8ffdd-6233-4dd4-ba71-36eccb8204e2,999-43-1135 diff --git a/fhir-server/src/test/resources/responses/ExtractQueryTest/whereInMultipleColumns.tsv b/fhir-server/src/test/resources/responses/ExtractQueryTest/whereInMultipleColumns.tsv new file mode 100644 index 0000000000..88c9c12de4 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ExtractQueryTest/whereInMultipleColumns.tsv @@ -0,0 +1,9 @@ +121503c8-9564-4b48-9086-a22df717948e f34e77c9-df31-49c4-92e2-e871fa76026e 999-77-4115 +2b36c1e2-bbe1-45ae-8124-4adad2677702 2f9fd91d-9096-40e8-8307-825f68780599 999-42-8004 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 11fd8dbd-dd20-479f-b82b-558fed60bae7 999-14-8633 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 0dc85075-4f59-4e4f-b75d-a2f601d0cf24 999-21-1297 +9360820c-8602-4335-8b50-c88d627a0c20 eb4458b4-cfda-463e-ba15-eaad8d291d1d 999-23-9134 +a7eb2ce7-1075-426c-addd-957b861b0e55 3d67a22e-4e26-4d7a-a586-ea79114faa50 999-36-4411 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 e993ce4e-4cb9-4e7c-b082-a9144e9efb03 999-38-1164 +beff242e-580b-47c0-9844-c1a68c36c5bf 1f276fc3-7e91-4fc9-a287-be19228e8807 999-56-3056 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 2cc8ffdd-6233-4dd4-ba71-36eccb8204e2 999-43-1135 diff --git a/fhir-server/src/test/resources/responses/ExtractTest/extract.csv b/fhir-server/src/test/resources/responses/ExtractTest/extract.tsv similarity index 100% rename from fhir-server/src/test/resources/responses/ExtractTest/extract.csv rename to fhir-server/src/test/resources/responses/ExtractTest/extract.tsv diff --git a/fhir-server/src/test/resources/responses/ParserTest/ageAtTimeOfEncounter.csv b/fhir-server/src/test/resources/responses/ParserTest/ageAtTimeOfEncounter.csv deleted file mode 100644 index c3c42afdb5..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/ageAtTimeOfEncounter.csv +++ /dev/null @@ -1,217 +0,0 @@ -0134901a-4a69-4b39-92ea-d6f48ec83c6e,false -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,false -0250a217-a663-403b-a708-5d14eadf0c40,false -02b993c0-b358-481c-b0d0-0767387feb9f,false -0364073f-91d8-47a1-b8b0-107c6318a691,false -04945715-8ad5-4d8f-bfda-ae26662a3610,false -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,false -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,false -0af4a77e-892e-4b3f-9110-4c5224782250,false -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,false -102e16c5-4920-4dad-b142-0b280b2aacad,false -11dafd5c-85e7-4275-a147-89a63641e35e,false -12b212d8-bc6e-415a-93b0-594381726668,false -12df1bed-5472-4c48-8e88-2cbb3e5eab33,false -151e3048-66ad-4411-8753-677877e3bf0a,false -16a3408d-c927-4d02-a1ae-cad32419caab,false -16d280a2-c61f-487f-9034-22e884158969,false -17bcf2ea-d921-4715-91c5-6b15226b33d3,false -18ebf5ea-b0a2-4333-9e9c-40217de809ff,false -19502b5a-2031-487d-9d9c-2001f959408b,false -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,false -1db1c71b-9eeb-4a98-abc1-eb699b38510c,false -1f3443ee-d15e-4894-b18e-185cfadfcdea,false -1fd714a6-48b4-425a-99b3-ba5c1a995cce,false -202131ae-78bb-4104-89b0-fb90645760f6,false -20fbcb6e-d793-4b05-8e29-8ff82572b0db,false -21995497-0eb8-4c0b-8c23-e6a829f3d596,false -21c6e0fc-bf47-4f80-b1ff-85b940445bdf,false -224e538d-3622-4f5e-b772-211ed358d8de,false -23780032-f3bb-4b40-af2e-3fa6b1677376,false -24c4dd5b-748b-4165-a0cc-2867b76c2dc2,false -2514cdf0-1216-4c5e-bfa8-0c11c20c6b93,false -274a2e49-9170-4945-bca2-ab6ef4bded75,false -2830e99a-e6b0-411b-a051-7ea1e9f815d1,false -29d4e919-2bd2-44e6-bb8a-380a780f60ff,false -2aff9edd-def2-487a-b435-a162e11a303c,false -2bd766e5-60e4-4469-bb97-54f0503a1eb0,false -2bddd98c-86e3-4ae0-9dc6-87da16ab6267,false -2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5,false -2d2e07ec-e697-4384-a679-867e93740921,false -2de1f829-c9d2-4f22-bf39-536dcb82fc3e,false -2e8eb256-84c9-499a-8bf6-bb39504373e1,false -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,false -2ee2ab26-09fb-4e12-bf21-9fed3b4c38de,false -2fc75f11-2097-4e1e-8390-dbff3e844b7a,false -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,false -308eba53-29fa-489a-97dc-741b077841a7,false -30ae4a13-0cc6-4d12-a5dd-fa9288efcc09,false -31d4c712-75b1-4ecb-9d1a-ccc4b8453826,false -3397a796-894d-409c-97de-a9e6f3f88198,false -359cb842-c25b-429f-ba9b-d52f171e5631,false -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,false -374fd699-6b74-4500-9d60-2473c7e0364f,false -395019b6-84e8-4919-8b8b-ac64e4a6eade,false -39efc9b8-a75f-43a3-98a2-5bdc1c8207d2,false -3ddf46fe-b5be-456d-810f-ea6a7402236d,false -4148ac36-1dcb-4e96-89cd-355e7e8f919b,false -41d26b1c-57b9-408c-b955-bf0ee1db4809,false -4215b5d0-bdd9-4222-a04a-81bb637d60af,false -42a9a333-d09d-4b9e-af96-1f02af26bac3,true -42aa48c7-68cc-4bee-9730-cff29f0e36c5,false -4506aa76-9d0d-40e4-85bc-5735f74522a0,false -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,false -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,false -48cc2275-a478-4ade-b076-cf38e1d16017,false -498f4b18-bd4c-470d-9cde-2fca70ec8964,false -4a464bb5-9373-4f1b-9c03-053c64797114,false -4adcaf72-5241-4877-b61c-f34060576c50,false -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,false -4cd95073-6db3-4eb2-ac4b-0aacb182b352,false -4cfe72fe-21a0-4201-87b6-ef93695d2495,false -4db144d3-a596-4718-a50a-8ac9175ad386,false -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,false -4f14ca7f-5332-4263-a7b2-f0a838380309,false -4f342a71-dec3-403e-970b-e4c21b9eb98b,false -4f3d1e93-a28f-446e-91af-2baf0168b82b,false -50eac25a-3761-4de3-8a69-aae52e658300,true -51241857-a7a4-4517-96e3-21f797581f89,false -522e2abe-ad96-4d1a-b5a5-748faa997531,false -54c750aa-570a-4e8e-9a02-418781923e39,false -55f17f44-287f-41aa-a1bc-d1c0104af36e,false -56999896-e36b-4e63-a6b6-dbb85dfe1936,false -578cc35c-0982-4d72-a729-b304c026f075,false -5abe533b-ae5f-47ad-8746-f60caf7483c0,false -5cedf18a-fc44-4c39-a80f-2106a0d76934,false -5e1fe0f2-4517-462b-8287-7824f9a60f4f,false -5f64131b-d410-449a-9de6-35415919fec5,false -62059efc-7607-41c9-a3ba-70948f6a8a1d,false -634f0889-3a83-484a-bcc8-86f48e333c7b,false -67e5cf95-236b-4f75-abc5-034ca2966448,false -67fc12fc-bb9f-40f1-9051-127a788b3081,false -683714ad-5194-49e7-9b8f-4e306cf68ae1,false -6954c5c2-dd77-490a-9152-f1d78eff913d,false -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,false -6af73cae-2e4e-485f-a74d-6f4307eb3af3,false -6c84348c-5065-4e89-9412-bfda023683f2,false -6d47a2fe-3645-4c5c-bebe-1b822eff197f,false -6e187f47-91a1-4217-a185-31b6f01db225,false -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,false -721e4027-a080-40d8-bc4a-43cd33477611,false -72788e7f-1bf1-40b5-a1f1-27c669dc7278,false -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,false -749babeb-6883-4bd4-92a5-bec52769071c,false -75a09d4f-eef2-4404-a386-dfcb408ec9ed,false -777aa5f2-2a09-4aed-92ea-912694e28b48,false -79be3b69-23d8-4408-8f01-68d993e63b6c,false -7ac56d5a-32a7-4b40-a956-356e3006faed,false -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,false -7e7322a6-7912-4101-b3be-d134245a0626,false -7ea43fc6-b1f7-46be-a308-5ecb275a1081,false -7f410064-638a-4477-85c4-97fc2bb14a49,false -8089a9ac-9e71-4487-a231-f720e4bc8d3e,false -836b2e59-fb97-4014-ab0c-d5a5dc750969,false -85481b3f-c75e-40cd-bacd-b0afb79e893c,false -85c997bf-63d9-4ebb-8e0b-320de3dddc6c,false -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,false -86415df5-7e47-4637-9e09-aaad9e7628d1,false -8743e69b-17aa-44ff-b8fa-4f582665efc6,false -87b04a97-1d33-4548-aec9-3d2856070702,false -88b16960-bfff-41d0-81e4-9a4630834a00,false -89f260bb-68b4-4dec-91f4-d52e673e0ff7,false -8a1908f7-47cc-4f82-859c-781a193e9901,false -8bc7af32-e5ad-4849-ba4d-b446db833ab4,false -8be81657-8ba6-4ec5-8ff9-4809367096ea,false -8dc2ce26-aec7-43c0-9771-350af4257ad8,false -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,false -8f76d729-9f74-4cfd-be2a-8b033b568e18,false -900ce9fe-02d3-476b-902a-9467767ecdcf,false -922e2443-9cc5-421f-8310-9cd23f6e9f2d,false -96b2282d-1384-4cfb-9958-f009fb501626,false -97b42d67-8691-433d-8a31-dada076162ae,false -984067fc-3dfc-47b7-8ee0-4d9b27d43860,false -990c21ab-433b-4920-873e-f9dfc7103d9d,false -99fc3558-79f3-4d14-9aa1-ff63f621ecd9,false -9b2eb632-6f1a-4e97-912b-3c8378a1b11c,false -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,false -9bc516d4-7c82-4340-a90c-bb493f96fbe4,false -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,false -9ed8703e-3b40-424c-9e98-b3b404051f99,false -a1228f20-7c50-4d3a-b88c-2d277ca79d79,false -a221198d-000e-497b-8b70-bb4d37f7bbe1,false -a2c3ee1d-ec35-4b26-9bab-12de3f47d604,false -a6790cbd-8349-432e-a976-5dfc07451203,false -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,false -a6cb423a-7571-46df-83e2-ccc6820adb36,false -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,false -acb2329d-b1c3-45c3-ad28-91a09aa521e1,false -add6d754-a075-4693-a33a-c061c9a368ff,false -ae7ddaef-4911-418c-8401-d978617e145b,false -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,false -b00df815-7528-4967-bfe2-311130d91c21,false -b07fb98d-2da4-423a-83b4-7a673de93096,false -b0d337c5-3555-4817-ba59-4ceea5ad8a91,false -b1ccd55a-6b88-4240-b20c-ca893f36e23b,false -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,false -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,false -b6900c21-d310-4fb4-8b8f-176a09c91989,false -b7772635-1801-48e4-a442-f4aaa6860544,false -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,false -bac374c0-50e8-4a5c-947b-82a8e9a747a9,false -baf48725-f0f9-4357-a71d-b1104910deae,false -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,true -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,false -bf895c48-1d52-4780-8e9a-532d69356d28,false -bfc93cb7-e53a-49f7-b86e-81d6a13cfdea,false -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,false -c3ec1f2e-1411-41e4-8370-acd5a8f68cf4,false -c57d51a7-45de-41b9-92fc-efd0634effaf,false -c5c6eed5-aec6-4b8a-b689-adbb219c2267,false -c892b1d7-7485-407d-8883-54fb7fecebc1,false -c90aae7e-5704-4e13-8794-41ab377193bd,false -ca3e0486-fd6b-4a13-a741-3a47760b412d,false -ca7f269d-3794-4994-8c46-d8e9f2aa6378,false -cc56af6d-25c6-480e-9767-da02a55551da,false -cdb8493c-e3b0-447f-b16b-e58dd64660f3,false -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,false -cfcbe34a-edc5-4442-bc05-5927fa00336b,false -d05461d6-13bf-402c-890d-db6404933f7c,false -d080c116-681a-494a-a928-45924109b49d,false -d535b213-2abc-438f-aa6a-c06476d60b09,false -d6988116-3c63-44f8-9f94-9e9d51cc5a37,false -d79728e1-8834-4f4a-8edc-ce18aca18be6,true -d7a48a26-7731-4046-bf22-78f0b8084eb9,false -d90676d2-ce74-4867-a00f-6dbffa7c00be,false -d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2,false -db79f75d-af3c-4f82-b4e5-9b5d26598eef,false -def5fd23-2b74-4c64-85e9-fac27e626bb7,false -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,false -e0dfbc08-45ad-4a81-b648-7e65c066f673,false -e1040d58-53bc-4467-8101-ca53b772cede,false -e1c2ad9f-6f61-4f16-805a-2f405b63659a,false -e2387a81-5ff5-4daf-b2e8-881998d0d917,false -e26ae29a-213a-4f79-98c2-cc81cf2f451d,false -e363eb67-64c7-440f-93fd-5c8787c69a85,false -e4358f28-62a8-4e06-b2bd-cb00d3310349,false -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,false -e8c41168-a093-40eb-8baa-6802d24f554a,false -e8eba267-fecb-477e-9625-771fbcfc3cba,false -ede7745a-8f3e-4e20-9f16-33756be7ab6c,false -ee24bf89-81a3-4259-a382-86917f3829d4,false -f1293c5e-c997-4ba9-8bfd-438e7a840dc8,false -f18b08bd-40da-43c1-87ec-4ba23542635f,false -f19eefca-c1ad-4860-bdda-4674a631d464,false -f28931e5-1f35-4f9f-a02e-78398735ea67,false -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,false -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,false -f3aa491a-4dbd-4436-b674-18b2177dcf18,false -f3c2f842-6aa3-42c9-a0f3-895c40456868,false -f3eb0c38-2571-44e7-be39-732eb52cf1df,false -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,false -f77a8561-5adc-452a-ac9a-76273b6a6678,true -f883f2ca-d523-4c9f-86b2-0add137901de,false -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,false -fc771883-3bd6-46d9-9626-a130e1b902de,false -fcab9efe-fb05-42d6-b366-ce6db1cc4093,false -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,false -fce20464-ff98-4051-8714-6b9584e52740,false diff --git a/fhir-server/src/test/resources/responses/ParserTest/ageAtTimeOfEncounter.tsv b/fhir-server/src/test/resources/responses/ParserTest/ageAtTimeOfEncounter.tsv new file mode 100644 index 0000000000..ecfc10a83f --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/ageAtTimeOfEncounter.tsv @@ -0,0 +1,217 @@ +0134901a-4a69-4b39-92ea-d6f48ec83c6e false +01ac1476-0c9c-4cd7-82a6-4ff526018e9a false +0250a217-a663-403b-a708-5d14eadf0c40 false +02b993c0-b358-481c-b0d0-0767387feb9f false +0364073f-91d8-47a1-b8b0-107c6318a691 false +04945715-8ad5-4d8f-bfda-ae26662a3610 false +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 false +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 false +0af4a77e-892e-4b3f-9110-4c5224782250 false +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd false +102e16c5-4920-4dad-b142-0b280b2aacad false +11dafd5c-85e7-4275-a147-89a63641e35e false +12b212d8-bc6e-415a-93b0-594381726668 false +12df1bed-5472-4c48-8e88-2cbb3e5eab33 false +151e3048-66ad-4411-8753-677877e3bf0a false +16a3408d-c927-4d02-a1ae-cad32419caab false +16d280a2-c61f-487f-9034-22e884158969 false +17bcf2ea-d921-4715-91c5-6b15226b33d3 false +18ebf5ea-b0a2-4333-9e9c-40217de809ff false +19502b5a-2031-487d-9d9c-2001f959408b false +1cb88e7a-3db6-4a88-9b68-b0e1492114bc false +1db1c71b-9eeb-4a98-abc1-eb699b38510c false +1f3443ee-d15e-4894-b18e-185cfadfcdea false +1fd714a6-48b4-425a-99b3-ba5c1a995cce false +202131ae-78bb-4104-89b0-fb90645760f6 false +20fbcb6e-d793-4b05-8e29-8ff82572b0db false +21995497-0eb8-4c0b-8c23-e6a829f3d596 false +21c6e0fc-bf47-4f80-b1ff-85b940445bdf false +224e538d-3622-4f5e-b772-211ed358d8de false +23780032-f3bb-4b40-af2e-3fa6b1677376 false +24c4dd5b-748b-4165-a0cc-2867b76c2dc2 false +2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 false +274a2e49-9170-4945-bca2-ab6ef4bded75 false +2830e99a-e6b0-411b-a051-7ea1e9f815d1 false +29d4e919-2bd2-44e6-bb8a-380a780f60ff false +2aff9edd-def2-487a-b435-a162e11a303c false +2bd766e5-60e4-4469-bb97-54f0503a1eb0 false +2bddd98c-86e3-4ae0-9dc6-87da16ab6267 false +2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5 false +2d2e07ec-e697-4384-a679-867e93740921 false +2de1f829-c9d2-4f22-bf39-536dcb82fc3e false +2e8eb256-84c9-499a-8bf6-bb39504373e1 false +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f false +2ee2ab26-09fb-4e12-bf21-9fed3b4c38de false +2fc75f11-2097-4e1e-8390-dbff3e844b7a false +2ffd6142-b75b-406f-bc06-cdb1c02eaefc false +308eba53-29fa-489a-97dc-741b077841a7 false +30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 false +31d4c712-75b1-4ecb-9d1a-ccc4b8453826 false +3397a796-894d-409c-97de-a9e6f3f88198 false +359cb842-c25b-429f-ba9b-d52f171e5631 false +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 false +374fd699-6b74-4500-9d60-2473c7e0364f false +395019b6-84e8-4919-8b8b-ac64e4a6eade false +39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 false +3ddf46fe-b5be-456d-810f-ea6a7402236d false +4148ac36-1dcb-4e96-89cd-355e7e8f919b false +41d26b1c-57b9-408c-b955-bf0ee1db4809 false +4215b5d0-bdd9-4222-a04a-81bb637d60af false +42a9a333-d09d-4b9e-af96-1f02af26bac3 true +42aa48c7-68cc-4bee-9730-cff29f0e36c5 false +4506aa76-9d0d-40e4-85bc-5735f74522a0 false +45b25ced-6eed-4fca-8ec1-b7d32ea13efe false +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 false +48cc2275-a478-4ade-b076-cf38e1d16017 false +498f4b18-bd4c-470d-9cde-2fca70ec8964 false +4a464bb5-9373-4f1b-9c03-053c64797114 false +4adcaf72-5241-4877-b61c-f34060576c50 false +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 false +4cd95073-6db3-4eb2-ac4b-0aacb182b352 false +4cfe72fe-21a0-4201-87b6-ef93695d2495 false +4db144d3-a596-4718-a50a-8ac9175ad386 false +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 false +4f14ca7f-5332-4263-a7b2-f0a838380309 false +4f342a71-dec3-403e-970b-e4c21b9eb98b false +4f3d1e93-a28f-446e-91af-2baf0168b82b false +50eac25a-3761-4de3-8a69-aae52e658300 true +51241857-a7a4-4517-96e3-21f797581f89 false +522e2abe-ad96-4d1a-b5a5-748faa997531 false +54c750aa-570a-4e8e-9a02-418781923e39 false +55f17f44-287f-41aa-a1bc-d1c0104af36e false +56999896-e36b-4e63-a6b6-dbb85dfe1936 false +578cc35c-0982-4d72-a729-b304c026f075 false +5abe533b-ae5f-47ad-8746-f60caf7483c0 false +5cedf18a-fc44-4c39-a80f-2106a0d76934 false +5e1fe0f2-4517-462b-8287-7824f9a60f4f false +5f64131b-d410-449a-9de6-35415919fec5 false +62059efc-7607-41c9-a3ba-70948f6a8a1d false +634f0889-3a83-484a-bcc8-86f48e333c7b false +67e5cf95-236b-4f75-abc5-034ca2966448 false +67fc12fc-bb9f-40f1-9051-127a788b3081 false +683714ad-5194-49e7-9b8f-4e306cf68ae1 false +6954c5c2-dd77-490a-9152-f1d78eff913d false +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 false +6af73cae-2e4e-485f-a74d-6f4307eb3af3 false +6c84348c-5065-4e89-9412-bfda023683f2 false +6d47a2fe-3645-4c5c-bebe-1b822eff197f false +6e187f47-91a1-4217-a185-31b6f01db225 false +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 false +721e4027-a080-40d8-bc4a-43cd33477611 false +72788e7f-1bf1-40b5-a1f1-27c669dc7278 false +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e false +749babeb-6883-4bd4-92a5-bec52769071c false +75a09d4f-eef2-4404-a386-dfcb408ec9ed false +777aa5f2-2a09-4aed-92ea-912694e28b48 false +79be3b69-23d8-4408-8f01-68d993e63b6c false +7ac56d5a-32a7-4b40-a956-356e3006faed false +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 false +7e7322a6-7912-4101-b3be-d134245a0626 false +7ea43fc6-b1f7-46be-a308-5ecb275a1081 false +7f410064-638a-4477-85c4-97fc2bb14a49 false +8089a9ac-9e71-4487-a231-f720e4bc8d3e false +836b2e59-fb97-4014-ab0c-d5a5dc750969 false +85481b3f-c75e-40cd-bacd-b0afb79e893c false +85c997bf-63d9-4ebb-8e0b-320de3dddc6c false +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b false +86415df5-7e47-4637-9e09-aaad9e7628d1 false +8743e69b-17aa-44ff-b8fa-4f582665efc6 false +87b04a97-1d33-4548-aec9-3d2856070702 false +88b16960-bfff-41d0-81e4-9a4630834a00 false +89f260bb-68b4-4dec-91f4-d52e673e0ff7 false +8a1908f7-47cc-4f82-859c-781a193e9901 false +8bc7af32-e5ad-4849-ba4d-b446db833ab4 false +8be81657-8ba6-4ec5-8ff9-4809367096ea false +8dc2ce26-aec7-43c0-9771-350af4257ad8 false +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 false +8f76d729-9f74-4cfd-be2a-8b033b568e18 false +900ce9fe-02d3-476b-902a-9467767ecdcf false +922e2443-9cc5-421f-8310-9cd23f6e9f2d false +96b2282d-1384-4cfb-9958-f009fb501626 false +97b42d67-8691-433d-8a31-dada076162ae false +984067fc-3dfc-47b7-8ee0-4d9b27d43860 false +990c21ab-433b-4920-873e-f9dfc7103d9d false +99fc3558-79f3-4d14-9aa1-ff63f621ecd9 false +9b2eb632-6f1a-4e97-912b-3c8378a1b11c false +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 false +9bc516d4-7c82-4340-a90c-bb493f96fbe4 false +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 false +9ed8703e-3b40-424c-9e98-b3b404051f99 false +a1228f20-7c50-4d3a-b88c-2d277ca79d79 false +a221198d-000e-497b-8b70-bb4d37f7bbe1 false +a2c3ee1d-ec35-4b26-9bab-12de3f47d604 false +a6790cbd-8349-432e-a976-5dfc07451203 false +a6b084fb-ada7-480a-9adc-f180d4eb1e2a false +a6cb423a-7571-46df-83e2-ccc6820adb36 false +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 false +acb2329d-b1c3-45c3-ad28-91a09aa521e1 false +add6d754-a075-4693-a33a-c061c9a368ff false +ae7ddaef-4911-418c-8401-d978617e145b false +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 false +b00df815-7528-4967-bfe2-311130d91c21 false +b07fb98d-2da4-423a-83b4-7a673de93096 false +b0d337c5-3555-4817-ba59-4ceea5ad8a91 false +b1ccd55a-6b88-4240-b20c-ca893f36e23b false +b1d4dc41-07ae-44db-ae17-1df86c5a62cf false +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa false +b6900c21-d310-4fb4-8b8f-176a09c91989 false +b7772635-1801-48e4-a442-f4aaa6860544 false +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 false +bac374c0-50e8-4a5c-947b-82a8e9a747a9 false +baf48725-f0f9-4357-a71d-b1104910deae false +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 true +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb false +bf895c48-1d52-4780-8e9a-532d69356d28 false +bfc93cb7-e53a-49f7-b86e-81d6a13cfdea false +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a false +c3ec1f2e-1411-41e4-8370-acd5a8f68cf4 false +c57d51a7-45de-41b9-92fc-efd0634effaf false +c5c6eed5-aec6-4b8a-b689-adbb219c2267 false +c892b1d7-7485-407d-8883-54fb7fecebc1 false +c90aae7e-5704-4e13-8794-41ab377193bd false +ca3e0486-fd6b-4a13-a741-3a47760b412d false +ca7f269d-3794-4994-8c46-d8e9f2aa6378 false +cc56af6d-25c6-480e-9767-da02a55551da false +cdb8493c-e3b0-447f-b16b-e58dd64660f3 false +cf67a8d4-48e2-4dc9-a521-6aabcff0330b false +cfcbe34a-edc5-4442-bc05-5927fa00336b false +d05461d6-13bf-402c-890d-db6404933f7c false +d080c116-681a-494a-a928-45924109b49d false +d535b213-2abc-438f-aa6a-c06476d60b09 false +d6988116-3c63-44f8-9f94-9e9d51cc5a37 false +d79728e1-8834-4f4a-8edc-ce18aca18be6 true +d7a48a26-7731-4046-bf22-78f0b8084eb9 false +d90676d2-ce74-4867-a00f-6dbffa7c00be false +d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2 false +db79f75d-af3c-4f82-b4e5-9b5d26598eef false +def5fd23-2b74-4c64-85e9-fac27e626bb7 false +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 false +e0dfbc08-45ad-4a81-b648-7e65c066f673 false +e1040d58-53bc-4467-8101-ca53b772cede false +e1c2ad9f-6f61-4f16-805a-2f405b63659a false +e2387a81-5ff5-4daf-b2e8-881998d0d917 false +e26ae29a-213a-4f79-98c2-cc81cf2f451d false +e363eb67-64c7-440f-93fd-5c8787c69a85 false +e4358f28-62a8-4e06-b2bd-cb00d3310349 false +e7f68d35-ae55-4fa4-b0be-0672a5a7186a false +e8c41168-a093-40eb-8baa-6802d24f554a false +e8eba267-fecb-477e-9625-771fbcfc3cba false +ede7745a-8f3e-4e20-9f16-33756be7ab6c false +ee24bf89-81a3-4259-a382-86917f3829d4 false +f1293c5e-c997-4ba9-8bfd-438e7a840dc8 false +f18b08bd-40da-43c1-87ec-4ba23542635f false +f19eefca-c1ad-4860-bdda-4674a631d464 false +f28931e5-1f35-4f9f-a02e-78398735ea67 false +f29330d0-5b32-4f75-b558-a1c7f05c0b4a false +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c false +f3aa491a-4dbd-4436-b674-18b2177dcf18 false +f3c2f842-6aa3-42c9-a0f3-895c40456868 false +f3eb0c38-2571-44e7-be39-732eb52cf1df false +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 false +f77a8561-5adc-452a-ac9a-76273b6a6678 true +f883f2ca-d523-4c9f-86b2-0add137901de false +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c false +fc771883-3bd6-46d9-9626-a130e1b902de false +fcab9efe-fb05-42d6-b366-ce6db1cc4093 false +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 false +fce20464-ff98-4051-8714-6b9584e52740 false diff --git a/fhir-server/src/test/resources/responses/ParserTest/lengthObservationComparison.csv b/fhir-server/src/test/resources/responses/ParserTest/lengthObservationComparison.csv deleted file mode 100644 index e7abc33dc5..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/lengthObservationComparison.csv +++ /dev/null @@ -1,1502 +0,0 @@ -0020b035-7314-4867-9453-7921f158d003,"" -002593d4-d271-46a9-a900-1c498c322988,"" -00e9f82f-f16c-422f-81a3-77fa98958da3,"" -00f02590-e737-42c4-9c73-206cf66b02b7,"" -011f5b11-f944-46ae-9cde-0d802e5c0073,"" -0143f01c-3692-479a-9a72-592260dbbd64,"" -01a4de83-37d0-4612-82eb-2ff2dbc2e02b,"" -01c177be-05bb-4a44-a199-1df387e68d8e,"" -01c17bf0-9240-4149-983e-f0b3ccfb52be,"" -01d57966-1281-42ab-9e05-02d96d146b6e,"" -01e27cc9-c9c0-4c33-82f6-9507394f5f2a,"" -0218f780-899c-4c1b-9167-45e33d02acfb,"" -021bf465-c299-4302-99ca-e29a6d25bda5,"" -022ebe16-f618-4acb-9d99-88e23d686c34,"" -023a9220-3016-4202-8c7d-360d7d3bece9,"" -025aa37b-4ae6-473b-9ae6-c12eece968c5,"" -02741df6-79f0-477c-963c-8b3f8cb4374f,"" -02b87530-10e2-4009-8734-4196642cef1d,"" -02c07924-f5c7-4909-ab49-dec34c051dfc,"" -02c4fa06-3281-46a0-9bb9-c7b99497c91d,false -03365fa5-36d2-41b3-b3d9-1fea9f94c73e,"" -03555f06-db5f-4b8c-aae3-f6d2c547f499,"" -03610d59-94a2-41de-a039-c671241b075a,"" -038ced3d-5233-44c6-9a8c-1f6f9cc4be11,"" -03c7ba3d-8af2-44d1-a3c0-2c56b9fbc7c6,"" -03dadf3e-cdc9-4f63-8a4d-89bae77bd529,false -03e44215-f743-4c4e-8d19-aaf761f54503,"" -0429fff2-2839-45d8-b0bd-1d86aa92f966,true -042bb1e3-e5aa-471e-9e83-bb4a1b90af22,"" -043108b6-98cc-4feb-8ad0-1394cd10a289,"" -04367369-256f-4d47-b1ef-8014109ab46f,"" -04661736-24d8-41f7-9f05-af5682696df0,"" -05048b00-24df-4311-806b-2f9a7cc16380,"" -0531e6b8-2ede-4e12-a91b-cfe7e12e552e,false -054334cd-cfc9-4a1c-acf3-d0824cafd3a5,"" -055824d7-b398-40b2-8fed-bc40552fffce,"" -05643ef2-65e0-4812-b739-4fadfa3c066e,"" -05bed469-760c-439b-9729-0479a6550b76,"" -05c37921-dde8-4b70-869e-82680975ab77,"" -05daccae-2d73-459d-8aea-7ee75966e52b,"" -0624683a-8572-4885-b51d-2197dde395bf,"" -062ab32e-318d-4ff7-81ea-f3be53204988,"" -068d13a5-8b46-4a3b-aca0-ac9ffe70f998,"" -071b684c-70a5-4c93-8289-9630d25496d2,"" -072312b8-c984-4cb4-a4f8-97560368576d,"" -07279076-3064-44aa-b07b-346a7edf6abb,"" -072bd136-7e5d-49a2-b878-aaa2c7fde1f2,"" -0735b01a-c65e-4d59-9016-5e3bed1fcdcf,"" -0785f996-ee5e-46f3-8966-107235de4cde,"" -07906572-63db-4046-8dbf-1ae866664f41,"" -07d441b6-4e92-4d6d-af4b-7f38471fa45e,"" -07dfda29-5378-4a66-ac90-fcb7ec6283ca,false -07f5fcdd-3aa0-4ccd-9122-f9686886dbd4,"" -07f95b0d-656b-4830-ade2-864e58ac5ed6,"" -088cdf48-9645-4dbf-b824-8836d6a83d24,"" -08d37a26-1ec6-4faa-985a-10a9d0ee54a9,"" -092b1b96-4ad3-4716-850e-8056ccd456d8,"" -094a486e-7e50-4717-a0c3-07a456a4cd5f,"" -09541c80-54b4-4bb0-8776-37745a9ac36e,"" -09941e9e-ff5e-44e5-82cd-4c3865e39c01,"" -09ab1f16-7bff-44ec-aed5-8ba400930bbc,"" -0a005143-4645-4ce4-89d5-9d49dca18ec6,"" -0a40e26c-d0ac-4653-a54f-17e23ed9a0ce,"" -0a6c1262-dae3-4ec2-b911-0d1caca68302,"" -0a7887db-203b-4a3a-b5fb-cabe9a886074,"" -0ab96751-edf1-4941-981c-c6bdcba75380,"" -0ae172de-1755-46d4-8729-d3acc96ce82f,"" -0b6ee068-26bf-4f1b-8423-30f1c591861b,"" -0b95920f-7c6d-4b30-89da-51807a6aa557,"" -0bbc7111-589b-460e-a742-20f2131c9c64,"" -0bd78173-c348-41f1-9edd-884e3561ad47,"" -0c4242e4-1c5e-49d3-bf57-3810f8b88d56,"" -0c93a4a8-3b59-41f8-a7a5-22a9691710ab,"" -0c93c94d-b626-40b2-9561-52168c4961ca,"" -0cbe226b-6432-41c9-b3dd-0a14c6d54d7e,"" -0d1af4fe-50f7-4d8e-823f-192f8a08ec8a,"" -0d7a0166-7fb2-4e1a-b1e5-b03a53fb5602,"" -0dbafa74-2f5b-400e-b27e-fddeac8c28ad,"" -0df8e035-a226-425d-9684-a12d0c58b844,"" -0e03654b-d81d-4a62-8f53-b479e8ce3edd,"" -0e11e8ea-dc1b-4e8f-b809-39f0f07c7499,"" -0e8063ca-3601-4741-a2c0-c0381cc6098b,"" -0e92574a-cc09-4564-a2ff-1cdbc89d210c,"" -0e99ca14-e99e-4394-b962-59c66427218d,false -0e9cc169-ca91-4201-a78f-1b0f724507cb,"" -0ed9ee59-d968-443c-b7e4-c3732ec666e3,"" -0edd2d17-3ac8-427c-ae05-40c99b401484,"" -0ee77005-b9a1-4c51-a599-bd233897e431,"" -0eea9efa-148d-41d4-b05a-d39484526333,"" -0f13292a-a654-4473-b78e-c8f1b3328ff2,"" -0f67bae5-3bbc-4c7d-84f4-34583758335d,"" -0f68a16f-1c70-4d73-a9b2-fb384585526b,"" -0f7373d2-3052-4c98-9c04-7074250f44cc,"" -1016a9db-50e6-4c17-9e93-18a97b67269e,"" -1037da65-9451-48d1-a35d-bfcae76c2c60,"" -10a7d4aa-af9d-4d03-8d1b-bb53ed123f62,"" -10ad7a5d-e26a-4d4b-b063-239af96b0e87,"" -10b85b80-f6fb-4473-a3a6-266f610dcbc6,"" -10cd7171-38a4-4276-b911-bcc8092eff7c,"" -10fef9b1-6bdf-4eef-b6b5-e12504e8fc7d,"" -1104d152-e95e-4a6a-8225-ab200e4530b4,"" -11148ea5-efc3-47ad-8672-799331f8e491,"" -114ead73-4920-48fd-847d-f9aa1a99cab3,"" -11597b54-9453-4ddc-9171-2be17df7a511,"" -1167e4a5-ce86-48b0-8b96-6d70125e2f9e,"" -11a0c4d2-8908-4c0f-8e51-bf4be1605441,"" -11af3d82-bb56-464a-9203-8714c270f4a5,"" -11cc59ea-52cb-41e1-97e4-7ed651037442,"" -12254e32-b86c-45dd-93b0-c8cbee619a14,"" -12257920-12cf-4945-a72f-80551181d7f1,"" -1266203f-94c9-43ac-af02-a0b8db8a93dc,"" -12689eb6-84ee-4d28-9c65-3f3e305266f3,false -127b14bb-027d-4380-932e-ea34c76592d4,"" -128cf2ff-4f97-4039-983b-72e2dd3267aa,"" -1333fbed-c09d-4622-b22c-c67dd32e4583,"" -133a1365-e43c-4d92-a6f8-8c3947b13b65,"" -1359fe32-3275-4c1b-ba16-9dc903334044,"" -137d6ce5-93fb-4201-8bf9-0e97e9595b77,"" -13bf741f-7ad6-4a14-908f-cbfe18651381,"" -13f4a39d-ec9c-43b4-958e-be28a97972e2,"" -143099ce-8738-4ccf-85fe-63e54943c7f6,"" -146000e6-9926-43da-9322-de3bf31e8142,"" -147add3d-eba6-4cea-bbe9-823b44794b9a,"" -14a9ad6b-b7bf-4cdd-972c-bda91e5066bd,"" -14afc7c1-89c8-4740-a932-a76d579e98c3,"" -151df048-c17f-4a51-83a9-3de5f99be125,"" -1520180e-fb4a-4df6-80be-0b3d559c1049,"" -15268027-ae33-4f5e-9c2c-76327ccdeb44,"" -155d0003-f37b-45de-b34c-d611c2a2cf35,"" -15821e91-ec03-4e76-a909-d2f2a14c3219,"" -159c1f96-038f-4722-9e36-98f4dfdb6b66,"" -159c3713-0d0f-499f-8ff6-ecddd44b958a,"" -15d9442a-2018-4390-beef-55fd90cf6ea3,"" -15e70015-e207-49dd-93a3-7c0ea5cc456f,"" -1631e3a1-0eb3-4d16-a7e5-b0d8a8a9e630,"" -16af45f4-69d1-4190-9bca-b309c17bd865,"" -16be31b9-a434-46e2-a481-459c5584c907,false -170e5f14-b29a-4aa8-ba71-4bf381a38d1c,"" -17505b4c-c5e8-4acd-9d10-fb001190a8c5,"" -1795e970-0ce6-4d90-b198-32567c690869,"" -17d74e33-f6c3-4b19-8708-4110a2f55245,"" -18a57e28-9c23-4e73-9d90-3694f2e373f2,"" -18c6a217-c947-4e97-a764-c43f33a409fb,"" -18e59ddb-b9b9-401d-9239-0d2cb81936c8,"" -18ee3e63-a8a2-4004-a313-3e332af8a173,"" -190f22f4-5fe5-47e5-974a-fbbe63a676c3,"" -191232f1-58d7-4c4a-93a9-10aee15e5cea,"" -1913a538-60f6-4510-9a76-2466896d2a27,"" -193a64c2-63ea-4677-9302-b56e3f0531ac,"" -19a30860-10da-4a1b-abdf-0b03d7b8f370,"" -19b01508-db72-4c22-a5b4-4e015a3207f9,"" -19c965ca-c1ae-4eec-816f-73903fe43aa8,false -19d27e58-26a0-48ca-a199-88d48894da5c,"" -1a3ad6e3-cb5e-483b-babc-ba7d34ffacf7,"" -1a7b0249-5aa4-4b0b-adb8-2c958e137d54,"" -1ac6d3a1-d1a6-4b0c-b405-0aaaaf37c7db,"" -1adf64f4-1fb4-47f9-a8a3-0ebeb3e7f414,"" -1af4a596-d0c3-4f7c-a217-b83a0755c3b7,"" -1b091e60-4c4b-4fb4-b9b8-ee53bccc9b52,"" -1b251a42-a224-4a0f-9d36-44ecc8902d08,"" -1b29b772-b873-447b-8aa0-5dd5e9ee1b80,"" -1b614d48-0af0-4d09-a6af-4f3af065da14,"" -1b785bb6-a40c-48e6-a464-f8022ea3cdcb,false -1bb3d0c2-c6de-42ec-bd60-edc8e3ccd092,"" -1bdd0326-987d-445b-9514-2ea30d948093,"" -1c01b494-d0c1-4a99-b110-9de392bbcf69,"" -1c120e92-0385-4120-ac83-1b42191597b8,"" -1c32d0e8-7312-4821-aede-6745a83ea45b,"" -1c51f4a8-14a6-48ad-9821-c6408dd0cd33,"" -1c52440e-0063-4170-9cff-a3caaf7c63fb,"" -1c6dd7c8-4942-498d-a369-65fac6a837d6,"" -1c79c271-a128-41be-9537-1d744f4971a8,"" -1ce6a20a-2413-470f-86ca-2dacd8811e72,"" -1d9b87ab-8b41-4184-9519-fa9822f48aa1,"" -1da7f725-2fee-4f33-9a89-96253d2449fe,"" -1dcdb77f-b0e5-4b2f-ad4e-d446a6ffa079,"" -1ddc3c30-fccc-4e5d-a9b2-e09ac4f64dc0,"" -1e2bcc56-3121-4b1f-b9c7-7e5503b867db,"" -1e5b7df3-5e81-41c5-b096-d9b5409268ae,"" -1e75c4d7-b570-4339-8c0a-06bdfc9bb485,"" -1e84c68a-4e0e-4980-adea-e46ae2506896,"" -1ea31cfd-f340-4c68-a547-b76365abd1bd,false -1f170771-0d08-45db-a38d-da3c539b46b0,"" -1f182acd-6bb8-4031-aa86-2f3dd83b4cb7,"" -1f3e4d9e-1fec-4a71-98fd-4912ab64d3d2,"" -1f6b8ac1-b12c-4eb1-a158-25b15965294c,"" -1f8ad9f7-f796-4900-b10b-365fe442f2e1,"" -1fb81c48-c8d0-44e1-9b9b-df53ad7d9456,"" -1fcab6a5-85c8-41b7-a820-fe6e2d84eaba,"" -2031d9a4-3e03-47a2-ab74-6fe7f2fa4ba1,"" -20d47a7e-f7f1-475b-a9b5-6192f2fce0ca,"" -20ee5488-a11f-418a-86a9-cb981c2fdae5,"" -2123f57e-0815-49e1-aa34-994270134f3a,"" -2160f03b-4276-436a-8278-300bedd07f7b,"" -21936507-6a38-4d80-807d-0d7cb5ea0311,"" -21d2fab0-5092-411b-b752-4859134a7aab,"" -223cae66-3049-48bf-be7b-cf027c415743,"" -22440788-7e64-452d-9025-9a25e4d2be60,"" -2245208e-d17e-4bb7-9bd7-ac75ac0ba134,"" -22462877-9663-4c06-bb5e-040d76bb144f,"" -2253a94d-e511-42bf-a17c-599ae2d5cf0f,"" -226d433b-9abf-4e98-bf0e-47cb3a37b41c,"" -227d84f4-ec92-464a-b3de-f596866ce825,"" -228b1b4e-f5e7-4e33-91fc-b7cbe6979102,"" -22b95566-7a4c-47b1-92af-7e0ecbfee6fa,"" -230b746a-26de-434a-9774-54db1ad98a7c,"" -2324dacf-f221-4edf-bdf6-924449cc916c,"" -2381d397-0068-4d6a-88e1-a0257d81a323,"" -23831569-6aea-4071-820e-6eb5440acc1f,"" -23b4fe3f-62c7-4439-9364-247ceb9251c1,"" -23f91a73-1b46-414b-becb-918e84177886,"" -2439d9a3-6c58-4ced-a82e-27908ed3df94,"" -2450caca-d74c-4610-ac24-1fae13801eb7,"" -24d8f04b-f035-4a9b-b03f-75be9e244ceb,"" -25085ba1-43d7-4a60-82ab-e15c477313b0,"" -251e7497-4a55-42fc-8e6a-32febded738f,"" -2534b076-583b-4872-84e3-bc96bdee5ef0,"" -2583c0bf-6a7e-4100-a5c1-d249de6ec56d,"" -25da1be8-51c6-41fd-b48d-5227e804ebfa,"" -25e1a428-ab0b-40f5-ac25-1e9899458ec7,"" -2606b58b-a924-4128-b3a4-5e4b91deb692,"" -261583fb-e2d0-44c0-a677-00bd89a3450b,"" -26344e09-20c8-4405-b674-49638169a7a0,"" -266c347b-fa2d-4cc1-a2fb-fe1ba20fbc2e,"" -266cf373-c41a-4cc3-8600-c165a4349a6a,"" -26b4001f-2917-4bf5-bcbf-1ce9dc258171,"" -26fb6882-7f3e-44da-9100-58057c9aeec8,"" -277e09d4-cb89-4748-b9da-39bee94df8b3,"" -27a75a54-d318-4026-bd63-c80f73899376,"" -2803a9d2-2555-4bc6-9718-7a9dcabd6ce4,"" -2803acc2-63bf-49a3-b4fb-0dd0742fb816,"" -281d5649-179d-4c8c-9a0a-d1159a5df8f1,"" -28503262-5aab-44eb-bf51-6fb7534c25ab,"" -2866a0de-36ab-4ec0-a1e1-cce5c697ef85,false -286decfc-83d1-42e8-9a2f-880f145c71b3,"" -2875fd27-8c5b-46ec-91f0-ef3a7a7727b3,"" -28aa1696-dde7-40ff-bb7a-6c5b614405dd,"" -28e8966e-ba9b-4499-bc9c-7f51222e15aa,true -290440da-118e-4e8b-8d0e-d50043c24eac,"" -292aa324-379d-4b82-9750-4c8e23587f92,"" -29506f88-302c-4264-81b7-cf762de48615,"" -29546a71-e5a0-47ae-96da-421275c2cce1,"" -29676c08-88c7-4b0c-82c9-5f6d7dd51f68,"" -2974e695-f2e6-477f-91e4-da8eb654bf24,"" -298120fe-5dbb-4d99-902a-3a6783fc5b6b,"" -298e5099-df2f-4381-8259-b10d0b8c0570,"" -299c851f-3566-405f-a217-84c0703d859c,"" -29b17fc8-8e80-4d7d-93dc-bdba1f2cfb9d,"" -29bfab6a-d760-449c-9198-38e8c63574f2,"" -29d4d79f-8a85-490c-b52d-aaf3bcd9a100,"" -29e54cc5-b484-4818-9c3e-08074812dc7f,"" -2a0d7eba-2dbf-4566-af0a-c16161a8b189,"" -2a133e8f-4dd9-49fd-9e3f-5cf122294091,"" -2a67b2c1-18cc-45a0-ad53-825c73a3aa15,"" -2a71ca62-7f39-4c9b-b3de-6dfcf299298a,"" -2a7f0959-9d50-4ccd-88e1-6635f2502905,"" -2af24d01-6215-4c7b-88b4-6cee45a156f3,"" -2b265cf1-0763-4922-a403-e6f644ed95ba,"" -2b41d0b5-97d4-4a80-bf65-b62ac0cb530a,"" -2b4fd497-9f28-4c87-9b13-64f0d00c0cc3,"" -2b54e31d-4af9-4071-ad49-60cae644ba39,"" -2b60fba0-0143-4f31-8e4c-27236c7f41d8,"" -2b927027-35f1-488b-bb5b-127a8985cc5e,"" -2bc92150-c490-44da-a925-ebddec5cd953,"" -2be63080-a881-43df-95b5-e371df93c6bb,"" -2c018134-14c6-425d-8ab4-bc3974402b05,"" -2c3b614e-7860-47ad-9320-11c5f45d27b0,"" -2d358911-c5eb-480a-a9e6-a6fab52ad7c2,"" -2d45c298-d386-43ab-b3e0-ba07dc527a68,"" -2d6cb13b-fdfe-4219-bc13-7de385998bcc,"" -2dbd4567-e6aa-4e1a-996b-af8384f66b37,"" -2df38530-e49e-4ff9-93a3-816de29d9bfb,false -2df89473-f746-4086-a2f3-8ee2d4f63271,"" -2e0bc41c-fdfa-42ab-abdb-28f13cf2ca20,"" -2e13cb39-148f-4299-b74a-0a8e10092e9b,"" -2e45b9b5-8d45-4994-aaba-1fab72c77c5d,"" -2e6b11f5-cede-4152-bfe9-523b030a2bdf,"" -2e8c312f-53b4-4cd3-b6da-653c34945d76,"" -2ea2af0e-343f-47af-b0fe-6dea108836be,"" -2eb09ab4-c554-4536-a3ce-2979cfb988c6,"" -2edee289-e99a-4a53-a067-f83d29db4c2a,"" -2ee72185-ca8f-40e3-b82d-30408dc8c6f2,false -2ef91918-bdb6-4a6e-9c39-41d8c513b552,"" -2f1bbdc4-beaa-4d49-8791-6548094c7291,"" -2f24078d-9646-40fb-908d-2628d555dcb1,"" -2f260daf-8fb3-4fb9-a435-670ec8f0180c,"" -2f30832d-e6b8-4946-8cf4-0d8da5c359c0,"" -2f33b981-f22f-4983-b347-f81d7d72c50a,"" -2f3f7f7a-ba31-4c1e-8a23-c7d1eb1f255d,"" -2f5d7d1b-847e-430c-988a-40028f9f595e,"" -2f8b35f6-2b55-4ff0-8d79-5de7805d6da1,"" -2fc9f8c2-4a1e-4e8d-8d91-a6c3b3937e8a,"" -2fcfe30d-dc47-4eaf-9df0-7943a2c37fc5,"" -2feac451-2301-4ed5-8bca-baef62790656,false -2febeae3-eb61-4743-8ff9-42547a77a40e,"" -3006707b-dcca-41ce-b138-5d62e6b56984,"" -30371578-4ad0-498b-a491-4e194c7466fd,"" -305d5449-57c6-4362-83cf-55b4dae133de,"" -30ba9f80-e595-460a-abf2-b8f46a62e606,"" -31504825-77aa-4a0f-a634-ca806bd886d3,"" -3169cf91-33f7-4b7e-b49f-d4b884760685,"" -319dfae4-0305-4520-8ad1-9679383cb74f,"" -31acc736-076e-426d-8a32-426d91bf6511,"" -31e26c46-af6e-43a2-8e27-a8342f53e817,"" -31fd2352-7eb4-4ce8-9dfb-b3d3a1410c82,"" -322637fc-2d75-4a8f-89a6-a4c963869112,"" -32596eae-6236-4b3a-99fd-e50856971f59,"" -3286c996-861f-4fe3-90a8-86ec826cdf9a,"" -32bed860-5df5-4bbb-8bd1-c95c016561ee,"" -32d92e2f-d02b-4006-bf16-d1c535948e80,"" -32f3d9fa-6c64-49e8-a6dc-fb51679753e5,"" -330fde18-0a86-4e67-850d-fe1c8b376dab,false -3322da98-67e0-445f-ba1f-225eb7d297a3,"" -33333f69-f8a6-4def-a5f2-bab5a26bf054,"" -33826788-b600-476b-9843-17fbee2cd849,"" -33a0b9dd-c88a-46cd-ba8f-af3d4d6cfc27,"" -33ac0c39-1552-408b-97ea-a90c9187d61e,"" -33b22ef7-01a8-4e8f-8b5d-9aef37e46896,"" -33be0567-5c42-451b-bd71-c09f7de30524,"" -340dbb93-0888-4f47-b18d-0b86d3e40248,"" -341634fb-2646-4c69-b57d-b5f00ff4c83d,"" -3420a3d5-d5c7-402b-a4c5-02e7198c5b7e,"" -342699a7-cb58-4a9f-bb79-306483dfa652,"" -34335eed-f6c0-4e05-ba6b-1ecaa1fc121c,"" -346734fc-4798-48e3-8880-a603d52e9ca9,"" -346a282a-439f-48ac-875a-cc34643d70cc,"" -34a83c04-2ea0-4638-ab6a-0a4bb9a3ad4a,"" -34e44ce8-26fe-48c3-8fe8-5cd8f260aee0,"" -34ea288c-f987-476a-a8ed-0ab2c1b92833,false -3590f230-1be0-4c7b-afc6-5bffec9933ad,"" -35cf6113-d23a-476c-b18d-f86fbfb3ea2d,"" -36160c58-889f-4543-bf04-dd29cac9ce19,"" -361688f0-e909-4c9a-b5d8-10727c9a18fd,"" -365ca17e-d5b1-41d2-982e-3ca7afdc0a0d,"" -3669a209-82a0-4f9e-8026-43cebad7f3ab,"" -366fa6b3-9595-4624-ae30-20257ef4885a,"" -36957f27-97d1-483a-a8ea-bd5b1484ad12,"" -36c9ce76-e047-487a-837c-faffdb2676de,false -36cc6703-7757-4c6b-91a0-a43055c19bb6,"" -3732940c-2fa8-4301-942a-e58fd8d73421,"" -373609ea-276f-4b96-9c94-d96ad532246b,"" -37770a6d-bb26-4499-a6e6-2eb399a8ad07,"" -3793c189-6e61-4f1f-bb19-a01a306267d7,"" -379779f8-dbce-4dfd-b18f-ce0485b0d6aa,"" -37e1570f-2c29-418a-acee-6688b6ab4279,"" -37ea3114-1b65-4c47-9e65-4cbcf339f8c5,"" -37fa89b6-2f6e-4985-b733-1ee4b2fd59e2,"" -381dcabc-52cf-42e9-9624-d4e1270518a9,"" -383aa480-8db2-49ed-9542-c738af90f344,"" -38898dca-756f-4267-aa1d-1fe52a6e4749,"" -389079a9-b902-4212-a8c1-7187e818e016,"" -389a8f10-ad75-4d27-9d11-2c4ba0dc55a6,"" -38c329c2-4078-450f-ae85-e411b80f30ce,"" -38c7c98e-0372-4892-aea0-ba21ed298958,"" -38d59ef7-b207-4d55-b74a-a1da3d0c10df,"" -39d3bc28-0ce7-4d1d-9adc-bb9933568809,"" -39d7db36-7baf-43d7-8a0d-970ae00456d0,"" -3a1941c5-c6bd-45b7-b306-d2a1a51c5eab,"" -3a8a0b91-a492-42de-9b64-fc552476d8ff,"" -3aacaf9d-af9b-4220-a836-1d0c3cfd5245,"" -3ad5553b-58b8-46c9-8023-e1acb85be08c,"" -3b4bf6ba-0f27-4228-b6e1-c16dbf82e5cb,"" -3b7c654f-9a67-4402-a571-a5b0a61559ab,"" -3bae6ece-f05c-4e3d-81a7-64f10f907bb2,"" -3bb8d5a9-87a6-4155-8f3a-7a0c841672c1,"" -3bbe3af0-0f3c-41ee-9c41-70d89b411852,"" -3be31b98-5218-44c6-aa92-1e054793d9df,"" -3c496888-2ead-4d5f-afa4-8ab39b57ca03,"" -3c504a58-92fd-4b2f-a308-21a9588b1850,false -3c8d926d-3156-464b-b385-192257411798,"" -3d2c40b9-2ef8-46d7-8215-bb898170902f,"" -3d87abf4-a569-49b5-962b-32c11816a53f,"" -3dac5f75-f71c-4092-b87a-7b8434af633f,"" -3db4a850-ef45-4fa2-8eb1-b1cba4477d8d,"" -3dc0190f-39b2-4a1f-87d9-1ef41a73fa75,"" -3e4206fe-8856-46be-98af-d6fecd52db92,"" -3e58093d-c387-4a07-85c2-41a519d0c398,"" -3ebd1cdb-5546-4dea-ba79-a88aef933640,"" -3edb8e33-d674-4c25-aa75-7303da0229b9,"" -3f5c46c8-4a8a-4575-b169-6bfd5d752d98,"" -3f771434-cfed-4f34-9671-0ef302b5f9e7,false -40100d97-ad9a-459b-b044-66568d36c0bd,"" -408159a9-8c38-4cb3-9488-d83331f1762d,"" -40a1dd3f-6ffd-4463-bf71-42057ee61689,"" -40a75d7a-909f-4657-9ac1-5b56df539129,"" -40eec0cb-8f50-4f61-92a4-7682c48511f9,"" -40f3ae1e-4855-4d78-9fe9-c8348058088e,"" -4115b0b4-f738-405b-9351-ae11ba4bd07a,"" -411cbadd-c2d1-4fb3-8ddb-d8bcea3aa176,"" -41496990-adc5-44d1-96b8-15010bdecc89,"" -41a3cf00-0965-4c17-bfb5-247a7da43699,"" -41a86c21-2ea1-436a-86a3-9173c7d95d9c,"" -41de6042-e95f-4bca-ba69-f55293248f7c,"" -41ecc0b6-ae98-40e0-a836-9d26070e248c,false -42187018-d27b-4831-913c-575d07e33c54,"" -421e31e1-5ffb-4c6e-957c-728b6218d6da,"" -42350e13-ab61-49ef-aea1-802f6b7ea955,"" -423800b8-5a89-4eec-a12f-451c533fac50,"" -42895609-81ce-4cef-81db-069efe9a207d,"" -429c5342-c5ae-4c55-98aa-e4f7bf0b4670,"" -42ac25e5-8828-49bf-bb5c-a6001966c6ff,"" -42c5e47c-ede1-48eb-bc82-2ed835d67495,"" -42fe77c3-239a-4afe-ad76-8c221b543e32,"" -43179e01-e94b-4f83-bb60-60f181388048,"" -432bf1ce-0e8b-4621-b998-819fee53f80a,"" -43522eea-96c7-41f6-ad41-1c190cece6aa,"" -4358ee55-cc06-46a0-8b44-6b89d09c380f,true -4380d762-baa4-4c30-905c-59b33dbd793f,"" -439dfa4f-917f-40c5-9313-0d5dcf7ecf2f,"" -43bdfaaa-7474-4503-ad93-730d777e6db8,"" -4416e165-8ac3-43e0-a9e4-6c3e789ffdd2,"" -44233ad5-4eb9-4709-9c6c-e7797d9c9f1d,"" -44237e1a-31ca-4635-9ff3-e3c71f587972,"" -4442a5e3-5793-43ae-92ab-f2d87a70cc0b,"" -446f7fac-2202-4149-9860-fe48335349b3,"" -44713d16-f673-456a-881b-b0526ee36651,"" -44b741ee-bdb2-4732-b484-e435854de348,false -44dbff22-7cac-4f70-886f-b8ac2dea7130,"" -44f714c0-88de-43a6-b69a-4d62d8fee57e,"" -4522ef75-31ae-43e5-8e5a-5caf5c83fb35,"" -453cd2e7-723b-4e1d-b414-f46f9152edcf,"" -45467cbe-cc21-4822-94f5-82e1203056ad,"" -4546f579-74ab-445c-8619-431f60a6efdd,"" -455f8ebf-b074-4035-915c-91cd15380d72,"" -459f6da9-0eb8-4b26-85b9-a8f63c587fab,false -45a306de-3731-40c2-8bd5-2c0fee3b9f99,"" -45d45783-5a68-4c06-9b2f-88b7ea1f8347,"" -461bea3d-934e-46fa-a112-9a761140372a,"" -4672ebd9-08d2-43fc-ba92-ce13b587af3b,"" -4675fa7c-6ac8-4bef-8756-88f757370a10,"" -4680ae70-6d17-40fe-b55a-c3152ce8b246,"" -4687b275-d990-41cd-a146-2715079a8ecd,"" -47014b4d-07f1-4bef-9792-82c027525c0f,"" -473bf60d-dab0-4531-9903-95dc6680a660,"" -475e95a3-6d57-40f3-a0af-9daf1b35618b,"" -478fbabf-520e-48a5-b3a1-a3e535d64345,"" -47971ac1-1e72-4788-ba6f-bea608e59076,"" -47f066d8-abf5-4064-84fd-dfb05649e0fc,"" -4808c4d5-7733-4601-8401-632bf5924c06,"" -484a19b7-9816-4a66-8f0b-51cb5f146818,true -48693756-94c2-434e-ad88-e573e5278a6e,"" -48fae21d-0ac1-4e4a-93b7-ac4ba7c2a606,"" -49088daf-d19c-4bab-9084-c67f0fba2c90,"" -491118c7-998f-4d45-8b58-da724ac011e1,"" -4925b797-e285-4d02-9aed-dfac19b0875b,"" -492f6c9e-3e1c-4043-aa08-03c610da33a7,"" -49466fee-df56-4b9f-898f-96b65318d833,"" -495bdeba-9ab9-4756-b4e0-a4aa8b039a02,"" -496f0696-cee8-425a-81d8-1df8fd17e4bd,"" -49cce532-3ba4-4db6-b518-ea934588bfae,"" -49ee8b11-a7c9-455e-9a9b-835dd617e861,"" -49f41136-fc63-48a8-8ee2-b7536d3bf0e7,"" -4a6eb9f9-49a5-4d36-85dd-78512fbe593a,"" -4acf651e-af55-447d-bf0d-9adaef4cf6bf,"" -4ae0f855-534f-41dc-a4b0-879c771e3379,"" -4aeca194-6d0d-48ad-8fd4-6aea3be783fd,"" -4b322f4a-fa06-4509-9783-dbbf12960b77,"" -4b68a6bb-4fca-45ae-bfb4-b814c67bdac7,"" -4b86609f-db97-4a0b-9860-3ce519072521,"" -4bbf3839-bd13-406d-9f60-d9eb9daa4dcd,"" -4bd9e2b4-e195-4a83-ba1c-478e66a7bb5f,"" -4be334e3-94a8-4223-846e-39b5877e9ff7,"" -4be59cbe-d89e-4abc-aca4-410a7e8cf580,"" -4be72d80-9418-44de-ab47-e8f2f2b9f2e4,false -4c9a28de-ab9a-4e69-9ca6-375512c2f149,"" -4ca8a902-7ac4-4634-b1df-456175c39d14,"" -4cef34bd-c677-4647-b940-840a6868cb9c,"" -4d0e9887-c81c-4ea0-9026-41ceb9ec9a51,"" -4d45564e-e14c-4289-a637-06193bfcef2e,"" -4d79c0f1-72b2-4e94-addb-9adce3f49c2f,"" -4dad93cd-b188-4750-bdb0-4a5501568d81,"" -4db059c5-c4d2-440a-84ac-360312e3c590,"" -4dba5ae8-2d30-479e-b831-80d39c396280,"" -4de45bfc-2a41-4e0d-a0e7-c6caf6ca0f24,"" -4df7ae55-1947-4d89-b51a-496b34c4250b,"" -4dfadd90-3f14-4ee9-abbd-a348a5d6a7d9,"" -4e0d77a5-782a-4b03-8867-f51a9704368f,"" -4e74bab4-e328-447e-84d9-a29bb7fa1548,"" -4e8a0b34-71b8-4da8-80f9-f015782b182c,"" -4e9a316e-406b-496b-acf5-6eec80f25ee7,"" -4eab3a46-0ea6-431a-8c65-5b5bdee521cc,"" -4ec34b7a-8389-4d01-bca5-06e678089d41,"" -4f147e12-0e70-4ddb-b4e9-4ea25361f290,"" -4f18952b-be4a-4d77-ad4a-05276a6bce1d,false -4f3f5836-8683-49cb-b5e8-a9881796a202,"" -4f62ec59-e478-40ef-8617-e6ed599866f1,"" -4fe2d379-ba65-4ee2-a584-f0bc63a8523e,"" -5005cfbc-b57d-442d-976b-34164aeb1977,"" -504859eb-2ed0-4184-99f1-79f436fcd692,"" -5085d62c-dc5e-4f25-8f0c-700e57bb71fb,"" -508de40d-3c1a-4bfc-8713-11dbae730004,"" -509d7086-3120-4f61-bc92-4ab924f30ffa,"" -50a9341a-67f3-4d18-9118-373db23eea3c,"" -50b76e4f-6575-47f6-9e15-a96b58c492f3,"" -50dbd55a-e4db-406d-943e-7624d96c554b,"" -50fbe67f-8d29-41a7-8cc6-3df916cd922f,"" -5105a245-dc96-483d-9a0e-52b39e664d27,"" -5106bb8d-43a4-4783-97c9-42d707990a2c,false -51164c4a-fa5e-4246-80e4-a01e1d7707d6,"" -512a1f5b-9f83-4420-a4e4-978e33432dba,"" -5253d689-c7bd-441c-ab16-3721f1e3b5cb,"" -527721f5-443d-4367-84e8-0f15982e7587,"" -529479a1-36f6-4133-bb4e-2f3cf677626d,"" -52c03be3-5715-4edd-9809-ebff7e2ba57c,"" -52d51fd3-d0c8-4046-9ec3-1a3f523d0b75,"" -53010db2-bba4-4751-9f12-f51429c9c558,"" -53339064-e7f3-47b0-a5c6-ce77d50b7489,"" -537bd7f9-9f4c-4bf1-bc21-9d09dd655a13,false -53e5edab-cd6e-42cd-b6e3-a36d57be3685,"" -5441b3d3-b8e8-4e36-84c5-623e9b199daf,"" -54487624-db9d-43cd-b4a6-0b4d3934f7bb,true -54627df6-151a-4f3c-9fea-374217afba2b,"" -5496c32e-97eb-4f6f-a7ee-46bd2123f966,"" -55054683-a9bc-4c84-b1b9-6a1540e1168f,"" -55449cc5-a88a-4181-9550-5d2bb23a293e,"" -5565df4e-b483-407b-8816-ca9e8ddefc27,"" -5578c18e-84ae-4f7a-8cb6-ce285e1b1ff4,false -55936779-3082-43b8-a1e8-a8329bf19a8c,"" -55954c84-4a6f-4667-b070-5fa9185fe3d2,"" -55dc6b95-e01c-4812-a812-2cbf20de74cf,false -562f66c7-19ed-4d05-a14d-6aaa37e6eb16,"" -56617f73-b5fd-4e8b-8bd5-467af440f0a9,"" -569f7616-50c5-47c5-94c8-fa3b3097bc97,false -5703a2ef-ccc3-4ca0-b100-a1c567f1dfc4,"" -572e67ff-4f0c-49c4-b55a-36a958d3e02f,"" -5742e203-dd0b-43c6-9dfa-304240238737,"" -574873f1-84ed-4171-a51b-6a2c609e9afa,"" -5760bdfc-cbd9-4763-841a-bb31a972f551,"" -5781b9ed-eeb3-45d3-a364-43cf8ea348f6,"" -579a0bf9-87da-4887-9700-a5098cd7d720,"" -579eb155-01ea-4066-9dae-9ddce5ea2041,"" -57d84d96-a1fd-44ec-8460-588b477502a5,"" -57fa04a3-a8e7-4699-9115-95957be21dea,"" -57fec813-6327-4290-a2e2-ca74a04cccf8,"" -588b882b-0bff-427d-8b34-294f2fe55189,"" -58aa2384-e80d-4083-a173-ba534f152680,"" -591075a3-92bb-42b7-a6b7-d761add1ad2f,"" -598288b2-fa85-4bf8-9e01-2d0e86711aa7,"" -59857d17-d8e0-4932-b92c-97bd36a31d3d,"" -59a25b17-fe55-4592-abfc-0c22e237bbd9,"" -59b131b5-2db4-4877-bcc1-2206865df718,"" -59e29c4a-102b-46a3-a950-3babee7cfccc,"" -5a0eb3ab-86ce-467f-b389-94f026fe0f7f,"" -5a4bc06f-8ed6-4cbb-93f7-5c5faee7e855,"" -5a9300fb-d831-4b67-88b2-843d2eca4441,"" -5b0954c2-a2cd-4013-b866-a6e0474c12c6,"" -5b2730a8-6a24-4bf7-aacd-f56fd380c696,"" -5b44153a-1ea5-45a1-9d58-bbc0a474ce1d,"" -5b5f6d2c-1899-474a-afae-a98762630b06,"" -5b72c7eb-4584-45c0-9039-5fc762b1e50d,"" -5ba15ba9-7112-45ab-b7e5-353d52af5fe3,"" -5bb09da7-8ce1-4642-a113-fdd1fbc73897,"" -5c085687-51d6-4cbe-9b71-0a58718c1d24,"" -5c17c3d7-a7ba-4756-9991-97bc0b3a46ad,"" -5c48ba78-4e71-441d-b03e-ef305d9e2bde,"" -5cce02c2-3537-4d34-b085-7cf145590292,"" -5cd558f0-20d1-40c7-8197-5a41ef97e634,"" -5cec04b3-e52d-4a6c-af98-6ee5d104b20a,"" -5cf73976-33ee-44ac-aade-5c64e56d7cd9,"" -5d54c744-365a-4db7-9675-79d64bdf55a6,"" -5d5dad82-9b58-4302-a494-02f44c3e94bd,"" -5d790c80-5b87-4802-9b89-02f949846f39,"" -5d883f31-f417-4d8b-bdbe-2cf73d1aa4b5,"" -5d9e8c9f-8c95-4ca2-9de2-2cf875868ff9,"" -5ecf975d-fa2b-4c6c-a5e6-fed730c9e035,"" -5ef7c113-d5bc-486c-9be9-aa39aea779d0,"" -5f6ad8e2-eed9-4b2d-8697-4e2e742ee1d4,"" -5f92feba-5842-4894-907b-9ceaf36d0f38,"" -5fc1d33e-168e-435c-93ae-c619999b6f9b,"" -5ffb50f0-0963-43d0-b032-3e6b2621e1d7,false -602f4e55-f69c-4103-bb6e-7d23fd1b06ac,"" -6042dc41-8cdc-4825-9ab2-bb22af5c5fa6,"" -60635e6e-3d74-4f56-acb3-be8edc522837,"" -60959eb0-ed9b-4043-b8a1-0b0f27789642,"" -60996e5d-082f-4d1e-8cc5-6cc626974925,"" -609a5704-81f0-4b67-881c-d00e87b74c0a,"" -6198d258-17e2-4956-8064-1448f4744303,"" -61e75b9d-46a2-4d74-a0f0-37dc66a09450,"" -6220b92e-946e-449a-a2bd-3241e6a453a9,false -62346d78-6be2-4cfb-850e-6ca1fc3f5e35,"" -62348772-d9cb-476a-a016-32f16e44cb33,"" -62ad1938-6235-48d8-aafb-2f773a40c6e1,"" -62ccafe7-fbcb-4cbf-8584-224cd58ef421,"" -62e4244f-3f34-48f4-b865-c8b1c3ad97f5,"" -62f342e2-5499-47c8-959c-4d786aa5fd67,"" -62f6ce69-77ca-436c-9d40-3eb541853798,"" -6320229b-25c2-4355-a288-17efb054df6f,"" -633dac04-7a54-476c-8482-9631c351ac26,"" -639dab1d-3282-4de9-84f7-727d0462f11f,"" -63b2b9b1-aaf2-4539-97f6-aa6815e75f21,"" -63d6c1e2-ce14-4e89-9bcc-f3e3e0b6fe72,"" -63e5b7be-525c-4a0b-852f-4cb090178d30,"" -63e709bb-a56a-452b-a48c-5125a367572a,"" -641068ee-0805-4403-a159-4fdb185cfdad,"" -64115066-e659-4cd0-9a56-fcd43a6df27d,"" -64148c8b-89e4-4394-b044-27088ab3adc6,"" -6433bbab-cad9-4e98-b488-c06e37acf268,"" -645572a4-5806-4854-b89c-939463a904f7,"" -64a2df8b-688c-4368-b672-0406ac7032ad,false -64c6ac23-d619-44aa-88fc-969376bebfcb,"" -657aed74-c373-4c45-9de7-b75cdf62577f,"" -65ad0ff6-ba27-4eea-a400-3d96240a5702,"" -65b9a660-2f5f-4d9b-9f15-5e572d65ee9a,"" -65e87fc6-9232-4ba9-baae-61d7af6e54f4,"" -66037242-eaa2-43af-98ed-190c3079833e,"" -667d62a5-160b-4657-be40-beeb787d51d7,"" -6683a92d-2d09-4875-9403-b977bc2eb37f,"" -66be2f9e-fffe-454e-86ae-75dc713ee934,"" -678223e6-745c-4d58-888c-b910dbb9039f,"" -67cf1dde-11a3-4aff-a7f2-a8b8288aac8e,"" -67ed7f4f-4873-409b-a04a-b928cb461719,"" -68059a10-b5eb-41c5-bb44-c56ecdb6baf1,"" -68249708-6a5c-474d-b1f1-214514fe5dbb,"" -682b9750-e94b-4522-b277-0d572b67e94d,"" -68935870-c60d-44c5-b57a-000c0a99e9d2,"" -68a1d32e-915c-417d-98c9-3154ffbc9249,"" -68a84b5d-edb4-41ee-82aa-8612ab7b2b38,"" -68dc301a-6b81-449d-bcf0-7b2f3d3335db,"" -692cc2c1-44b0-4c47-86b1-2d3c4a5848bb,"" -6938bd7d-06d6-4a9d-8234-9123aa238788,"" -697d10ec-d72e-49b1-b2f9-ac8a3be5c856,"" -698df1c2-7cc5-44cc-ab61-5420d92c598a,"" -699d6871-1043-4581-8dfd-fd1b6e47d518,"" -699f0f86-fc10-4f15-9a78-19515e4e4958,"" -69ae7f71-a967-4c7e-a046-cef77b622f70,"" -69c3f870-e1a5-4785-8dc4-067a7590b949,"" -69e71f61-bde7-494c-b65e-346f5972d21a,"" -69fe60c1-d372-41ec-8671-94090b970a7f,"" -6a4adab1-6b87-4e61-a400-dfa4aa4af613,"" -6a50955b-b259-464b-b3ab-9fa173caeaa5,false -6a8a69b4-f6d9-457e-8466-fdf87ac0585c,"" -6a967445-47ae-46aa-8754-2e48915efc2e,"" -6aba4aa4-9fe6-4759-9f76-705127be70c0,"" -6b8c1da8-2bca-4e16-a04c-a99a04c76d85,"" -6b9af3c5-cc2a-40af-9264-1c68ea98d031,false -6c18b368-8579-42e7-b0da-3500ed15ab82,"" -6c3be815-1494-4c82-b655-a3e7c92ee945,false -6c7689e6-db2a-4544-bf19-0b70c6d74279,false -6ca383d2-551e-4c89-b07f-c0faf9b36dcf,"" -6cac250a-d650-4c9d-a896-b38dd7e72955,"" -6cc22c4d-6014-44f6-a1e9-0b8eaf3f837a,"" -6d2776a9-6add-4222-bb43-64bd4dda65da,"" -6d34e7f6-d885-49f6-b072-6d06f6677a2d,"" -6d564224-4c2f-4bbc-af57-9895ad3b57d6,"" -6d5ff978-ef70-4eba-a1db-536985901dce,"" -6d759795-d565-43ce-90d5-91b167b18c10,"" -6d813c64-a15b-4e50-af82-c2f18f82a0cb,"" -6d94712f-7565-424b-ad4a-e1789fe2a639,"" -6d955124-bd23-4958-bad6-b36cdad50a5a,"" -6d986c2e-b12b-4a6f-ac80-d3bcdaede076,"" -6e02f522-f081-402b-88f2-38e6622bd006,"" -6e362fd8-b0b4-4cff-85c3-6586920627ad,"" -6e750faa-cf8a-4f5e-a02d-1471741cfdab,"" -6ea2fcf1-31dd-46e4-be06-592626e212f5,"" -6ea7ff7c-35f3-4350-9f9f-104c5182cd0b,"" -6ead6acc-52c8-4df1-b00e-b46e2e2f0a04,"" -6ec2fab5-4d76-4c80-ab2d-577b041c1aee,"" -6f550b68-a7cb-4314-ba74-d7d2a6d56d0f,"" -6f63c413-6f3c-48db-990b-fcc749cc6355,"" -6fb0d554-b14a-4bbb-aa5f-a4d5c082c2dd,"" -6fd78455-d026-412e-ab31-d9e1a02923dd,"" -6fdefa93-f23b-4eab-82d0-1e9ce7c6c17c,"" -70777428-a2a0-4dd6-b048-1ef2a27d9ec6,"" -7084e394-9ee8-4b0e-a3f5-e8d091ab4fae,"" -70a25de3-45dd-4848-a394-e87d4f6b6930,"" -70af4c33-277a-4a9b-8022-60d6bb0ac6aa,"" -7102c0da-2afa-4da2-a79b-a82742e4e3da,"" -713231d3-d739-455e-a913-41d5a1dc8571,"" -71f73b19-86e6-494b-8038-46a67396de4b,"" -7227c5bf-3699-48cf-a0c4-fb8f5d50540d,"" -72619c45-9cb0-4abf-a98e-20cc49a4e4f8,"" -7287ec50-e64d-418f-a83d-0d3d93981f62,"" -728f469e-7a39-4a77-8118-944f83c2cf21,"" -72972472-355f-4538-b69a-23aee8614438,"" -72baabec-7047-49ff-9e67-338361a06e5f,"" -72dd2d03-e239-4a2c-b509-769886057217,"" -73364971-4250-436d-b85e-93df377b2911,"" -734b53ff-038a-4c79-91e5-c14340b43145,"" -73fe21cd-4493-4afb-ba72-376f75bc2469,"" -7409225f-77a9-474b-889e-38eb4db0da2f,"" -74556136-3dd8-4074-8e26-160ae1b5f780,"" -748bb216-e420-46e3-9aef-6fe6b2020a74,"" -74cfd92e-5da2-4fae-be59-1546f5a22e35,"" -74dbc21d-e056-4050-8f0f-04194ac43995,"" -74eca765-7327-44ac-9868-ed01c1dc163d,"" -751225c7-3a03-40d5-980c-89f6020bc2ca,"" -752045ea-d278-4939-a82f-3ef29dd7616a,"" -752b713a-e049-403f-9d4a-f560ee0d4681,"" -752f97cd-9398-4830-ad13-38e26e3a8bcb,"" -753ae998-68fb-4917-93ed-484ef1753d9e,"" -75509cde-925c-4645-9a91-c1f244863842,"" -7569ce07-0cbd-4a30-bab7-d5f482a27883,"" -75969f3d-bddc-4e9c-8872-a33f108027a2,"" -75a6eba8-9d1c-4de1-b084-00a5aff10eab,"" -766ee5bf-c1a3-4837-8ee4-0c81e7252c91,"" -7690d499-c3d6-4092-89b7-81b3425164be,false -76a04620-5da7-4914-a5b7-5a8506221f16,true -76b1d775-0f28-4bdb-b6cc-a2441e8e17b9,"" -76cb0eb7-3679-4104-8121-9e01d36de541,"" -76d885d5-454a-4541-8ec8-61f7972f17f7,true -76f4f348-f65f-42da-a338-1e1f28df0ffe,"" -770f3f03-8a4b-4e32-8443-49b14d54ad3b,"" -7714ce28-a83c-48dd-a84c-ba3565432818,"" -7728c15e-82ee-48c2-90a4-afa79e048a52,"" -77293357-c730-4960-a8aa-c25ea0775c3f,"" -773da43c-fa5a-4a21-85c1-724b9d72bbbb,"" -776cf7db-0b09-48dc-b089-2d24410ed4d0,"" -77a645b1-5231-4486-8f16-0470ecc3e49f,"" -77a7196b-7391-4a38-89a1-e01b15e9e2a6,"" -7879670e-7953-4dc8-86ce-88f346cee653,"" -78a727b9-5bb5-4ed3-aec3-a3f804c79734,"" -78bfe231-0661-4502-b18d-28c24b223dce,"" -78edeaed-91f4-4f03-ab2d-38b720e5e6df,"" -7914b992-59e8-4541-8250-427de113802f,"" -794a6b0e-1e0f-4c12-ae52-47b53f385a03,"" -7957634e-ec90-46dc-9779-fb000649bd40,"" -795d85f9-c2d4-4219-8e06-f6fbcef30e33,"" -7968bcd8-e538-443f-b4a6-e41b9766965b,"" -797ac9d5-9ef4-49bd-af56-1bd0c971467e,"" -797d8c9f-56eb-4487-ae30-5c4dca379bf9,false -79813005-389e-4671-850d-b9a5fbc2345d,"" -798cfbc1-a7b2-47cb-91af-43af0ae0119f,"" -79a428bb-83a1-4338-a613-9b732f6feb4d,"" -79c5ee51-42c7-4fc0-8e8b-7ce9acb5133b,"" -7a281b03-7145-409f-9c5a-c45b6e544a4c,"" -7a543c90-f6bd-4343-8311-6818c3a9066e,"" -7aa8cd33-8365-4c53-ad1c-e07dcf2f86dc,"" -7ab27dbd-f952-44d9-bd9a-f67d5790fc82,"" -7b4255ec-4801-47ee-b12e-4b874c5eec1d,"" -7b8c6a88-3a53-48bd-9b73-bdc3f087e6b7,"" -7b9a52de-eb76-473a-a622-4b19d50b6bc9,"" -7ba6211d-542d-4508-96fc-712328eefc69,"" -7bb75949-ef13-4934-a19b-798f6a5e7866,"" -7bdf88dd-7924-4b93-9ff3-d543a170e0d8,"" -7bf5b1d8-3edd-458a-9730-187ac6738857,"" -7c197c69-7872-44cc-93bb-c518b8403da2,"" -7c6c40db-df29-4b8c-8881-3f1765e51c2d,"" -7c6e1d99-bc80-4172-b76c-226f6a618b48,"" -7c85bdf0-7156-4555-aa37-49e4195ce0e4,"" -7c94eac0-d189-4107-9443-452d124db20f,"" -7ce02db8-4ca5-42c9-afe5-b818e42e1767,"" -7d4a09ac-d7e0-4675-a9f0-313c73fa7906,"" -7d83ed97-4c6b-4ba0-9dd2-423f59e8bba2,"" -7da3d62d-56ee-4707-b036-2c1b5e111aff,"" -7de9fea9-9923-4dec-9c83-1cf13b81ca39,"" -7df8f003-7458-4083-8ef6-20a049526c19,"" -7e194149-713e-46fb-ab75-4a7cc2acd94f,"" -7e618ba1-1acc-4c2d-9b80-fee3d12884c9,"" -7eb4427c-1b25-405e-8ef1-38fabbfb1a0d,"" -7ebecbcf-c963-4cae-9b1a-8fe44e175008,"" -7ec8d0b4-41e3-40c7-8f18-fa10b249e023,"" -7f054bf1-9bff-4ade-8841-ebc67d4299c8,"" -7f35f61b-9551-45f5-9641-c9a74d7ef110,"" -7f8f71fd-602d-4ce0-ad32-211f5e7ca765,"" -805e5d13-6a31-405f-b43d-3449d73a50d3,"" -806085ba-3993-48ca-851b-a5dbe0690926,"" -80aca9f1-6907-4658-b0e4-bf6f53838148,"" -80e8b1f5-ea5b-4abb-9a06-b51bcce673e6,"" -81155341-1ad4-4473-ac14-9257704203e4,"" -8118e76c-1974-471c-9cf9-0b31ed231efd,"" -8131895a-c6f1-4b0c-aba6-cc3a52066646,"" -814970aa-0552-4357-b958-d572f56421ac,"" -8177f823-7335-4da6-aaf9-354e05299d71,false -8197ccde-83c1-4ab3-bf4b-e849667f1f31,"" -81dd2be8-9b50-4d63-bc4c-22c4dcba127e,"" -81f1e8f4-a335-4910-8a27-151141514c39,"" -822dee6c-337f-4a5a-a22b-7de9e64d1144,"" -822e3fd3-ae35-4e89-a5be-feac7c6f62cd,"" -8242d422-fa39-4374-85c7-5d267d68a0a7,"" -826cf99c-9d92-4cef-b717-d5c16ea7525c,"" -82e58dcf-ea72-4a50-9eab-a74460d58e4d,"" -82e8a61e-9eb4-4fce-ad43-881202cdfbef,"" -82f09a48-3b8f-4a2b-9067-61b51d85c500,"" -830df854-5f4b-474e-9aef-e7be6b452a92,"" -832e240d-da2b-4658-b638-cd2cf2f08912,"" -83648485-484e-46b6-9a11-64a48fe471f0,false -836f63ac-40e4-4170-a9f0-f170c4654db8,"" -83b971fa-bf7a-49bc-b1d9-09680e8eeb24,"" -84a15a42-428d-4092-9f54-235b55b11de3,"" -84c41fdc-0d34-4d95-8999-7677e4235e5b,"" -84c90e57-2348-4843-a557-444e3a82ce43,"" -84d36e49-fc69-46a1-a5be-e9842e8e192f,"" -84ed43cd-526c-4055-9422-c31dfefa43ef,"" -84f38578-0070-46e8-809a-65c75623829d,"" -8502fd55-eeba-4c91-8274-4f34dfe89977,"" -857895a5-3a82-45fa-9fc0-00380ef01b47,"" -85827568-1a34-42f7-877a-99a2d3a0f5c8,"" -8591ca33-6c89-4352-adf3-8261ce1bf58e,"" -85ea993c-f6d7-4b7f-836c-3c76206d66c1,"" -8644762e-70c7-45ba-a0fb-92fdb9e6c73b,"" -8668b20d-ed68-4d67-91fe-d35c385a3408,"" -866bd41c-debf-4a53-9108-fd9c0784103c,"" -86786682-7825-48f0-bbc3-3b1a127aa654,"" -86859d13-26a6-429a-a38e-be69f1af9b4d,"" -86946982-ff92-46cf-b6cf-caa45ba4a986,"" -86bc9f92-f89b-4fb1-affd-093a785ac1db,"" -86fee3a6-5135-4265-8c8c-04ac00ea8e12,"" -87061f96-823c-4e0e-9dde-8034682651a8,"" -87346977-9c76-4d76-b8a7-23ded71369a2,"" -8746d3f8-1943-4ab0-af49-f7d40aad5cc9,"" -875be020-8c24-450d-9ddf-f642e60e62f2,"" -87e5046c-332f-4ab2-b8a9-ab15cfa91fa5,"" -88274e29-0d4c-41c2-9315-f96c93d44c0d,"" -886a561a-e64b-44f8-bc77-76724e0c369d,"" -888dfba9-08c4-4048-bb9f-23fe80dfcf5f,"" -88a3a7ea-728e-4bbb-be44-73093e564cce,"" -88aecc11-264c-4a3e-a2b7-94c99d39fde7,"" -88cf49a9-ec8a-461c-87bb-003866a4e28e,"" -88db01b2-8bcd-4671-9447-f466279f21f8,"" -890cdafa-a6ba-47df-9cc6-5ea4d4febcfe,false -893f9661-bc71-4f65-83de-6483e73bbce3,"" -8979e8fc-9f34-4305-80fe-122fc619c7dd,"" -89cefdfd-b6d9-41fa-ab28-50563fe44a74,"" -89da332a-d11f-4f77-977f-6275f337495c,"" -89ee0500-71a0-47c2-a59a-1b681d9a92fb,"" -8a31ea5f-804f-4b8b-8f91-6ac4bea2c4dd,"" -8a56157c-ea64-4f80-81db-0c89a9d0b547,"" -8a5eacbe-4ee5-4e3f-9a0d-d0d1a13ae2ed,"" -8a8432ce-47e9-4fff-b71a-3147ad298fd0,"" -8ab69964-40a4-4d5e-8d2d-82d07174989c,"" -8ad34764-84db-4613-8483-4e8281f8f984,"" -8bbcf675-5176-4df5-bbeb-38603e8d9c42,"" -8bf02dec-bd39-4a1d-b074-e0ef2167b93a,"" -8bfb6668-fc94-4b2d-9ec2-3385f1780d58,"" -8c39345a-45b8-44db-b5f3-6b5914b3fd93,true -8c492ef6-59bb-4ccd-bc64-48d58d3da475,"" -8cda568d-3e48-4e77-afa1-374809ed3c3a,"" -8d671817-97a1-46c9-b23b-e3013f13edb4,"" -8d98567b-2930-47f8-ad39-759745179c38,"" -8dcdbdec-4dd9-4928-9e46-4f46fd6df9dc,"" -8e1303b5-0694-4bc3-a0f1-559d51e05364,"" -8e55e68c-f1ca-468b-b4f7-11c11c2956cd,"" -8e5ebd37-bb67-4c84-b9ec-3422ed7c3b19,"" -8e9c8ff0-82b8-4974-91a7-888dce4f7f8d,"" -8ea94f05-b5b6-4aeb-987a-567f627a4216,"" -8ef5e898-02ca-4617-b966-d5954b2cf403,"" -8f051932-7f43-4faa-b3b4-c2254756c603,"" -8f19178b-b9b8-4858-8f94-c0f1e6eaf2c9,"" -8f5f16b4-fcef-4a03-98c8-a6aecea249f0,"" -8f62568e-7778-4729-a454-369f5a3f12f9,"" -8f62ada0-3878-437b-aeca-d59d658ece1e,"" -8fc9a4e9-9ca1-4144-8cf3-032a5ed6bac2,"" -8fea3b61-246a-4333-ad04-78348cb59585,"" -8ff0c7a4-f1c3-400b-bb31-c66df4866cfc,"" -900405a8-53f2-4a6a-bb75-80e9725f3439,"" -90126aaa-1dde-4136-9be1-4e30e2c1b667,"" -90e6ec4e-bc73-413b-a07a-e8d3b675c027,false -91357869-20f4-4e58-8237-5b028e1f733e,"" -915eb7b9-47d0-4abd-a424-29605ef5f497,"" -91874305-9f9f-44b1-bac3-28dc0fabd0d2,"" -91f7217e-5c7c-4462-9f7a-ad7234bddf6d,"" -929edb45-2558-4fe7-b87b-ad4a801a26eb,"" -92e25da6-dacd-4d0c-9457-751bc135c126,"" -9307ddb3-12ef-414f-beb2-62a145d3f63f,"" -933302fd-1717-4cef-852f-0bc28dcfea35,"" -937de252-6da2-4dae-a80c-8ef39c6eb99b,"" -93851c17-f570-4055-b6bf-ff928624666d,"" -9389f1c7-bba0-4286-ab0e-73ca1430824b,"" -9391e021-6734-4c01-a6d4-a9e151e58201,"" -93c74a74-55b1-4f45-8b7f-3a0442ea5006,"" -93f749e0-9ee0-4cfc-b970-20f1757aa207,"" -94b95bc4-3355-4881-93ed-5db3dbcabc26,"" -94c1cc51-4dc2-4dd2-8a48-b6793675a44c,"" -95accbba-6ae9-4e70-9984-235e7371234c,"" -95ce9659-ea2b-43a9-a6cb-0b85fb4d9a48,false -95e68649-5482-4d3b-bc6d-ca77d83531a6,"" -95f21d98-e820-4054-970e-ab04c6b0ef69,"" -960866ac-7fbe-4f4c-8a41-f5c6d756a394,"" -962aaceb-7115-481d-8496-5ee5d12ac43c,"" -96526997-d779-49ea-999c-43bd07d250e0,true -96a5c8b4-5c9d-4092-9171-03da2422df80,"" -96df629e-35be-42c9-ad60-2fe43dfba0f1,"" -975a8040-5330-47c9-9bb6-13a1d07fd3c9,"" -979229e7-5022-4a8e-9f23-8673564bbf4e,"" -97bd3c96-d38e-4a46-9e9e-0ff3466b80a6,"" -97d8b079-ffd3-4bfe-a1a4-da06038813eb,"" -97f558d6-5624-426b-b21a-6a92c463d004,"" -98216728-4494-43ee-9af2-930201bcf2fc,"" -9873061d-c9e3-48f8-9a27-6a92255869c6,"" -98da112b-cca4-4bfb-9b85-32accfe1d295,"" -98e90113-a97a-40fb-b6e3-dd5f2e646901,"" -993d7bc1-e96f-477f-b7e4-ad8d25c59e94,"" -995d662c-8641-4089-bb52-417a2eaba272,"" -996a443e-1ca6-4e05-b1d8-b84455f2412c,true -99893b11-009f-4ae2-9ce4-c0db476800bb,"" -99929bed-454e-4131-8368-9d152ab1da8a,"" -99b31b91-ec68-4c94-b23f-2b75d9302cea,"" -9a1f1ac4-3ad5-4ada-80b2-cc31176393a6,"" -9ab5ad8c-b789-482b-838b-6cdcbd184330,"" -9acec191-7d26-4519-aac1-b50e00f3a2db,"" -9ad11a61-23e0-4603-90fe-c3001fdc7320,"" -9b4a1f69-52d8-4c2a-9e8c-072f71c2bd18,"" -9b876e7b-aa09-4b8f-8c58-a0cab74580b3,"" -9b9b76e9-88d4-4440-868a-c5b153c277d9,"" -9be9764e-cac0-489a-85e7-9f3c995e7061,"" -9c3ab93c-6424-4402-bd4f-0f0f7dd2b265,"" -9c630f43-c0db-42b7-b223-607d5a9a39a5,"" -9c9cebe1-ea55-48c0-9edf-22033f5ebebd,"" -9cb03b1e-f448-4f94-8c42-7f9271202a1c,"" -9cc11154-43fe-4bbc-a9ee-c5b8e0158d51,false -9cece101-e41b-4d77-be20-ae0c0cd2cb9f,"" -9d3c20e2-321f-488d-9472-5756fa061ca0,"" -9d415448-4c3d-40c3-b910-ee3ff0df9b3c,"" -9d495d7d-837a-4d3b-bb10-608f9abc919e,"" -9ddbf3cc-7d54-4532-a670-5c361df676d4,"" -9ddc0101-8632-4103-a12b-bf8c5590f624,"" -9deb306d-e6ef-4bdb-9a20-c9f5ebc556d5,"" -9e1b3c33-ef02-48ce-9b60-15b62a9a499e,"" -9e32e6c5-44d7-45b2-bb87-65e98f8464b0,"" -9e3fc42d-d08c-4cb4-93a0-a567c5f6a19f,"" -9e5071df-d3ea-4bd0-83b7-e08ba3fbb896,"" -9e58de28-a944-4ca2-b53b-b8da836ebd93,"" -9ea9ede3-8f0d-43c0-9f35-cc9fcfb622e3,false -9eac5165-a8e6-4acf-8d52-4ad49e2ba851,"" -9ed868fd-baf3-490d-b665-113b9427520d,"" -9f078010-f724-422a-8d8c-8855f2513eec,"" -9f3630e5-1b35-43bb-b53f-c191634ba14b,"" -9fa9a6d4-e6fe-4eba-bbe4-7319b735c63b,"" -a0238799-d0f0-49a9-96b4-33cfc1688279,"" -a0520e0d-7dc2-4218-9176-32eb842de85f,"" -a05480c6-8b4a-4406-9226-8dce4bbb4d21,"" -a0eff999-b331-46a2-b8dc-16727efc7580,"" -a0f58ee1-6e61-41a9-b0c7-5acdb005ac0d,"" -a1007ed7-d907-443d-813a-0bbb0ed84e50,"" -a1155987-73aa-4d48-b127-5da6d4cb7b02,"" -a1bd990a-7d22-47af-8258-5d4a05b269a6,"" -a1e0979c-d57c-4482-9087-88077cf657f9,"" -a2224a99-ae98-4018-8651-38f1f145c76b,"" -a2466ef3-db84-4434-9e07-af11c15c7490,"" -a253f260-5b6f-40a2-b4cc-d2c66856ffcb,"" -a27b5fe8-3465-4b6f-b63d-cca66626727f,"" -a28cbadf-918a-4831-be0e-9ab6c83133d7,"" -a2a6355a-2c94-4da3-a707-cc6db6553ce2,"" -a2bf4c9c-46d8-41c4-8eb3-044e832c78c6,"" -a2cb445a-10da-4b47-add1-16584c34d8e3,"" -a3240817-7453-496f-9d7b-058889858399,"" -a34042e1-cc97-476e-8564-382d941458b4,"" -a35cdf18-7bae-4776-8aaa-652bfc135af9,"" -a3b8ad3e-c982-4023-8977-e9717a33d645,true -a3f4de14-3398-4b34-9165-3a24c5742de1,"" -a3f9fe69-1527-4d04-a51d-ca9f1955d210,"" -a452f6e1-013f-4745-aa25-e2cd16ca4423,"" -a46ec80d-9cd6-49d6-863c-5f5425899445,"" -a49f38eb-8de7-49e1-b7de-97ab32fb277e,"" -a4b245ca-a50f-4bb2-ad64-1124c31eb166,"" -a4d85c0a-4687-4454-89be-085b9a2632f9,"" -a4fec49a-dc46-496d-86a7-5387c2490c3b,"" -a500b66b-fab5-4ee9-bda7-11fb6170fff0,"" -a533451e-9e8e-448e-bc5c-1beace2c397c,"" -a5a464c0-3b64-4ccc-bdb4-62aac198eddc,"" -a5c68c36-129c-482e-a9eb-023d7ec1ef0d,"" -a5d76237-0ea6-4016-91a1-33463ef1e9e4,"" -a60eddcd-ef19-4a84-ac8f-9206ed2aeed0,"" -a618792b-5b12-4545-a3e7-c07313d188f3,"" -a65e37e5-cf18-488e-8870-e4aeb021ee41,"" -a674a795-2553-478c-a5e4-6fbde13d7523,"" -a67aa655-62ae-4905-8042-30fd3ea68944,"" -a6a1fe2b-4322-4ea8-9ca9-3bcf288afe36,"" -a6d86605-7084-4477-8c8a-8cf25c4d1b9b,"" -a6fbb09b-5b17-49f7-a510-5c1596b424a3,"" -a717a4a4-b0b2-4aba-af9d-7b431e7883e2,"" -a71de636-d8aa-4455-ac06-932ae6d848ee,"" -a7575150-4148-4b9c-b941-64db8cc61356,true -a769b6ce-379a-4f6d-ad06-5df3a58d25ff,"" -a7a4ad54-167b-4cc8-8f88-dcb555c9dfb4,"" -a7b38940-3cd3-43b1-bba9-6adb6090d1d3,true -a82170ad-dc53-4899-a7ee-cdbb50e6a91e,"" -a83fed45-f814-46b9-9fdb-50892a1a1736,"" -a8624bea-ae5f-4fd8-b6ed-11b0fbba9254,"" -a863d462-6836-4f42-a4a7-8925574b76fd,"" -a8c9520b-bfa8-4915-bd66-a6133e1c5ffe,"" -a8f862f6-2e8b-4cec-81ab-eb3be51d71a8,false -a906f033-5f9b-4c1e-aaff-287cffc6cc7f,"" -a93d01a3-5bc6-40c2-9895-64a21fbd6253,"" -a9584e1e-deb8-45f9-830c-bef1caeb21c8,"" -a961ce1f-cf0e-42f7-ada6-f283daeac650,"" -a972425d-8c4b-48ab-ae2e-fc7db6661f67,"" -a9888a76-f2fd-4161-9d15-6126ba398899,"" -a9b103bc-f890-4d91-9714-8d674aa842fa,false -aa5fe96b-324a-4010-b128-a5063ce0be77,"" -aa86bd26-a862-4a03-9d8c-a04ec4c70c0e,"" -aaada565-8a59-4f69-b92a-a6cd9a323a24,"" -ab3e98bd-050f-4bfa-b6e9-d829291290a0,"" -ab50086c-5cac-4c6b-8e71-6988453228aa,"" -ab52bac3-d430-4700-b1e6-7efe2f5613da,"" -ab63c859-8b0c-4874-9404-cf7c89a368e8,"" -ab8aa7ee-f65c-4a29-9995-ef33c4135035,"" -abcfc7ff-d824-4414-a586-3c33c263a990,"" -ac139e4b-9d46-460e-92d6-0ac43ce8cb67,false -ac5bafbf-2bfe-4dc4-8e13-adb2d80e8dee,"" -ac645c40-0c51-4d77-a5fd-32e619019c60,"" -accb0b6f-b012-42cd-9374-91355ed49aa8,"" -acd3b2a0-1a81-4738-9bbc-9ab0c56b0a85,false -acd69c03-a3c4-44fa-b3d6-749476309db9,"" -ace8301d-b547-4cb2-8311-e27a8980633a,"" -ad07d661-6987-4928-93a0-e7b902b24ffd,"" -ad2a1d8e-6e19-4d5b-90a7-ab24050735a2,"" -ad48d6a7-890b-4eba-919c-ff6b63e35304,"" -adad08de-aeef-42cc-9a2d-b99fab5ab1dd,"" -adbb15f3-b71e-49a7-a954-2f117a93b972,"" -ade38b82-c9cd-4a9c-a7f6-23130baac87e,"" -ae13da32-7194-4f04-9fd5-c7f114a2e5b0,"" -ae4da701-069d-4ede-b59f-a2524c6529f0,"" -ae52de8a-deeb-4144-bfd3-52c2cab5fbb0,"" -ae56f2bc-f7f6-4bc3-ad8a-3b1193e40f71,"" -aea61272-bb6b-4fab-9817-584a8fa2f55b,"" -aec815b1-23cf-4504-8576-fe03b8798092,"" -aee86188-c252-4910-98cf-a8d3ca9d029a,"" -aef2f04d-7449-4088-9ab5-a227d1d7c36d,"" -aef45e8a-6923-4db5-9bb0-18f472f7f0ee,"" -af12db23-5b03-4e68-9127-acc14775690a,"" -af52f728-fcd9-464a-9e25-28013b84fa1f,"" -af73e4bf-2ba0-44ec-b619-a04f1c13c9a1,"" -af77032e-b1e1-42c5-8540-68bcb906f134,"" -afd2cfb1-57b0-44ab-a8f5-6edc92bc870b,"" -b0138fc8-736a-433d-831b-0bc39281d67d,"" -b0525467-772d-4d5f-b326-095fe72890e9,"" -b07bd4e7-6586-49d4-9f43-19d58889743f,"" -b0a1b45a-9dfb-482b-96d4-362cd1ee625a,"" -b0a510f8-2c98-436d-841a-eee086eba241,"" -b0ad5c48-9538-493c-b5a7-87f8f5ff858a,"" -b0b8b0c1-8184-401e-9346-37422a6f2f81,"" -b0d0e0b3-acc5-4887-a39e-df765111f99c,"" -b0e1d515-5be7-409a-8c86-3aab8ab6787a,"" -b101ebeb-ddbc-4bb2-b5c9-2bc6a23e0a16,"" -b109fc82-447e-4d4a-a447-1c7f0569bbc9,"" -b12f7f41-f8f8-4eb6-b2fa-dd5a18639c11,"" -b155b9d3-54b8-4906-a564-601bf123f027,"" -b18674a0-77ec-4d7a-b563-aad0856ce45e,false -b2071f60-d582-424d-b873-9ea4c2615442,"" -b217ba96-19d5-4184-a1ce-890bfdec478a,"" -b243e613-8498-427d-bc20-acc54786ed44,"" -b2503fbd-507b-44db-b4d0-32700a3ba5f5,"" -b260cc9d-644b-4e25-a99e-9afc44dc3879,"" -b2766451-0904-4ccb-90c3-fa7fcdbe19c4,"" -b2b2289a-0762-4846-a4a9-fbd7dc1a3822,"" -b2f8bfb5-d8a4-4df9-8724-bbce63e4dcdb,"" -b3394219-debb-4c75-988e-98fef99f0500,"" -b34ef85b-d2fd-4fbd-bb2f-6c21d737e21a,"" -b376a344-8079-4b95-99a9-d515f9dfa4a3,"" -b379efa9-29ce-44ad-b653-9cc03ac2bfe6,"" -b3861c55-ae00-4319-8a76-03627b8c8bf4,"" -b42a782d-79e6-4276-9fa8-744d5d1bea24,"" -b444d377-d1b0-4eb3-aca4-c24d3fac8c37,"" -b4771fd0-758c-413d-bbc2-17e8e2cc79bd,true -b48e7fa4-1922-4d4b-858f-a4f3bd8810a0,"" -b4a9bcfc-73ae-4798-a221-bbfe9991e0f0,"" -b4ce60ee-7591-48eb-81ec-0466d931b2a1,"" -b4f80b37-afaa-4974-9cd7-f53d788b2be0,"" -b508dc50-ca89-4b92-a12b-b032d3bf3a5f,"" -b516b7eb-4029-454b-a8ec-9bdede40ffb7,"" -b55a959a-73e0-4b7c-bb03-88bb22844c09,"" -b59c5c8e-7d5f-48bf-b3d8-900e18a64b12,"" -b67c717e-f9fc-4ec3-8cdc-ade1c8d1b95f,"" -b6d48a75-7d74-45e6-9fb0-154ca5264744,"" -b6ed4e6f-6911-4cd5-92de-f50e8d1916e4,"" -b7289502-ed78-469e-8b73-04811493a484,"" -b7597773-8d3f-4ee7-b54a-6c8b60b6d47a,"" -b762619c-c328-4367-99a9-41f10751cedf,"" -b7d87d24-1805-4a12-8f8d-2d14509d5eab,"" -b7f806c7-6ed8-4b7c-bf42-dae336e3f7b3,"" -b80e31af-6b74-4685-a83e-50c5d7f762fe,"" -b862117b-c899-4938-9df3-6baf43911d1b,"" -b89dd640-c21c-413c-8774-f9a154d897f8,"" -b9578448-2338-4f49-9d63-5b886a537f07,"" -b9701217-a4ec-4ef4-84f8-49ae5825b45f,"" -b97f2f71-17ed-4e3b-8e3c-374241e6faa6,"" -b982af8c-9e7b-4dba-ba74-76770efffefe,"" -b98be876-6fcc-41e8-ad4f-a78b1661acb1,"" -b9dc5e38-47af-4d07-8ad1-b0425c5cb386,"" -ba047f08-841c-4d5c-8f98-b3e614906d17,"" -ba5a0e2c-6e96-44d3-800a-991a81337994,"" -ba7bf8c1-e6a4-4437-b1ef-cf9e443971d0,"" -babdc581-4c93-4674-8743-1be4b18ba156,"" -bb182025-e149-489f-b69f-debf6907da09,"" -bb2c53fc-6d97-4747-a60b-8010f62b7f63,"" -bb770b6e-11d2-457f-858e-2e2c6c86c0bf,"" -bb94d817-e843-416a-bc31-4858001e7976,"" -bb9c95de-6073-4239-bb5d-023d18344157,"" -bbcd2900-31df-492c-9d43-e8e0243f7e1b,"" -bbe0b9ec-5a6a-44a9-9770-fbe4117593da,"" -bbf5580d-bef6-4cd7-a231-b6a532d68aed,"" -bc1bab6f-5d84-4c95-afa7-b4ab39038ced,"" -bc23a56d-f42d-4bdd-b704-07376d4a24af,"" -bc6abddc-4fa4-4ae8-97f4-9c566558b227,"" -bc75ec01-01c4-463d-b64b-597c106d4eac,"" -bc929bf3-06ac-408b-a17f-6ecdb28759a7,"" -bca14431-d108-4ceb-831b-b6cedb7b1b22,"" -bca1f5dd-414a-4570-9f0f-ed733bd170f6,"" -bcec2bc4-4e04-48cf-b52c-8e1fa2ac3875,"" -bcf5154c-180c-4366-836d-13aa6bfce980,"" -bd3b7896-2b3e-4c1a-a2b0-aca1c2ea23c1,"" -bd687828-857d-440d-836e-5230d3254b1c,"" -bddfe37f-fb3f-4a24-9ce9-181f250aa0b8,"" -bde29534-91ef-4817-a1f0-00a5cc9475bd,"" -be196f96-4e26-4c23-8241-b0a10a39e831,false -be1ca606-c535-4751-b52e-c61cfe9b6c73,"" -be3057cb-9635-4a25-863f-b4e79cda75c3,"" -be6d80a5-e2df-4e99-9721-80097129bd8c,"" -be7a093e-d70a-4a23-9db3-bd95aca334a6,"" -beee46de-ddd6-4fe4-80bc-6f1f4b8fd42a,"" -bef4846a-ca76-4000-a98a-00a9d327107d,"" -bf88c593-87b8-46d5-808e-8a332c5b50d5,"" -c015fb2e-4062-417d-86be-681d98f8a13b,"" -c0381889-4401-4bc4-9056-641340fa7ec9,"" -c070e6de-9ae3-42fc-9cf3-023839ad0442,"" -c086a87e-ec86-403c-8bc6-37a9b77924f0,"" -c095cfbf-cd84-4681-ba67-7b3ed318186b,"" -c0a2c5cd-58d4-49ca-baa7-d5859abc680b,"" -c1003a14-56be-43d0-9aed-41fb6b048273,"" -c1370f98-3833-49d3-b58a-21c581ac4ab2,"" -c14127c7-9943-471c-b5c8-ee9f0bb95bd7,"" -c1d430d5-32b8-4410-a1b5-6212b2c411ec,"" -c2253c94-aa7a-4e88-8410-e528b08654f2,"" -c24b807b-174b-4e5f-91d2-93599fb21548,"" -c25d3d58-1f6d-4004-85c4-3a162dbf48ec,"" -c286cd77-3c89-446c-a91a-4769aa088be1,"" -c297cce1-5d10-48da-8ef9-e860ac55f387,"" -c2a32eca-f03c-4fe3-8c29-3b69c9798b5a,"" -c34d92cb-7086-40e9-b303-97e204d2ab4a,"" -c36767f9-a3d8-4f57-a370-4a088ea0b023,"" -c37a2b82-a495-4d05-8f8b-99a757d0b83d,"" -c391f109-1259-47f6-979b-e72492709b66,"" -c3aaa4df-ead6-4a84-982d-3c28752e1070,"" -c3ab4714-5eda-433e-b461-c1710b769476,"" -c3de8ca9-c03a-4689-8701-dd524884b5b5,"" -c3e98495-15f0-4040-b92c-31aa40235dcf,"" -c47474bb-570e-45af-ad38-8f5352ee3774,"" -c4757dc4-d69a-4e2b-9b8d-61353a5b21e7,"" -c4e0ccb4-df43-4b20-873d-3a66faab42b1,"" -c4e31ea7-6ad0-42b7-8579-c3b7fcafa740,"" -c4e6e6bd-2135-47e8-81ae-7e1d6d5e7482,"" -c4f82d4f-f53a-4d32-bc78-67aa7786ed1e,"" -c52dbae3-07a4-4039-b04e-d79a86941c76,"" -c52de404-7bbd-4b07-95a0-a66df22a3221,"" -c533b9cd-e56b-4e32-93b2-1189f37eb646,"" -c557b460-3e1e-4ec4-9b4d-ea7bc0c8d64c,false -c5609af5-9f49-49f1-80ac-b18fccbc9720,"" -c5f20b88-cbd1-4283-ad46-5b0bf31fa3ba,"" -c6386600-4824-43b9-b366-4504f4e74d66,"" -c6442145-3aad-4b57-b2cb-b7eff0699959,"" -c6700fda-f009-496e-83ec-0532f34bfa92,"" -c6a0f008-4d97-4319-8099-7e0cb6708438,"" -c6a3be9a-b061-4a5f-879c-e948aa895f3a,"" -c6b44473-ebbc-48dc-8a42-44b7da0de840,"" -c6cb9c06-0c13-4cd7-8d20-ca7b99f7d141,"" -c761f0d9-c918-41b3-9131-856aad1f5b2b,"" -c7e313a6-4659-4fad-9402-946879a91616,"" -c7f263ed-22de-479e-909b-f6ceaf5f2eb0,"" -c80ab964-cf3d-4e89-9232-8e787cb0def5,"" -c80ec189-5f9e-4049-922e-969d28b3bdde,"" -c837974e-0093-438d-a822-19e97249f0dd,"" -c895019e-6471-423d-ba15-4f06ed628c7f,"" -c8bbf035-8a01-4c57-934e-988a584c98b9,"" -c8c936bd-3e6e-4500-bbd5-051659c59309,"" -c8cb1ae0-6a31-4869-a396-78dd5dc7f302,"" -c8f123b5-8e57-4c9a-8770-bd4b4c2c064a,"" -c8fd40e1-35fe-4c8b-af6b-730fd6fb5fab,"" -c92a4616-7215-4ba1-99df-40aea82eedc3,"" -c9445880-656a-4def-ad1b-2a4200d76c13,"" -c9647fa4-976a-4d9f-9be4-26b9c71f6eb1,"" -c98372ea-e9ae-4438-8715-b54ccdcc8f30,"" -c99c2510-b392-42ee-97e1-16f6a3b50978,"" -c9cd4daa-907f-490d-b3e5-a43eedd74c32,"" -ca45fe64-e7c9-48ea-bc02-3bc6a0b58e46,"" -cab21bd5-4314-4bb1-8b16-0967f8b08757,"" -cab53124-f25d-4b4a-a238-3d028e788b2e,"" -cac06963-2da1-4935-a6c3-d6152132c7eb,"" -cad24afe-7131-4158-9153-742c9fd16c9b,"" -cb1c6a1f-e659-4fe2-9cfd-f28e776a4f06,"" -cb5c302b-35a6-4d8d-8d6f-72ad1a3ed1cd,"" -cba58256-d558-48df-83d7-be5939d6dfab,"" -cbafa157-0905-418d-983e-827cc88faa94,"" -cbfd98df-8ac2-4497-988b-bc4d1c9c6095,false -cc0b8f72-76dd-4eaa-84c1-c343982e18e2,"" -cc1e584e-6752-4bf3-8c51-ac0192b5df44,"" -cc372f1d-a83f-438e-8d8e-c346eb03fee6,"" -cc38b529-3137-4d89-804e-51c6bd60ca97,"" -cc3a3e25-87b0-4012-869b-9905b31f8319,"" -cc5fd0ac-6ce5-43a6-bb4a-67279156ee8c,"" -cc7e521d-4fa6-4215-942a-48f84ddf9a49,"" -cc8ade38-d9f1-4700-aad3-dc866f35d1a1,"" -cca1c9e8-c68f-4fe0-ae79-693399d8ee31,"" -ccb56a74-40db-4d58-b0c4-cd485e8a7fe8,"" -ccb810a0-28a5-4248-92dd-7259a0fa0c22,"" -ccbda4c9-3ec5-46c1-a8d6-a215651dfb43,"" -ccbda933-c3d4-44bc-acfc-fd26bc234bb6,"" -ccd5afad-8f2f-4305-b646-26137eff0a0d,"" -ccd69a01-cd72-41e7-87b9-de8454411ee3,"" -cce63ba1-abd2-45d1-9ba0-a2cd0fa9b00e,"" -cce6dbc6-3a49-4f8c-b908-e6f11be81ac6,"" -ccf36cc5-5009-4fbc-8e8e-81f38d3195a7,"" -cd013d11-8609-41d9-a9c6-7fb33a499f51,"" -cd09f5d3-c846-41e7-a1ff-aabb55973663,"" -cd2c3f78-ddb1-45f7-9939-987e070d9414,"" -cd3d18de-96d3-4132-ae73-b80795a53ad0,"" -cd71ffde-9d7e-4c27-bc22-306f81cb46b7,"" -cdc96f77-a589-4ae4-85b6-c6d2c89487ba,"" -cdcad0ef-8049-41c2-a8ee-21835323b05d,"" -cdcae470-3531-4548-a2a9-ba665fca8930,"" -cdfbf7e0-ad10-4c22-9ee4-888bb6bdd198,"" -ce1b7543-fee6-4267-a00d-daa5a5ea2861,"" -ce225925-36ec-4c1c-a327-c64a146bef46,"" -ce512c4f-3808-4ef6-9853-6f7d14e146f9,"" -ce7037fc-2614-46f4-a8db-40a2a0f9e3ac,"" -cea2e600-da22-42b8-aa08-f0e951ff35d5,"" -ceea3ce9-5ab6-4cbe-99b2-34ca9e6eb468,"" -cf17f19b-9f66-4391-bd2c-55384690d9bf,"" -cfced5ea-1e8e-4c85-8318-ef91a041f4e0,"" -cfff2bee-7cf4-459f-8690-3638266a07e3,"" -d000b381-5ba8-4a5b-84c7-f371e104eeaa,"" -d012051d-65bd-4a59-b4cd-f454d6010888,"" -d05fad91-9a3d-4d8b-ba99-5a33fcc82081,"" -d083a1aa-9662-482a-8192-4709f904f16f,"" -d0b22c1a-d0f6-4d83-bd3c-475a3acb0b83,"" -d0be6a2c-ebc5-405a-a2a5-006a5ea52c76,"" -d15e2267-a699-4f43-96a3-e98de7ace044,"" -d168f31c-483c-4340-a2f4-6a60cc8cab30,"" -d17dfd97-60aa-4313-bcd7-f48935faa2e8,"" -d19385b9-bc04-4000-bddf-1d09425a2b4f,"" -d1ba146a-9503-472e-b668-3686ff275ef2,false -d1c2f4a3-8af5-4ad4-bd0c-032b947d3c25,"" -d21af0cb-c5a1-401d-b605-a2e3ba3f46a1,"" -d2272949-75dd-4949-88fc-f0f157c83105,"" -d230ce54-d5fe-4983-9e91-519ce1249322,"" -d241d156-478f-47a0-989a-96a5fff36780,"" -d2663716-cdce-4d88-90f8-51fb5d59aa4d,"" -d2a3d703-44e0-4dee-a8bf-9fc0457841ae,"" -d2c11b5d-93c6-4500-bc4c-8dc50057f362,"" -d2c4f328-e7a4-4d87-b180-9a0eee13d165,"" -d2d08017-f955-4e0e-baf0-2d5d37acc304,"" -d31bee09-f02b-44c5-aa15-a06343e0bf9f,"" -d355c55f-53b5-407e-8785-33e4e54bb3fe,"" -d37a07ee-5d7b-41d0-bbf1-331277662c52,"" -d397d6be-6008-47bb-9f72-628fb5c41cb1,"" -d3997124-9e16-4df3-8c60-f43f62bbf036,"" -d3e6de0a-bfef-438a-b982-4818145651b9,"" -d3f2c0c5-1f4c-4156-8cc0-e2d87db500ea,"" -d44741c1-d58a-495e-9f30-cc0f19fafc4f,"" -d46e0f9c-5dd1-4e88-9a22-96ca436674d3,"" -d4aa494b-3328-48fa-9873-39be6536a437,"" -d4dc8d84-20e2-4053-b244-47549e74680c,"" -d524b58b-90df-4d29-b4c8-308ac04dda2b,"" -d56569d3-0a16-4968-9470-93d410916f11,"" -d5944a91-3bf9-4915-8512-0f1f50a8eb7c,"" -d5aa4b50-1ac3-48bc-90ee-7ad6bd78a078,"" -d5ca4448-871c-4175-801f-38bb43cdb48d,"" -d60c26c8-1b83-4de9-be17-81fca1ef2575,"" -d6c622c5-3662-4b29-9180-0b4055afa612,true -d6f1073c-bf1c-4232-8ee2-5b92f0a32d8e,"" -d702593f-4121-4ac3-bc3f-dae097c7a044,"" -d702f666-bb61-4d55-9579-e56efc3e0e91,"" -d704d649-9e08-4502-a9c5-820f503dac9a,"" -d7c0876e-3031-4be6-aa2b-bbd8e2bd8884,"" -d7eba5f5-103c-4442-9a7f-62c81a9dd9e5,"" -d7f76663-5195-4de4-bc43-ee61e467fee4,"" -d8534558-7fea-4dcb-a1fb-2981b725dd61,"" -d86d32ef-f50e-46d6-b434-fd03bc807cc4,"" -d8a42c84-6930-4b12-8c1f-9a492d290454,"" -d8cdd01f-c8b2-46fc-8f69-35da565e1b99,"" -d909c299-c622-4c39-829f-415621e09eb1,"" -d94160a6-7943-496d-8b66-172e9ec6804e,"" -d97e5072-19d7-49bb-aaf1-f3a56816f3e3,"" -d9e59fa9-630b-4d97-aa71-4e03bd6fe1ad,"" -d9ebe2d3-2a6e-4079-8f59-43a327e68ebf,"" -d9fb7469-d50c-4492-8c1c-8bac1ccf81bd,"" -da33cd96-8f15-47f1-aeaa-846234a6b9cd,"" -da412e67-cea6-4ad9-8c66-75bf7bd9d0f9,"" -da438390-6d67-444a-be2e-354a21d30559,"" -da4c7a8d-c75b-4050-bbac-5edb50e48d4e,"" -da5de060-6e29-4379-9119-175b394f9bac,"" -da98f3f0-236f-4c41-b123-09f1fdb5bfc6,"" -daa0c0a8-91bc-41c5-9627-adad04a7b15b,"" -dabbe6e8-3692-406f-8a2d-f81217eb6964,"" -dac01aa1-44bc-4ff5-b123-4868f8f278ef,"" -dac87128-5796-449e-85ea-2c1f8e09ba0b,"" -dad4c0b8-0e02-470c-b7be-6477008d925f,"" -dae5b7a2-ad95-411c-9721-067d3c578262,"" -dae62ac4-666c-48f7-8587-ff9330a9e9e6,false -dafa1e16-2149-4e76-a693-88d2e7e29a0a,"" -db0a2a90-dc62-4e6e-9a46-88b9a3145ab8,"" -db16da7a-538a-4270-bd0e-1b4398a922a6,"" -db1788c0-f515-419f-ada6-834a37168001,"" -db17c5e1-6d1e-4b0b-a345-8dca770e7097,false -db5a6739-388b-453c-99d0-58cfce24d5d5,"" -db8a9775-faff-49c4-8b35-74e92245f439,"" -dba78a31-21c5-4600-b18e-3e90237ab40b,"" -dbbc9b03-4d76-4118-8637-185659ce2adb,"" -dbc0cb40-5e33-468e-bc9e-2cf8ed602d01,"" -dc2a8c49-ac89-487f-a22c-dc126a5c7ed1,"" -dc36732e-8054-4d6b-a860-e7c54f04a0bf,"" -dc567aee-5073-493e-bec1-b6b0f4e160d5,"" -dca15405-893d-4468-84ad-574e54f45c22,"" -dca8db58-f563-4a61-b747-94f5ec5bf135,"" -dd09736a-85a6-4c2c-979b-f2a7027308d1,"" -dd9a2411-d45c-4385-a6cc-650df658ae9e,"" -ddccfc35-d71d-4e65-afd3-4cf2a21e607f,"" -ddda9bf7-d645-40a2-8aed-eb9efa425d01,"" -de1c574a-a49c-41d2-b321-849c005c89e0,"" -de81cdd8-a6ed-4ed7-af7b-6ee027d0d5cf,"" -de9bffc9-c6ee-4ad2-8055-1e0d0e48887d,"" -dea4f32f-0f51-4bf0-af67-e79b34674fac,false -dee72459-d0ba-4771-af17-f38f8396adb0,"" -deee1173-4f31-4784-8c63-d02e2a7b16ba,"" -df471b44-cfeb-43fe-9488-8f85a03952fa,true -df529c86-f229-404c-8f0c-44e1b29e4e95,"" -df54eb2c-59c6-46cf-9cc0-201e94acc07d,"" -df6522a8-552f-4ba1-906c-139f58a0a85e,"" -df8a0f98-893b-400b-9da4-2514e50c0b24,"" -dfa10a30-82d4-433b-9df5-b0d2291378ae,"" -dfa72495-2737-4fe8-9fcb-c799ab51ddcb,"" -dfb67b9c-64b1-422f-887e-139a8f676fa4,"" -dfb6cb62-17e3-416b-9214-6ae6f432874a,"" -dfef1286-acc9-44ee-a736-b9676812235b,"" -dffc6fbb-8592-4f0a-8190-1e64eb2bc634,"" -e0010996-fa96-46be-9431-ebbe139c081c,"" -e00ec480-db7a-4aab-84ab-7e5e7184bfdc,"" -e0ef5017-54ab-469b-9871-f706d4899465,"" -e0f2d024-4210-4f18-82ce-51755627e13f,"" -e1309aae-d59f-44f0-b630-fc538bab9027,"" -e134ed0a-49e0-4a0d-b09b-4e874f422e60,"" -e16400e0-a6e6-459a-a16d-361a7b2ffd1d,"" -e1a9f42d-103d-4e01-b98a-e70f98aa1c00,"" -e1c6ba82-6bc7-4f15-b71c-c669cb78bcc9,"" -e1e4e2e8-1c7b-4c67-bc7a-3940e6670357,"" -e1f9bb17-3719-45d6-b4bd-3be88f17e429,"" -e1faaf81-1761-4a4f-b01d-c179db8474f3,"" -e20067e7-f2fb-472b-811a-3d28a08bf31d,"" -e2501541-dbeb-4e28-b4d8-598e963d6158,"" -e2b04908-769e-4916-bbc4-45ad7db85e02,"" -e2eccb3c-0755-49c8-8e82-cc510481ed04,"" -e2fbc85e-dbc4-422e-9a11-0007d5c194ae,"" -e3784666-7f48-455f-a137-e6262a617ccf,"" -e3c2c69c-bbe5-4678-aa6c-3e5c7ff996f2,"" -e3c5df86-3d35-4dd8-a081-48632a3b67c8,"" -e3c94ca7-a487-468d-83f2-8068a592a774,"" -e3e4b2a1-6c40-4f42-a5b5-17b352209b40,"" -e47d814f-5139-4339-a710-fe953efcfa74,"" -e484c47e-4da7-425b-a636-de512fcbc822,"" -e50c31e2-5d52-4d45-9b8f-8ae3fdd26488,"" -e53c4c4c-976f-4cb9-95af-fb11c264cb72,"" -e53ec08c-9029-4b50-bc1b-a302878b17e6,"" -e562454d-7fbe-43a2-8046-bfc5b6ab043b,"" -e5803192-2273-43bf-8acc-945cc13af70f,"" -e5c7eb1c-238e-4bf8-82bb-205c383f1517,"" -e5d9ff02-57a4-4e05-b573-0dbc80e19958,"" -e5fa00b8-1779-467d-b881-757b6796d8b1,"" -e5ffe973-b166-4240-b5d5-17eaad746c4d,"" -e6e9da2c-7684-41fb-9123-def8a7cb4dba,"" -e7098f3c-9dec-4aa9-9554-b3ad9a7d842a,"" -e73f4c39-965c-47ca-adb5-0544e20d45bc,"" -e757ee7c-a50b-4575-b383-587e6f782bed,"" -e780a3b4-6b8a-40a1-a9ba-5f95725399ea,"" -e78c789e-7b81-49ea-b7e7-d17d8e8925aa,"" -e79745a2-654a-46d6-a886-dd1d21acccf4,"" -e7b3ee1d-2d5e-4dd6-8ae0-ebddf36aee4d,"" -e7d53c39-4adb-441d-a4e1-50c5f60fd3ef,"" -e81ec213-328e-446b-9c33-ee80bc850d44,"" -e83d90d6-c694-47fc-8120-e91546fdda8c,"" -e8ba3683-aa88-4800-a248-737d39ecb6c6,"" -e8f05409-642b-419f-8475-28449c5f9569,"" -e952a654-0605-409f-ac45-19d00d4b53c5,"" -e97e9781-3cb3-44b0-89a9-b704d456fcc2,"" -e9ab227b-2a64-4c48-a8a0-6c166bf4b970,"" -ea2f0657-1625-414b-9c7c-bd0b01a12eff,"" -ea91b2ad-37c8-4a4c-9178-11d7a5e4e75f,"" -eac7851d-1d61-4a35-9f08-c5da53335f7f,false -eace08fa-4018-40fa-a7fc-3daf10b2f21d,"" -eae386dd-ef2b-4629-817f-6f8c44ce5817,"" -eb2419c3-3b91-4d73-b4be-a40e759ab957,"" -eb2781af-72a5-4760-a9a7-20d212f56230,"" -eb511148-31f2-4292-a83f-d16d3be6631e,"" -eba22453-8605-43d3-81f4-f703e58a230d,"" -ebad9165-a532-4846-aff7-5038cd5c694f,"" -ec3d7e48-c9f4-4f64-ada9-7e2891a62d1b,"" -ec462f5c-2aaf-45fa-8c25-6f556349af00,"" -ec4d4a14-0934-4d28-ad20-64db8fe78d42,"" -ec9ccc2d-1e6d-4ebc-b1e5-86d493d19eb4,"" -ed352845-d1af-40f9-afca-53503a6518d8,"" -ed7a65e1-de6f-4c2f-bd47-aa78d0c2189c,"" -edd41c4a-a5f3-4531-b506-d81c04e1ebc6,"" -ee004b78-c05d-4c34-a9f3-290a9259d3a3,false -ee6749cf-0622-4376-958c-229ebc0f9618,"" -ee8905ec-84b9-4fa0-b1b5-0908206b7af9,"" -eeaa2e25-739d-443e-ae83-b0ef04e2836f,false -eed7cb9f-7fe4-4f9d-bb4d-cdaeea6e144f,"" -eeeed70d-3cc8-4b11-9f6e-ab44da414b90,"" -ef079a39-9f59-4681-91fa-3d745c5eaec1,"" -ef10cf22-bdb9-44b5-bc9e-bb777ad8f090,"" -ef1c5dab-b3a9-444b-b825-8fe3d0248e02,"" -ef9360c1-b9b4-4dd9-86de-f996cd22e7a1,"" -efbe6910-0629-46d6-bcfb-5880a1795742,"" -eff17621-813f-4e99-a307-118ac9273efc,"" -f018a615-d5ff-4c3e-84f8-d4ebb1f342bc,true -f04f4b77-a9f2-4ce1-9d0d-6b43d39483f7,"" -f07f7d83-1fe3-4da4-9c33-86aae3ac67e5,"" -f08704a2-c1f5-44d5-a3f6-43c5608dc987,"" -f0a81ab6-10ea-4859-a064-b3b0dc662f24,"" -f177f59d-ecf0-41a6-b81b-a311eb760976,"" -f1a1aa8c-3779-4c42-8302-6006dcb95151,true -f1b36b35-1bd3-4328-aea9-4c5b3a7c7404,"" -f226c31e-37f4-4a4a-aa1f-aa400447a799,"" -f22fe03d-e1e9-40d5-9ff7-a5bb6e7a2d71,"" -f27187d5-f8f0-4188-ae0c-f77ca494a9b3,"" -f27a59e9-c785-47d7-88a4-de7706bb4f92,"" -f28962fb-0214-4664-b9c3-7c67d4eb2e8d,"" -f29f4e3c-81f9-49b1-bc52-47a60a3076bb,"" -f2fd6221-529b-42ca-a060-30db0996a398,"" -f3047ad3-3524-4a48-a00f-603a65fa0891,"" -f310aaca-1c10-41fc-ac49-8430c4c23656,"" -f314d140-929d-4ced-bbd9-c7f0204e99eb,"" -f32094e2-ab74-4567-98c7-e35fe529393a,"" -f3338731-d7b9-4c1f-8e3d-e73b5b756c7b,"" -f3deb81d-79ca-4176-9b6d-bd96bb5b7a16,"" -f3e99f4d-5bbb-4f11-9519-4d3aa15e25cb,"" -f3fd705f-3f03-4f26-a477-f36e65fe7499,false -f4077b1d-84fd-4489-b8b1-bbc9f5b61da2,"" -f475b875-3df0-49fc-b4f9-10b2ac1c40a6,"" -f4bba159-e786-4f15-8100-6bff75c03635,"" -f4e59f5d-12a4-4005-bdae-e157e145c119,"" -f5042323-b136-4c9d-bf57-0b7b70609495,"" -f51f7f2d-968a-4630-8e06-d6beb52b9f6b,"" -f55f863e-ffbf-4eb5-8f76-e2069098adcf,"" -f568f305-55f4-4d1f-849f-6c8f371f5051,"" -f5dec406-3636-4ac4-894f-1035ef3ef570,"" -f5e51d8b-bda1-4093-b887-f2355ea171cd,"" -f5ebecec-331b-4d57-b353-3f50538cae2a,"" -f5ed5cb8-adc0-424c-8371-ffb8e7cc9bc4,"" -f6514087-db2a-40d8-897a-21b5af7d590e,"" -f65d9eb9-e3d4-42fd-8d3b-168a6009f6ed,"" -f66efc52-e910-4d73-a9a7-fddc110c16c0,"" -f67e345d-7f27-48bf-afe4-00d79c3cf300,"" -f67f612c-6534-40d1-8e56-c056e886b98d,"" -f6b9b783-c8af-44c2-8443-44b80e972686,"" -f6bc9618-f245-4402-b69e-74d424736c4b,"" -f703fec9-ca54-4363-9ef0-4c4acdc37e0c,"" -f73ca57b-a00f-4217-9106-aedd26812b3d,"" -f74957d5-eef8-4683-976f-c59ffabdeadc,"" -f7d2872f-bfc1-496e-ae92-a9d446aa72da,"" -f7d6ad2c-1f7e-47ef-adfc-16a5d4d0189a,"" -f8043ec1-8313-45f9-a6dc-4cc1e5ad5f54,"" -f8673366-8964-4c6c-aa9f-02e0920f21dc,"" -f8683b38-42d3-4f32-a543-034c05dc07f6,"" -f8816dca-a41e-4ef9-8a17-b40fc7d707ad,"" -f89b5e47-c8c3-4067-99ea-bd22f8932ee1,"" -f8b6ebd6-8d2a-4592-ad16-06772ad32cc7,"" -f8ddb5b8-70ff-4d5b-adf0-1752811f7be2,"" -f8f99d6a-94dc-4fca-8772-d0c3fe887a7f,"" -f912a4cb-2eeb-41f3-a410-0ddc3eab9cdd,"" -f9185eb8-d2e5-47aa-916b-a5ff24612ddf,"" -f92f93d5-3037-4692-a062-ee84b9961510,"" -f93aa334-4532-4774-b82c-6d040d3c7156,"" -f93e975b-d07d-434d-b227-666e744892c7,"" -f95dafda-7f63-4ddb-acbf-e363dc901b1b,"" -f95f8351-818a-4cb1-b26a-507c4baced47,"" -f9aff2d6-fe39-4d17-90cf-917edf5d2bc8,"" -f9f5480b-27da-44fd-84a6-1ee4d4a55e36,"" -fa0ee43e-abc9-44cb-9248-3acc6c40e693,"" -fa4daa52-528e-45f5-8dd6-fe2a2c9fe8ff,"" -fa522b1a-3244-4203-966f-546dcfb0c62c,"" -fa5e8aa4-84e3-44b7-9dfe-326abdf02162,"" -fa7087f0-0140-4c94-b7c9-185b89c613c8,"" -fa786b6e-dbf4-4c51-a8c6-3006a029e8ba,"" -fa80db64-ef07-4b14-aa7d-e93585fd84e2,"" -faa80ca5-9c2a-40f4-aeec-26d982e663ae,"" -faeaaf02-2ae6-4f5b-b729-61fd56d3946c,false -faf774dd-13ef-4b09-8cc9-3cf33a562482,"" -fb176a2c-4b8f-40d7-bb81-f370c96c3f96,"" -fb1df190-d719-42b7-8e9c-d7830f92882f,"" -fb42fd3a-123d-4b2b-8a2f-d7e37af0a675,"" -fb55aea6-a5e7-4a55-a8df-967f504023f6,"" -fb93624f-3d94-4c5d-9aba-10a3eaea0434,"" -fba16e5e-1b98-484e-8296-43a3995b9f75,"" -fbacdef8-f2e5-4a1c-8a01-b25442a5af89,"" -fc348304-8ef3-4b3d-afcc-a66718315d89,"" -fc6dd798-66ac-45e2-a663-ea69b976c120,"" -fcdd2cbf-917f-4429-a41b-48f8fe2a188b,"" -fd325c37-4113-45e9-9e55-abd6b2ce6dbf,true -fd694334-1bcc-40f0-863a-017311fbaecb,"" -fd75e2b2-c780-477c-a813-046ccec6fe51,"" -fd7a8bc2-9005-4230-a4f5-348f4b1f5b16,"" -fd7ef037-c576-498d-bdef-cec86fdd60b0,"" -fdaeb95c-9d66-4da9-87cf-eabbdc593007,"" -fdb161bd-ad0e-47fa-a5bf-f17ece98f28c,"" -fdb49a29-6648-4dc5-bf56-ef948b00d885,"" -fdbbdafe-ed7e-4bd1-ae67-86430153a738,"" -fdc10bfc-2b5b-4a23-a93a-eaa85803bbc3,true -fdce38d2-ae3a-474f-8024-52279c43b745,"" -fdfcdb50-716b-4452-a4c8-e03d3b8f89a3,"" -fe4c4989-ff06-446e-bf01-5c5d8f69f1eb,"" -fe66bca4-eedf-42a9-8def-d29ef3dd01de,"" -fe719bdd-cc9a-4c93-b2c9-982d051fceb1,"" -fe7906dc-a882-488e-b576-2ef3486bcf79,"" -fe7c2db1-c229-4681-9373-e388dccb9e37,false -fe7cb896-d81d-4b38-885c-4134ea64020e,"" -fe97c34d-b787-4c3a-a972-821eb42a3442,"" -fead13f8-4b89-4f33-950b-ee9254a36c4b,"" -fead2d6e-2979-4de7-8118-698906954d06,"" -ff9df3bf-b8fe-4789-aa34-b205b5143a92,"" -ffab80f3-741c-4c67-af21-4585f1c05d2f,"" -ffb89ee2-c3d9-4fd5-b897-a0e0eb692143,false -fff8bb82-5151-41f9-b150-801ed4957277,"" diff --git a/fhir-server/src/test/resources/responses/ParserTest/lengthObservationComparison.tsv b/fhir-server/src/test/resources/responses/ParserTest/lengthObservationComparison.tsv new file mode 100644 index 0000000000..d6e9d5c0e9 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/lengthObservationComparison.tsv @@ -0,0 +1,1502 @@ +0020b035-7314-4867-9453-7921f158d003 +002593d4-d271-46a9-a900-1c498c322988 +00e9f82f-f16c-422f-81a3-77fa98958da3 +00f02590-e737-42c4-9c73-206cf66b02b7 +011f5b11-f944-46ae-9cde-0d802e5c0073 +0143f01c-3692-479a-9a72-592260dbbd64 +01a4de83-37d0-4612-82eb-2ff2dbc2e02b +01c177be-05bb-4a44-a199-1df387e68d8e +01c17bf0-9240-4149-983e-f0b3ccfb52be +01d57966-1281-42ab-9e05-02d96d146b6e +01e27cc9-c9c0-4c33-82f6-9507394f5f2a +0218f780-899c-4c1b-9167-45e33d02acfb +021bf465-c299-4302-99ca-e29a6d25bda5 +022ebe16-f618-4acb-9d99-88e23d686c34 +023a9220-3016-4202-8c7d-360d7d3bece9 +025aa37b-4ae6-473b-9ae6-c12eece968c5 +02741df6-79f0-477c-963c-8b3f8cb4374f +02b87530-10e2-4009-8734-4196642cef1d +02c07924-f5c7-4909-ab49-dec34c051dfc +02c4fa06-3281-46a0-9bb9-c7b99497c91d false +03365fa5-36d2-41b3-b3d9-1fea9f94c73e +03555f06-db5f-4b8c-aae3-f6d2c547f499 +03610d59-94a2-41de-a039-c671241b075a +038ced3d-5233-44c6-9a8c-1f6f9cc4be11 +03c7ba3d-8af2-44d1-a3c0-2c56b9fbc7c6 +03dadf3e-cdc9-4f63-8a4d-89bae77bd529 false +03e44215-f743-4c4e-8d19-aaf761f54503 +0429fff2-2839-45d8-b0bd-1d86aa92f966 true +042bb1e3-e5aa-471e-9e83-bb4a1b90af22 +043108b6-98cc-4feb-8ad0-1394cd10a289 +04367369-256f-4d47-b1ef-8014109ab46f +04661736-24d8-41f7-9f05-af5682696df0 +05048b00-24df-4311-806b-2f9a7cc16380 +0531e6b8-2ede-4e12-a91b-cfe7e12e552e false +054334cd-cfc9-4a1c-acf3-d0824cafd3a5 +055824d7-b398-40b2-8fed-bc40552fffce +05643ef2-65e0-4812-b739-4fadfa3c066e +05bed469-760c-439b-9729-0479a6550b76 +05c37921-dde8-4b70-869e-82680975ab77 +05daccae-2d73-459d-8aea-7ee75966e52b +0624683a-8572-4885-b51d-2197dde395bf +062ab32e-318d-4ff7-81ea-f3be53204988 +068d13a5-8b46-4a3b-aca0-ac9ffe70f998 +071b684c-70a5-4c93-8289-9630d25496d2 +072312b8-c984-4cb4-a4f8-97560368576d +07279076-3064-44aa-b07b-346a7edf6abb +072bd136-7e5d-49a2-b878-aaa2c7fde1f2 +0735b01a-c65e-4d59-9016-5e3bed1fcdcf +0785f996-ee5e-46f3-8966-107235de4cde +07906572-63db-4046-8dbf-1ae866664f41 +07d441b6-4e92-4d6d-af4b-7f38471fa45e +07dfda29-5378-4a66-ac90-fcb7ec6283ca false +07f5fcdd-3aa0-4ccd-9122-f9686886dbd4 +07f95b0d-656b-4830-ade2-864e58ac5ed6 +088cdf48-9645-4dbf-b824-8836d6a83d24 +08d37a26-1ec6-4faa-985a-10a9d0ee54a9 +092b1b96-4ad3-4716-850e-8056ccd456d8 +094a486e-7e50-4717-a0c3-07a456a4cd5f +09541c80-54b4-4bb0-8776-37745a9ac36e +09941e9e-ff5e-44e5-82cd-4c3865e39c01 +09ab1f16-7bff-44ec-aed5-8ba400930bbc +0a005143-4645-4ce4-89d5-9d49dca18ec6 +0a40e26c-d0ac-4653-a54f-17e23ed9a0ce +0a6c1262-dae3-4ec2-b911-0d1caca68302 +0a7887db-203b-4a3a-b5fb-cabe9a886074 +0ab96751-edf1-4941-981c-c6bdcba75380 +0ae172de-1755-46d4-8729-d3acc96ce82f +0b6ee068-26bf-4f1b-8423-30f1c591861b +0b95920f-7c6d-4b30-89da-51807a6aa557 +0bbc7111-589b-460e-a742-20f2131c9c64 +0bd78173-c348-41f1-9edd-884e3561ad47 +0c4242e4-1c5e-49d3-bf57-3810f8b88d56 +0c93a4a8-3b59-41f8-a7a5-22a9691710ab +0c93c94d-b626-40b2-9561-52168c4961ca +0cbe226b-6432-41c9-b3dd-0a14c6d54d7e +0d1af4fe-50f7-4d8e-823f-192f8a08ec8a +0d7a0166-7fb2-4e1a-b1e5-b03a53fb5602 +0dbafa74-2f5b-400e-b27e-fddeac8c28ad +0df8e035-a226-425d-9684-a12d0c58b844 +0e03654b-d81d-4a62-8f53-b479e8ce3edd +0e11e8ea-dc1b-4e8f-b809-39f0f07c7499 +0e8063ca-3601-4741-a2c0-c0381cc6098b +0e92574a-cc09-4564-a2ff-1cdbc89d210c +0e99ca14-e99e-4394-b962-59c66427218d false +0e9cc169-ca91-4201-a78f-1b0f724507cb +0ed9ee59-d968-443c-b7e4-c3732ec666e3 +0edd2d17-3ac8-427c-ae05-40c99b401484 +0ee77005-b9a1-4c51-a599-bd233897e431 +0eea9efa-148d-41d4-b05a-d39484526333 +0f13292a-a654-4473-b78e-c8f1b3328ff2 +0f67bae5-3bbc-4c7d-84f4-34583758335d +0f68a16f-1c70-4d73-a9b2-fb384585526b +0f7373d2-3052-4c98-9c04-7074250f44cc +1016a9db-50e6-4c17-9e93-18a97b67269e +1037da65-9451-48d1-a35d-bfcae76c2c60 +10a7d4aa-af9d-4d03-8d1b-bb53ed123f62 +10ad7a5d-e26a-4d4b-b063-239af96b0e87 +10b85b80-f6fb-4473-a3a6-266f610dcbc6 +10cd7171-38a4-4276-b911-bcc8092eff7c +10fef9b1-6bdf-4eef-b6b5-e12504e8fc7d +1104d152-e95e-4a6a-8225-ab200e4530b4 +11148ea5-efc3-47ad-8672-799331f8e491 +114ead73-4920-48fd-847d-f9aa1a99cab3 +11597b54-9453-4ddc-9171-2be17df7a511 +1167e4a5-ce86-48b0-8b96-6d70125e2f9e +11a0c4d2-8908-4c0f-8e51-bf4be1605441 +11af3d82-bb56-464a-9203-8714c270f4a5 +11cc59ea-52cb-41e1-97e4-7ed651037442 +12254e32-b86c-45dd-93b0-c8cbee619a14 +12257920-12cf-4945-a72f-80551181d7f1 +1266203f-94c9-43ac-af02-a0b8db8a93dc +12689eb6-84ee-4d28-9c65-3f3e305266f3 false +127b14bb-027d-4380-932e-ea34c76592d4 +128cf2ff-4f97-4039-983b-72e2dd3267aa +1333fbed-c09d-4622-b22c-c67dd32e4583 +133a1365-e43c-4d92-a6f8-8c3947b13b65 +1359fe32-3275-4c1b-ba16-9dc903334044 +137d6ce5-93fb-4201-8bf9-0e97e9595b77 +13bf741f-7ad6-4a14-908f-cbfe18651381 +13f4a39d-ec9c-43b4-958e-be28a97972e2 +143099ce-8738-4ccf-85fe-63e54943c7f6 +146000e6-9926-43da-9322-de3bf31e8142 +147add3d-eba6-4cea-bbe9-823b44794b9a +14a9ad6b-b7bf-4cdd-972c-bda91e5066bd +14afc7c1-89c8-4740-a932-a76d579e98c3 +151df048-c17f-4a51-83a9-3de5f99be125 +1520180e-fb4a-4df6-80be-0b3d559c1049 +15268027-ae33-4f5e-9c2c-76327ccdeb44 +155d0003-f37b-45de-b34c-d611c2a2cf35 +15821e91-ec03-4e76-a909-d2f2a14c3219 +159c1f96-038f-4722-9e36-98f4dfdb6b66 +159c3713-0d0f-499f-8ff6-ecddd44b958a +15d9442a-2018-4390-beef-55fd90cf6ea3 +15e70015-e207-49dd-93a3-7c0ea5cc456f +1631e3a1-0eb3-4d16-a7e5-b0d8a8a9e630 +16af45f4-69d1-4190-9bca-b309c17bd865 +16be31b9-a434-46e2-a481-459c5584c907 false +170e5f14-b29a-4aa8-ba71-4bf381a38d1c +17505b4c-c5e8-4acd-9d10-fb001190a8c5 +1795e970-0ce6-4d90-b198-32567c690869 +17d74e33-f6c3-4b19-8708-4110a2f55245 +18a57e28-9c23-4e73-9d90-3694f2e373f2 +18c6a217-c947-4e97-a764-c43f33a409fb +18e59ddb-b9b9-401d-9239-0d2cb81936c8 +18ee3e63-a8a2-4004-a313-3e332af8a173 +190f22f4-5fe5-47e5-974a-fbbe63a676c3 +191232f1-58d7-4c4a-93a9-10aee15e5cea +1913a538-60f6-4510-9a76-2466896d2a27 +193a64c2-63ea-4677-9302-b56e3f0531ac +19a30860-10da-4a1b-abdf-0b03d7b8f370 +19b01508-db72-4c22-a5b4-4e015a3207f9 +19c965ca-c1ae-4eec-816f-73903fe43aa8 false +19d27e58-26a0-48ca-a199-88d48894da5c +1a3ad6e3-cb5e-483b-babc-ba7d34ffacf7 +1a7b0249-5aa4-4b0b-adb8-2c958e137d54 +1ac6d3a1-d1a6-4b0c-b405-0aaaaf37c7db +1adf64f4-1fb4-47f9-a8a3-0ebeb3e7f414 +1af4a596-d0c3-4f7c-a217-b83a0755c3b7 +1b091e60-4c4b-4fb4-b9b8-ee53bccc9b52 +1b251a42-a224-4a0f-9d36-44ecc8902d08 +1b29b772-b873-447b-8aa0-5dd5e9ee1b80 +1b614d48-0af0-4d09-a6af-4f3af065da14 +1b785bb6-a40c-48e6-a464-f8022ea3cdcb false +1bb3d0c2-c6de-42ec-bd60-edc8e3ccd092 +1bdd0326-987d-445b-9514-2ea30d948093 +1c01b494-d0c1-4a99-b110-9de392bbcf69 +1c120e92-0385-4120-ac83-1b42191597b8 +1c32d0e8-7312-4821-aede-6745a83ea45b +1c51f4a8-14a6-48ad-9821-c6408dd0cd33 +1c52440e-0063-4170-9cff-a3caaf7c63fb +1c6dd7c8-4942-498d-a369-65fac6a837d6 +1c79c271-a128-41be-9537-1d744f4971a8 +1ce6a20a-2413-470f-86ca-2dacd8811e72 +1d9b87ab-8b41-4184-9519-fa9822f48aa1 +1da7f725-2fee-4f33-9a89-96253d2449fe +1dcdb77f-b0e5-4b2f-ad4e-d446a6ffa079 +1ddc3c30-fccc-4e5d-a9b2-e09ac4f64dc0 +1e2bcc56-3121-4b1f-b9c7-7e5503b867db +1e5b7df3-5e81-41c5-b096-d9b5409268ae +1e75c4d7-b570-4339-8c0a-06bdfc9bb485 +1e84c68a-4e0e-4980-adea-e46ae2506896 +1ea31cfd-f340-4c68-a547-b76365abd1bd false +1f170771-0d08-45db-a38d-da3c539b46b0 +1f182acd-6bb8-4031-aa86-2f3dd83b4cb7 +1f3e4d9e-1fec-4a71-98fd-4912ab64d3d2 +1f6b8ac1-b12c-4eb1-a158-25b15965294c +1f8ad9f7-f796-4900-b10b-365fe442f2e1 +1fb81c48-c8d0-44e1-9b9b-df53ad7d9456 +1fcab6a5-85c8-41b7-a820-fe6e2d84eaba +2031d9a4-3e03-47a2-ab74-6fe7f2fa4ba1 +20d47a7e-f7f1-475b-a9b5-6192f2fce0ca +20ee5488-a11f-418a-86a9-cb981c2fdae5 +2123f57e-0815-49e1-aa34-994270134f3a +2160f03b-4276-436a-8278-300bedd07f7b +21936507-6a38-4d80-807d-0d7cb5ea0311 +21d2fab0-5092-411b-b752-4859134a7aab +223cae66-3049-48bf-be7b-cf027c415743 +22440788-7e64-452d-9025-9a25e4d2be60 +2245208e-d17e-4bb7-9bd7-ac75ac0ba134 +22462877-9663-4c06-bb5e-040d76bb144f +2253a94d-e511-42bf-a17c-599ae2d5cf0f +226d433b-9abf-4e98-bf0e-47cb3a37b41c +227d84f4-ec92-464a-b3de-f596866ce825 +228b1b4e-f5e7-4e33-91fc-b7cbe6979102 +22b95566-7a4c-47b1-92af-7e0ecbfee6fa +230b746a-26de-434a-9774-54db1ad98a7c +2324dacf-f221-4edf-bdf6-924449cc916c +2381d397-0068-4d6a-88e1-a0257d81a323 +23831569-6aea-4071-820e-6eb5440acc1f +23b4fe3f-62c7-4439-9364-247ceb9251c1 +23f91a73-1b46-414b-becb-918e84177886 +2439d9a3-6c58-4ced-a82e-27908ed3df94 +2450caca-d74c-4610-ac24-1fae13801eb7 +24d8f04b-f035-4a9b-b03f-75be9e244ceb +25085ba1-43d7-4a60-82ab-e15c477313b0 +251e7497-4a55-42fc-8e6a-32febded738f +2534b076-583b-4872-84e3-bc96bdee5ef0 +2583c0bf-6a7e-4100-a5c1-d249de6ec56d +25da1be8-51c6-41fd-b48d-5227e804ebfa +25e1a428-ab0b-40f5-ac25-1e9899458ec7 +2606b58b-a924-4128-b3a4-5e4b91deb692 +261583fb-e2d0-44c0-a677-00bd89a3450b +26344e09-20c8-4405-b674-49638169a7a0 +266c347b-fa2d-4cc1-a2fb-fe1ba20fbc2e +266cf373-c41a-4cc3-8600-c165a4349a6a +26b4001f-2917-4bf5-bcbf-1ce9dc258171 +26fb6882-7f3e-44da-9100-58057c9aeec8 +277e09d4-cb89-4748-b9da-39bee94df8b3 +27a75a54-d318-4026-bd63-c80f73899376 +2803a9d2-2555-4bc6-9718-7a9dcabd6ce4 +2803acc2-63bf-49a3-b4fb-0dd0742fb816 +281d5649-179d-4c8c-9a0a-d1159a5df8f1 +28503262-5aab-44eb-bf51-6fb7534c25ab +2866a0de-36ab-4ec0-a1e1-cce5c697ef85 false +286decfc-83d1-42e8-9a2f-880f145c71b3 +2875fd27-8c5b-46ec-91f0-ef3a7a7727b3 +28aa1696-dde7-40ff-bb7a-6c5b614405dd +28e8966e-ba9b-4499-bc9c-7f51222e15aa true +290440da-118e-4e8b-8d0e-d50043c24eac +292aa324-379d-4b82-9750-4c8e23587f92 +29506f88-302c-4264-81b7-cf762de48615 +29546a71-e5a0-47ae-96da-421275c2cce1 +29676c08-88c7-4b0c-82c9-5f6d7dd51f68 +2974e695-f2e6-477f-91e4-da8eb654bf24 +298120fe-5dbb-4d99-902a-3a6783fc5b6b +298e5099-df2f-4381-8259-b10d0b8c0570 +299c851f-3566-405f-a217-84c0703d859c +29b17fc8-8e80-4d7d-93dc-bdba1f2cfb9d +29bfab6a-d760-449c-9198-38e8c63574f2 +29d4d79f-8a85-490c-b52d-aaf3bcd9a100 +29e54cc5-b484-4818-9c3e-08074812dc7f +2a0d7eba-2dbf-4566-af0a-c16161a8b189 +2a133e8f-4dd9-49fd-9e3f-5cf122294091 +2a67b2c1-18cc-45a0-ad53-825c73a3aa15 +2a71ca62-7f39-4c9b-b3de-6dfcf299298a +2a7f0959-9d50-4ccd-88e1-6635f2502905 +2af24d01-6215-4c7b-88b4-6cee45a156f3 +2b265cf1-0763-4922-a403-e6f644ed95ba +2b41d0b5-97d4-4a80-bf65-b62ac0cb530a +2b4fd497-9f28-4c87-9b13-64f0d00c0cc3 +2b54e31d-4af9-4071-ad49-60cae644ba39 +2b60fba0-0143-4f31-8e4c-27236c7f41d8 +2b927027-35f1-488b-bb5b-127a8985cc5e +2bc92150-c490-44da-a925-ebddec5cd953 +2be63080-a881-43df-95b5-e371df93c6bb +2c018134-14c6-425d-8ab4-bc3974402b05 +2c3b614e-7860-47ad-9320-11c5f45d27b0 +2d358911-c5eb-480a-a9e6-a6fab52ad7c2 +2d45c298-d386-43ab-b3e0-ba07dc527a68 +2d6cb13b-fdfe-4219-bc13-7de385998bcc +2dbd4567-e6aa-4e1a-996b-af8384f66b37 +2df38530-e49e-4ff9-93a3-816de29d9bfb false +2df89473-f746-4086-a2f3-8ee2d4f63271 +2e0bc41c-fdfa-42ab-abdb-28f13cf2ca20 +2e13cb39-148f-4299-b74a-0a8e10092e9b +2e45b9b5-8d45-4994-aaba-1fab72c77c5d +2e6b11f5-cede-4152-bfe9-523b030a2bdf +2e8c312f-53b4-4cd3-b6da-653c34945d76 +2ea2af0e-343f-47af-b0fe-6dea108836be +2eb09ab4-c554-4536-a3ce-2979cfb988c6 +2edee289-e99a-4a53-a067-f83d29db4c2a +2ee72185-ca8f-40e3-b82d-30408dc8c6f2 false +2ef91918-bdb6-4a6e-9c39-41d8c513b552 +2f1bbdc4-beaa-4d49-8791-6548094c7291 +2f24078d-9646-40fb-908d-2628d555dcb1 +2f260daf-8fb3-4fb9-a435-670ec8f0180c +2f30832d-e6b8-4946-8cf4-0d8da5c359c0 +2f33b981-f22f-4983-b347-f81d7d72c50a +2f3f7f7a-ba31-4c1e-8a23-c7d1eb1f255d +2f5d7d1b-847e-430c-988a-40028f9f595e +2f8b35f6-2b55-4ff0-8d79-5de7805d6da1 +2fc9f8c2-4a1e-4e8d-8d91-a6c3b3937e8a +2fcfe30d-dc47-4eaf-9df0-7943a2c37fc5 +2feac451-2301-4ed5-8bca-baef62790656 false +2febeae3-eb61-4743-8ff9-42547a77a40e +3006707b-dcca-41ce-b138-5d62e6b56984 +30371578-4ad0-498b-a491-4e194c7466fd +305d5449-57c6-4362-83cf-55b4dae133de +30ba9f80-e595-460a-abf2-b8f46a62e606 +31504825-77aa-4a0f-a634-ca806bd886d3 +3169cf91-33f7-4b7e-b49f-d4b884760685 +319dfae4-0305-4520-8ad1-9679383cb74f +31acc736-076e-426d-8a32-426d91bf6511 +31e26c46-af6e-43a2-8e27-a8342f53e817 +31fd2352-7eb4-4ce8-9dfb-b3d3a1410c82 +322637fc-2d75-4a8f-89a6-a4c963869112 +32596eae-6236-4b3a-99fd-e50856971f59 +3286c996-861f-4fe3-90a8-86ec826cdf9a +32bed860-5df5-4bbb-8bd1-c95c016561ee +32d92e2f-d02b-4006-bf16-d1c535948e80 +32f3d9fa-6c64-49e8-a6dc-fb51679753e5 +330fde18-0a86-4e67-850d-fe1c8b376dab false +3322da98-67e0-445f-ba1f-225eb7d297a3 +33333f69-f8a6-4def-a5f2-bab5a26bf054 +33826788-b600-476b-9843-17fbee2cd849 +33a0b9dd-c88a-46cd-ba8f-af3d4d6cfc27 +33ac0c39-1552-408b-97ea-a90c9187d61e +33b22ef7-01a8-4e8f-8b5d-9aef37e46896 +33be0567-5c42-451b-bd71-c09f7de30524 +340dbb93-0888-4f47-b18d-0b86d3e40248 +341634fb-2646-4c69-b57d-b5f00ff4c83d +3420a3d5-d5c7-402b-a4c5-02e7198c5b7e +342699a7-cb58-4a9f-bb79-306483dfa652 +34335eed-f6c0-4e05-ba6b-1ecaa1fc121c +346734fc-4798-48e3-8880-a603d52e9ca9 +346a282a-439f-48ac-875a-cc34643d70cc +34a83c04-2ea0-4638-ab6a-0a4bb9a3ad4a +34e44ce8-26fe-48c3-8fe8-5cd8f260aee0 +34ea288c-f987-476a-a8ed-0ab2c1b92833 false +3590f230-1be0-4c7b-afc6-5bffec9933ad +35cf6113-d23a-476c-b18d-f86fbfb3ea2d +36160c58-889f-4543-bf04-dd29cac9ce19 +361688f0-e909-4c9a-b5d8-10727c9a18fd +365ca17e-d5b1-41d2-982e-3ca7afdc0a0d +3669a209-82a0-4f9e-8026-43cebad7f3ab +366fa6b3-9595-4624-ae30-20257ef4885a +36957f27-97d1-483a-a8ea-bd5b1484ad12 +36c9ce76-e047-487a-837c-faffdb2676de false +36cc6703-7757-4c6b-91a0-a43055c19bb6 +3732940c-2fa8-4301-942a-e58fd8d73421 +373609ea-276f-4b96-9c94-d96ad532246b +37770a6d-bb26-4499-a6e6-2eb399a8ad07 +3793c189-6e61-4f1f-bb19-a01a306267d7 +379779f8-dbce-4dfd-b18f-ce0485b0d6aa +37e1570f-2c29-418a-acee-6688b6ab4279 +37ea3114-1b65-4c47-9e65-4cbcf339f8c5 +37fa89b6-2f6e-4985-b733-1ee4b2fd59e2 +381dcabc-52cf-42e9-9624-d4e1270518a9 +383aa480-8db2-49ed-9542-c738af90f344 +38898dca-756f-4267-aa1d-1fe52a6e4749 +389079a9-b902-4212-a8c1-7187e818e016 +389a8f10-ad75-4d27-9d11-2c4ba0dc55a6 +38c329c2-4078-450f-ae85-e411b80f30ce +38c7c98e-0372-4892-aea0-ba21ed298958 +38d59ef7-b207-4d55-b74a-a1da3d0c10df +39d3bc28-0ce7-4d1d-9adc-bb9933568809 +39d7db36-7baf-43d7-8a0d-970ae00456d0 +3a1941c5-c6bd-45b7-b306-d2a1a51c5eab +3a8a0b91-a492-42de-9b64-fc552476d8ff +3aacaf9d-af9b-4220-a836-1d0c3cfd5245 +3ad5553b-58b8-46c9-8023-e1acb85be08c +3b4bf6ba-0f27-4228-b6e1-c16dbf82e5cb +3b7c654f-9a67-4402-a571-a5b0a61559ab +3bae6ece-f05c-4e3d-81a7-64f10f907bb2 +3bb8d5a9-87a6-4155-8f3a-7a0c841672c1 +3bbe3af0-0f3c-41ee-9c41-70d89b411852 +3be31b98-5218-44c6-aa92-1e054793d9df +3c496888-2ead-4d5f-afa4-8ab39b57ca03 +3c504a58-92fd-4b2f-a308-21a9588b1850 false +3c8d926d-3156-464b-b385-192257411798 +3d2c40b9-2ef8-46d7-8215-bb898170902f +3d87abf4-a569-49b5-962b-32c11816a53f +3dac5f75-f71c-4092-b87a-7b8434af633f +3db4a850-ef45-4fa2-8eb1-b1cba4477d8d +3dc0190f-39b2-4a1f-87d9-1ef41a73fa75 +3e4206fe-8856-46be-98af-d6fecd52db92 +3e58093d-c387-4a07-85c2-41a519d0c398 +3ebd1cdb-5546-4dea-ba79-a88aef933640 +3edb8e33-d674-4c25-aa75-7303da0229b9 +3f5c46c8-4a8a-4575-b169-6bfd5d752d98 +3f771434-cfed-4f34-9671-0ef302b5f9e7 false +40100d97-ad9a-459b-b044-66568d36c0bd +408159a9-8c38-4cb3-9488-d83331f1762d +40a1dd3f-6ffd-4463-bf71-42057ee61689 +40a75d7a-909f-4657-9ac1-5b56df539129 +40eec0cb-8f50-4f61-92a4-7682c48511f9 +40f3ae1e-4855-4d78-9fe9-c8348058088e +4115b0b4-f738-405b-9351-ae11ba4bd07a +411cbadd-c2d1-4fb3-8ddb-d8bcea3aa176 +41496990-adc5-44d1-96b8-15010bdecc89 +41a3cf00-0965-4c17-bfb5-247a7da43699 +41a86c21-2ea1-436a-86a3-9173c7d95d9c +41de6042-e95f-4bca-ba69-f55293248f7c +41ecc0b6-ae98-40e0-a836-9d26070e248c false +42187018-d27b-4831-913c-575d07e33c54 +421e31e1-5ffb-4c6e-957c-728b6218d6da +42350e13-ab61-49ef-aea1-802f6b7ea955 +423800b8-5a89-4eec-a12f-451c533fac50 +42895609-81ce-4cef-81db-069efe9a207d +429c5342-c5ae-4c55-98aa-e4f7bf0b4670 +42ac25e5-8828-49bf-bb5c-a6001966c6ff +42c5e47c-ede1-48eb-bc82-2ed835d67495 +42fe77c3-239a-4afe-ad76-8c221b543e32 +43179e01-e94b-4f83-bb60-60f181388048 +432bf1ce-0e8b-4621-b998-819fee53f80a +43522eea-96c7-41f6-ad41-1c190cece6aa +4358ee55-cc06-46a0-8b44-6b89d09c380f true +4380d762-baa4-4c30-905c-59b33dbd793f +439dfa4f-917f-40c5-9313-0d5dcf7ecf2f +43bdfaaa-7474-4503-ad93-730d777e6db8 +4416e165-8ac3-43e0-a9e4-6c3e789ffdd2 +44233ad5-4eb9-4709-9c6c-e7797d9c9f1d +44237e1a-31ca-4635-9ff3-e3c71f587972 +4442a5e3-5793-43ae-92ab-f2d87a70cc0b +446f7fac-2202-4149-9860-fe48335349b3 +44713d16-f673-456a-881b-b0526ee36651 +44b741ee-bdb2-4732-b484-e435854de348 false +44dbff22-7cac-4f70-886f-b8ac2dea7130 +44f714c0-88de-43a6-b69a-4d62d8fee57e +4522ef75-31ae-43e5-8e5a-5caf5c83fb35 +453cd2e7-723b-4e1d-b414-f46f9152edcf +45467cbe-cc21-4822-94f5-82e1203056ad +4546f579-74ab-445c-8619-431f60a6efdd +455f8ebf-b074-4035-915c-91cd15380d72 +459f6da9-0eb8-4b26-85b9-a8f63c587fab false +45a306de-3731-40c2-8bd5-2c0fee3b9f99 +45d45783-5a68-4c06-9b2f-88b7ea1f8347 +461bea3d-934e-46fa-a112-9a761140372a +4672ebd9-08d2-43fc-ba92-ce13b587af3b +4675fa7c-6ac8-4bef-8756-88f757370a10 +4680ae70-6d17-40fe-b55a-c3152ce8b246 +4687b275-d990-41cd-a146-2715079a8ecd +47014b4d-07f1-4bef-9792-82c027525c0f +473bf60d-dab0-4531-9903-95dc6680a660 +475e95a3-6d57-40f3-a0af-9daf1b35618b +478fbabf-520e-48a5-b3a1-a3e535d64345 +47971ac1-1e72-4788-ba6f-bea608e59076 +47f066d8-abf5-4064-84fd-dfb05649e0fc +4808c4d5-7733-4601-8401-632bf5924c06 +484a19b7-9816-4a66-8f0b-51cb5f146818 true +48693756-94c2-434e-ad88-e573e5278a6e +48fae21d-0ac1-4e4a-93b7-ac4ba7c2a606 +49088daf-d19c-4bab-9084-c67f0fba2c90 +491118c7-998f-4d45-8b58-da724ac011e1 +4925b797-e285-4d02-9aed-dfac19b0875b +492f6c9e-3e1c-4043-aa08-03c610da33a7 +49466fee-df56-4b9f-898f-96b65318d833 +495bdeba-9ab9-4756-b4e0-a4aa8b039a02 +496f0696-cee8-425a-81d8-1df8fd17e4bd +49cce532-3ba4-4db6-b518-ea934588bfae +49ee8b11-a7c9-455e-9a9b-835dd617e861 +49f41136-fc63-48a8-8ee2-b7536d3bf0e7 +4a6eb9f9-49a5-4d36-85dd-78512fbe593a +4acf651e-af55-447d-bf0d-9adaef4cf6bf +4ae0f855-534f-41dc-a4b0-879c771e3379 +4aeca194-6d0d-48ad-8fd4-6aea3be783fd +4b322f4a-fa06-4509-9783-dbbf12960b77 +4b68a6bb-4fca-45ae-bfb4-b814c67bdac7 +4b86609f-db97-4a0b-9860-3ce519072521 +4bbf3839-bd13-406d-9f60-d9eb9daa4dcd +4bd9e2b4-e195-4a83-ba1c-478e66a7bb5f +4be334e3-94a8-4223-846e-39b5877e9ff7 +4be59cbe-d89e-4abc-aca4-410a7e8cf580 +4be72d80-9418-44de-ab47-e8f2f2b9f2e4 false +4c9a28de-ab9a-4e69-9ca6-375512c2f149 +4ca8a902-7ac4-4634-b1df-456175c39d14 +4cef34bd-c677-4647-b940-840a6868cb9c +4d0e9887-c81c-4ea0-9026-41ceb9ec9a51 +4d45564e-e14c-4289-a637-06193bfcef2e +4d79c0f1-72b2-4e94-addb-9adce3f49c2f +4dad93cd-b188-4750-bdb0-4a5501568d81 +4db059c5-c4d2-440a-84ac-360312e3c590 +4dba5ae8-2d30-479e-b831-80d39c396280 +4de45bfc-2a41-4e0d-a0e7-c6caf6ca0f24 +4df7ae55-1947-4d89-b51a-496b34c4250b +4dfadd90-3f14-4ee9-abbd-a348a5d6a7d9 +4e0d77a5-782a-4b03-8867-f51a9704368f +4e74bab4-e328-447e-84d9-a29bb7fa1548 +4e8a0b34-71b8-4da8-80f9-f015782b182c +4e9a316e-406b-496b-acf5-6eec80f25ee7 +4eab3a46-0ea6-431a-8c65-5b5bdee521cc +4ec34b7a-8389-4d01-bca5-06e678089d41 +4f147e12-0e70-4ddb-b4e9-4ea25361f290 +4f18952b-be4a-4d77-ad4a-05276a6bce1d false +4f3f5836-8683-49cb-b5e8-a9881796a202 +4f62ec59-e478-40ef-8617-e6ed599866f1 +4fe2d379-ba65-4ee2-a584-f0bc63a8523e +5005cfbc-b57d-442d-976b-34164aeb1977 +504859eb-2ed0-4184-99f1-79f436fcd692 +5085d62c-dc5e-4f25-8f0c-700e57bb71fb +508de40d-3c1a-4bfc-8713-11dbae730004 +509d7086-3120-4f61-bc92-4ab924f30ffa +50a9341a-67f3-4d18-9118-373db23eea3c +50b76e4f-6575-47f6-9e15-a96b58c492f3 +50dbd55a-e4db-406d-943e-7624d96c554b +50fbe67f-8d29-41a7-8cc6-3df916cd922f +5105a245-dc96-483d-9a0e-52b39e664d27 +5106bb8d-43a4-4783-97c9-42d707990a2c false +51164c4a-fa5e-4246-80e4-a01e1d7707d6 +512a1f5b-9f83-4420-a4e4-978e33432dba +5253d689-c7bd-441c-ab16-3721f1e3b5cb +527721f5-443d-4367-84e8-0f15982e7587 +529479a1-36f6-4133-bb4e-2f3cf677626d +52c03be3-5715-4edd-9809-ebff7e2ba57c +52d51fd3-d0c8-4046-9ec3-1a3f523d0b75 +53010db2-bba4-4751-9f12-f51429c9c558 +53339064-e7f3-47b0-a5c6-ce77d50b7489 +537bd7f9-9f4c-4bf1-bc21-9d09dd655a13 false +53e5edab-cd6e-42cd-b6e3-a36d57be3685 +5441b3d3-b8e8-4e36-84c5-623e9b199daf +54487624-db9d-43cd-b4a6-0b4d3934f7bb true +54627df6-151a-4f3c-9fea-374217afba2b +5496c32e-97eb-4f6f-a7ee-46bd2123f966 +55054683-a9bc-4c84-b1b9-6a1540e1168f +55449cc5-a88a-4181-9550-5d2bb23a293e +5565df4e-b483-407b-8816-ca9e8ddefc27 +5578c18e-84ae-4f7a-8cb6-ce285e1b1ff4 false +55936779-3082-43b8-a1e8-a8329bf19a8c +55954c84-4a6f-4667-b070-5fa9185fe3d2 +55dc6b95-e01c-4812-a812-2cbf20de74cf false +562f66c7-19ed-4d05-a14d-6aaa37e6eb16 +56617f73-b5fd-4e8b-8bd5-467af440f0a9 +569f7616-50c5-47c5-94c8-fa3b3097bc97 false +5703a2ef-ccc3-4ca0-b100-a1c567f1dfc4 +572e67ff-4f0c-49c4-b55a-36a958d3e02f +5742e203-dd0b-43c6-9dfa-304240238737 +574873f1-84ed-4171-a51b-6a2c609e9afa +5760bdfc-cbd9-4763-841a-bb31a972f551 +5781b9ed-eeb3-45d3-a364-43cf8ea348f6 +579a0bf9-87da-4887-9700-a5098cd7d720 +579eb155-01ea-4066-9dae-9ddce5ea2041 +57d84d96-a1fd-44ec-8460-588b477502a5 +57fa04a3-a8e7-4699-9115-95957be21dea +57fec813-6327-4290-a2e2-ca74a04cccf8 +588b882b-0bff-427d-8b34-294f2fe55189 +58aa2384-e80d-4083-a173-ba534f152680 +591075a3-92bb-42b7-a6b7-d761add1ad2f +598288b2-fa85-4bf8-9e01-2d0e86711aa7 +59857d17-d8e0-4932-b92c-97bd36a31d3d +59a25b17-fe55-4592-abfc-0c22e237bbd9 +59b131b5-2db4-4877-bcc1-2206865df718 +59e29c4a-102b-46a3-a950-3babee7cfccc +5a0eb3ab-86ce-467f-b389-94f026fe0f7f +5a4bc06f-8ed6-4cbb-93f7-5c5faee7e855 +5a9300fb-d831-4b67-88b2-843d2eca4441 +5b0954c2-a2cd-4013-b866-a6e0474c12c6 +5b2730a8-6a24-4bf7-aacd-f56fd380c696 +5b44153a-1ea5-45a1-9d58-bbc0a474ce1d +5b5f6d2c-1899-474a-afae-a98762630b06 +5b72c7eb-4584-45c0-9039-5fc762b1e50d +5ba15ba9-7112-45ab-b7e5-353d52af5fe3 +5bb09da7-8ce1-4642-a113-fdd1fbc73897 +5c085687-51d6-4cbe-9b71-0a58718c1d24 +5c17c3d7-a7ba-4756-9991-97bc0b3a46ad +5c48ba78-4e71-441d-b03e-ef305d9e2bde +5cce02c2-3537-4d34-b085-7cf145590292 +5cd558f0-20d1-40c7-8197-5a41ef97e634 +5cec04b3-e52d-4a6c-af98-6ee5d104b20a +5cf73976-33ee-44ac-aade-5c64e56d7cd9 +5d54c744-365a-4db7-9675-79d64bdf55a6 +5d5dad82-9b58-4302-a494-02f44c3e94bd +5d790c80-5b87-4802-9b89-02f949846f39 +5d883f31-f417-4d8b-bdbe-2cf73d1aa4b5 +5d9e8c9f-8c95-4ca2-9de2-2cf875868ff9 +5ecf975d-fa2b-4c6c-a5e6-fed730c9e035 +5ef7c113-d5bc-486c-9be9-aa39aea779d0 +5f6ad8e2-eed9-4b2d-8697-4e2e742ee1d4 +5f92feba-5842-4894-907b-9ceaf36d0f38 +5fc1d33e-168e-435c-93ae-c619999b6f9b +5ffb50f0-0963-43d0-b032-3e6b2621e1d7 false +602f4e55-f69c-4103-bb6e-7d23fd1b06ac +6042dc41-8cdc-4825-9ab2-bb22af5c5fa6 +60635e6e-3d74-4f56-acb3-be8edc522837 +60959eb0-ed9b-4043-b8a1-0b0f27789642 +60996e5d-082f-4d1e-8cc5-6cc626974925 +609a5704-81f0-4b67-881c-d00e87b74c0a +6198d258-17e2-4956-8064-1448f4744303 +61e75b9d-46a2-4d74-a0f0-37dc66a09450 +6220b92e-946e-449a-a2bd-3241e6a453a9 false +62346d78-6be2-4cfb-850e-6ca1fc3f5e35 +62348772-d9cb-476a-a016-32f16e44cb33 +62ad1938-6235-48d8-aafb-2f773a40c6e1 +62ccafe7-fbcb-4cbf-8584-224cd58ef421 +62e4244f-3f34-48f4-b865-c8b1c3ad97f5 +62f342e2-5499-47c8-959c-4d786aa5fd67 +62f6ce69-77ca-436c-9d40-3eb541853798 +6320229b-25c2-4355-a288-17efb054df6f +633dac04-7a54-476c-8482-9631c351ac26 +639dab1d-3282-4de9-84f7-727d0462f11f +63b2b9b1-aaf2-4539-97f6-aa6815e75f21 +63d6c1e2-ce14-4e89-9bcc-f3e3e0b6fe72 +63e5b7be-525c-4a0b-852f-4cb090178d30 +63e709bb-a56a-452b-a48c-5125a367572a +641068ee-0805-4403-a159-4fdb185cfdad +64115066-e659-4cd0-9a56-fcd43a6df27d +64148c8b-89e4-4394-b044-27088ab3adc6 +6433bbab-cad9-4e98-b488-c06e37acf268 +645572a4-5806-4854-b89c-939463a904f7 +64a2df8b-688c-4368-b672-0406ac7032ad false +64c6ac23-d619-44aa-88fc-969376bebfcb +657aed74-c373-4c45-9de7-b75cdf62577f +65ad0ff6-ba27-4eea-a400-3d96240a5702 +65b9a660-2f5f-4d9b-9f15-5e572d65ee9a +65e87fc6-9232-4ba9-baae-61d7af6e54f4 +66037242-eaa2-43af-98ed-190c3079833e +667d62a5-160b-4657-be40-beeb787d51d7 +6683a92d-2d09-4875-9403-b977bc2eb37f +66be2f9e-fffe-454e-86ae-75dc713ee934 +678223e6-745c-4d58-888c-b910dbb9039f +67cf1dde-11a3-4aff-a7f2-a8b8288aac8e +67ed7f4f-4873-409b-a04a-b928cb461719 +68059a10-b5eb-41c5-bb44-c56ecdb6baf1 +68249708-6a5c-474d-b1f1-214514fe5dbb +682b9750-e94b-4522-b277-0d572b67e94d +68935870-c60d-44c5-b57a-000c0a99e9d2 +68a1d32e-915c-417d-98c9-3154ffbc9249 +68a84b5d-edb4-41ee-82aa-8612ab7b2b38 +68dc301a-6b81-449d-bcf0-7b2f3d3335db +692cc2c1-44b0-4c47-86b1-2d3c4a5848bb +6938bd7d-06d6-4a9d-8234-9123aa238788 +697d10ec-d72e-49b1-b2f9-ac8a3be5c856 +698df1c2-7cc5-44cc-ab61-5420d92c598a +699d6871-1043-4581-8dfd-fd1b6e47d518 +699f0f86-fc10-4f15-9a78-19515e4e4958 +69ae7f71-a967-4c7e-a046-cef77b622f70 +69c3f870-e1a5-4785-8dc4-067a7590b949 +69e71f61-bde7-494c-b65e-346f5972d21a +69fe60c1-d372-41ec-8671-94090b970a7f +6a4adab1-6b87-4e61-a400-dfa4aa4af613 +6a50955b-b259-464b-b3ab-9fa173caeaa5 false +6a8a69b4-f6d9-457e-8466-fdf87ac0585c +6a967445-47ae-46aa-8754-2e48915efc2e +6aba4aa4-9fe6-4759-9f76-705127be70c0 +6b8c1da8-2bca-4e16-a04c-a99a04c76d85 +6b9af3c5-cc2a-40af-9264-1c68ea98d031 false +6c18b368-8579-42e7-b0da-3500ed15ab82 +6c3be815-1494-4c82-b655-a3e7c92ee945 false +6c7689e6-db2a-4544-bf19-0b70c6d74279 false +6ca383d2-551e-4c89-b07f-c0faf9b36dcf +6cac250a-d650-4c9d-a896-b38dd7e72955 +6cc22c4d-6014-44f6-a1e9-0b8eaf3f837a +6d2776a9-6add-4222-bb43-64bd4dda65da +6d34e7f6-d885-49f6-b072-6d06f6677a2d +6d564224-4c2f-4bbc-af57-9895ad3b57d6 +6d5ff978-ef70-4eba-a1db-536985901dce +6d759795-d565-43ce-90d5-91b167b18c10 +6d813c64-a15b-4e50-af82-c2f18f82a0cb +6d94712f-7565-424b-ad4a-e1789fe2a639 +6d955124-bd23-4958-bad6-b36cdad50a5a +6d986c2e-b12b-4a6f-ac80-d3bcdaede076 +6e02f522-f081-402b-88f2-38e6622bd006 +6e362fd8-b0b4-4cff-85c3-6586920627ad +6e750faa-cf8a-4f5e-a02d-1471741cfdab +6ea2fcf1-31dd-46e4-be06-592626e212f5 +6ea7ff7c-35f3-4350-9f9f-104c5182cd0b +6ead6acc-52c8-4df1-b00e-b46e2e2f0a04 +6ec2fab5-4d76-4c80-ab2d-577b041c1aee +6f550b68-a7cb-4314-ba74-d7d2a6d56d0f +6f63c413-6f3c-48db-990b-fcc749cc6355 +6fb0d554-b14a-4bbb-aa5f-a4d5c082c2dd +6fd78455-d026-412e-ab31-d9e1a02923dd +6fdefa93-f23b-4eab-82d0-1e9ce7c6c17c +70777428-a2a0-4dd6-b048-1ef2a27d9ec6 +7084e394-9ee8-4b0e-a3f5-e8d091ab4fae +70a25de3-45dd-4848-a394-e87d4f6b6930 +70af4c33-277a-4a9b-8022-60d6bb0ac6aa +7102c0da-2afa-4da2-a79b-a82742e4e3da +713231d3-d739-455e-a913-41d5a1dc8571 +71f73b19-86e6-494b-8038-46a67396de4b +7227c5bf-3699-48cf-a0c4-fb8f5d50540d +72619c45-9cb0-4abf-a98e-20cc49a4e4f8 +7287ec50-e64d-418f-a83d-0d3d93981f62 +728f469e-7a39-4a77-8118-944f83c2cf21 +72972472-355f-4538-b69a-23aee8614438 +72baabec-7047-49ff-9e67-338361a06e5f +72dd2d03-e239-4a2c-b509-769886057217 +73364971-4250-436d-b85e-93df377b2911 +734b53ff-038a-4c79-91e5-c14340b43145 +73fe21cd-4493-4afb-ba72-376f75bc2469 +7409225f-77a9-474b-889e-38eb4db0da2f +74556136-3dd8-4074-8e26-160ae1b5f780 +748bb216-e420-46e3-9aef-6fe6b2020a74 +74cfd92e-5da2-4fae-be59-1546f5a22e35 +74dbc21d-e056-4050-8f0f-04194ac43995 +74eca765-7327-44ac-9868-ed01c1dc163d +751225c7-3a03-40d5-980c-89f6020bc2ca +752045ea-d278-4939-a82f-3ef29dd7616a +752b713a-e049-403f-9d4a-f560ee0d4681 +752f97cd-9398-4830-ad13-38e26e3a8bcb +753ae998-68fb-4917-93ed-484ef1753d9e +75509cde-925c-4645-9a91-c1f244863842 +7569ce07-0cbd-4a30-bab7-d5f482a27883 +75969f3d-bddc-4e9c-8872-a33f108027a2 +75a6eba8-9d1c-4de1-b084-00a5aff10eab +766ee5bf-c1a3-4837-8ee4-0c81e7252c91 +7690d499-c3d6-4092-89b7-81b3425164be false +76a04620-5da7-4914-a5b7-5a8506221f16 true +76b1d775-0f28-4bdb-b6cc-a2441e8e17b9 +76cb0eb7-3679-4104-8121-9e01d36de541 +76d885d5-454a-4541-8ec8-61f7972f17f7 true +76f4f348-f65f-42da-a338-1e1f28df0ffe +770f3f03-8a4b-4e32-8443-49b14d54ad3b +7714ce28-a83c-48dd-a84c-ba3565432818 +7728c15e-82ee-48c2-90a4-afa79e048a52 +77293357-c730-4960-a8aa-c25ea0775c3f +773da43c-fa5a-4a21-85c1-724b9d72bbbb +776cf7db-0b09-48dc-b089-2d24410ed4d0 +77a645b1-5231-4486-8f16-0470ecc3e49f +77a7196b-7391-4a38-89a1-e01b15e9e2a6 +7879670e-7953-4dc8-86ce-88f346cee653 +78a727b9-5bb5-4ed3-aec3-a3f804c79734 +78bfe231-0661-4502-b18d-28c24b223dce +78edeaed-91f4-4f03-ab2d-38b720e5e6df +7914b992-59e8-4541-8250-427de113802f +794a6b0e-1e0f-4c12-ae52-47b53f385a03 +7957634e-ec90-46dc-9779-fb000649bd40 +795d85f9-c2d4-4219-8e06-f6fbcef30e33 +7968bcd8-e538-443f-b4a6-e41b9766965b +797ac9d5-9ef4-49bd-af56-1bd0c971467e +797d8c9f-56eb-4487-ae30-5c4dca379bf9 false +79813005-389e-4671-850d-b9a5fbc2345d +798cfbc1-a7b2-47cb-91af-43af0ae0119f +79a428bb-83a1-4338-a613-9b732f6feb4d +79c5ee51-42c7-4fc0-8e8b-7ce9acb5133b +7a281b03-7145-409f-9c5a-c45b6e544a4c +7a543c90-f6bd-4343-8311-6818c3a9066e +7aa8cd33-8365-4c53-ad1c-e07dcf2f86dc +7ab27dbd-f952-44d9-bd9a-f67d5790fc82 +7b4255ec-4801-47ee-b12e-4b874c5eec1d +7b8c6a88-3a53-48bd-9b73-bdc3f087e6b7 +7b9a52de-eb76-473a-a622-4b19d50b6bc9 +7ba6211d-542d-4508-96fc-712328eefc69 +7bb75949-ef13-4934-a19b-798f6a5e7866 +7bdf88dd-7924-4b93-9ff3-d543a170e0d8 +7bf5b1d8-3edd-458a-9730-187ac6738857 +7c197c69-7872-44cc-93bb-c518b8403da2 +7c6c40db-df29-4b8c-8881-3f1765e51c2d +7c6e1d99-bc80-4172-b76c-226f6a618b48 +7c85bdf0-7156-4555-aa37-49e4195ce0e4 +7c94eac0-d189-4107-9443-452d124db20f +7ce02db8-4ca5-42c9-afe5-b818e42e1767 +7d4a09ac-d7e0-4675-a9f0-313c73fa7906 +7d83ed97-4c6b-4ba0-9dd2-423f59e8bba2 +7da3d62d-56ee-4707-b036-2c1b5e111aff +7de9fea9-9923-4dec-9c83-1cf13b81ca39 +7df8f003-7458-4083-8ef6-20a049526c19 +7e194149-713e-46fb-ab75-4a7cc2acd94f +7e618ba1-1acc-4c2d-9b80-fee3d12884c9 +7eb4427c-1b25-405e-8ef1-38fabbfb1a0d +7ebecbcf-c963-4cae-9b1a-8fe44e175008 +7ec8d0b4-41e3-40c7-8f18-fa10b249e023 +7f054bf1-9bff-4ade-8841-ebc67d4299c8 +7f35f61b-9551-45f5-9641-c9a74d7ef110 +7f8f71fd-602d-4ce0-ad32-211f5e7ca765 +805e5d13-6a31-405f-b43d-3449d73a50d3 +806085ba-3993-48ca-851b-a5dbe0690926 +80aca9f1-6907-4658-b0e4-bf6f53838148 +80e8b1f5-ea5b-4abb-9a06-b51bcce673e6 +81155341-1ad4-4473-ac14-9257704203e4 +8118e76c-1974-471c-9cf9-0b31ed231efd +8131895a-c6f1-4b0c-aba6-cc3a52066646 +814970aa-0552-4357-b958-d572f56421ac +8177f823-7335-4da6-aaf9-354e05299d71 false +8197ccde-83c1-4ab3-bf4b-e849667f1f31 +81dd2be8-9b50-4d63-bc4c-22c4dcba127e +81f1e8f4-a335-4910-8a27-151141514c39 +822dee6c-337f-4a5a-a22b-7de9e64d1144 +822e3fd3-ae35-4e89-a5be-feac7c6f62cd +8242d422-fa39-4374-85c7-5d267d68a0a7 +826cf99c-9d92-4cef-b717-d5c16ea7525c +82e58dcf-ea72-4a50-9eab-a74460d58e4d +82e8a61e-9eb4-4fce-ad43-881202cdfbef +82f09a48-3b8f-4a2b-9067-61b51d85c500 +830df854-5f4b-474e-9aef-e7be6b452a92 +832e240d-da2b-4658-b638-cd2cf2f08912 +83648485-484e-46b6-9a11-64a48fe471f0 false +836f63ac-40e4-4170-a9f0-f170c4654db8 +83b971fa-bf7a-49bc-b1d9-09680e8eeb24 +84a15a42-428d-4092-9f54-235b55b11de3 +84c41fdc-0d34-4d95-8999-7677e4235e5b +84c90e57-2348-4843-a557-444e3a82ce43 +84d36e49-fc69-46a1-a5be-e9842e8e192f +84ed43cd-526c-4055-9422-c31dfefa43ef +84f38578-0070-46e8-809a-65c75623829d +8502fd55-eeba-4c91-8274-4f34dfe89977 +857895a5-3a82-45fa-9fc0-00380ef01b47 +85827568-1a34-42f7-877a-99a2d3a0f5c8 +8591ca33-6c89-4352-adf3-8261ce1bf58e +85ea993c-f6d7-4b7f-836c-3c76206d66c1 +8644762e-70c7-45ba-a0fb-92fdb9e6c73b +8668b20d-ed68-4d67-91fe-d35c385a3408 +866bd41c-debf-4a53-9108-fd9c0784103c +86786682-7825-48f0-bbc3-3b1a127aa654 +86859d13-26a6-429a-a38e-be69f1af9b4d +86946982-ff92-46cf-b6cf-caa45ba4a986 +86bc9f92-f89b-4fb1-affd-093a785ac1db +86fee3a6-5135-4265-8c8c-04ac00ea8e12 +87061f96-823c-4e0e-9dde-8034682651a8 +87346977-9c76-4d76-b8a7-23ded71369a2 +8746d3f8-1943-4ab0-af49-f7d40aad5cc9 +875be020-8c24-450d-9ddf-f642e60e62f2 +87e5046c-332f-4ab2-b8a9-ab15cfa91fa5 +88274e29-0d4c-41c2-9315-f96c93d44c0d +886a561a-e64b-44f8-bc77-76724e0c369d +888dfba9-08c4-4048-bb9f-23fe80dfcf5f +88a3a7ea-728e-4bbb-be44-73093e564cce +88aecc11-264c-4a3e-a2b7-94c99d39fde7 +88cf49a9-ec8a-461c-87bb-003866a4e28e +88db01b2-8bcd-4671-9447-f466279f21f8 +890cdafa-a6ba-47df-9cc6-5ea4d4febcfe false +893f9661-bc71-4f65-83de-6483e73bbce3 +8979e8fc-9f34-4305-80fe-122fc619c7dd +89cefdfd-b6d9-41fa-ab28-50563fe44a74 +89da332a-d11f-4f77-977f-6275f337495c +89ee0500-71a0-47c2-a59a-1b681d9a92fb +8a31ea5f-804f-4b8b-8f91-6ac4bea2c4dd +8a56157c-ea64-4f80-81db-0c89a9d0b547 +8a5eacbe-4ee5-4e3f-9a0d-d0d1a13ae2ed +8a8432ce-47e9-4fff-b71a-3147ad298fd0 +8ab69964-40a4-4d5e-8d2d-82d07174989c +8ad34764-84db-4613-8483-4e8281f8f984 +8bbcf675-5176-4df5-bbeb-38603e8d9c42 +8bf02dec-bd39-4a1d-b074-e0ef2167b93a +8bfb6668-fc94-4b2d-9ec2-3385f1780d58 +8c39345a-45b8-44db-b5f3-6b5914b3fd93 true +8c492ef6-59bb-4ccd-bc64-48d58d3da475 +8cda568d-3e48-4e77-afa1-374809ed3c3a +8d671817-97a1-46c9-b23b-e3013f13edb4 +8d98567b-2930-47f8-ad39-759745179c38 +8dcdbdec-4dd9-4928-9e46-4f46fd6df9dc +8e1303b5-0694-4bc3-a0f1-559d51e05364 +8e55e68c-f1ca-468b-b4f7-11c11c2956cd +8e5ebd37-bb67-4c84-b9ec-3422ed7c3b19 +8e9c8ff0-82b8-4974-91a7-888dce4f7f8d +8ea94f05-b5b6-4aeb-987a-567f627a4216 +8ef5e898-02ca-4617-b966-d5954b2cf403 +8f051932-7f43-4faa-b3b4-c2254756c603 +8f19178b-b9b8-4858-8f94-c0f1e6eaf2c9 +8f5f16b4-fcef-4a03-98c8-a6aecea249f0 +8f62568e-7778-4729-a454-369f5a3f12f9 +8f62ada0-3878-437b-aeca-d59d658ece1e +8fc9a4e9-9ca1-4144-8cf3-032a5ed6bac2 +8fea3b61-246a-4333-ad04-78348cb59585 +8ff0c7a4-f1c3-400b-bb31-c66df4866cfc +900405a8-53f2-4a6a-bb75-80e9725f3439 +90126aaa-1dde-4136-9be1-4e30e2c1b667 +90e6ec4e-bc73-413b-a07a-e8d3b675c027 false +91357869-20f4-4e58-8237-5b028e1f733e +915eb7b9-47d0-4abd-a424-29605ef5f497 +91874305-9f9f-44b1-bac3-28dc0fabd0d2 +91f7217e-5c7c-4462-9f7a-ad7234bddf6d +929edb45-2558-4fe7-b87b-ad4a801a26eb +92e25da6-dacd-4d0c-9457-751bc135c126 +9307ddb3-12ef-414f-beb2-62a145d3f63f +933302fd-1717-4cef-852f-0bc28dcfea35 +937de252-6da2-4dae-a80c-8ef39c6eb99b +93851c17-f570-4055-b6bf-ff928624666d +9389f1c7-bba0-4286-ab0e-73ca1430824b +9391e021-6734-4c01-a6d4-a9e151e58201 +93c74a74-55b1-4f45-8b7f-3a0442ea5006 +93f749e0-9ee0-4cfc-b970-20f1757aa207 +94b95bc4-3355-4881-93ed-5db3dbcabc26 +94c1cc51-4dc2-4dd2-8a48-b6793675a44c +95accbba-6ae9-4e70-9984-235e7371234c +95ce9659-ea2b-43a9-a6cb-0b85fb4d9a48 false +95e68649-5482-4d3b-bc6d-ca77d83531a6 +95f21d98-e820-4054-970e-ab04c6b0ef69 +960866ac-7fbe-4f4c-8a41-f5c6d756a394 +962aaceb-7115-481d-8496-5ee5d12ac43c +96526997-d779-49ea-999c-43bd07d250e0 true +96a5c8b4-5c9d-4092-9171-03da2422df80 +96df629e-35be-42c9-ad60-2fe43dfba0f1 +975a8040-5330-47c9-9bb6-13a1d07fd3c9 +979229e7-5022-4a8e-9f23-8673564bbf4e +97bd3c96-d38e-4a46-9e9e-0ff3466b80a6 +97d8b079-ffd3-4bfe-a1a4-da06038813eb +97f558d6-5624-426b-b21a-6a92c463d004 +98216728-4494-43ee-9af2-930201bcf2fc +9873061d-c9e3-48f8-9a27-6a92255869c6 +98da112b-cca4-4bfb-9b85-32accfe1d295 +98e90113-a97a-40fb-b6e3-dd5f2e646901 +993d7bc1-e96f-477f-b7e4-ad8d25c59e94 +995d662c-8641-4089-bb52-417a2eaba272 +996a443e-1ca6-4e05-b1d8-b84455f2412c true +99893b11-009f-4ae2-9ce4-c0db476800bb +99929bed-454e-4131-8368-9d152ab1da8a +99b31b91-ec68-4c94-b23f-2b75d9302cea +9a1f1ac4-3ad5-4ada-80b2-cc31176393a6 +9ab5ad8c-b789-482b-838b-6cdcbd184330 +9acec191-7d26-4519-aac1-b50e00f3a2db +9ad11a61-23e0-4603-90fe-c3001fdc7320 +9b4a1f69-52d8-4c2a-9e8c-072f71c2bd18 +9b876e7b-aa09-4b8f-8c58-a0cab74580b3 +9b9b76e9-88d4-4440-868a-c5b153c277d9 +9be9764e-cac0-489a-85e7-9f3c995e7061 +9c3ab93c-6424-4402-bd4f-0f0f7dd2b265 +9c630f43-c0db-42b7-b223-607d5a9a39a5 +9c9cebe1-ea55-48c0-9edf-22033f5ebebd +9cb03b1e-f448-4f94-8c42-7f9271202a1c +9cc11154-43fe-4bbc-a9ee-c5b8e0158d51 false +9cece101-e41b-4d77-be20-ae0c0cd2cb9f +9d3c20e2-321f-488d-9472-5756fa061ca0 +9d415448-4c3d-40c3-b910-ee3ff0df9b3c +9d495d7d-837a-4d3b-bb10-608f9abc919e +9ddbf3cc-7d54-4532-a670-5c361df676d4 +9ddc0101-8632-4103-a12b-bf8c5590f624 +9deb306d-e6ef-4bdb-9a20-c9f5ebc556d5 +9e1b3c33-ef02-48ce-9b60-15b62a9a499e +9e32e6c5-44d7-45b2-bb87-65e98f8464b0 +9e3fc42d-d08c-4cb4-93a0-a567c5f6a19f +9e5071df-d3ea-4bd0-83b7-e08ba3fbb896 +9e58de28-a944-4ca2-b53b-b8da836ebd93 +9ea9ede3-8f0d-43c0-9f35-cc9fcfb622e3 false +9eac5165-a8e6-4acf-8d52-4ad49e2ba851 +9ed868fd-baf3-490d-b665-113b9427520d +9f078010-f724-422a-8d8c-8855f2513eec +9f3630e5-1b35-43bb-b53f-c191634ba14b +9fa9a6d4-e6fe-4eba-bbe4-7319b735c63b +a0238799-d0f0-49a9-96b4-33cfc1688279 +a0520e0d-7dc2-4218-9176-32eb842de85f +a05480c6-8b4a-4406-9226-8dce4bbb4d21 +a0eff999-b331-46a2-b8dc-16727efc7580 +a0f58ee1-6e61-41a9-b0c7-5acdb005ac0d +a1007ed7-d907-443d-813a-0bbb0ed84e50 +a1155987-73aa-4d48-b127-5da6d4cb7b02 +a1bd990a-7d22-47af-8258-5d4a05b269a6 +a1e0979c-d57c-4482-9087-88077cf657f9 +a2224a99-ae98-4018-8651-38f1f145c76b +a2466ef3-db84-4434-9e07-af11c15c7490 +a253f260-5b6f-40a2-b4cc-d2c66856ffcb +a27b5fe8-3465-4b6f-b63d-cca66626727f +a28cbadf-918a-4831-be0e-9ab6c83133d7 +a2a6355a-2c94-4da3-a707-cc6db6553ce2 +a2bf4c9c-46d8-41c4-8eb3-044e832c78c6 +a2cb445a-10da-4b47-add1-16584c34d8e3 +a3240817-7453-496f-9d7b-058889858399 +a34042e1-cc97-476e-8564-382d941458b4 +a35cdf18-7bae-4776-8aaa-652bfc135af9 +a3b8ad3e-c982-4023-8977-e9717a33d645 true +a3f4de14-3398-4b34-9165-3a24c5742de1 +a3f9fe69-1527-4d04-a51d-ca9f1955d210 +a452f6e1-013f-4745-aa25-e2cd16ca4423 +a46ec80d-9cd6-49d6-863c-5f5425899445 +a49f38eb-8de7-49e1-b7de-97ab32fb277e +a4b245ca-a50f-4bb2-ad64-1124c31eb166 +a4d85c0a-4687-4454-89be-085b9a2632f9 +a4fec49a-dc46-496d-86a7-5387c2490c3b +a500b66b-fab5-4ee9-bda7-11fb6170fff0 +a533451e-9e8e-448e-bc5c-1beace2c397c +a5a464c0-3b64-4ccc-bdb4-62aac198eddc +a5c68c36-129c-482e-a9eb-023d7ec1ef0d +a5d76237-0ea6-4016-91a1-33463ef1e9e4 +a60eddcd-ef19-4a84-ac8f-9206ed2aeed0 +a618792b-5b12-4545-a3e7-c07313d188f3 +a65e37e5-cf18-488e-8870-e4aeb021ee41 +a674a795-2553-478c-a5e4-6fbde13d7523 +a67aa655-62ae-4905-8042-30fd3ea68944 +a6a1fe2b-4322-4ea8-9ca9-3bcf288afe36 +a6d86605-7084-4477-8c8a-8cf25c4d1b9b +a6fbb09b-5b17-49f7-a510-5c1596b424a3 +a717a4a4-b0b2-4aba-af9d-7b431e7883e2 +a71de636-d8aa-4455-ac06-932ae6d848ee +a7575150-4148-4b9c-b941-64db8cc61356 true +a769b6ce-379a-4f6d-ad06-5df3a58d25ff +a7a4ad54-167b-4cc8-8f88-dcb555c9dfb4 +a7b38940-3cd3-43b1-bba9-6adb6090d1d3 true +a82170ad-dc53-4899-a7ee-cdbb50e6a91e +a83fed45-f814-46b9-9fdb-50892a1a1736 +a8624bea-ae5f-4fd8-b6ed-11b0fbba9254 +a863d462-6836-4f42-a4a7-8925574b76fd +a8c9520b-bfa8-4915-bd66-a6133e1c5ffe +a8f862f6-2e8b-4cec-81ab-eb3be51d71a8 false +a906f033-5f9b-4c1e-aaff-287cffc6cc7f +a93d01a3-5bc6-40c2-9895-64a21fbd6253 +a9584e1e-deb8-45f9-830c-bef1caeb21c8 +a961ce1f-cf0e-42f7-ada6-f283daeac650 +a972425d-8c4b-48ab-ae2e-fc7db6661f67 +a9888a76-f2fd-4161-9d15-6126ba398899 +a9b103bc-f890-4d91-9714-8d674aa842fa false +aa5fe96b-324a-4010-b128-a5063ce0be77 +aa86bd26-a862-4a03-9d8c-a04ec4c70c0e +aaada565-8a59-4f69-b92a-a6cd9a323a24 +ab3e98bd-050f-4bfa-b6e9-d829291290a0 +ab50086c-5cac-4c6b-8e71-6988453228aa +ab52bac3-d430-4700-b1e6-7efe2f5613da +ab63c859-8b0c-4874-9404-cf7c89a368e8 +ab8aa7ee-f65c-4a29-9995-ef33c4135035 +abcfc7ff-d824-4414-a586-3c33c263a990 +ac139e4b-9d46-460e-92d6-0ac43ce8cb67 false +ac5bafbf-2bfe-4dc4-8e13-adb2d80e8dee +ac645c40-0c51-4d77-a5fd-32e619019c60 +accb0b6f-b012-42cd-9374-91355ed49aa8 +acd3b2a0-1a81-4738-9bbc-9ab0c56b0a85 false +acd69c03-a3c4-44fa-b3d6-749476309db9 +ace8301d-b547-4cb2-8311-e27a8980633a +ad07d661-6987-4928-93a0-e7b902b24ffd +ad2a1d8e-6e19-4d5b-90a7-ab24050735a2 +ad48d6a7-890b-4eba-919c-ff6b63e35304 +adad08de-aeef-42cc-9a2d-b99fab5ab1dd +adbb15f3-b71e-49a7-a954-2f117a93b972 +ade38b82-c9cd-4a9c-a7f6-23130baac87e +ae13da32-7194-4f04-9fd5-c7f114a2e5b0 +ae4da701-069d-4ede-b59f-a2524c6529f0 +ae52de8a-deeb-4144-bfd3-52c2cab5fbb0 +ae56f2bc-f7f6-4bc3-ad8a-3b1193e40f71 +aea61272-bb6b-4fab-9817-584a8fa2f55b +aec815b1-23cf-4504-8576-fe03b8798092 +aee86188-c252-4910-98cf-a8d3ca9d029a +aef2f04d-7449-4088-9ab5-a227d1d7c36d +aef45e8a-6923-4db5-9bb0-18f472f7f0ee +af12db23-5b03-4e68-9127-acc14775690a +af52f728-fcd9-464a-9e25-28013b84fa1f +af73e4bf-2ba0-44ec-b619-a04f1c13c9a1 +af77032e-b1e1-42c5-8540-68bcb906f134 +afd2cfb1-57b0-44ab-a8f5-6edc92bc870b +b0138fc8-736a-433d-831b-0bc39281d67d +b0525467-772d-4d5f-b326-095fe72890e9 +b07bd4e7-6586-49d4-9f43-19d58889743f +b0a1b45a-9dfb-482b-96d4-362cd1ee625a +b0a510f8-2c98-436d-841a-eee086eba241 +b0ad5c48-9538-493c-b5a7-87f8f5ff858a +b0b8b0c1-8184-401e-9346-37422a6f2f81 +b0d0e0b3-acc5-4887-a39e-df765111f99c +b0e1d515-5be7-409a-8c86-3aab8ab6787a +b101ebeb-ddbc-4bb2-b5c9-2bc6a23e0a16 +b109fc82-447e-4d4a-a447-1c7f0569bbc9 +b12f7f41-f8f8-4eb6-b2fa-dd5a18639c11 +b155b9d3-54b8-4906-a564-601bf123f027 +b18674a0-77ec-4d7a-b563-aad0856ce45e false +b2071f60-d582-424d-b873-9ea4c2615442 +b217ba96-19d5-4184-a1ce-890bfdec478a +b243e613-8498-427d-bc20-acc54786ed44 +b2503fbd-507b-44db-b4d0-32700a3ba5f5 +b260cc9d-644b-4e25-a99e-9afc44dc3879 +b2766451-0904-4ccb-90c3-fa7fcdbe19c4 +b2b2289a-0762-4846-a4a9-fbd7dc1a3822 +b2f8bfb5-d8a4-4df9-8724-bbce63e4dcdb +b3394219-debb-4c75-988e-98fef99f0500 +b34ef85b-d2fd-4fbd-bb2f-6c21d737e21a +b376a344-8079-4b95-99a9-d515f9dfa4a3 +b379efa9-29ce-44ad-b653-9cc03ac2bfe6 +b3861c55-ae00-4319-8a76-03627b8c8bf4 +b42a782d-79e6-4276-9fa8-744d5d1bea24 +b444d377-d1b0-4eb3-aca4-c24d3fac8c37 +b4771fd0-758c-413d-bbc2-17e8e2cc79bd true +b48e7fa4-1922-4d4b-858f-a4f3bd8810a0 +b4a9bcfc-73ae-4798-a221-bbfe9991e0f0 +b4ce60ee-7591-48eb-81ec-0466d931b2a1 +b4f80b37-afaa-4974-9cd7-f53d788b2be0 +b508dc50-ca89-4b92-a12b-b032d3bf3a5f +b516b7eb-4029-454b-a8ec-9bdede40ffb7 +b55a959a-73e0-4b7c-bb03-88bb22844c09 +b59c5c8e-7d5f-48bf-b3d8-900e18a64b12 +b67c717e-f9fc-4ec3-8cdc-ade1c8d1b95f +b6d48a75-7d74-45e6-9fb0-154ca5264744 +b6ed4e6f-6911-4cd5-92de-f50e8d1916e4 +b7289502-ed78-469e-8b73-04811493a484 +b7597773-8d3f-4ee7-b54a-6c8b60b6d47a +b762619c-c328-4367-99a9-41f10751cedf +b7d87d24-1805-4a12-8f8d-2d14509d5eab +b7f806c7-6ed8-4b7c-bf42-dae336e3f7b3 +b80e31af-6b74-4685-a83e-50c5d7f762fe +b862117b-c899-4938-9df3-6baf43911d1b +b89dd640-c21c-413c-8774-f9a154d897f8 +b9578448-2338-4f49-9d63-5b886a537f07 +b9701217-a4ec-4ef4-84f8-49ae5825b45f +b97f2f71-17ed-4e3b-8e3c-374241e6faa6 +b982af8c-9e7b-4dba-ba74-76770efffefe +b98be876-6fcc-41e8-ad4f-a78b1661acb1 +b9dc5e38-47af-4d07-8ad1-b0425c5cb386 +ba047f08-841c-4d5c-8f98-b3e614906d17 +ba5a0e2c-6e96-44d3-800a-991a81337994 +ba7bf8c1-e6a4-4437-b1ef-cf9e443971d0 +babdc581-4c93-4674-8743-1be4b18ba156 +bb182025-e149-489f-b69f-debf6907da09 +bb2c53fc-6d97-4747-a60b-8010f62b7f63 +bb770b6e-11d2-457f-858e-2e2c6c86c0bf +bb94d817-e843-416a-bc31-4858001e7976 +bb9c95de-6073-4239-bb5d-023d18344157 +bbcd2900-31df-492c-9d43-e8e0243f7e1b +bbe0b9ec-5a6a-44a9-9770-fbe4117593da +bbf5580d-bef6-4cd7-a231-b6a532d68aed +bc1bab6f-5d84-4c95-afa7-b4ab39038ced +bc23a56d-f42d-4bdd-b704-07376d4a24af +bc6abddc-4fa4-4ae8-97f4-9c566558b227 +bc75ec01-01c4-463d-b64b-597c106d4eac +bc929bf3-06ac-408b-a17f-6ecdb28759a7 +bca14431-d108-4ceb-831b-b6cedb7b1b22 +bca1f5dd-414a-4570-9f0f-ed733bd170f6 +bcec2bc4-4e04-48cf-b52c-8e1fa2ac3875 +bcf5154c-180c-4366-836d-13aa6bfce980 +bd3b7896-2b3e-4c1a-a2b0-aca1c2ea23c1 +bd687828-857d-440d-836e-5230d3254b1c +bddfe37f-fb3f-4a24-9ce9-181f250aa0b8 +bde29534-91ef-4817-a1f0-00a5cc9475bd +be196f96-4e26-4c23-8241-b0a10a39e831 false +be1ca606-c535-4751-b52e-c61cfe9b6c73 +be3057cb-9635-4a25-863f-b4e79cda75c3 +be6d80a5-e2df-4e99-9721-80097129bd8c +be7a093e-d70a-4a23-9db3-bd95aca334a6 +beee46de-ddd6-4fe4-80bc-6f1f4b8fd42a +bef4846a-ca76-4000-a98a-00a9d327107d +bf88c593-87b8-46d5-808e-8a332c5b50d5 +c015fb2e-4062-417d-86be-681d98f8a13b +c0381889-4401-4bc4-9056-641340fa7ec9 +c070e6de-9ae3-42fc-9cf3-023839ad0442 +c086a87e-ec86-403c-8bc6-37a9b77924f0 +c095cfbf-cd84-4681-ba67-7b3ed318186b +c0a2c5cd-58d4-49ca-baa7-d5859abc680b +c1003a14-56be-43d0-9aed-41fb6b048273 +c1370f98-3833-49d3-b58a-21c581ac4ab2 +c14127c7-9943-471c-b5c8-ee9f0bb95bd7 +c1d430d5-32b8-4410-a1b5-6212b2c411ec +c2253c94-aa7a-4e88-8410-e528b08654f2 +c24b807b-174b-4e5f-91d2-93599fb21548 +c25d3d58-1f6d-4004-85c4-3a162dbf48ec +c286cd77-3c89-446c-a91a-4769aa088be1 +c297cce1-5d10-48da-8ef9-e860ac55f387 +c2a32eca-f03c-4fe3-8c29-3b69c9798b5a +c34d92cb-7086-40e9-b303-97e204d2ab4a +c36767f9-a3d8-4f57-a370-4a088ea0b023 +c37a2b82-a495-4d05-8f8b-99a757d0b83d +c391f109-1259-47f6-979b-e72492709b66 +c3aaa4df-ead6-4a84-982d-3c28752e1070 +c3ab4714-5eda-433e-b461-c1710b769476 +c3de8ca9-c03a-4689-8701-dd524884b5b5 +c3e98495-15f0-4040-b92c-31aa40235dcf +c47474bb-570e-45af-ad38-8f5352ee3774 +c4757dc4-d69a-4e2b-9b8d-61353a5b21e7 +c4e0ccb4-df43-4b20-873d-3a66faab42b1 +c4e31ea7-6ad0-42b7-8579-c3b7fcafa740 +c4e6e6bd-2135-47e8-81ae-7e1d6d5e7482 +c4f82d4f-f53a-4d32-bc78-67aa7786ed1e +c52dbae3-07a4-4039-b04e-d79a86941c76 +c52de404-7bbd-4b07-95a0-a66df22a3221 +c533b9cd-e56b-4e32-93b2-1189f37eb646 +c557b460-3e1e-4ec4-9b4d-ea7bc0c8d64c false +c5609af5-9f49-49f1-80ac-b18fccbc9720 +c5f20b88-cbd1-4283-ad46-5b0bf31fa3ba +c6386600-4824-43b9-b366-4504f4e74d66 +c6442145-3aad-4b57-b2cb-b7eff0699959 +c6700fda-f009-496e-83ec-0532f34bfa92 +c6a0f008-4d97-4319-8099-7e0cb6708438 +c6a3be9a-b061-4a5f-879c-e948aa895f3a +c6b44473-ebbc-48dc-8a42-44b7da0de840 +c6cb9c06-0c13-4cd7-8d20-ca7b99f7d141 +c761f0d9-c918-41b3-9131-856aad1f5b2b +c7e313a6-4659-4fad-9402-946879a91616 +c7f263ed-22de-479e-909b-f6ceaf5f2eb0 +c80ab964-cf3d-4e89-9232-8e787cb0def5 +c80ec189-5f9e-4049-922e-969d28b3bdde +c837974e-0093-438d-a822-19e97249f0dd +c895019e-6471-423d-ba15-4f06ed628c7f +c8bbf035-8a01-4c57-934e-988a584c98b9 +c8c936bd-3e6e-4500-bbd5-051659c59309 +c8cb1ae0-6a31-4869-a396-78dd5dc7f302 +c8f123b5-8e57-4c9a-8770-bd4b4c2c064a +c8fd40e1-35fe-4c8b-af6b-730fd6fb5fab +c92a4616-7215-4ba1-99df-40aea82eedc3 +c9445880-656a-4def-ad1b-2a4200d76c13 +c9647fa4-976a-4d9f-9be4-26b9c71f6eb1 +c98372ea-e9ae-4438-8715-b54ccdcc8f30 +c99c2510-b392-42ee-97e1-16f6a3b50978 +c9cd4daa-907f-490d-b3e5-a43eedd74c32 +ca45fe64-e7c9-48ea-bc02-3bc6a0b58e46 +cab21bd5-4314-4bb1-8b16-0967f8b08757 +cab53124-f25d-4b4a-a238-3d028e788b2e +cac06963-2da1-4935-a6c3-d6152132c7eb +cad24afe-7131-4158-9153-742c9fd16c9b +cb1c6a1f-e659-4fe2-9cfd-f28e776a4f06 +cb5c302b-35a6-4d8d-8d6f-72ad1a3ed1cd +cba58256-d558-48df-83d7-be5939d6dfab +cbafa157-0905-418d-983e-827cc88faa94 +cbfd98df-8ac2-4497-988b-bc4d1c9c6095 false +cc0b8f72-76dd-4eaa-84c1-c343982e18e2 +cc1e584e-6752-4bf3-8c51-ac0192b5df44 +cc372f1d-a83f-438e-8d8e-c346eb03fee6 +cc38b529-3137-4d89-804e-51c6bd60ca97 +cc3a3e25-87b0-4012-869b-9905b31f8319 +cc5fd0ac-6ce5-43a6-bb4a-67279156ee8c +cc7e521d-4fa6-4215-942a-48f84ddf9a49 +cc8ade38-d9f1-4700-aad3-dc866f35d1a1 +cca1c9e8-c68f-4fe0-ae79-693399d8ee31 +ccb56a74-40db-4d58-b0c4-cd485e8a7fe8 +ccb810a0-28a5-4248-92dd-7259a0fa0c22 +ccbda4c9-3ec5-46c1-a8d6-a215651dfb43 +ccbda933-c3d4-44bc-acfc-fd26bc234bb6 +ccd5afad-8f2f-4305-b646-26137eff0a0d +ccd69a01-cd72-41e7-87b9-de8454411ee3 +cce63ba1-abd2-45d1-9ba0-a2cd0fa9b00e +cce6dbc6-3a49-4f8c-b908-e6f11be81ac6 +ccf36cc5-5009-4fbc-8e8e-81f38d3195a7 +cd013d11-8609-41d9-a9c6-7fb33a499f51 +cd09f5d3-c846-41e7-a1ff-aabb55973663 +cd2c3f78-ddb1-45f7-9939-987e070d9414 +cd3d18de-96d3-4132-ae73-b80795a53ad0 +cd71ffde-9d7e-4c27-bc22-306f81cb46b7 +cdc96f77-a589-4ae4-85b6-c6d2c89487ba +cdcad0ef-8049-41c2-a8ee-21835323b05d +cdcae470-3531-4548-a2a9-ba665fca8930 +cdfbf7e0-ad10-4c22-9ee4-888bb6bdd198 +ce1b7543-fee6-4267-a00d-daa5a5ea2861 +ce225925-36ec-4c1c-a327-c64a146bef46 +ce512c4f-3808-4ef6-9853-6f7d14e146f9 +ce7037fc-2614-46f4-a8db-40a2a0f9e3ac +cea2e600-da22-42b8-aa08-f0e951ff35d5 +ceea3ce9-5ab6-4cbe-99b2-34ca9e6eb468 +cf17f19b-9f66-4391-bd2c-55384690d9bf +cfced5ea-1e8e-4c85-8318-ef91a041f4e0 +cfff2bee-7cf4-459f-8690-3638266a07e3 +d000b381-5ba8-4a5b-84c7-f371e104eeaa +d012051d-65bd-4a59-b4cd-f454d6010888 +d05fad91-9a3d-4d8b-ba99-5a33fcc82081 +d083a1aa-9662-482a-8192-4709f904f16f +d0b22c1a-d0f6-4d83-bd3c-475a3acb0b83 +d0be6a2c-ebc5-405a-a2a5-006a5ea52c76 +d15e2267-a699-4f43-96a3-e98de7ace044 +d168f31c-483c-4340-a2f4-6a60cc8cab30 +d17dfd97-60aa-4313-bcd7-f48935faa2e8 +d19385b9-bc04-4000-bddf-1d09425a2b4f +d1ba146a-9503-472e-b668-3686ff275ef2 false +d1c2f4a3-8af5-4ad4-bd0c-032b947d3c25 +d21af0cb-c5a1-401d-b605-a2e3ba3f46a1 +d2272949-75dd-4949-88fc-f0f157c83105 +d230ce54-d5fe-4983-9e91-519ce1249322 +d241d156-478f-47a0-989a-96a5fff36780 +d2663716-cdce-4d88-90f8-51fb5d59aa4d +d2a3d703-44e0-4dee-a8bf-9fc0457841ae +d2c11b5d-93c6-4500-bc4c-8dc50057f362 +d2c4f328-e7a4-4d87-b180-9a0eee13d165 +d2d08017-f955-4e0e-baf0-2d5d37acc304 +d31bee09-f02b-44c5-aa15-a06343e0bf9f +d355c55f-53b5-407e-8785-33e4e54bb3fe +d37a07ee-5d7b-41d0-bbf1-331277662c52 +d397d6be-6008-47bb-9f72-628fb5c41cb1 +d3997124-9e16-4df3-8c60-f43f62bbf036 +d3e6de0a-bfef-438a-b982-4818145651b9 +d3f2c0c5-1f4c-4156-8cc0-e2d87db500ea +d44741c1-d58a-495e-9f30-cc0f19fafc4f +d46e0f9c-5dd1-4e88-9a22-96ca436674d3 +d4aa494b-3328-48fa-9873-39be6536a437 +d4dc8d84-20e2-4053-b244-47549e74680c +d524b58b-90df-4d29-b4c8-308ac04dda2b +d56569d3-0a16-4968-9470-93d410916f11 +d5944a91-3bf9-4915-8512-0f1f50a8eb7c +d5aa4b50-1ac3-48bc-90ee-7ad6bd78a078 +d5ca4448-871c-4175-801f-38bb43cdb48d +d60c26c8-1b83-4de9-be17-81fca1ef2575 +d6c622c5-3662-4b29-9180-0b4055afa612 true +d6f1073c-bf1c-4232-8ee2-5b92f0a32d8e +d702593f-4121-4ac3-bc3f-dae097c7a044 +d702f666-bb61-4d55-9579-e56efc3e0e91 +d704d649-9e08-4502-a9c5-820f503dac9a +d7c0876e-3031-4be6-aa2b-bbd8e2bd8884 +d7eba5f5-103c-4442-9a7f-62c81a9dd9e5 +d7f76663-5195-4de4-bc43-ee61e467fee4 +d8534558-7fea-4dcb-a1fb-2981b725dd61 +d86d32ef-f50e-46d6-b434-fd03bc807cc4 +d8a42c84-6930-4b12-8c1f-9a492d290454 +d8cdd01f-c8b2-46fc-8f69-35da565e1b99 +d909c299-c622-4c39-829f-415621e09eb1 +d94160a6-7943-496d-8b66-172e9ec6804e +d97e5072-19d7-49bb-aaf1-f3a56816f3e3 +d9e59fa9-630b-4d97-aa71-4e03bd6fe1ad +d9ebe2d3-2a6e-4079-8f59-43a327e68ebf +d9fb7469-d50c-4492-8c1c-8bac1ccf81bd +da33cd96-8f15-47f1-aeaa-846234a6b9cd +da412e67-cea6-4ad9-8c66-75bf7bd9d0f9 +da438390-6d67-444a-be2e-354a21d30559 +da4c7a8d-c75b-4050-bbac-5edb50e48d4e +da5de060-6e29-4379-9119-175b394f9bac +da98f3f0-236f-4c41-b123-09f1fdb5bfc6 +daa0c0a8-91bc-41c5-9627-adad04a7b15b +dabbe6e8-3692-406f-8a2d-f81217eb6964 +dac01aa1-44bc-4ff5-b123-4868f8f278ef +dac87128-5796-449e-85ea-2c1f8e09ba0b +dad4c0b8-0e02-470c-b7be-6477008d925f +dae5b7a2-ad95-411c-9721-067d3c578262 +dae62ac4-666c-48f7-8587-ff9330a9e9e6 false +dafa1e16-2149-4e76-a693-88d2e7e29a0a +db0a2a90-dc62-4e6e-9a46-88b9a3145ab8 +db16da7a-538a-4270-bd0e-1b4398a922a6 +db1788c0-f515-419f-ada6-834a37168001 +db17c5e1-6d1e-4b0b-a345-8dca770e7097 false +db5a6739-388b-453c-99d0-58cfce24d5d5 +db8a9775-faff-49c4-8b35-74e92245f439 +dba78a31-21c5-4600-b18e-3e90237ab40b +dbbc9b03-4d76-4118-8637-185659ce2adb +dbc0cb40-5e33-468e-bc9e-2cf8ed602d01 +dc2a8c49-ac89-487f-a22c-dc126a5c7ed1 +dc36732e-8054-4d6b-a860-e7c54f04a0bf +dc567aee-5073-493e-bec1-b6b0f4e160d5 +dca15405-893d-4468-84ad-574e54f45c22 +dca8db58-f563-4a61-b747-94f5ec5bf135 +dd09736a-85a6-4c2c-979b-f2a7027308d1 +dd9a2411-d45c-4385-a6cc-650df658ae9e +ddccfc35-d71d-4e65-afd3-4cf2a21e607f +ddda9bf7-d645-40a2-8aed-eb9efa425d01 +de1c574a-a49c-41d2-b321-849c005c89e0 +de81cdd8-a6ed-4ed7-af7b-6ee027d0d5cf +de9bffc9-c6ee-4ad2-8055-1e0d0e48887d +dea4f32f-0f51-4bf0-af67-e79b34674fac false +dee72459-d0ba-4771-af17-f38f8396adb0 +deee1173-4f31-4784-8c63-d02e2a7b16ba +df471b44-cfeb-43fe-9488-8f85a03952fa true +df529c86-f229-404c-8f0c-44e1b29e4e95 +df54eb2c-59c6-46cf-9cc0-201e94acc07d +df6522a8-552f-4ba1-906c-139f58a0a85e +df8a0f98-893b-400b-9da4-2514e50c0b24 +dfa10a30-82d4-433b-9df5-b0d2291378ae +dfa72495-2737-4fe8-9fcb-c799ab51ddcb +dfb67b9c-64b1-422f-887e-139a8f676fa4 +dfb6cb62-17e3-416b-9214-6ae6f432874a +dfef1286-acc9-44ee-a736-b9676812235b +dffc6fbb-8592-4f0a-8190-1e64eb2bc634 +e0010996-fa96-46be-9431-ebbe139c081c +e00ec480-db7a-4aab-84ab-7e5e7184bfdc +e0ef5017-54ab-469b-9871-f706d4899465 +e0f2d024-4210-4f18-82ce-51755627e13f +e1309aae-d59f-44f0-b630-fc538bab9027 +e134ed0a-49e0-4a0d-b09b-4e874f422e60 +e16400e0-a6e6-459a-a16d-361a7b2ffd1d +e1a9f42d-103d-4e01-b98a-e70f98aa1c00 +e1c6ba82-6bc7-4f15-b71c-c669cb78bcc9 +e1e4e2e8-1c7b-4c67-bc7a-3940e6670357 +e1f9bb17-3719-45d6-b4bd-3be88f17e429 +e1faaf81-1761-4a4f-b01d-c179db8474f3 +e20067e7-f2fb-472b-811a-3d28a08bf31d +e2501541-dbeb-4e28-b4d8-598e963d6158 +e2b04908-769e-4916-bbc4-45ad7db85e02 +e2eccb3c-0755-49c8-8e82-cc510481ed04 +e2fbc85e-dbc4-422e-9a11-0007d5c194ae +e3784666-7f48-455f-a137-e6262a617ccf +e3c2c69c-bbe5-4678-aa6c-3e5c7ff996f2 +e3c5df86-3d35-4dd8-a081-48632a3b67c8 +e3c94ca7-a487-468d-83f2-8068a592a774 +e3e4b2a1-6c40-4f42-a5b5-17b352209b40 +e47d814f-5139-4339-a710-fe953efcfa74 +e484c47e-4da7-425b-a636-de512fcbc822 +e50c31e2-5d52-4d45-9b8f-8ae3fdd26488 +e53c4c4c-976f-4cb9-95af-fb11c264cb72 +e53ec08c-9029-4b50-bc1b-a302878b17e6 +e562454d-7fbe-43a2-8046-bfc5b6ab043b +e5803192-2273-43bf-8acc-945cc13af70f +e5c7eb1c-238e-4bf8-82bb-205c383f1517 +e5d9ff02-57a4-4e05-b573-0dbc80e19958 +e5fa00b8-1779-467d-b881-757b6796d8b1 +e5ffe973-b166-4240-b5d5-17eaad746c4d +e6e9da2c-7684-41fb-9123-def8a7cb4dba +e7098f3c-9dec-4aa9-9554-b3ad9a7d842a +e73f4c39-965c-47ca-adb5-0544e20d45bc +e757ee7c-a50b-4575-b383-587e6f782bed +e780a3b4-6b8a-40a1-a9ba-5f95725399ea +e78c789e-7b81-49ea-b7e7-d17d8e8925aa +e79745a2-654a-46d6-a886-dd1d21acccf4 +e7b3ee1d-2d5e-4dd6-8ae0-ebddf36aee4d +e7d53c39-4adb-441d-a4e1-50c5f60fd3ef +e81ec213-328e-446b-9c33-ee80bc850d44 +e83d90d6-c694-47fc-8120-e91546fdda8c +e8ba3683-aa88-4800-a248-737d39ecb6c6 +e8f05409-642b-419f-8475-28449c5f9569 +e952a654-0605-409f-ac45-19d00d4b53c5 +e97e9781-3cb3-44b0-89a9-b704d456fcc2 +e9ab227b-2a64-4c48-a8a0-6c166bf4b970 +ea2f0657-1625-414b-9c7c-bd0b01a12eff +ea91b2ad-37c8-4a4c-9178-11d7a5e4e75f +eac7851d-1d61-4a35-9f08-c5da53335f7f false +eace08fa-4018-40fa-a7fc-3daf10b2f21d +eae386dd-ef2b-4629-817f-6f8c44ce5817 +eb2419c3-3b91-4d73-b4be-a40e759ab957 +eb2781af-72a5-4760-a9a7-20d212f56230 +eb511148-31f2-4292-a83f-d16d3be6631e +eba22453-8605-43d3-81f4-f703e58a230d +ebad9165-a532-4846-aff7-5038cd5c694f +ec3d7e48-c9f4-4f64-ada9-7e2891a62d1b +ec462f5c-2aaf-45fa-8c25-6f556349af00 +ec4d4a14-0934-4d28-ad20-64db8fe78d42 +ec9ccc2d-1e6d-4ebc-b1e5-86d493d19eb4 +ed352845-d1af-40f9-afca-53503a6518d8 +ed7a65e1-de6f-4c2f-bd47-aa78d0c2189c +edd41c4a-a5f3-4531-b506-d81c04e1ebc6 +ee004b78-c05d-4c34-a9f3-290a9259d3a3 false +ee6749cf-0622-4376-958c-229ebc0f9618 +ee8905ec-84b9-4fa0-b1b5-0908206b7af9 +eeaa2e25-739d-443e-ae83-b0ef04e2836f false +eed7cb9f-7fe4-4f9d-bb4d-cdaeea6e144f +eeeed70d-3cc8-4b11-9f6e-ab44da414b90 +ef079a39-9f59-4681-91fa-3d745c5eaec1 +ef10cf22-bdb9-44b5-bc9e-bb777ad8f090 +ef1c5dab-b3a9-444b-b825-8fe3d0248e02 +ef9360c1-b9b4-4dd9-86de-f996cd22e7a1 +efbe6910-0629-46d6-bcfb-5880a1795742 +eff17621-813f-4e99-a307-118ac9273efc +f018a615-d5ff-4c3e-84f8-d4ebb1f342bc true +f04f4b77-a9f2-4ce1-9d0d-6b43d39483f7 +f07f7d83-1fe3-4da4-9c33-86aae3ac67e5 +f08704a2-c1f5-44d5-a3f6-43c5608dc987 +f0a81ab6-10ea-4859-a064-b3b0dc662f24 +f177f59d-ecf0-41a6-b81b-a311eb760976 +f1a1aa8c-3779-4c42-8302-6006dcb95151 true +f1b36b35-1bd3-4328-aea9-4c5b3a7c7404 +f226c31e-37f4-4a4a-aa1f-aa400447a799 +f22fe03d-e1e9-40d5-9ff7-a5bb6e7a2d71 +f27187d5-f8f0-4188-ae0c-f77ca494a9b3 +f27a59e9-c785-47d7-88a4-de7706bb4f92 +f28962fb-0214-4664-b9c3-7c67d4eb2e8d +f29f4e3c-81f9-49b1-bc52-47a60a3076bb +f2fd6221-529b-42ca-a060-30db0996a398 +f3047ad3-3524-4a48-a00f-603a65fa0891 +f310aaca-1c10-41fc-ac49-8430c4c23656 +f314d140-929d-4ced-bbd9-c7f0204e99eb +f32094e2-ab74-4567-98c7-e35fe529393a +f3338731-d7b9-4c1f-8e3d-e73b5b756c7b +f3deb81d-79ca-4176-9b6d-bd96bb5b7a16 +f3e99f4d-5bbb-4f11-9519-4d3aa15e25cb +f3fd705f-3f03-4f26-a477-f36e65fe7499 false +f4077b1d-84fd-4489-b8b1-bbc9f5b61da2 +f475b875-3df0-49fc-b4f9-10b2ac1c40a6 +f4bba159-e786-4f15-8100-6bff75c03635 +f4e59f5d-12a4-4005-bdae-e157e145c119 +f5042323-b136-4c9d-bf57-0b7b70609495 +f51f7f2d-968a-4630-8e06-d6beb52b9f6b +f55f863e-ffbf-4eb5-8f76-e2069098adcf +f568f305-55f4-4d1f-849f-6c8f371f5051 +f5dec406-3636-4ac4-894f-1035ef3ef570 +f5e51d8b-bda1-4093-b887-f2355ea171cd +f5ebecec-331b-4d57-b353-3f50538cae2a +f5ed5cb8-adc0-424c-8371-ffb8e7cc9bc4 +f6514087-db2a-40d8-897a-21b5af7d590e +f65d9eb9-e3d4-42fd-8d3b-168a6009f6ed +f66efc52-e910-4d73-a9a7-fddc110c16c0 +f67e345d-7f27-48bf-afe4-00d79c3cf300 +f67f612c-6534-40d1-8e56-c056e886b98d +f6b9b783-c8af-44c2-8443-44b80e972686 +f6bc9618-f245-4402-b69e-74d424736c4b +f703fec9-ca54-4363-9ef0-4c4acdc37e0c +f73ca57b-a00f-4217-9106-aedd26812b3d +f74957d5-eef8-4683-976f-c59ffabdeadc +f7d2872f-bfc1-496e-ae92-a9d446aa72da +f7d6ad2c-1f7e-47ef-adfc-16a5d4d0189a +f8043ec1-8313-45f9-a6dc-4cc1e5ad5f54 +f8673366-8964-4c6c-aa9f-02e0920f21dc +f8683b38-42d3-4f32-a543-034c05dc07f6 +f8816dca-a41e-4ef9-8a17-b40fc7d707ad +f89b5e47-c8c3-4067-99ea-bd22f8932ee1 +f8b6ebd6-8d2a-4592-ad16-06772ad32cc7 +f8ddb5b8-70ff-4d5b-adf0-1752811f7be2 +f8f99d6a-94dc-4fca-8772-d0c3fe887a7f +f912a4cb-2eeb-41f3-a410-0ddc3eab9cdd +f9185eb8-d2e5-47aa-916b-a5ff24612ddf +f92f93d5-3037-4692-a062-ee84b9961510 +f93aa334-4532-4774-b82c-6d040d3c7156 +f93e975b-d07d-434d-b227-666e744892c7 +f95dafda-7f63-4ddb-acbf-e363dc901b1b +f95f8351-818a-4cb1-b26a-507c4baced47 +f9aff2d6-fe39-4d17-90cf-917edf5d2bc8 +f9f5480b-27da-44fd-84a6-1ee4d4a55e36 +fa0ee43e-abc9-44cb-9248-3acc6c40e693 +fa4daa52-528e-45f5-8dd6-fe2a2c9fe8ff +fa522b1a-3244-4203-966f-546dcfb0c62c +fa5e8aa4-84e3-44b7-9dfe-326abdf02162 +fa7087f0-0140-4c94-b7c9-185b89c613c8 +fa786b6e-dbf4-4c51-a8c6-3006a029e8ba +fa80db64-ef07-4b14-aa7d-e93585fd84e2 +faa80ca5-9c2a-40f4-aeec-26d982e663ae +faeaaf02-2ae6-4f5b-b729-61fd56d3946c false +faf774dd-13ef-4b09-8cc9-3cf33a562482 +fb176a2c-4b8f-40d7-bb81-f370c96c3f96 +fb1df190-d719-42b7-8e9c-d7830f92882f +fb42fd3a-123d-4b2b-8a2f-d7e37af0a675 +fb55aea6-a5e7-4a55-a8df-967f504023f6 +fb93624f-3d94-4c5d-9aba-10a3eaea0434 +fba16e5e-1b98-484e-8296-43a3995b9f75 +fbacdef8-f2e5-4a1c-8a01-b25442a5af89 +fc348304-8ef3-4b3d-afcc-a66718315d89 +fc6dd798-66ac-45e2-a663-ea69b976c120 +fcdd2cbf-917f-4429-a41b-48f8fe2a188b +fd325c37-4113-45e9-9e55-abd6b2ce6dbf true +fd694334-1bcc-40f0-863a-017311fbaecb +fd75e2b2-c780-477c-a813-046ccec6fe51 +fd7a8bc2-9005-4230-a4f5-348f4b1f5b16 +fd7ef037-c576-498d-bdef-cec86fdd60b0 +fdaeb95c-9d66-4da9-87cf-eabbdc593007 +fdb161bd-ad0e-47fa-a5bf-f17ece98f28c +fdb49a29-6648-4dc5-bf56-ef948b00d885 +fdbbdafe-ed7e-4bd1-ae67-86430153a738 +fdc10bfc-2b5b-4a23-a93a-eaa85803bbc3 true +fdce38d2-ae3a-474f-8024-52279c43b745 +fdfcdb50-716b-4452-a4c8-e03d3b8f89a3 +fe4c4989-ff06-446e-bf01-5c5d8f69f1eb +fe66bca4-eedf-42a9-8def-d29ef3dd01de +fe719bdd-cc9a-4c93-b2c9-982d051fceb1 +fe7906dc-a882-488e-b576-2ef3486bcf79 +fe7c2db1-c229-4681-9373-e388dccb9e37 false +fe7cb896-d81d-4b38-885c-4134ea64020e +fe97c34d-b787-4c3a-a972-821eb42a3442 +fead13f8-4b89-4f33-950b-ee9254a36c4b +fead2d6e-2979-4de7-8118-698906954d06 +ff9df3bf-b8fe-4789-aa34-b205b5143a92 +ffab80f3-741c-4c67-af21-4585f1c05d2f +ffb89ee2-c3d9-4fd5-b897-a0e0eb692143 false +fff8bb82-5151-41f9-b150-801ed4957277 diff --git a/fhir-server/src/test/resources/responses/ParserTest/lengthObservationSubtraction.csv b/fhir-server/src/test/resources/responses/ParserTest/lengthObservationSubtraction.csv deleted file mode 100644 index be423195dd..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/lengthObservationSubtraction.csv +++ /dev/null @@ -1,1502 +0,0 @@ -0020b035-7314-4867-9453-7921f158d003,"" -002593d4-d271-46a9-a900-1c498c322988,"" -00e9f82f-f16c-422f-81a3-77fa98958da3,true -00f02590-e737-42c4-9c73-206cf66b02b7,"" -011f5b11-f944-46ae-9cde-0d802e5c0073,"" -0143f01c-3692-479a-9a72-592260dbbd64,"" -01a4de83-37d0-4612-82eb-2ff2dbc2e02b,"" -01c177be-05bb-4a44-a199-1df387e68d8e,true -01c17bf0-9240-4149-983e-f0b3ccfb52be,"" -01d57966-1281-42ab-9e05-02d96d146b6e,true -01e27cc9-c9c0-4c33-82f6-9507394f5f2a,"" -0218f780-899c-4c1b-9167-45e33d02acfb,"" -021bf465-c299-4302-99ca-e29a6d25bda5,true -022ebe16-f618-4acb-9d99-88e23d686c34,"" -023a9220-3016-4202-8c7d-360d7d3bece9,true -025aa37b-4ae6-473b-9ae6-c12eece968c5,true -02741df6-79f0-477c-963c-8b3f8cb4374f,true -02b87530-10e2-4009-8734-4196642cef1d,"" -02c07924-f5c7-4909-ab49-dec34c051dfc,"" -02c4fa06-3281-46a0-9bb9-c7b99497c91d,"" -03365fa5-36d2-41b3-b3d9-1fea9f94c73e,true -03555f06-db5f-4b8c-aae3-f6d2c547f499,"" -03610d59-94a2-41de-a039-c671241b075a,true -038ced3d-5233-44c6-9a8c-1f6f9cc4be11,"" -03c7ba3d-8af2-44d1-a3c0-2c56b9fbc7c6,true -03dadf3e-cdc9-4f63-8a4d-89bae77bd529,"" -03e44215-f743-4c4e-8d19-aaf761f54503,"" -0429fff2-2839-45d8-b0bd-1d86aa92f966,"" -042bb1e3-e5aa-471e-9e83-bb4a1b90af22,"" -043108b6-98cc-4feb-8ad0-1394cd10a289,true -04367369-256f-4d47-b1ef-8014109ab46f,"" -04661736-24d8-41f7-9f05-af5682696df0,true -05048b00-24df-4311-806b-2f9a7cc16380,"" -0531e6b8-2ede-4e12-a91b-cfe7e12e552e,"" -054334cd-cfc9-4a1c-acf3-d0824cafd3a5,true -055824d7-b398-40b2-8fed-bc40552fffce,true -05643ef2-65e0-4812-b739-4fadfa3c066e,true -05bed469-760c-439b-9729-0479a6550b76,"" -05c37921-dde8-4b70-869e-82680975ab77,"" -05daccae-2d73-459d-8aea-7ee75966e52b,"" -0624683a-8572-4885-b51d-2197dde395bf,"" -062ab32e-318d-4ff7-81ea-f3be53204988,"" -068d13a5-8b46-4a3b-aca0-ac9ffe70f998,true -071b684c-70a5-4c93-8289-9630d25496d2,"" -072312b8-c984-4cb4-a4f8-97560368576d,"" -07279076-3064-44aa-b07b-346a7edf6abb,"" -072bd136-7e5d-49a2-b878-aaa2c7fde1f2,"" -0735b01a-c65e-4d59-9016-5e3bed1fcdcf,"" -0785f996-ee5e-46f3-8966-107235de4cde,"" -07906572-63db-4046-8dbf-1ae866664f41,"" -07d441b6-4e92-4d6d-af4b-7f38471fa45e,"" -07dfda29-5378-4a66-ac90-fcb7ec6283ca,"" -07f5fcdd-3aa0-4ccd-9122-f9686886dbd4,"" -07f95b0d-656b-4830-ade2-864e58ac5ed6,"" -088cdf48-9645-4dbf-b824-8836d6a83d24,"" -08d37a26-1ec6-4faa-985a-10a9d0ee54a9,"" -092b1b96-4ad3-4716-850e-8056ccd456d8,true -094a486e-7e50-4717-a0c3-07a456a4cd5f,"" -09541c80-54b4-4bb0-8776-37745a9ac36e,"" -09941e9e-ff5e-44e5-82cd-4c3865e39c01,"" -09ab1f16-7bff-44ec-aed5-8ba400930bbc,"" -0a005143-4645-4ce4-89d5-9d49dca18ec6,"" -0a40e26c-d0ac-4653-a54f-17e23ed9a0ce,"" -0a6c1262-dae3-4ec2-b911-0d1caca68302,true -0a7887db-203b-4a3a-b5fb-cabe9a886074,"" -0ab96751-edf1-4941-981c-c6bdcba75380,"" -0ae172de-1755-46d4-8729-d3acc96ce82f,"" -0b6ee068-26bf-4f1b-8423-30f1c591861b,true -0b95920f-7c6d-4b30-89da-51807a6aa557,true -0bbc7111-589b-460e-a742-20f2131c9c64,true -0bd78173-c348-41f1-9edd-884e3561ad47,"" -0c4242e4-1c5e-49d3-bf57-3810f8b88d56,"" -0c93a4a8-3b59-41f8-a7a5-22a9691710ab,true -0c93c94d-b626-40b2-9561-52168c4961ca,true -0cbe226b-6432-41c9-b3dd-0a14c6d54d7e,true -0d1af4fe-50f7-4d8e-823f-192f8a08ec8a,"" -0d7a0166-7fb2-4e1a-b1e5-b03a53fb5602,true -0dbafa74-2f5b-400e-b27e-fddeac8c28ad,"" -0df8e035-a226-425d-9684-a12d0c58b844,"" -0e03654b-d81d-4a62-8f53-b479e8ce3edd,"" -0e11e8ea-dc1b-4e8f-b809-39f0f07c7499,"" -0e8063ca-3601-4741-a2c0-c0381cc6098b,"" -0e92574a-cc09-4564-a2ff-1cdbc89d210c,"" -0e99ca14-e99e-4394-b962-59c66427218d,"" -0e9cc169-ca91-4201-a78f-1b0f724507cb,true -0ed9ee59-d968-443c-b7e4-c3732ec666e3,"" -0edd2d17-3ac8-427c-ae05-40c99b401484,"" -0ee77005-b9a1-4c51-a599-bd233897e431,true -0eea9efa-148d-41d4-b05a-d39484526333,"" -0f13292a-a654-4473-b78e-c8f1b3328ff2,"" -0f67bae5-3bbc-4c7d-84f4-34583758335d,"" -0f68a16f-1c70-4d73-a9b2-fb384585526b,"" -0f7373d2-3052-4c98-9c04-7074250f44cc,true -1016a9db-50e6-4c17-9e93-18a97b67269e,"" -1037da65-9451-48d1-a35d-bfcae76c2c60,"" -10a7d4aa-af9d-4d03-8d1b-bb53ed123f62,"" -10ad7a5d-e26a-4d4b-b063-239af96b0e87,"" -10b85b80-f6fb-4473-a3a6-266f610dcbc6,true -10cd7171-38a4-4276-b911-bcc8092eff7c,true -10fef9b1-6bdf-4eef-b6b5-e12504e8fc7d,true -1104d152-e95e-4a6a-8225-ab200e4530b4,"" -11148ea5-efc3-47ad-8672-799331f8e491,true -114ead73-4920-48fd-847d-f9aa1a99cab3,true -11597b54-9453-4ddc-9171-2be17df7a511,"" -1167e4a5-ce86-48b0-8b96-6d70125e2f9e,"" -11a0c4d2-8908-4c0f-8e51-bf4be1605441,"" -11af3d82-bb56-464a-9203-8714c270f4a5,"" -11cc59ea-52cb-41e1-97e4-7ed651037442,true -12254e32-b86c-45dd-93b0-c8cbee619a14,"" -12257920-12cf-4945-a72f-80551181d7f1,"" -1266203f-94c9-43ac-af02-a0b8db8a93dc,true -12689eb6-84ee-4d28-9c65-3f3e305266f3,"" -127b14bb-027d-4380-932e-ea34c76592d4,true -128cf2ff-4f97-4039-983b-72e2dd3267aa,"" -1333fbed-c09d-4622-b22c-c67dd32e4583,"" -133a1365-e43c-4d92-a6f8-8c3947b13b65,"" -1359fe32-3275-4c1b-ba16-9dc903334044,"" -137d6ce5-93fb-4201-8bf9-0e97e9595b77,true -13bf741f-7ad6-4a14-908f-cbfe18651381,"" -13f4a39d-ec9c-43b4-958e-be28a97972e2,"" -143099ce-8738-4ccf-85fe-63e54943c7f6,"" -146000e6-9926-43da-9322-de3bf31e8142,"" -147add3d-eba6-4cea-bbe9-823b44794b9a,"" -14a9ad6b-b7bf-4cdd-972c-bda91e5066bd,"" -14afc7c1-89c8-4740-a932-a76d579e98c3,true -151df048-c17f-4a51-83a9-3de5f99be125,"" -1520180e-fb4a-4df6-80be-0b3d559c1049,"" -15268027-ae33-4f5e-9c2c-76327ccdeb44,"" -155d0003-f37b-45de-b34c-d611c2a2cf35,true -15821e91-ec03-4e76-a909-d2f2a14c3219,"" -159c1f96-038f-4722-9e36-98f4dfdb6b66,"" -159c3713-0d0f-499f-8ff6-ecddd44b958a,true -15d9442a-2018-4390-beef-55fd90cf6ea3,"" -15e70015-e207-49dd-93a3-7c0ea5cc456f,"" -1631e3a1-0eb3-4d16-a7e5-b0d8a8a9e630,"" -16af45f4-69d1-4190-9bca-b309c17bd865,"" -16be31b9-a434-46e2-a481-459c5584c907,"" -170e5f14-b29a-4aa8-ba71-4bf381a38d1c,"" -17505b4c-c5e8-4acd-9d10-fb001190a8c5,"" -1795e970-0ce6-4d90-b198-32567c690869,true -17d74e33-f6c3-4b19-8708-4110a2f55245,"" -18a57e28-9c23-4e73-9d90-3694f2e373f2,"" -18c6a217-c947-4e97-a764-c43f33a409fb,"" -18e59ddb-b9b9-401d-9239-0d2cb81936c8,"" -18ee3e63-a8a2-4004-a313-3e332af8a173,"" -190f22f4-5fe5-47e5-974a-fbbe63a676c3,"" -191232f1-58d7-4c4a-93a9-10aee15e5cea,"" -1913a538-60f6-4510-9a76-2466896d2a27,"" -193a64c2-63ea-4677-9302-b56e3f0531ac,true -19a30860-10da-4a1b-abdf-0b03d7b8f370,true -19b01508-db72-4c22-a5b4-4e015a3207f9,"" -19c965ca-c1ae-4eec-816f-73903fe43aa8,"" -19d27e58-26a0-48ca-a199-88d48894da5c,"" -1a3ad6e3-cb5e-483b-babc-ba7d34ffacf7,true -1a7b0249-5aa4-4b0b-adb8-2c958e137d54,true -1ac6d3a1-d1a6-4b0c-b405-0aaaaf37c7db,"" -1adf64f4-1fb4-47f9-a8a3-0ebeb3e7f414,"" -1af4a596-d0c3-4f7c-a217-b83a0755c3b7,true -1b091e60-4c4b-4fb4-b9b8-ee53bccc9b52,"" -1b251a42-a224-4a0f-9d36-44ecc8902d08,true -1b29b772-b873-447b-8aa0-5dd5e9ee1b80,"" -1b614d48-0af0-4d09-a6af-4f3af065da14,"" -1b785bb6-a40c-48e6-a464-f8022ea3cdcb,"" -1bb3d0c2-c6de-42ec-bd60-edc8e3ccd092,"" -1bdd0326-987d-445b-9514-2ea30d948093,true -1c01b494-d0c1-4a99-b110-9de392bbcf69,true -1c120e92-0385-4120-ac83-1b42191597b8,"" -1c32d0e8-7312-4821-aede-6745a83ea45b,"" -1c51f4a8-14a6-48ad-9821-c6408dd0cd33,"" -1c52440e-0063-4170-9cff-a3caaf7c63fb,true -1c6dd7c8-4942-498d-a369-65fac6a837d6,"" -1c79c271-a128-41be-9537-1d744f4971a8,"" -1ce6a20a-2413-470f-86ca-2dacd8811e72,"" -1d9b87ab-8b41-4184-9519-fa9822f48aa1,"" -1da7f725-2fee-4f33-9a89-96253d2449fe,"" -1dcdb77f-b0e5-4b2f-ad4e-d446a6ffa079,"" -1ddc3c30-fccc-4e5d-a9b2-e09ac4f64dc0,"" -1e2bcc56-3121-4b1f-b9c7-7e5503b867db,"" -1e5b7df3-5e81-41c5-b096-d9b5409268ae,"" -1e75c4d7-b570-4339-8c0a-06bdfc9bb485,"" -1e84c68a-4e0e-4980-adea-e46ae2506896,"" -1ea31cfd-f340-4c68-a547-b76365abd1bd,"" -1f170771-0d08-45db-a38d-da3c539b46b0,"" -1f182acd-6bb8-4031-aa86-2f3dd83b4cb7,"" -1f3e4d9e-1fec-4a71-98fd-4912ab64d3d2,"" -1f6b8ac1-b12c-4eb1-a158-25b15965294c,"" -1f8ad9f7-f796-4900-b10b-365fe442f2e1,"" -1fb81c48-c8d0-44e1-9b9b-df53ad7d9456,"" -1fcab6a5-85c8-41b7-a820-fe6e2d84eaba,"" -2031d9a4-3e03-47a2-ab74-6fe7f2fa4ba1,"" -20d47a7e-f7f1-475b-a9b5-6192f2fce0ca,"" -20ee5488-a11f-418a-86a9-cb981c2fdae5,true -2123f57e-0815-49e1-aa34-994270134f3a,"" -2160f03b-4276-436a-8278-300bedd07f7b,"" -21936507-6a38-4d80-807d-0d7cb5ea0311,"" -21d2fab0-5092-411b-b752-4859134a7aab,true -223cae66-3049-48bf-be7b-cf027c415743,"" -22440788-7e64-452d-9025-9a25e4d2be60,"" -2245208e-d17e-4bb7-9bd7-ac75ac0ba134,"" -22462877-9663-4c06-bb5e-040d76bb144f,"" -2253a94d-e511-42bf-a17c-599ae2d5cf0f,"" -226d433b-9abf-4e98-bf0e-47cb3a37b41c,"" -227d84f4-ec92-464a-b3de-f596866ce825,true -228b1b4e-f5e7-4e33-91fc-b7cbe6979102,"" -22b95566-7a4c-47b1-92af-7e0ecbfee6fa,"" -230b746a-26de-434a-9774-54db1ad98a7c,"" -2324dacf-f221-4edf-bdf6-924449cc916c,true -2381d397-0068-4d6a-88e1-a0257d81a323,true -23831569-6aea-4071-820e-6eb5440acc1f,"" -23b4fe3f-62c7-4439-9364-247ceb9251c1,"" -23f91a73-1b46-414b-becb-918e84177886,"" -2439d9a3-6c58-4ced-a82e-27908ed3df94,"" -2450caca-d74c-4610-ac24-1fae13801eb7,"" -24d8f04b-f035-4a9b-b03f-75be9e244ceb,true -25085ba1-43d7-4a60-82ab-e15c477313b0,"" -251e7497-4a55-42fc-8e6a-32febded738f,true -2534b076-583b-4872-84e3-bc96bdee5ef0,true -2583c0bf-6a7e-4100-a5c1-d249de6ec56d,"" -25da1be8-51c6-41fd-b48d-5227e804ebfa,"" -25e1a428-ab0b-40f5-ac25-1e9899458ec7,"" -2606b58b-a924-4128-b3a4-5e4b91deb692,true -261583fb-e2d0-44c0-a677-00bd89a3450b,"" -26344e09-20c8-4405-b674-49638169a7a0,true -266c347b-fa2d-4cc1-a2fb-fe1ba20fbc2e,"" -266cf373-c41a-4cc3-8600-c165a4349a6a,"" -26b4001f-2917-4bf5-bcbf-1ce9dc258171,true -26fb6882-7f3e-44da-9100-58057c9aeec8,true -277e09d4-cb89-4748-b9da-39bee94df8b3,"" -27a75a54-d318-4026-bd63-c80f73899376,"" -2803a9d2-2555-4bc6-9718-7a9dcabd6ce4,"" -2803acc2-63bf-49a3-b4fb-0dd0742fb816,"" -281d5649-179d-4c8c-9a0a-d1159a5df8f1,true -28503262-5aab-44eb-bf51-6fb7534c25ab,"" -2866a0de-36ab-4ec0-a1e1-cce5c697ef85,"" -286decfc-83d1-42e8-9a2f-880f145c71b3,true -2875fd27-8c5b-46ec-91f0-ef3a7a7727b3,"" -28aa1696-dde7-40ff-bb7a-6c5b614405dd,"" -28e8966e-ba9b-4499-bc9c-7f51222e15aa,"" -290440da-118e-4e8b-8d0e-d50043c24eac,true -292aa324-379d-4b82-9750-4c8e23587f92,"" -29506f88-302c-4264-81b7-cf762de48615,"" -29546a71-e5a0-47ae-96da-421275c2cce1,"" -29676c08-88c7-4b0c-82c9-5f6d7dd51f68,"" -2974e695-f2e6-477f-91e4-da8eb654bf24,"" -298120fe-5dbb-4d99-902a-3a6783fc5b6b,"" -298e5099-df2f-4381-8259-b10d0b8c0570,true -299c851f-3566-405f-a217-84c0703d859c,"" -29b17fc8-8e80-4d7d-93dc-bdba1f2cfb9d,"" -29bfab6a-d760-449c-9198-38e8c63574f2,"" -29d4d79f-8a85-490c-b52d-aaf3bcd9a100,"" -29e54cc5-b484-4818-9c3e-08074812dc7f,"" -2a0d7eba-2dbf-4566-af0a-c16161a8b189,"" -2a133e8f-4dd9-49fd-9e3f-5cf122294091,true -2a67b2c1-18cc-45a0-ad53-825c73a3aa15,"" -2a71ca62-7f39-4c9b-b3de-6dfcf299298a,"" -2a7f0959-9d50-4ccd-88e1-6635f2502905,"" -2af24d01-6215-4c7b-88b4-6cee45a156f3,true -2b265cf1-0763-4922-a403-e6f644ed95ba,"" -2b41d0b5-97d4-4a80-bf65-b62ac0cb530a,"" -2b4fd497-9f28-4c87-9b13-64f0d00c0cc3,true -2b54e31d-4af9-4071-ad49-60cae644ba39,true -2b60fba0-0143-4f31-8e4c-27236c7f41d8,"" -2b927027-35f1-488b-bb5b-127a8985cc5e,"" -2bc92150-c490-44da-a925-ebddec5cd953,"" -2be63080-a881-43df-95b5-e371df93c6bb,"" -2c018134-14c6-425d-8ab4-bc3974402b05,true -2c3b614e-7860-47ad-9320-11c5f45d27b0,true -2d358911-c5eb-480a-a9e6-a6fab52ad7c2,"" -2d45c298-d386-43ab-b3e0-ba07dc527a68,"" -2d6cb13b-fdfe-4219-bc13-7de385998bcc,"" -2dbd4567-e6aa-4e1a-996b-af8384f66b37,true -2df38530-e49e-4ff9-93a3-816de29d9bfb,"" -2df89473-f746-4086-a2f3-8ee2d4f63271,"" -2e0bc41c-fdfa-42ab-abdb-28f13cf2ca20,"" -2e13cb39-148f-4299-b74a-0a8e10092e9b,"" -2e45b9b5-8d45-4994-aaba-1fab72c77c5d,true -2e6b11f5-cede-4152-bfe9-523b030a2bdf,"" -2e8c312f-53b4-4cd3-b6da-653c34945d76,true -2ea2af0e-343f-47af-b0fe-6dea108836be,"" -2eb09ab4-c554-4536-a3ce-2979cfb988c6,"" -2edee289-e99a-4a53-a067-f83d29db4c2a,"" -2ee72185-ca8f-40e3-b82d-30408dc8c6f2,"" -2ef91918-bdb6-4a6e-9c39-41d8c513b552,"" -2f1bbdc4-beaa-4d49-8791-6548094c7291,"" -2f24078d-9646-40fb-908d-2628d555dcb1,"" -2f260daf-8fb3-4fb9-a435-670ec8f0180c,true -2f30832d-e6b8-4946-8cf4-0d8da5c359c0,true -2f33b981-f22f-4983-b347-f81d7d72c50a,"" -2f3f7f7a-ba31-4c1e-8a23-c7d1eb1f255d,true -2f5d7d1b-847e-430c-988a-40028f9f595e,"" -2f8b35f6-2b55-4ff0-8d79-5de7805d6da1,"" -2fc9f8c2-4a1e-4e8d-8d91-a6c3b3937e8a,"" -2fcfe30d-dc47-4eaf-9df0-7943a2c37fc5,"" -2feac451-2301-4ed5-8bca-baef62790656,"" -2febeae3-eb61-4743-8ff9-42547a77a40e,"" -3006707b-dcca-41ce-b138-5d62e6b56984,true -30371578-4ad0-498b-a491-4e194c7466fd,"" -305d5449-57c6-4362-83cf-55b4dae133de,"" -30ba9f80-e595-460a-abf2-b8f46a62e606,"" -31504825-77aa-4a0f-a634-ca806bd886d3,"" -3169cf91-33f7-4b7e-b49f-d4b884760685,"" -319dfae4-0305-4520-8ad1-9679383cb74f,"" -31acc736-076e-426d-8a32-426d91bf6511,"" -31e26c46-af6e-43a2-8e27-a8342f53e817,"" -31fd2352-7eb4-4ce8-9dfb-b3d3a1410c82,"" -322637fc-2d75-4a8f-89a6-a4c963869112,"" -32596eae-6236-4b3a-99fd-e50856971f59,true -3286c996-861f-4fe3-90a8-86ec826cdf9a,"" -32bed860-5df5-4bbb-8bd1-c95c016561ee,true -32d92e2f-d02b-4006-bf16-d1c535948e80,"" -32f3d9fa-6c64-49e8-a6dc-fb51679753e5,true -330fde18-0a86-4e67-850d-fe1c8b376dab,"" -3322da98-67e0-445f-ba1f-225eb7d297a3,"" -33333f69-f8a6-4def-a5f2-bab5a26bf054,true -33826788-b600-476b-9843-17fbee2cd849,true -33a0b9dd-c88a-46cd-ba8f-af3d4d6cfc27,"" -33ac0c39-1552-408b-97ea-a90c9187d61e,"" -33b22ef7-01a8-4e8f-8b5d-9aef37e46896,"" -33be0567-5c42-451b-bd71-c09f7de30524,true -340dbb93-0888-4f47-b18d-0b86d3e40248,"" -341634fb-2646-4c69-b57d-b5f00ff4c83d,true -3420a3d5-d5c7-402b-a4c5-02e7198c5b7e,"" -342699a7-cb58-4a9f-bb79-306483dfa652,"" -34335eed-f6c0-4e05-ba6b-1ecaa1fc121c,true -346734fc-4798-48e3-8880-a603d52e9ca9,"" -346a282a-439f-48ac-875a-cc34643d70cc,"" -34a83c04-2ea0-4638-ab6a-0a4bb9a3ad4a,"" -34e44ce8-26fe-48c3-8fe8-5cd8f260aee0,true -34ea288c-f987-476a-a8ed-0ab2c1b92833,"" -3590f230-1be0-4c7b-afc6-5bffec9933ad,true -35cf6113-d23a-476c-b18d-f86fbfb3ea2d,"" -36160c58-889f-4543-bf04-dd29cac9ce19,"" -361688f0-e909-4c9a-b5d8-10727c9a18fd,true -365ca17e-d5b1-41d2-982e-3ca7afdc0a0d,"" -3669a209-82a0-4f9e-8026-43cebad7f3ab,"" -366fa6b3-9595-4624-ae30-20257ef4885a,"" -36957f27-97d1-483a-a8ea-bd5b1484ad12,"" -36c9ce76-e047-487a-837c-faffdb2676de,"" -36cc6703-7757-4c6b-91a0-a43055c19bb6,"" -3732940c-2fa8-4301-942a-e58fd8d73421,"" -373609ea-276f-4b96-9c94-d96ad532246b,"" -37770a6d-bb26-4499-a6e6-2eb399a8ad07,true -3793c189-6e61-4f1f-bb19-a01a306267d7,"" -379779f8-dbce-4dfd-b18f-ce0485b0d6aa,"" -37e1570f-2c29-418a-acee-6688b6ab4279,"" -37ea3114-1b65-4c47-9e65-4cbcf339f8c5,"" -37fa89b6-2f6e-4985-b733-1ee4b2fd59e2,true -381dcabc-52cf-42e9-9624-d4e1270518a9,true -383aa480-8db2-49ed-9542-c738af90f344,"" -38898dca-756f-4267-aa1d-1fe52a6e4749,true -389079a9-b902-4212-a8c1-7187e818e016,"" -389a8f10-ad75-4d27-9d11-2c4ba0dc55a6,"" -38c329c2-4078-450f-ae85-e411b80f30ce,"" -38c7c98e-0372-4892-aea0-ba21ed298958,"" -38d59ef7-b207-4d55-b74a-a1da3d0c10df,"" -39d3bc28-0ce7-4d1d-9adc-bb9933568809,"" -39d7db36-7baf-43d7-8a0d-970ae00456d0,true -3a1941c5-c6bd-45b7-b306-d2a1a51c5eab,"" -3a8a0b91-a492-42de-9b64-fc552476d8ff,"" -3aacaf9d-af9b-4220-a836-1d0c3cfd5245,true -3ad5553b-58b8-46c9-8023-e1acb85be08c,true -3b4bf6ba-0f27-4228-b6e1-c16dbf82e5cb,true -3b7c654f-9a67-4402-a571-a5b0a61559ab,"" -3bae6ece-f05c-4e3d-81a7-64f10f907bb2,"" -3bb8d5a9-87a6-4155-8f3a-7a0c841672c1,true -3bbe3af0-0f3c-41ee-9c41-70d89b411852,true -3be31b98-5218-44c6-aa92-1e054793d9df,"" -3c496888-2ead-4d5f-afa4-8ab39b57ca03,true -3c504a58-92fd-4b2f-a308-21a9588b1850,"" -3c8d926d-3156-464b-b385-192257411798,true -3d2c40b9-2ef8-46d7-8215-bb898170902f,"" -3d87abf4-a569-49b5-962b-32c11816a53f,"" -3dac5f75-f71c-4092-b87a-7b8434af633f,true -3db4a850-ef45-4fa2-8eb1-b1cba4477d8d,"" -3dc0190f-39b2-4a1f-87d9-1ef41a73fa75,"" -3e4206fe-8856-46be-98af-d6fecd52db92,true -3e58093d-c387-4a07-85c2-41a519d0c398,"" -3ebd1cdb-5546-4dea-ba79-a88aef933640,true -3edb8e33-d674-4c25-aa75-7303da0229b9,"" -3f5c46c8-4a8a-4575-b169-6bfd5d752d98,"" -3f771434-cfed-4f34-9671-0ef302b5f9e7,"" -40100d97-ad9a-459b-b044-66568d36c0bd,"" -408159a9-8c38-4cb3-9488-d83331f1762d,"" -40a1dd3f-6ffd-4463-bf71-42057ee61689,"" -40a75d7a-909f-4657-9ac1-5b56df539129,"" -40eec0cb-8f50-4f61-92a4-7682c48511f9,"" -40f3ae1e-4855-4d78-9fe9-c8348058088e,"" -4115b0b4-f738-405b-9351-ae11ba4bd07a,"" -411cbadd-c2d1-4fb3-8ddb-d8bcea3aa176,"" -41496990-adc5-44d1-96b8-15010bdecc89,true -41a3cf00-0965-4c17-bfb5-247a7da43699,true -41a86c21-2ea1-436a-86a3-9173c7d95d9c,"" -41de6042-e95f-4bca-ba69-f55293248f7c,"" -41ecc0b6-ae98-40e0-a836-9d26070e248c,"" -42187018-d27b-4831-913c-575d07e33c54,"" -421e31e1-5ffb-4c6e-957c-728b6218d6da,"" -42350e13-ab61-49ef-aea1-802f6b7ea955,"" -423800b8-5a89-4eec-a12f-451c533fac50,"" -42895609-81ce-4cef-81db-069efe9a207d,"" -429c5342-c5ae-4c55-98aa-e4f7bf0b4670,"" -42ac25e5-8828-49bf-bb5c-a6001966c6ff,"" -42c5e47c-ede1-48eb-bc82-2ed835d67495,true -42fe77c3-239a-4afe-ad76-8c221b543e32,"" -43179e01-e94b-4f83-bb60-60f181388048,true -432bf1ce-0e8b-4621-b998-819fee53f80a,"" -43522eea-96c7-41f6-ad41-1c190cece6aa,"" -4358ee55-cc06-46a0-8b44-6b89d09c380f,"" -4380d762-baa4-4c30-905c-59b33dbd793f,true -439dfa4f-917f-40c5-9313-0d5dcf7ecf2f,"" -43bdfaaa-7474-4503-ad93-730d777e6db8,"" -4416e165-8ac3-43e0-a9e4-6c3e789ffdd2,"" -44233ad5-4eb9-4709-9c6c-e7797d9c9f1d,true -44237e1a-31ca-4635-9ff3-e3c71f587972,"" -4442a5e3-5793-43ae-92ab-f2d87a70cc0b,"" -446f7fac-2202-4149-9860-fe48335349b3,"" -44713d16-f673-456a-881b-b0526ee36651,"" -44b741ee-bdb2-4732-b484-e435854de348,"" -44dbff22-7cac-4f70-886f-b8ac2dea7130,"" -44f714c0-88de-43a6-b69a-4d62d8fee57e,"" -4522ef75-31ae-43e5-8e5a-5caf5c83fb35,true -453cd2e7-723b-4e1d-b414-f46f9152edcf,"" -45467cbe-cc21-4822-94f5-82e1203056ad,"" -4546f579-74ab-445c-8619-431f60a6efdd,"" -455f8ebf-b074-4035-915c-91cd15380d72,"" -459f6da9-0eb8-4b26-85b9-a8f63c587fab,"" -45a306de-3731-40c2-8bd5-2c0fee3b9f99,true -45d45783-5a68-4c06-9b2f-88b7ea1f8347,"" -461bea3d-934e-46fa-a112-9a761140372a,"" -4672ebd9-08d2-43fc-ba92-ce13b587af3b,"" -4675fa7c-6ac8-4bef-8756-88f757370a10,true -4680ae70-6d17-40fe-b55a-c3152ce8b246,"" -4687b275-d990-41cd-a146-2715079a8ecd,"" -47014b4d-07f1-4bef-9792-82c027525c0f,true -473bf60d-dab0-4531-9903-95dc6680a660,true -475e95a3-6d57-40f3-a0af-9daf1b35618b,"" -478fbabf-520e-48a5-b3a1-a3e535d64345,true -47971ac1-1e72-4788-ba6f-bea608e59076,"" -47f066d8-abf5-4064-84fd-dfb05649e0fc,"" -4808c4d5-7733-4601-8401-632bf5924c06,"" -484a19b7-9816-4a66-8f0b-51cb5f146818,"" -48693756-94c2-434e-ad88-e573e5278a6e,"" -48fae21d-0ac1-4e4a-93b7-ac4ba7c2a606,"" -49088daf-d19c-4bab-9084-c67f0fba2c90,"" -491118c7-998f-4d45-8b58-da724ac011e1,"" -4925b797-e285-4d02-9aed-dfac19b0875b,true -492f6c9e-3e1c-4043-aa08-03c610da33a7,true -49466fee-df56-4b9f-898f-96b65318d833,true -495bdeba-9ab9-4756-b4e0-a4aa8b039a02,"" -496f0696-cee8-425a-81d8-1df8fd17e4bd,"" -49cce532-3ba4-4db6-b518-ea934588bfae,"" -49ee8b11-a7c9-455e-9a9b-835dd617e861,"" -49f41136-fc63-48a8-8ee2-b7536d3bf0e7,"" -4a6eb9f9-49a5-4d36-85dd-78512fbe593a,"" -4acf651e-af55-447d-bf0d-9adaef4cf6bf,true -4ae0f855-534f-41dc-a4b0-879c771e3379,"" -4aeca194-6d0d-48ad-8fd4-6aea3be783fd,true -4b322f4a-fa06-4509-9783-dbbf12960b77,"" -4b68a6bb-4fca-45ae-bfb4-b814c67bdac7,true -4b86609f-db97-4a0b-9860-3ce519072521,"" -4bbf3839-bd13-406d-9f60-d9eb9daa4dcd,"" -4bd9e2b4-e195-4a83-ba1c-478e66a7bb5f,true -4be334e3-94a8-4223-846e-39b5877e9ff7,true -4be59cbe-d89e-4abc-aca4-410a7e8cf580,"" -4be72d80-9418-44de-ab47-e8f2f2b9f2e4,"" -4c9a28de-ab9a-4e69-9ca6-375512c2f149,"" -4ca8a902-7ac4-4634-b1df-456175c39d14,"" -4cef34bd-c677-4647-b940-840a6868cb9c,"" -4d0e9887-c81c-4ea0-9026-41ceb9ec9a51,true -4d45564e-e14c-4289-a637-06193bfcef2e,"" -4d79c0f1-72b2-4e94-addb-9adce3f49c2f,"" -4dad93cd-b188-4750-bdb0-4a5501568d81,"" -4db059c5-c4d2-440a-84ac-360312e3c590,true -4dba5ae8-2d30-479e-b831-80d39c396280,true -4de45bfc-2a41-4e0d-a0e7-c6caf6ca0f24,true -4df7ae55-1947-4d89-b51a-496b34c4250b,true -4dfadd90-3f14-4ee9-abbd-a348a5d6a7d9,"" -4e0d77a5-782a-4b03-8867-f51a9704368f,"" -4e74bab4-e328-447e-84d9-a29bb7fa1548,"" -4e8a0b34-71b8-4da8-80f9-f015782b182c,"" -4e9a316e-406b-496b-acf5-6eec80f25ee7,true -4eab3a46-0ea6-431a-8c65-5b5bdee521cc,"" -4ec34b7a-8389-4d01-bca5-06e678089d41,"" -4f147e12-0e70-4ddb-b4e9-4ea25361f290,true -4f18952b-be4a-4d77-ad4a-05276a6bce1d,"" -4f3f5836-8683-49cb-b5e8-a9881796a202,true -4f62ec59-e478-40ef-8617-e6ed599866f1,true -4fe2d379-ba65-4ee2-a584-f0bc63a8523e,"" -5005cfbc-b57d-442d-976b-34164aeb1977,true -504859eb-2ed0-4184-99f1-79f436fcd692,"" -5085d62c-dc5e-4f25-8f0c-700e57bb71fb,"" -508de40d-3c1a-4bfc-8713-11dbae730004,true -509d7086-3120-4f61-bc92-4ab924f30ffa,true -50a9341a-67f3-4d18-9118-373db23eea3c,"" -50b76e4f-6575-47f6-9e15-a96b58c492f3,true -50dbd55a-e4db-406d-943e-7624d96c554b,"" -50fbe67f-8d29-41a7-8cc6-3df916cd922f,true -5105a245-dc96-483d-9a0e-52b39e664d27,"" -5106bb8d-43a4-4783-97c9-42d707990a2c,"" -51164c4a-fa5e-4246-80e4-a01e1d7707d6,"" -512a1f5b-9f83-4420-a4e4-978e33432dba,"" -5253d689-c7bd-441c-ab16-3721f1e3b5cb,"" -527721f5-443d-4367-84e8-0f15982e7587,true -529479a1-36f6-4133-bb4e-2f3cf677626d,"" -52c03be3-5715-4edd-9809-ebff7e2ba57c,true -52d51fd3-d0c8-4046-9ec3-1a3f523d0b75,"" -53010db2-bba4-4751-9f12-f51429c9c558,"" -53339064-e7f3-47b0-a5c6-ce77d50b7489,"" -537bd7f9-9f4c-4bf1-bc21-9d09dd655a13,"" -53e5edab-cd6e-42cd-b6e3-a36d57be3685,"" -5441b3d3-b8e8-4e36-84c5-623e9b199daf,"" -54487624-db9d-43cd-b4a6-0b4d3934f7bb,"" -54627df6-151a-4f3c-9fea-374217afba2b,true -5496c32e-97eb-4f6f-a7ee-46bd2123f966,"" -55054683-a9bc-4c84-b1b9-6a1540e1168f,"" -55449cc5-a88a-4181-9550-5d2bb23a293e,"" -5565df4e-b483-407b-8816-ca9e8ddefc27,"" -5578c18e-84ae-4f7a-8cb6-ce285e1b1ff4,"" -55936779-3082-43b8-a1e8-a8329bf19a8c,true -55954c84-4a6f-4667-b070-5fa9185fe3d2,"" -55dc6b95-e01c-4812-a812-2cbf20de74cf,"" -562f66c7-19ed-4d05-a14d-6aaa37e6eb16,"" -56617f73-b5fd-4e8b-8bd5-467af440f0a9,"" -569f7616-50c5-47c5-94c8-fa3b3097bc97,"" -5703a2ef-ccc3-4ca0-b100-a1c567f1dfc4,true -572e67ff-4f0c-49c4-b55a-36a958d3e02f,"" -5742e203-dd0b-43c6-9dfa-304240238737,"" -574873f1-84ed-4171-a51b-6a2c609e9afa,true -5760bdfc-cbd9-4763-841a-bb31a972f551,true -5781b9ed-eeb3-45d3-a364-43cf8ea348f6,"" -579a0bf9-87da-4887-9700-a5098cd7d720,true -579eb155-01ea-4066-9dae-9ddce5ea2041,true -57d84d96-a1fd-44ec-8460-588b477502a5,true -57fa04a3-a8e7-4699-9115-95957be21dea,"" -57fec813-6327-4290-a2e2-ca74a04cccf8,"" -588b882b-0bff-427d-8b34-294f2fe55189,"" -58aa2384-e80d-4083-a173-ba534f152680,true -591075a3-92bb-42b7-a6b7-d761add1ad2f,"" -598288b2-fa85-4bf8-9e01-2d0e86711aa7,"" -59857d17-d8e0-4932-b92c-97bd36a31d3d,"" -59a25b17-fe55-4592-abfc-0c22e237bbd9,true -59b131b5-2db4-4877-bcc1-2206865df718,"" -59e29c4a-102b-46a3-a950-3babee7cfccc,true -5a0eb3ab-86ce-467f-b389-94f026fe0f7f,"" -5a4bc06f-8ed6-4cbb-93f7-5c5faee7e855,"" -5a9300fb-d831-4b67-88b2-843d2eca4441,"" -5b0954c2-a2cd-4013-b866-a6e0474c12c6,"" -5b2730a8-6a24-4bf7-aacd-f56fd380c696,"" -5b44153a-1ea5-45a1-9d58-bbc0a474ce1d,"" -5b5f6d2c-1899-474a-afae-a98762630b06,"" -5b72c7eb-4584-45c0-9039-5fc762b1e50d,true -5ba15ba9-7112-45ab-b7e5-353d52af5fe3,"" -5bb09da7-8ce1-4642-a113-fdd1fbc73897,"" -5c085687-51d6-4cbe-9b71-0a58718c1d24,"" -5c17c3d7-a7ba-4756-9991-97bc0b3a46ad,"" -5c48ba78-4e71-441d-b03e-ef305d9e2bde,"" -5cce02c2-3537-4d34-b085-7cf145590292,true -5cd558f0-20d1-40c7-8197-5a41ef97e634,"" -5cec04b3-e52d-4a6c-af98-6ee5d104b20a,true -5cf73976-33ee-44ac-aade-5c64e56d7cd9,"" -5d54c744-365a-4db7-9675-79d64bdf55a6,"" -5d5dad82-9b58-4302-a494-02f44c3e94bd,"" -5d790c80-5b87-4802-9b89-02f949846f39,true -5d883f31-f417-4d8b-bdbe-2cf73d1aa4b5,true -5d9e8c9f-8c95-4ca2-9de2-2cf875868ff9,"" -5ecf975d-fa2b-4c6c-a5e6-fed730c9e035,"" -5ef7c113-d5bc-486c-9be9-aa39aea779d0,"" -5f6ad8e2-eed9-4b2d-8697-4e2e742ee1d4,"" -5f92feba-5842-4894-907b-9ceaf36d0f38,"" -5fc1d33e-168e-435c-93ae-c619999b6f9b,"" -5ffb50f0-0963-43d0-b032-3e6b2621e1d7,"" -602f4e55-f69c-4103-bb6e-7d23fd1b06ac,true -6042dc41-8cdc-4825-9ab2-bb22af5c5fa6,"" -60635e6e-3d74-4f56-acb3-be8edc522837,"" -60959eb0-ed9b-4043-b8a1-0b0f27789642,true -60996e5d-082f-4d1e-8cc5-6cc626974925,"" -609a5704-81f0-4b67-881c-d00e87b74c0a,"" -6198d258-17e2-4956-8064-1448f4744303,"" -61e75b9d-46a2-4d74-a0f0-37dc66a09450,"" -6220b92e-946e-449a-a2bd-3241e6a453a9,"" -62346d78-6be2-4cfb-850e-6ca1fc3f5e35,"" -62348772-d9cb-476a-a016-32f16e44cb33,"" -62ad1938-6235-48d8-aafb-2f773a40c6e1,true -62ccafe7-fbcb-4cbf-8584-224cd58ef421,"" -62e4244f-3f34-48f4-b865-c8b1c3ad97f5,true -62f342e2-5499-47c8-959c-4d786aa5fd67,true -62f6ce69-77ca-436c-9d40-3eb541853798,"" -6320229b-25c2-4355-a288-17efb054df6f,"" -633dac04-7a54-476c-8482-9631c351ac26,"" -639dab1d-3282-4de9-84f7-727d0462f11f,"" -63b2b9b1-aaf2-4539-97f6-aa6815e75f21,"" -63d6c1e2-ce14-4e89-9bcc-f3e3e0b6fe72,"" -63e5b7be-525c-4a0b-852f-4cb090178d30,"" -63e709bb-a56a-452b-a48c-5125a367572a,"" -641068ee-0805-4403-a159-4fdb185cfdad,"" -64115066-e659-4cd0-9a56-fcd43a6df27d,true -64148c8b-89e4-4394-b044-27088ab3adc6,true -6433bbab-cad9-4e98-b488-c06e37acf268,true -645572a4-5806-4854-b89c-939463a904f7,"" -64a2df8b-688c-4368-b672-0406ac7032ad,"" -64c6ac23-d619-44aa-88fc-969376bebfcb,true -657aed74-c373-4c45-9de7-b75cdf62577f,"" -65ad0ff6-ba27-4eea-a400-3d96240a5702,"" -65b9a660-2f5f-4d9b-9f15-5e572d65ee9a,"" -65e87fc6-9232-4ba9-baae-61d7af6e54f4,true -66037242-eaa2-43af-98ed-190c3079833e,"" -667d62a5-160b-4657-be40-beeb787d51d7,"" -6683a92d-2d09-4875-9403-b977bc2eb37f,"" -66be2f9e-fffe-454e-86ae-75dc713ee934,"" -678223e6-745c-4d58-888c-b910dbb9039f,"" -67cf1dde-11a3-4aff-a7f2-a8b8288aac8e,"" -67ed7f4f-4873-409b-a04a-b928cb461719,"" -68059a10-b5eb-41c5-bb44-c56ecdb6baf1,"" -68249708-6a5c-474d-b1f1-214514fe5dbb,"" -682b9750-e94b-4522-b277-0d572b67e94d,true -68935870-c60d-44c5-b57a-000c0a99e9d2,"" -68a1d32e-915c-417d-98c9-3154ffbc9249,true -68a84b5d-edb4-41ee-82aa-8612ab7b2b38,"" -68dc301a-6b81-449d-bcf0-7b2f3d3335db,true -692cc2c1-44b0-4c47-86b1-2d3c4a5848bb,"" -6938bd7d-06d6-4a9d-8234-9123aa238788,true -697d10ec-d72e-49b1-b2f9-ac8a3be5c856,"" -698df1c2-7cc5-44cc-ab61-5420d92c598a,"" -699d6871-1043-4581-8dfd-fd1b6e47d518,true -699f0f86-fc10-4f15-9a78-19515e4e4958,"" -69ae7f71-a967-4c7e-a046-cef77b622f70,"" -69c3f870-e1a5-4785-8dc4-067a7590b949,"" -69e71f61-bde7-494c-b65e-346f5972d21a,"" -69fe60c1-d372-41ec-8671-94090b970a7f,"" -6a4adab1-6b87-4e61-a400-dfa4aa4af613,"" -6a50955b-b259-464b-b3ab-9fa173caeaa5,"" -6a8a69b4-f6d9-457e-8466-fdf87ac0585c,true -6a967445-47ae-46aa-8754-2e48915efc2e,true -6aba4aa4-9fe6-4759-9f76-705127be70c0,true -6b8c1da8-2bca-4e16-a04c-a99a04c76d85,"" -6b9af3c5-cc2a-40af-9264-1c68ea98d031,"" -6c18b368-8579-42e7-b0da-3500ed15ab82,"" -6c3be815-1494-4c82-b655-a3e7c92ee945,"" -6c7689e6-db2a-4544-bf19-0b70c6d74279,"" -6ca383d2-551e-4c89-b07f-c0faf9b36dcf,"" -6cac250a-d650-4c9d-a896-b38dd7e72955,"" -6cc22c4d-6014-44f6-a1e9-0b8eaf3f837a,"" -6d2776a9-6add-4222-bb43-64bd4dda65da,"" -6d34e7f6-d885-49f6-b072-6d06f6677a2d,"" -6d564224-4c2f-4bbc-af57-9895ad3b57d6,true -6d5ff978-ef70-4eba-a1db-536985901dce,"" -6d759795-d565-43ce-90d5-91b167b18c10,true -6d813c64-a15b-4e50-af82-c2f18f82a0cb,"" -6d94712f-7565-424b-ad4a-e1789fe2a639,"" -6d955124-bd23-4958-bad6-b36cdad50a5a,true -6d986c2e-b12b-4a6f-ac80-d3bcdaede076,"" -6e02f522-f081-402b-88f2-38e6622bd006,true -6e362fd8-b0b4-4cff-85c3-6586920627ad,"" -6e750faa-cf8a-4f5e-a02d-1471741cfdab,"" -6ea2fcf1-31dd-46e4-be06-592626e212f5,"" -6ea7ff7c-35f3-4350-9f9f-104c5182cd0b,true -6ead6acc-52c8-4df1-b00e-b46e2e2f0a04,"" -6ec2fab5-4d76-4c80-ab2d-577b041c1aee,"" -6f550b68-a7cb-4314-ba74-d7d2a6d56d0f,"" -6f63c413-6f3c-48db-990b-fcc749cc6355,"" -6fb0d554-b14a-4bbb-aa5f-a4d5c082c2dd,"" -6fd78455-d026-412e-ab31-d9e1a02923dd,"" -6fdefa93-f23b-4eab-82d0-1e9ce7c6c17c,"" -70777428-a2a0-4dd6-b048-1ef2a27d9ec6,"" -7084e394-9ee8-4b0e-a3f5-e8d091ab4fae,"" -70a25de3-45dd-4848-a394-e87d4f6b6930,true -70af4c33-277a-4a9b-8022-60d6bb0ac6aa,"" -7102c0da-2afa-4da2-a79b-a82742e4e3da,"" -713231d3-d739-455e-a913-41d5a1dc8571,true -71f73b19-86e6-494b-8038-46a67396de4b,true -7227c5bf-3699-48cf-a0c4-fb8f5d50540d,"" -72619c45-9cb0-4abf-a98e-20cc49a4e4f8,"" -7287ec50-e64d-418f-a83d-0d3d93981f62,"" -728f469e-7a39-4a77-8118-944f83c2cf21,"" -72972472-355f-4538-b69a-23aee8614438,"" -72baabec-7047-49ff-9e67-338361a06e5f,"" -72dd2d03-e239-4a2c-b509-769886057217,true -73364971-4250-436d-b85e-93df377b2911,"" -734b53ff-038a-4c79-91e5-c14340b43145,true -73fe21cd-4493-4afb-ba72-376f75bc2469,"" -7409225f-77a9-474b-889e-38eb4db0da2f,"" -74556136-3dd8-4074-8e26-160ae1b5f780,"" -748bb216-e420-46e3-9aef-6fe6b2020a74,true -74cfd92e-5da2-4fae-be59-1546f5a22e35,"" -74dbc21d-e056-4050-8f0f-04194ac43995,"" -74eca765-7327-44ac-9868-ed01c1dc163d,true -751225c7-3a03-40d5-980c-89f6020bc2ca,"" -752045ea-d278-4939-a82f-3ef29dd7616a,"" -752b713a-e049-403f-9d4a-f560ee0d4681,"" -752f97cd-9398-4830-ad13-38e26e3a8bcb,"" -753ae998-68fb-4917-93ed-484ef1753d9e,"" -75509cde-925c-4645-9a91-c1f244863842,"" -7569ce07-0cbd-4a30-bab7-d5f482a27883,"" -75969f3d-bddc-4e9c-8872-a33f108027a2,"" -75a6eba8-9d1c-4de1-b084-00a5aff10eab,"" -766ee5bf-c1a3-4837-8ee4-0c81e7252c91,true -7690d499-c3d6-4092-89b7-81b3425164be,"" -76a04620-5da7-4914-a5b7-5a8506221f16,"" -76b1d775-0f28-4bdb-b6cc-a2441e8e17b9,"" -76cb0eb7-3679-4104-8121-9e01d36de541,"" -76d885d5-454a-4541-8ec8-61f7972f17f7,"" -76f4f348-f65f-42da-a338-1e1f28df0ffe,"" -770f3f03-8a4b-4e32-8443-49b14d54ad3b,true -7714ce28-a83c-48dd-a84c-ba3565432818,"" -7728c15e-82ee-48c2-90a4-afa79e048a52,"" -77293357-c730-4960-a8aa-c25ea0775c3f,true -773da43c-fa5a-4a21-85c1-724b9d72bbbb,"" -776cf7db-0b09-48dc-b089-2d24410ed4d0,"" -77a645b1-5231-4486-8f16-0470ecc3e49f,"" -77a7196b-7391-4a38-89a1-e01b15e9e2a6,"" -7879670e-7953-4dc8-86ce-88f346cee653,"" -78a727b9-5bb5-4ed3-aec3-a3f804c79734,true -78bfe231-0661-4502-b18d-28c24b223dce,"" -78edeaed-91f4-4f03-ab2d-38b720e5e6df,"" -7914b992-59e8-4541-8250-427de113802f,"" -794a6b0e-1e0f-4c12-ae52-47b53f385a03,true -7957634e-ec90-46dc-9779-fb000649bd40,true -795d85f9-c2d4-4219-8e06-f6fbcef30e33,true -7968bcd8-e538-443f-b4a6-e41b9766965b,true -797ac9d5-9ef4-49bd-af56-1bd0c971467e,"" -797d8c9f-56eb-4487-ae30-5c4dca379bf9,"" -79813005-389e-4671-850d-b9a5fbc2345d,"" -798cfbc1-a7b2-47cb-91af-43af0ae0119f,"" -79a428bb-83a1-4338-a613-9b732f6feb4d,"" -79c5ee51-42c7-4fc0-8e8b-7ce9acb5133b,true -7a281b03-7145-409f-9c5a-c45b6e544a4c,true -7a543c90-f6bd-4343-8311-6818c3a9066e,"" -7aa8cd33-8365-4c53-ad1c-e07dcf2f86dc,"" -7ab27dbd-f952-44d9-bd9a-f67d5790fc82,true -7b4255ec-4801-47ee-b12e-4b874c5eec1d,"" -7b8c6a88-3a53-48bd-9b73-bdc3f087e6b7,true -7b9a52de-eb76-473a-a622-4b19d50b6bc9,"" -7ba6211d-542d-4508-96fc-712328eefc69,"" -7bb75949-ef13-4934-a19b-798f6a5e7866,"" -7bdf88dd-7924-4b93-9ff3-d543a170e0d8,"" -7bf5b1d8-3edd-458a-9730-187ac6738857,true -7c197c69-7872-44cc-93bb-c518b8403da2,true -7c6c40db-df29-4b8c-8881-3f1765e51c2d,"" -7c6e1d99-bc80-4172-b76c-226f6a618b48,true -7c85bdf0-7156-4555-aa37-49e4195ce0e4,"" -7c94eac0-d189-4107-9443-452d124db20f,true -7ce02db8-4ca5-42c9-afe5-b818e42e1767,true -7d4a09ac-d7e0-4675-a9f0-313c73fa7906,"" -7d83ed97-4c6b-4ba0-9dd2-423f59e8bba2,true -7da3d62d-56ee-4707-b036-2c1b5e111aff,true -7de9fea9-9923-4dec-9c83-1cf13b81ca39,"" -7df8f003-7458-4083-8ef6-20a049526c19,true -7e194149-713e-46fb-ab75-4a7cc2acd94f,"" -7e618ba1-1acc-4c2d-9b80-fee3d12884c9,true -7eb4427c-1b25-405e-8ef1-38fabbfb1a0d,"" -7ebecbcf-c963-4cae-9b1a-8fe44e175008,"" -7ec8d0b4-41e3-40c7-8f18-fa10b249e023,"" -7f054bf1-9bff-4ade-8841-ebc67d4299c8,"" -7f35f61b-9551-45f5-9641-c9a74d7ef110,true -7f8f71fd-602d-4ce0-ad32-211f5e7ca765,"" -805e5d13-6a31-405f-b43d-3449d73a50d3,"" -806085ba-3993-48ca-851b-a5dbe0690926,"" -80aca9f1-6907-4658-b0e4-bf6f53838148,"" -80e8b1f5-ea5b-4abb-9a06-b51bcce673e6,true -81155341-1ad4-4473-ac14-9257704203e4,true -8118e76c-1974-471c-9cf9-0b31ed231efd,"" -8131895a-c6f1-4b0c-aba6-cc3a52066646,true -814970aa-0552-4357-b958-d572f56421ac,"" -8177f823-7335-4da6-aaf9-354e05299d71,"" -8197ccde-83c1-4ab3-bf4b-e849667f1f31,"" -81dd2be8-9b50-4d63-bc4c-22c4dcba127e,"" -81f1e8f4-a335-4910-8a27-151141514c39,"" -822dee6c-337f-4a5a-a22b-7de9e64d1144,"" -822e3fd3-ae35-4e89-a5be-feac7c6f62cd,"" -8242d422-fa39-4374-85c7-5d267d68a0a7,"" -826cf99c-9d92-4cef-b717-d5c16ea7525c,true -82e58dcf-ea72-4a50-9eab-a74460d58e4d,"" -82e8a61e-9eb4-4fce-ad43-881202cdfbef,"" -82f09a48-3b8f-4a2b-9067-61b51d85c500,"" -830df854-5f4b-474e-9aef-e7be6b452a92,"" -832e240d-da2b-4658-b638-cd2cf2f08912,"" -83648485-484e-46b6-9a11-64a48fe471f0,"" -836f63ac-40e4-4170-a9f0-f170c4654db8,true -83b971fa-bf7a-49bc-b1d9-09680e8eeb24,"" -84a15a42-428d-4092-9f54-235b55b11de3,true -84c41fdc-0d34-4d95-8999-7677e4235e5b,true -84c90e57-2348-4843-a557-444e3a82ce43,"" -84d36e49-fc69-46a1-a5be-e9842e8e192f,"" -84ed43cd-526c-4055-9422-c31dfefa43ef,"" -84f38578-0070-46e8-809a-65c75623829d,"" -8502fd55-eeba-4c91-8274-4f34dfe89977,"" -857895a5-3a82-45fa-9fc0-00380ef01b47,true -85827568-1a34-42f7-877a-99a2d3a0f5c8,"" -8591ca33-6c89-4352-adf3-8261ce1bf58e,"" -85ea993c-f6d7-4b7f-836c-3c76206d66c1,"" -8644762e-70c7-45ba-a0fb-92fdb9e6c73b,true -8668b20d-ed68-4d67-91fe-d35c385a3408,"" -866bd41c-debf-4a53-9108-fd9c0784103c,true -86786682-7825-48f0-bbc3-3b1a127aa654,true -86859d13-26a6-429a-a38e-be69f1af9b4d,"" -86946982-ff92-46cf-b6cf-caa45ba4a986,"" -86bc9f92-f89b-4fb1-affd-093a785ac1db,true -86fee3a6-5135-4265-8c8c-04ac00ea8e12,"" -87061f96-823c-4e0e-9dde-8034682651a8,"" -87346977-9c76-4d76-b8a7-23ded71369a2,"" -8746d3f8-1943-4ab0-af49-f7d40aad5cc9,"" -875be020-8c24-450d-9ddf-f642e60e62f2,"" -87e5046c-332f-4ab2-b8a9-ab15cfa91fa5,"" -88274e29-0d4c-41c2-9315-f96c93d44c0d,true -886a561a-e64b-44f8-bc77-76724e0c369d,"" -888dfba9-08c4-4048-bb9f-23fe80dfcf5f,true -88a3a7ea-728e-4bbb-be44-73093e564cce,"" -88aecc11-264c-4a3e-a2b7-94c99d39fde7,"" -88cf49a9-ec8a-461c-87bb-003866a4e28e,"" -88db01b2-8bcd-4671-9447-f466279f21f8,"" -890cdafa-a6ba-47df-9cc6-5ea4d4febcfe,"" -893f9661-bc71-4f65-83de-6483e73bbce3,"" -8979e8fc-9f34-4305-80fe-122fc619c7dd,"" -89cefdfd-b6d9-41fa-ab28-50563fe44a74,"" -89da332a-d11f-4f77-977f-6275f337495c,true -89ee0500-71a0-47c2-a59a-1b681d9a92fb,"" -8a31ea5f-804f-4b8b-8f91-6ac4bea2c4dd,true -8a56157c-ea64-4f80-81db-0c89a9d0b547,"" -8a5eacbe-4ee5-4e3f-9a0d-d0d1a13ae2ed,"" -8a8432ce-47e9-4fff-b71a-3147ad298fd0,"" -8ab69964-40a4-4d5e-8d2d-82d07174989c,"" -8ad34764-84db-4613-8483-4e8281f8f984,true -8bbcf675-5176-4df5-bbeb-38603e8d9c42,"" -8bf02dec-bd39-4a1d-b074-e0ef2167b93a,"" -8bfb6668-fc94-4b2d-9ec2-3385f1780d58,"" -8c39345a-45b8-44db-b5f3-6b5914b3fd93,"" -8c492ef6-59bb-4ccd-bc64-48d58d3da475,true -8cda568d-3e48-4e77-afa1-374809ed3c3a,"" -8d671817-97a1-46c9-b23b-e3013f13edb4,"" -8d98567b-2930-47f8-ad39-759745179c38,"" -8dcdbdec-4dd9-4928-9e46-4f46fd6df9dc,"" -8e1303b5-0694-4bc3-a0f1-559d51e05364,"" -8e55e68c-f1ca-468b-b4f7-11c11c2956cd,"" -8e5ebd37-bb67-4c84-b9ec-3422ed7c3b19,true -8e9c8ff0-82b8-4974-91a7-888dce4f7f8d,"" -8ea94f05-b5b6-4aeb-987a-567f627a4216,"" -8ef5e898-02ca-4617-b966-d5954b2cf403,"" -8f051932-7f43-4faa-b3b4-c2254756c603,true -8f19178b-b9b8-4858-8f94-c0f1e6eaf2c9,"" -8f5f16b4-fcef-4a03-98c8-a6aecea249f0,"" -8f62568e-7778-4729-a454-369f5a3f12f9,"" -8f62ada0-3878-437b-aeca-d59d658ece1e,"" -8fc9a4e9-9ca1-4144-8cf3-032a5ed6bac2,"" -8fea3b61-246a-4333-ad04-78348cb59585,"" -8ff0c7a4-f1c3-400b-bb31-c66df4866cfc,"" -900405a8-53f2-4a6a-bb75-80e9725f3439,"" -90126aaa-1dde-4136-9be1-4e30e2c1b667,"" -90e6ec4e-bc73-413b-a07a-e8d3b675c027,"" -91357869-20f4-4e58-8237-5b028e1f733e,"" -915eb7b9-47d0-4abd-a424-29605ef5f497,"" -91874305-9f9f-44b1-bac3-28dc0fabd0d2,"" -91f7217e-5c7c-4462-9f7a-ad7234bddf6d,"" -929edb45-2558-4fe7-b87b-ad4a801a26eb,true -92e25da6-dacd-4d0c-9457-751bc135c126,true -9307ddb3-12ef-414f-beb2-62a145d3f63f,"" -933302fd-1717-4cef-852f-0bc28dcfea35,"" -937de252-6da2-4dae-a80c-8ef39c6eb99b,true -93851c17-f570-4055-b6bf-ff928624666d,"" -9389f1c7-bba0-4286-ab0e-73ca1430824b,"" -9391e021-6734-4c01-a6d4-a9e151e58201,true -93c74a74-55b1-4f45-8b7f-3a0442ea5006,"" -93f749e0-9ee0-4cfc-b970-20f1757aa207,"" -94b95bc4-3355-4881-93ed-5db3dbcabc26,"" -94c1cc51-4dc2-4dd2-8a48-b6793675a44c,"" -95accbba-6ae9-4e70-9984-235e7371234c,"" -95ce9659-ea2b-43a9-a6cb-0b85fb4d9a48,"" -95e68649-5482-4d3b-bc6d-ca77d83531a6,true -95f21d98-e820-4054-970e-ab04c6b0ef69,"" -960866ac-7fbe-4f4c-8a41-f5c6d756a394,"" -962aaceb-7115-481d-8496-5ee5d12ac43c,true -96526997-d779-49ea-999c-43bd07d250e0,"" -96a5c8b4-5c9d-4092-9171-03da2422df80,"" -96df629e-35be-42c9-ad60-2fe43dfba0f1,true -975a8040-5330-47c9-9bb6-13a1d07fd3c9,"" -979229e7-5022-4a8e-9f23-8673564bbf4e,"" -97bd3c96-d38e-4a46-9e9e-0ff3466b80a6,true -97d8b079-ffd3-4bfe-a1a4-da06038813eb,"" -97f558d6-5624-426b-b21a-6a92c463d004,"" -98216728-4494-43ee-9af2-930201bcf2fc,"" -9873061d-c9e3-48f8-9a27-6a92255869c6,"" -98da112b-cca4-4bfb-9b85-32accfe1d295,"" -98e90113-a97a-40fb-b6e3-dd5f2e646901,true -993d7bc1-e96f-477f-b7e4-ad8d25c59e94,"" -995d662c-8641-4089-bb52-417a2eaba272,true -996a443e-1ca6-4e05-b1d8-b84455f2412c,"" -99893b11-009f-4ae2-9ce4-c0db476800bb,"" -99929bed-454e-4131-8368-9d152ab1da8a,"" -99b31b91-ec68-4c94-b23f-2b75d9302cea,true -9a1f1ac4-3ad5-4ada-80b2-cc31176393a6,"" -9ab5ad8c-b789-482b-838b-6cdcbd184330,"" -9acec191-7d26-4519-aac1-b50e00f3a2db,"" -9ad11a61-23e0-4603-90fe-c3001fdc7320,"" -9b4a1f69-52d8-4c2a-9e8c-072f71c2bd18,"" -9b876e7b-aa09-4b8f-8c58-a0cab74580b3,true -9b9b76e9-88d4-4440-868a-c5b153c277d9,true -9be9764e-cac0-489a-85e7-9f3c995e7061,"" -9c3ab93c-6424-4402-bd4f-0f0f7dd2b265,"" -9c630f43-c0db-42b7-b223-607d5a9a39a5,"" -9c9cebe1-ea55-48c0-9edf-22033f5ebebd,"" -9cb03b1e-f448-4f94-8c42-7f9271202a1c,"" -9cc11154-43fe-4bbc-a9ee-c5b8e0158d51,"" -9cece101-e41b-4d77-be20-ae0c0cd2cb9f,"" -9d3c20e2-321f-488d-9472-5756fa061ca0,"" -9d415448-4c3d-40c3-b910-ee3ff0df9b3c,"" -9d495d7d-837a-4d3b-bb10-608f9abc919e,"" -9ddbf3cc-7d54-4532-a670-5c361df676d4,"" -9ddc0101-8632-4103-a12b-bf8c5590f624,true -9deb306d-e6ef-4bdb-9a20-c9f5ebc556d5,"" -9e1b3c33-ef02-48ce-9b60-15b62a9a499e,true -9e32e6c5-44d7-45b2-bb87-65e98f8464b0,true -9e3fc42d-d08c-4cb4-93a0-a567c5f6a19f,"" -9e5071df-d3ea-4bd0-83b7-e08ba3fbb896,"" -9e58de28-a944-4ca2-b53b-b8da836ebd93,"" -9ea9ede3-8f0d-43c0-9f35-cc9fcfb622e3,"" -9eac5165-a8e6-4acf-8d52-4ad49e2ba851,true -9ed868fd-baf3-490d-b665-113b9427520d,"" -9f078010-f724-422a-8d8c-8855f2513eec,"" -9f3630e5-1b35-43bb-b53f-c191634ba14b,"" -9fa9a6d4-e6fe-4eba-bbe4-7319b735c63b,"" -a0238799-d0f0-49a9-96b4-33cfc1688279,"" -a0520e0d-7dc2-4218-9176-32eb842de85f,"" -a05480c6-8b4a-4406-9226-8dce4bbb4d21,"" -a0eff999-b331-46a2-b8dc-16727efc7580,"" -a0f58ee1-6e61-41a9-b0c7-5acdb005ac0d,"" -a1007ed7-d907-443d-813a-0bbb0ed84e50,"" -a1155987-73aa-4d48-b127-5da6d4cb7b02,"" -a1bd990a-7d22-47af-8258-5d4a05b269a6,"" -a1e0979c-d57c-4482-9087-88077cf657f9,true -a2224a99-ae98-4018-8651-38f1f145c76b,true -a2466ef3-db84-4434-9e07-af11c15c7490,"" -a253f260-5b6f-40a2-b4cc-d2c66856ffcb,"" -a27b5fe8-3465-4b6f-b63d-cca66626727f,"" -a28cbadf-918a-4831-be0e-9ab6c83133d7,"" -a2a6355a-2c94-4da3-a707-cc6db6553ce2,"" -a2bf4c9c-46d8-41c4-8eb3-044e832c78c6,true -a2cb445a-10da-4b47-add1-16584c34d8e3,"" -a3240817-7453-496f-9d7b-058889858399,"" -a34042e1-cc97-476e-8564-382d941458b4,"" -a35cdf18-7bae-4776-8aaa-652bfc135af9,"" -a3b8ad3e-c982-4023-8977-e9717a33d645,"" -a3f4de14-3398-4b34-9165-3a24c5742de1,"" -a3f9fe69-1527-4d04-a51d-ca9f1955d210,"" -a452f6e1-013f-4745-aa25-e2cd16ca4423,true -a46ec80d-9cd6-49d6-863c-5f5425899445,"" -a49f38eb-8de7-49e1-b7de-97ab32fb277e,"" -a4b245ca-a50f-4bb2-ad64-1124c31eb166,"" -a4d85c0a-4687-4454-89be-085b9a2632f9,true -a4fec49a-dc46-496d-86a7-5387c2490c3b,true -a500b66b-fab5-4ee9-bda7-11fb6170fff0,"" -a533451e-9e8e-448e-bc5c-1beace2c397c,true -a5a464c0-3b64-4ccc-bdb4-62aac198eddc,"" -a5c68c36-129c-482e-a9eb-023d7ec1ef0d,"" -a5d76237-0ea6-4016-91a1-33463ef1e9e4,"" -a60eddcd-ef19-4a84-ac8f-9206ed2aeed0,"" -a618792b-5b12-4545-a3e7-c07313d188f3,"" -a65e37e5-cf18-488e-8870-e4aeb021ee41,"" -a674a795-2553-478c-a5e4-6fbde13d7523,"" -a67aa655-62ae-4905-8042-30fd3ea68944,true -a6a1fe2b-4322-4ea8-9ca9-3bcf288afe36,"" -a6d86605-7084-4477-8c8a-8cf25c4d1b9b,true -a6fbb09b-5b17-49f7-a510-5c1596b424a3,"" -a717a4a4-b0b2-4aba-af9d-7b431e7883e2,true -a71de636-d8aa-4455-ac06-932ae6d848ee,true -a7575150-4148-4b9c-b941-64db8cc61356,"" -a769b6ce-379a-4f6d-ad06-5df3a58d25ff,"" -a7a4ad54-167b-4cc8-8f88-dcb555c9dfb4,"" -a7b38940-3cd3-43b1-bba9-6adb6090d1d3,"" -a82170ad-dc53-4899-a7ee-cdbb50e6a91e,"" -a83fed45-f814-46b9-9fdb-50892a1a1736,true -a8624bea-ae5f-4fd8-b6ed-11b0fbba9254,"" -a863d462-6836-4f42-a4a7-8925574b76fd,"" -a8c9520b-bfa8-4915-bd66-a6133e1c5ffe,"" -a8f862f6-2e8b-4cec-81ab-eb3be51d71a8,"" -a906f033-5f9b-4c1e-aaff-287cffc6cc7f,true -a93d01a3-5bc6-40c2-9895-64a21fbd6253,true -a9584e1e-deb8-45f9-830c-bef1caeb21c8,"" -a961ce1f-cf0e-42f7-ada6-f283daeac650,true -a972425d-8c4b-48ab-ae2e-fc7db6661f67,"" -a9888a76-f2fd-4161-9d15-6126ba398899,"" -a9b103bc-f890-4d91-9714-8d674aa842fa,"" -aa5fe96b-324a-4010-b128-a5063ce0be77,"" -aa86bd26-a862-4a03-9d8c-a04ec4c70c0e,"" -aaada565-8a59-4f69-b92a-a6cd9a323a24,true -ab3e98bd-050f-4bfa-b6e9-d829291290a0,true -ab50086c-5cac-4c6b-8e71-6988453228aa,"" -ab52bac3-d430-4700-b1e6-7efe2f5613da,true -ab63c859-8b0c-4874-9404-cf7c89a368e8,"" -ab8aa7ee-f65c-4a29-9995-ef33c4135035,"" -abcfc7ff-d824-4414-a586-3c33c263a990,"" -ac139e4b-9d46-460e-92d6-0ac43ce8cb67,"" -ac5bafbf-2bfe-4dc4-8e13-adb2d80e8dee,"" -ac645c40-0c51-4d77-a5fd-32e619019c60,"" -accb0b6f-b012-42cd-9374-91355ed49aa8,"" -acd3b2a0-1a81-4738-9bbc-9ab0c56b0a85,"" -acd69c03-a3c4-44fa-b3d6-749476309db9,true -ace8301d-b547-4cb2-8311-e27a8980633a,true -ad07d661-6987-4928-93a0-e7b902b24ffd,"" -ad2a1d8e-6e19-4d5b-90a7-ab24050735a2,"" -ad48d6a7-890b-4eba-919c-ff6b63e35304,"" -adad08de-aeef-42cc-9a2d-b99fab5ab1dd,"" -adbb15f3-b71e-49a7-a954-2f117a93b972,"" -ade38b82-c9cd-4a9c-a7f6-23130baac87e,true -ae13da32-7194-4f04-9fd5-c7f114a2e5b0,true -ae4da701-069d-4ede-b59f-a2524c6529f0,"" -ae52de8a-deeb-4144-bfd3-52c2cab5fbb0,"" -ae56f2bc-f7f6-4bc3-ad8a-3b1193e40f71,"" -aea61272-bb6b-4fab-9817-584a8fa2f55b,"" -aec815b1-23cf-4504-8576-fe03b8798092,"" -aee86188-c252-4910-98cf-a8d3ca9d029a,true -aef2f04d-7449-4088-9ab5-a227d1d7c36d,"" -aef45e8a-6923-4db5-9bb0-18f472f7f0ee,"" -af12db23-5b03-4e68-9127-acc14775690a,true -af52f728-fcd9-464a-9e25-28013b84fa1f,"" -af73e4bf-2ba0-44ec-b619-a04f1c13c9a1,true -af77032e-b1e1-42c5-8540-68bcb906f134,true -afd2cfb1-57b0-44ab-a8f5-6edc92bc870b,"" -b0138fc8-736a-433d-831b-0bc39281d67d,"" -b0525467-772d-4d5f-b326-095fe72890e9,"" -b07bd4e7-6586-49d4-9f43-19d58889743f,"" -b0a1b45a-9dfb-482b-96d4-362cd1ee625a,"" -b0a510f8-2c98-436d-841a-eee086eba241,"" -b0ad5c48-9538-493c-b5a7-87f8f5ff858a,"" -b0b8b0c1-8184-401e-9346-37422a6f2f81,"" -b0d0e0b3-acc5-4887-a39e-df765111f99c,"" -b0e1d515-5be7-409a-8c86-3aab8ab6787a,"" -b101ebeb-ddbc-4bb2-b5c9-2bc6a23e0a16,"" -b109fc82-447e-4d4a-a447-1c7f0569bbc9,"" -b12f7f41-f8f8-4eb6-b2fa-dd5a18639c11,"" -b155b9d3-54b8-4906-a564-601bf123f027,true -b18674a0-77ec-4d7a-b563-aad0856ce45e,"" -b2071f60-d582-424d-b873-9ea4c2615442,"" -b217ba96-19d5-4184-a1ce-890bfdec478a,"" -b243e613-8498-427d-bc20-acc54786ed44,"" -b2503fbd-507b-44db-b4d0-32700a3ba5f5,"" -b260cc9d-644b-4e25-a99e-9afc44dc3879,true -b2766451-0904-4ccb-90c3-fa7fcdbe19c4,true -b2b2289a-0762-4846-a4a9-fbd7dc1a3822,true -b2f8bfb5-d8a4-4df9-8724-bbce63e4dcdb,"" -b3394219-debb-4c75-988e-98fef99f0500,"" -b34ef85b-d2fd-4fbd-bb2f-6c21d737e21a,"" -b376a344-8079-4b95-99a9-d515f9dfa4a3,"" -b379efa9-29ce-44ad-b653-9cc03ac2bfe6,true -b3861c55-ae00-4319-8a76-03627b8c8bf4,true -b42a782d-79e6-4276-9fa8-744d5d1bea24,true -b444d377-d1b0-4eb3-aca4-c24d3fac8c37,"" -b4771fd0-758c-413d-bbc2-17e8e2cc79bd,"" -b48e7fa4-1922-4d4b-858f-a4f3bd8810a0,"" -b4a9bcfc-73ae-4798-a221-bbfe9991e0f0,"" -b4ce60ee-7591-48eb-81ec-0466d931b2a1,"" -b4f80b37-afaa-4974-9cd7-f53d788b2be0,"" -b508dc50-ca89-4b92-a12b-b032d3bf3a5f,"" -b516b7eb-4029-454b-a8ec-9bdede40ffb7,"" -b55a959a-73e0-4b7c-bb03-88bb22844c09,true -b59c5c8e-7d5f-48bf-b3d8-900e18a64b12,true -b67c717e-f9fc-4ec3-8cdc-ade1c8d1b95f,"" -b6d48a75-7d74-45e6-9fb0-154ca5264744,true -b6ed4e6f-6911-4cd5-92de-f50e8d1916e4,"" -b7289502-ed78-469e-8b73-04811493a484,"" -b7597773-8d3f-4ee7-b54a-6c8b60b6d47a,"" -b762619c-c328-4367-99a9-41f10751cedf,"" -b7d87d24-1805-4a12-8f8d-2d14509d5eab,"" -b7f806c7-6ed8-4b7c-bf42-dae336e3f7b3,"" -b80e31af-6b74-4685-a83e-50c5d7f762fe,"" -b862117b-c899-4938-9df3-6baf43911d1b,"" -b89dd640-c21c-413c-8774-f9a154d897f8,"" -b9578448-2338-4f49-9d63-5b886a537f07,"" -b9701217-a4ec-4ef4-84f8-49ae5825b45f,true -b97f2f71-17ed-4e3b-8e3c-374241e6faa6,"" -b982af8c-9e7b-4dba-ba74-76770efffefe,"" -b98be876-6fcc-41e8-ad4f-a78b1661acb1,"" -b9dc5e38-47af-4d07-8ad1-b0425c5cb386,true -ba047f08-841c-4d5c-8f98-b3e614906d17,true -ba5a0e2c-6e96-44d3-800a-991a81337994,"" -ba7bf8c1-e6a4-4437-b1ef-cf9e443971d0,"" -babdc581-4c93-4674-8743-1be4b18ba156,"" -bb182025-e149-489f-b69f-debf6907da09,true -bb2c53fc-6d97-4747-a60b-8010f62b7f63,"" -bb770b6e-11d2-457f-858e-2e2c6c86c0bf,"" -bb94d817-e843-416a-bc31-4858001e7976,"" -bb9c95de-6073-4239-bb5d-023d18344157,"" -bbcd2900-31df-492c-9d43-e8e0243f7e1b,"" -bbe0b9ec-5a6a-44a9-9770-fbe4117593da,"" -bbf5580d-bef6-4cd7-a231-b6a532d68aed,true -bc1bab6f-5d84-4c95-afa7-b4ab39038ced,"" -bc23a56d-f42d-4bdd-b704-07376d4a24af,"" -bc6abddc-4fa4-4ae8-97f4-9c566558b227,true -bc75ec01-01c4-463d-b64b-597c106d4eac,"" -bc929bf3-06ac-408b-a17f-6ecdb28759a7,true -bca14431-d108-4ceb-831b-b6cedb7b1b22,true -bca1f5dd-414a-4570-9f0f-ed733bd170f6,"" -bcec2bc4-4e04-48cf-b52c-8e1fa2ac3875,"" -bcf5154c-180c-4366-836d-13aa6bfce980,"" -bd3b7896-2b3e-4c1a-a2b0-aca1c2ea23c1,"" -bd687828-857d-440d-836e-5230d3254b1c,true -bddfe37f-fb3f-4a24-9ce9-181f250aa0b8,"" -bde29534-91ef-4817-a1f0-00a5cc9475bd,true -be196f96-4e26-4c23-8241-b0a10a39e831,"" -be1ca606-c535-4751-b52e-c61cfe9b6c73,"" -be3057cb-9635-4a25-863f-b4e79cda75c3,"" -be6d80a5-e2df-4e99-9721-80097129bd8c,true -be7a093e-d70a-4a23-9db3-bd95aca334a6,"" -beee46de-ddd6-4fe4-80bc-6f1f4b8fd42a,true -bef4846a-ca76-4000-a98a-00a9d327107d,"" -bf88c593-87b8-46d5-808e-8a332c5b50d5,true -c015fb2e-4062-417d-86be-681d98f8a13b,true -c0381889-4401-4bc4-9056-641340fa7ec9,"" -c070e6de-9ae3-42fc-9cf3-023839ad0442,true -c086a87e-ec86-403c-8bc6-37a9b77924f0,true -c095cfbf-cd84-4681-ba67-7b3ed318186b,true -c0a2c5cd-58d4-49ca-baa7-d5859abc680b,"" -c1003a14-56be-43d0-9aed-41fb6b048273,"" -c1370f98-3833-49d3-b58a-21c581ac4ab2,"" -c14127c7-9943-471c-b5c8-ee9f0bb95bd7,"" -c1d430d5-32b8-4410-a1b5-6212b2c411ec,"" -c2253c94-aa7a-4e88-8410-e528b08654f2,"" -c24b807b-174b-4e5f-91d2-93599fb21548,true -c25d3d58-1f6d-4004-85c4-3a162dbf48ec,"" -c286cd77-3c89-446c-a91a-4769aa088be1,"" -c297cce1-5d10-48da-8ef9-e860ac55f387,"" -c2a32eca-f03c-4fe3-8c29-3b69c9798b5a,true -c34d92cb-7086-40e9-b303-97e204d2ab4a,true -c36767f9-a3d8-4f57-a370-4a088ea0b023,"" -c37a2b82-a495-4d05-8f8b-99a757d0b83d,"" -c391f109-1259-47f6-979b-e72492709b66,"" -c3aaa4df-ead6-4a84-982d-3c28752e1070,true -c3ab4714-5eda-433e-b461-c1710b769476,"" -c3de8ca9-c03a-4689-8701-dd524884b5b5,"" -c3e98495-15f0-4040-b92c-31aa40235dcf,"" -c47474bb-570e-45af-ad38-8f5352ee3774,"" -c4757dc4-d69a-4e2b-9b8d-61353a5b21e7,"" -c4e0ccb4-df43-4b20-873d-3a66faab42b1,"" -c4e31ea7-6ad0-42b7-8579-c3b7fcafa740,"" -c4e6e6bd-2135-47e8-81ae-7e1d6d5e7482,"" -c4f82d4f-f53a-4d32-bc78-67aa7786ed1e,true -c52dbae3-07a4-4039-b04e-d79a86941c76,true -c52de404-7bbd-4b07-95a0-a66df22a3221,"" -c533b9cd-e56b-4e32-93b2-1189f37eb646,true -c557b460-3e1e-4ec4-9b4d-ea7bc0c8d64c,"" -c5609af5-9f49-49f1-80ac-b18fccbc9720,true -c5f20b88-cbd1-4283-ad46-5b0bf31fa3ba,"" -c6386600-4824-43b9-b366-4504f4e74d66,"" -c6442145-3aad-4b57-b2cb-b7eff0699959,"" -c6700fda-f009-496e-83ec-0532f34bfa92,"" -c6a0f008-4d97-4319-8099-7e0cb6708438,true -c6a3be9a-b061-4a5f-879c-e948aa895f3a,"" -c6b44473-ebbc-48dc-8a42-44b7da0de840,"" -c6cb9c06-0c13-4cd7-8d20-ca7b99f7d141,"" -c761f0d9-c918-41b3-9131-856aad1f5b2b,"" -c7e313a6-4659-4fad-9402-946879a91616,"" -c7f263ed-22de-479e-909b-f6ceaf5f2eb0,"" -c80ab964-cf3d-4e89-9232-8e787cb0def5,"" -c80ec189-5f9e-4049-922e-969d28b3bdde,"" -c837974e-0093-438d-a822-19e97249f0dd,true -c895019e-6471-423d-ba15-4f06ed628c7f,"" -c8bbf035-8a01-4c57-934e-988a584c98b9,"" -c8c936bd-3e6e-4500-bbd5-051659c59309,"" -c8cb1ae0-6a31-4869-a396-78dd5dc7f302,"" -c8f123b5-8e57-4c9a-8770-bd4b4c2c064a,"" -c8fd40e1-35fe-4c8b-af6b-730fd6fb5fab,true -c92a4616-7215-4ba1-99df-40aea82eedc3,"" -c9445880-656a-4def-ad1b-2a4200d76c13,"" -c9647fa4-976a-4d9f-9be4-26b9c71f6eb1,"" -c98372ea-e9ae-4438-8715-b54ccdcc8f30,"" -c99c2510-b392-42ee-97e1-16f6a3b50978,"" -c9cd4daa-907f-490d-b3e5-a43eedd74c32,"" -ca45fe64-e7c9-48ea-bc02-3bc6a0b58e46,"" -cab21bd5-4314-4bb1-8b16-0967f8b08757,"" -cab53124-f25d-4b4a-a238-3d028e788b2e,"" -cac06963-2da1-4935-a6c3-d6152132c7eb,"" -cad24afe-7131-4158-9153-742c9fd16c9b,"" -cb1c6a1f-e659-4fe2-9cfd-f28e776a4f06,"" -cb5c302b-35a6-4d8d-8d6f-72ad1a3ed1cd,"" -cba58256-d558-48df-83d7-be5939d6dfab,"" -cbafa157-0905-418d-983e-827cc88faa94,true -cbfd98df-8ac2-4497-988b-bc4d1c9c6095,"" -cc0b8f72-76dd-4eaa-84c1-c343982e18e2,"" -cc1e584e-6752-4bf3-8c51-ac0192b5df44,"" -cc372f1d-a83f-438e-8d8e-c346eb03fee6,"" -cc38b529-3137-4d89-804e-51c6bd60ca97,true -cc3a3e25-87b0-4012-869b-9905b31f8319,true -cc5fd0ac-6ce5-43a6-bb4a-67279156ee8c,true -cc7e521d-4fa6-4215-942a-48f84ddf9a49,"" -cc8ade38-d9f1-4700-aad3-dc866f35d1a1,"" -cca1c9e8-c68f-4fe0-ae79-693399d8ee31,true -ccb56a74-40db-4d58-b0c4-cd485e8a7fe8,"" -ccb810a0-28a5-4248-92dd-7259a0fa0c22,"" -ccbda4c9-3ec5-46c1-a8d6-a215651dfb43,"" -ccbda933-c3d4-44bc-acfc-fd26bc234bb6,true -ccd5afad-8f2f-4305-b646-26137eff0a0d,"" -ccd69a01-cd72-41e7-87b9-de8454411ee3,true -cce63ba1-abd2-45d1-9ba0-a2cd0fa9b00e,"" -cce6dbc6-3a49-4f8c-b908-e6f11be81ac6,"" -ccf36cc5-5009-4fbc-8e8e-81f38d3195a7,"" -cd013d11-8609-41d9-a9c6-7fb33a499f51,"" -cd09f5d3-c846-41e7-a1ff-aabb55973663,true -cd2c3f78-ddb1-45f7-9939-987e070d9414,true -cd3d18de-96d3-4132-ae73-b80795a53ad0,"" -cd71ffde-9d7e-4c27-bc22-306f81cb46b7,"" -cdc96f77-a589-4ae4-85b6-c6d2c89487ba,"" -cdcad0ef-8049-41c2-a8ee-21835323b05d,true -cdcae470-3531-4548-a2a9-ba665fca8930,"" -cdfbf7e0-ad10-4c22-9ee4-888bb6bdd198,"" -ce1b7543-fee6-4267-a00d-daa5a5ea2861,"" -ce225925-36ec-4c1c-a327-c64a146bef46,"" -ce512c4f-3808-4ef6-9853-6f7d14e146f9,"" -ce7037fc-2614-46f4-a8db-40a2a0f9e3ac,true -cea2e600-da22-42b8-aa08-f0e951ff35d5,"" -ceea3ce9-5ab6-4cbe-99b2-34ca9e6eb468,true -cf17f19b-9f66-4391-bd2c-55384690d9bf,"" -cfced5ea-1e8e-4c85-8318-ef91a041f4e0,true -cfff2bee-7cf4-459f-8690-3638266a07e3,true -d000b381-5ba8-4a5b-84c7-f371e104eeaa,"" -d012051d-65bd-4a59-b4cd-f454d6010888,"" -d05fad91-9a3d-4d8b-ba99-5a33fcc82081,"" -d083a1aa-9662-482a-8192-4709f904f16f,"" -d0b22c1a-d0f6-4d83-bd3c-475a3acb0b83,"" -d0be6a2c-ebc5-405a-a2a5-006a5ea52c76,"" -d15e2267-a699-4f43-96a3-e98de7ace044,"" -d168f31c-483c-4340-a2f4-6a60cc8cab30,true -d17dfd97-60aa-4313-bcd7-f48935faa2e8,"" -d19385b9-bc04-4000-bddf-1d09425a2b4f,true -d1ba146a-9503-472e-b668-3686ff275ef2,"" -d1c2f4a3-8af5-4ad4-bd0c-032b947d3c25,"" -d21af0cb-c5a1-401d-b605-a2e3ba3f46a1,"" -d2272949-75dd-4949-88fc-f0f157c83105,"" -d230ce54-d5fe-4983-9e91-519ce1249322,"" -d241d156-478f-47a0-989a-96a5fff36780,"" -d2663716-cdce-4d88-90f8-51fb5d59aa4d,"" -d2a3d703-44e0-4dee-a8bf-9fc0457841ae,true -d2c11b5d-93c6-4500-bc4c-8dc50057f362,"" -d2c4f328-e7a4-4d87-b180-9a0eee13d165,"" -d2d08017-f955-4e0e-baf0-2d5d37acc304,"" -d31bee09-f02b-44c5-aa15-a06343e0bf9f,true -d355c55f-53b5-407e-8785-33e4e54bb3fe,"" -d37a07ee-5d7b-41d0-bbf1-331277662c52,"" -d397d6be-6008-47bb-9f72-628fb5c41cb1,true -d3997124-9e16-4df3-8c60-f43f62bbf036,true -d3e6de0a-bfef-438a-b982-4818145651b9,true -d3f2c0c5-1f4c-4156-8cc0-e2d87db500ea,true -d44741c1-d58a-495e-9f30-cc0f19fafc4f,"" -d46e0f9c-5dd1-4e88-9a22-96ca436674d3,"" -d4aa494b-3328-48fa-9873-39be6536a437,"" -d4dc8d84-20e2-4053-b244-47549e74680c,"" -d524b58b-90df-4d29-b4c8-308ac04dda2b,"" -d56569d3-0a16-4968-9470-93d410916f11,"" -d5944a91-3bf9-4915-8512-0f1f50a8eb7c,true -d5aa4b50-1ac3-48bc-90ee-7ad6bd78a078,true -d5ca4448-871c-4175-801f-38bb43cdb48d,"" -d60c26c8-1b83-4de9-be17-81fca1ef2575,"" -d6c622c5-3662-4b29-9180-0b4055afa612,"" -d6f1073c-bf1c-4232-8ee2-5b92f0a32d8e,"" -d702593f-4121-4ac3-bc3f-dae097c7a044,"" -d702f666-bb61-4d55-9579-e56efc3e0e91,true -d704d649-9e08-4502-a9c5-820f503dac9a,true -d7c0876e-3031-4be6-aa2b-bbd8e2bd8884,true -d7eba5f5-103c-4442-9a7f-62c81a9dd9e5,"" -d7f76663-5195-4de4-bc43-ee61e467fee4,"" -d8534558-7fea-4dcb-a1fb-2981b725dd61,"" -d86d32ef-f50e-46d6-b434-fd03bc807cc4,"" -d8a42c84-6930-4b12-8c1f-9a492d290454,"" -d8cdd01f-c8b2-46fc-8f69-35da565e1b99,"" -d909c299-c622-4c39-829f-415621e09eb1,"" -d94160a6-7943-496d-8b66-172e9ec6804e,"" -d97e5072-19d7-49bb-aaf1-f3a56816f3e3,"" -d9e59fa9-630b-4d97-aa71-4e03bd6fe1ad,"" -d9ebe2d3-2a6e-4079-8f59-43a327e68ebf,"" -d9fb7469-d50c-4492-8c1c-8bac1ccf81bd,"" -da33cd96-8f15-47f1-aeaa-846234a6b9cd,true -da412e67-cea6-4ad9-8c66-75bf7bd9d0f9,true -da438390-6d67-444a-be2e-354a21d30559,"" -da4c7a8d-c75b-4050-bbac-5edb50e48d4e,true -da5de060-6e29-4379-9119-175b394f9bac,"" -da98f3f0-236f-4c41-b123-09f1fdb5bfc6,true -daa0c0a8-91bc-41c5-9627-adad04a7b15b,"" -dabbe6e8-3692-406f-8a2d-f81217eb6964,true -dac01aa1-44bc-4ff5-b123-4868f8f278ef,"" -dac87128-5796-449e-85ea-2c1f8e09ba0b,"" -dad4c0b8-0e02-470c-b7be-6477008d925f,true -dae5b7a2-ad95-411c-9721-067d3c578262,"" -dae62ac4-666c-48f7-8587-ff9330a9e9e6,"" -dafa1e16-2149-4e76-a693-88d2e7e29a0a,"" -db0a2a90-dc62-4e6e-9a46-88b9a3145ab8,"" -db16da7a-538a-4270-bd0e-1b4398a922a6,"" -db1788c0-f515-419f-ada6-834a37168001,"" -db17c5e1-6d1e-4b0b-a345-8dca770e7097,"" -db5a6739-388b-453c-99d0-58cfce24d5d5,"" -db8a9775-faff-49c4-8b35-74e92245f439,"" -dba78a31-21c5-4600-b18e-3e90237ab40b,"" -dbbc9b03-4d76-4118-8637-185659ce2adb,"" -dbc0cb40-5e33-468e-bc9e-2cf8ed602d01,true -dc2a8c49-ac89-487f-a22c-dc126a5c7ed1,"" -dc36732e-8054-4d6b-a860-e7c54f04a0bf,"" -dc567aee-5073-493e-bec1-b6b0f4e160d5,"" -dca15405-893d-4468-84ad-574e54f45c22,true -dca8db58-f563-4a61-b747-94f5ec5bf135,"" -dd09736a-85a6-4c2c-979b-f2a7027308d1,"" -dd9a2411-d45c-4385-a6cc-650df658ae9e,"" -ddccfc35-d71d-4e65-afd3-4cf2a21e607f,"" -ddda9bf7-d645-40a2-8aed-eb9efa425d01,"" -de1c574a-a49c-41d2-b321-849c005c89e0,"" -de81cdd8-a6ed-4ed7-af7b-6ee027d0d5cf,"" -de9bffc9-c6ee-4ad2-8055-1e0d0e48887d,true -dea4f32f-0f51-4bf0-af67-e79b34674fac,"" -dee72459-d0ba-4771-af17-f38f8396adb0,"" -deee1173-4f31-4784-8c63-d02e2a7b16ba,"" -df471b44-cfeb-43fe-9488-8f85a03952fa,"" -df529c86-f229-404c-8f0c-44e1b29e4e95,true -df54eb2c-59c6-46cf-9cc0-201e94acc07d,"" -df6522a8-552f-4ba1-906c-139f58a0a85e,true -df8a0f98-893b-400b-9da4-2514e50c0b24,true -dfa10a30-82d4-433b-9df5-b0d2291378ae,true -dfa72495-2737-4fe8-9fcb-c799ab51ddcb,"" -dfb67b9c-64b1-422f-887e-139a8f676fa4,"" -dfb6cb62-17e3-416b-9214-6ae6f432874a,true -dfef1286-acc9-44ee-a736-b9676812235b,"" -dffc6fbb-8592-4f0a-8190-1e64eb2bc634,true -e0010996-fa96-46be-9431-ebbe139c081c,"" -e00ec480-db7a-4aab-84ab-7e5e7184bfdc,true -e0ef5017-54ab-469b-9871-f706d4899465,"" -e0f2d024-4210-4f18-82ce-51755627e13f,"" -e1309aae-d59f-44f0-b630-fc538bab9027,true -e134ed0a-49e0-4a0d-b09b-4e874f422e60,true -e16400e0-a6e6-459a-a16d-361a7b2ffd1d,"" -e1a9f42d-103d-4e01-b98a-e70f98aa1c00,true -e1c6ba82-6bc7-4f15-b71c-c669cb78bcc9,true -e1e4e2e8-1c7b-4c67-bc7a-3940e6670357,true -e1f9bb17-3719-45d6-b4bd-3be88f17e429,true -e1faaf81-1761-4a4f-b01d-c179db8474f3,"" -e20067e7-f2fb-472b-811a-3d28a08bf31d,"" -e2501541-dbeb-4e28-b4d8-598e963d6158,"" -e2b04908-769e-4916-bbc4-45ad7db85e02,true -e2eccb3c-0755-49c8-8e82-cc510481ed04,"" -e2fbc85e-dbc4-422e-9a11-0007d5c194ae,"" -e3784666-7f48-455f-a137-e6262a617ccf,"" -e3c2c69c-bbe5-4678-aa6c-3e5c7ff996f2,"" -e3c5df86-3d35-4dd8-a081-48632a3b67c8,"" -e3c94ca7-a487-468d-83f2-8068a592a774,"" -e3e4b2a1-6c40-4f42-a5b5-17b352209b40,true -e47d814f-5139-4339-a710-fe953efcfa74,"" -e484c47e-4da7-425b-a636-de512fcbc822,true -e50c31e2-5d52-4d45-9b8f-8ae3fdd26488,"" -e53c4c4c-976f-4cb9-95af-fb11c264cb72,"" -e53ec08c-9029-4b50-bc1b-a302878b17e6,"" -e562454d-7fbe-43a2-8046-bfc5b6ab043b,"" -e5803192-2273-43bf-8acc-945cc13af70f,true -e5c7eb1c-238e-4bf8-82bb-205c383f1517,true -e5d9ff02-57a4-4e05-b573-0dbc80e19958,"" -e5fa00b8-1779-467d-b881-757b6796d8b1,"" -e5ffe973-b166-4240-b5d5-17eaad746c4d,"" -e6e9da2c-7684-41fb-9123-def8a7cb4dba,true -e7098f3c-9dec-4aa9-9554-b3ad9a7d842a,true -e73f4c39-965c-47ca-adb5-0544e20d45bc,"" -e757ee7c-a50b-4575-b383-587e6f782bed,"" -e780a3b4-6b8a-40a1-a9ba-5f95725399ea,"" -e78c789e-7b81-49ea-b7e7-d17d8e8925aa,"" -e79745a2-654a-46d6-a886-dd1d21acccf4,"" -e7b3ee1d-2d5e-4dd6-8ae0-ebddf36aee4d,"" -e7d53c39-4adb-441d-a4e1-50c5f60fd3ef,"" -e81ec213-328e-446b-9c33-ee80bc850d44,true -e83d90d6-c694-47fc-8120-e91546fdda8c,true -e8ba3683-aa88-4800-a248-737d39ecb6c6,true -e8f05409-642b-419f-8475-28449c5f9569,true -e952a654-0605-409f-ac45-19d00d4b53c5,"" -e97e9781-3cb3-44b0-89a9-b704d456fcc2,"" -e9ab227b-2a64-4c48-a8a0-6c166bf4b970,"" -ea2f0657-1625-414b-9c7c-bd0b01a12eff,true -ea91b2ad-37c8-4a4c-9178-11d7a5e4e75f,true -eac7851d-1d61-4a35-9f08-c5da53335f7f,"" -eace08fa-4018-40fa-a7fc-3daf10b2f21d,"" -eae386dd-ef2b-4629-817f-6f8c44ce5817,true -eb2419c3-3b91-4d73-b4be-a40e759ab957,"" -eb2781af-72a5-4760-a9a7-20d212f56230,"" -eb511148-31f2-4292-a83f-d16d3be6631e,"" -eba22453-8605-43d3-81f4-f703e58a230d,"" -ebad9165-a532-4846-aff7-5038cd5c694f,"" -ec3d7e48-c9f4-4f64-ada9-7e2891a62d1b,"" -ec462f5c-2aaf-45fa-8c25-6f556349af00,"" -ec4d4a14-0934-4d28-ad20-64db8fe78d42,true -ec9ccc2d-1e6d-4ebc-b1e5-86d493d19eb4,"" -ed352845-d1af-40f9-afca-53503a6518d8,true -ed7a65e1-de6f-4c2f-bd47-aa78d0c2189c,"" -edd41c4a-a5f3-4531-b506-d81c04e1ebc6,true -ee004b78-c05d-4c34-a9f3-290a9259d3a3,"" -ee6749cf-0622-4376-958c-229ebc0f9618,true -ee8905ec-84b9-4fa0-b1b5-0908206b7af9,true -eeaa2e25-739d-443e-ae83-b0ef04e2836f,"" -eed7cb9f-7fe4-4f9d-bb4d-cdaeea6e144f,"" -eeeed70d-3cc8-4b11-9f6e-ab44da414b90,true -ef079a39-9f59-4681-91fa-3d745c5eaec1,"" -ef10cf22-bdb9-44b5-bc9e-bb777ad8f090,"" -ef1c5dab-b3a9-444b-b825-8fe3d0248e02,true -ef9360c1-b9b4-4dd9-86de-f996cd22e7a1,"" -efbe6910-0629-46d6-bcfb-5880a1795742,"" -eff17621-813f-4e99-a307-118ac9273efc,"" -f018a615-d5ff-4c3e-84f8-d4ebb1f342bc,"" -f04f4b77-a9f2-4ce1-9d0d-6b43d39483f7,"" -f07f7d83-1fe3-4da4-9c33-86aae3ac67e5,"" -f08704a2-c1f5-44d5-a3f6-43c5608dc987,"" -f0a81ab6-10ea-4859-a064-b3b0dc662f24,"" -f177f59d-ecf0-41a6-b81b-a311eb760976,"" -f1a1aa8c-3779-4c42-8302-6006dcb95151,"" -f1b36b35-1bd3-4328-aea9-4c5b3a7c7404,true -f226c31e-37f4-4a4a-aa1f-aa400447a799,"" -f22fe03d-e1e9-40d5-9ff7-a5bb6e7a2d71,true -f27187d5-f8f0-4188-ae0c-f77ca494a9b3,"" -f27a59e9-c785-47d7-88a4-de7706bb4f92,"" -f28962fb-0214-4664-b9c3-7c67d4eb2e8d,true -f29f4e3c-81f9-49b1-bc52-47a60a3076bb,"" -f2fd6221-529b-42ca-a060-30db0996a398,true -f3047ad3-3524-4a48-a00f-603a65fa0891,"" -f310aaca-1c10-41fc-ac49-8430c4c23656,true -f314d140-929d-4ced-bbd9-c7f0204e99eb,"" -f32094e2-ab74-4567-98c7-e35fe529393a,"" -f3338731-d7b9-4c1f-8e3d-e73b5b756c7b,"" -f3deb81d-79ca-4176-9b6d-bd96bb5b7a16,"" -f3e99f4d-5bbb-4f11-9519-4d3aa15e25cb,true -f3fd705f-3f03-4f26-a477-f36e65fe7499,"" -f4077b1d-84fd-4489-b8b1-bbc9f5b61da2,"" -f475b875-3df0-49fc-b4f9-10b2ac1c40a6,"" -f4bba159-e786-4f15-8100-6bff75c03635,"" -f4e59f5d-12a4-4005-bdae-e157e145c119,"" -f5042323-b136-4c9d-bf57-0b7b70609495,"" -f51f7f2d-968a-4630-8e06-d6beb52b9f6b,true -f55f863e-ffbf-4eb5-8f76-e2069098adcf,"" -f568f305-55f4-4d1f-849f-6c8f371f5051,"" -f5dec406-3636-4ac4-894f-1035ef3ef570,"" -f5e51d8b-bda1-4093-b887-f2355ea171cd,true -f5ebecec-331b-4d57-b353-3f50538cae2a,"" -f5ed5cb8-adc0-424c-8371-ffb8e7cc9bc4,"" -f6514087-db2a-40d8-897a-21b5af7d590e,true -f65d9eb9-e3d4-42fd-8d3b-168a6009f6ed,true -f66efc52-e910-4d73-a9a7-fddc110c16c0,"" -f67e345d-7f27-48bf-afe4-00d79c3cf300,"" -f67f612c-6534-40d1-8e56-c056e886b98d,"" -f6b9b783-c8af-44c2-8443-44b80e972686,true -f6bc9618-f245-4402-b69e-74d424736c4b,true -f703fec9-ca54-4363-9ef0-4c4acdc37e0c,true -f73ca57b-a00f-4217-9106-aedd26812b3d,"" -f74957d5-eef8-4683-976f-c59ffabdeadc,true -f7d2872f-bfc1-496e-ae92-a9d446aa72da,true -f7d6ad2c-1f7e-47ef-adfc-16a5d4d0189a,"" -f8043ec1-8313-45f9-a6dc-4cc1e5ad5f54,true -f8673366-8964-4c6c-aa9f-02e0920f21dc,"" -f8683b38-42d3-4f32-a543-034c05dc07f6,"" -f8816dca-a41e-4ef9-8a17-b40fc7d707ad,"" -f89b5e47-c8c3-4067-99ea-bd22f8932ee1,"" -f8b6ebd6-8d2a-4592-ad16-06772ad32cc7,"" -f8ddb5b8-70ff-4d5b-adf0-1752811f7be2,"" -f8f99d6a-94dc-4fca-8772-d0c3fe887a7f,"" -f912a4cb-2eeb-41f3-a410-0ddc3eab9cdd,"" -f9185eb8-d2e5-47aa-916b-a5ff24612ddf,true -f92f93d5-3037-4692-a062-ee84b9961510,"" -f93aa334-4532-4774-b82c-6d040d3c7156,true -f93e975b-d07d-434d-b227-666e744892c7,"" -f95dafda-7f63-4ddb-acbf-e363dc901b1b,"" -f95f8351-818a-4cb1-b26a-507c4baced47,true -f9aff2d6-fe39-4d17-90cf-917edf5d2bc8,"" -f9f5480b-27da-44fd-84a6-1ee4d4a55e36,"" -fa0ee43e-abc9-44cb-9248-3acc6c40e693,"" -fa4daa52-528e-45f5-8dd6-fe2a2c9fe8ff,true -fa522b1a-3244-4203-966f-546dcfb0c62c,true -fa5e8aa4-84e3-44b7-9dfe-326abdf02162,"" -fa7087f0-0140-4c94-b7c9-185b89c613c8,"" -fa786b6e-dbf4-4c51-a8c6-3006a029e8ba,true -fa80db64-ef07-4b14-aa7d-e93585fd84e2,"" -faa80ca5-9c2a-40f4-aeec-26d982e663ae,"" -faeaaf02-2ae6-4f5b-b729-61fd56d3946c,"" -faf774dd-13ef-4b09-8cc9-3cf33a562482,true -fb176a2c-4b8f-40d7-bb81-f370c96c3f96,true -fb1df190-d719-42b7-8e9c-d7830f92882f,"" -fb42fd3a-123d-4b2b-8a2f-d7e37af0a675,"" -fb55aea6-a5e7-4a55-a8df-967f504023f6,true -fb93624f-3d94-4c5d-9aba-10a3eaea0434,"" -fba16e5e-1b98-484e-8296-43a3995b9f75,"" -fbacdef8-f2e5-4a1c-8a01-b25442a5af89,true -fc348304-8ef3-4b3d-afcc-a66718315d89,"" -fc6dd798-66ac-45e2-a663-ea69b976c120,true -fcdd2cbf-917f-4429-a41b-48f8fe2a188b,"" -fd325c37-4113-45e9-9e55-abd6b2ce6dbf,"" -fd694334-1bcc-40f0-863a-017311fbaecb,"" -fd75e2b2-c780-477c-a813-046ccec6fe51,"" -fd7a8bc2-9005-4230-a4f5-348f4b1f5b16,"" -fd7ef037-c576-498d-bdef-cec86fdd60b0,"" -fdaeb95c-9d66-4da9-87cf-eabbdc593007,"" -fdb161bd-ad0e-47fa-a5bf-f17ece98f28c,"" -fdb49a29-6648-4dc5-bf56-ef948b00d885,"" -fdbbdafe-ed7e-4bd1-ae67-86430153a738,"" -fdc10bfc-2b5b-4a23-a93a-eaa85803bbc3,"" -fdce38d2-ae3a-474f-8024-52279c43b745,"" -fdfcdb50-716b-4452-a4c8-e03d3b8f89a3,"" -fe4c4989-ff06-446e-bf01-5c5d8f69f1eb,"" -fe66bca4-eedf-42a9-8def-d29ef3dd01de,"" -fe719bdd-cc9a-4c93-b2c9-982d051fceb1,true -fe7906dc-a882-488e-b576-2ef3486bcf79,"" -fe7c2db1-c229-4681-9373-e388dccb9e37,"" -fe7cb896-d81d-4b38-885c-4134ea64020e,"" -fe97c34d-b787-4c3a-a972-821eb42a3442,true -fead13f8-4b89-4f33-950b-ee9254a36c4b,true -fead2d6e-2979-4de7-8118-698906954d06,true -ff9df3bf-b8fe-4789-aa34-b205b5143a92,true -ffab80f3-741c-4c67-af21-4585f1c05d2f,"" -ffb89ee2-c3d9-4fd5-b897-a0e0eb692143,"" -fff8bb82-5151-41f9-b150-801ed4957277,true diff --git a/fhir-server/src/test/resources/responses/ParserTest/lengthObservationSubtraction.tsv b/fhir-server/src/test/resources/responses/ParserTest/lengthObservationSubtraction.tsv new file mode 100644 index 0000000000..b543dfb49b --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/lengthObservationSubtraction.tsv @@ -0,0 +1,1502 @@ +0020b035-7314-4867-9453-7921f158d003 +002593d4-d271-46a9-a900-1c498c322988 +00e9f82f-f16c-422f-81a3-77fa98958da3 true +00f02590-e737-42c4-9c73-206cf66b02b7 +011f5b11-f944-46ae-9cde-0d802e5c0073 +0143f01c-3692-479a-9a72-592260dbbd64 +01a4de83-37d0-4612-82eb-2ff2dbc2e02b +01c177be-05bb-4a44-a199-1df387e68d8e true +01c17bf0-9240-4149-983e-f0b3ccfb52be +01d57966-1281-42ab-9e05-02d96d146b6e true +01e27cc9-c9c0-4c33-82f6-9507394f5f2a +0218f780-899c-4c1b-9167-45e33d02acfb +021bf465-c299-4302-99ca-e29a6d25bda5 true +022ebe16-f618-4acb-9d99-88e23d686c34 +023a9220-3016-4202-8c7d-360d7d3bece9 true +025aa37b-4ae6-473b-9ae6-c12eece968c5 true +02741df6-79f0-477c-963c-8b3f8cb4374f true +02b87530-10e2-4009-8734-4196642cef1d +02c07924-f5c7-4909-ab49-dec34c051dfc +02c4fa06-3281-46a0-9bb9-c7b99497c91d +03365fa5-36d2-41b3-b3d9-1fea9f94c73e true +03555f06-db5f-4b8c-aae3-f6d2c547f499 +03610d59-94a2-41de-a039-c671241b075a true +038ced3d-5233-44c6-9a8c-1f6f9cc4be11 +03c7ba3d-8af2-44d1-a3c0-2c56b9fbc7c6 true +03dadf3e-cdc9-4f63-8a4d-89bae77bd529 +03e44215-f743-4c4e-8d19-aaf761f54503 +0429fff2-2839-45d8-b0bd-1d86aa92f966 +042bb1e3-e5aa-471e-9e83-bb4a1b90af22 +043108b6-98cc-4feb-8ad0-1394cd10a289 true +04367369-256f-4d47-b1ef-8014109ab46f +04661736-24d8-41f7-9f05-af5682696df0 true +05048b00-24df-4311-806b-2f9a7cc16380 +0531e6b8-2ede-4e12-a91b-cfe7e12e552e +054334cd-cfc9-4a1c-acf3-d0824cafd3a5 true +055824d7-b398-40b2-8fed-bc40552fffce true +05643ef2-65e0-4812-b739-4fadfa3c066e true +05bed469-760c-439b-9729-0479a6550b76 +05c37921-dde8-4b70-869e-82680975ab77 +05daccae-2d73-459d-8aea-7ee75966e52b +0624683a-8572-4885-b51d-2197dde395bf +062ab32e-318d-4ff7-81ea-f3be53204988 +068d13a5-8b46-4a3b-aca0-ac9ffe70f998 true +071b684c-70a5-4c93-8289-9630d25496d2 +072312b8-c984-4cb4-a4f8-97560368576d +07279076-3064-44aa-b07b-346a7edf6abb +072bd136-7e5d-49a2-b878-aaa2c7fde1f2 +0735b01a-c65e-4d59-9016-5e3bed1fcdcf +0785f996-ee5e-46f3-8966-107235de4cde +07906572-63db-4046-8dbf-1ae866664f41 +07d441b6-4e92-4d6d-af4b-7f38471fa45e +07dfda29-5378-4a66-ac90-fcb7ec6283ca +07f5fcdd-3aa0-4ccd-9122-f9686886dbd4 +07f95b0d-656b-4830-ade2-864e58ac5ed6 +088cdf48-9645-4dbf-b824-8836d6a83d24 +08d37a26-1ec6-4faa-985a-10a9d0ee54a9 +092b1b96-4ad3-4716-850e-8056ccd456d8 true +094a486e-7e50-4717-a0c3-07a456a4cd5f +09541c80-54b4-4bb0-8776-37745a9ac36e +09941e9e-ff5e-44e5-82cd-4c3865e39c01 +09ab1f16-7bff-44ec-aed5-8ba400930bbc +0a005143-4645-4ce4-89d5-9d49dca18ec6 +0a40e26c-d0ac-4653-a54f-17e23ed9a0ce +0a6c1262-dae3-4ec2-b911-0d1caca68302 true +0a7887db-203b-4a3a-b5fb-cabe9a886074 +0ab96751-edf1-4941-981c-c6bdcba75380 +0ae172de-1755-46d4-8729-d3acc96ce82f +0b6ee068-26bf-4f1b-8423-30f1c591861b true +0b95920f-7c6d-4b30-89da-51807a6aa557 true +0bbc7111-589b-460e-a742-20f2131c9c64 true +0bd78173-c348-41f1-9edd-884e3561ad47 +0c4242e4-1c5e-49d3-bf57-3810f8b88d56 +0c93a4a8-3b59-41f8-a7a5-22a9691710ab true +0c93c94d-b626-40b2-9561-52168c4961ca true +0cbe226b-6432-41c9-b3dd-0a14c6d54d7e true +0d1af4fe-50f7-4d8e-823f-192f8a08ec8a +0d7a0166-7fb2-4e1a-b1e5-b03a53fb5602 true +0dbafa74-2f5b-400e-b27e-fddeac8c28ad +0df8e035-a226-425d-9684-a12d0c58b844 +0e03654b-d81d-4a62-8f53-b479e8ce3edd +0e11e8ea-dc1b-4e8f-b809-39f0f07c7499 +0e8063ca-3601-4741-a2c0-c0381cc6098b +0e92574a-cc09-4564-a2ff-1cdbc89d210c +0e99ca14-e99e-4394-b962-59c66427218d +0e9cc169-ca91-4201-a78f-1b0f724507cb true +0ed9ee59-d968-443c-b7e4-c3732ec666e3 +0edd2d17-3ac8-427c-ae05-40c99b401484 +0ee77005-b9a1-4c51-a599-bd233897e431 true +0eea9efa-148d-41d4-b05a-d39484526333 +0f13292a-a654-4473-b78e-c8f1b3328ff2 +0f67bae5-3bbc-4c7d-84f4-34583758335d +0f68a16f-1c70-4d73-a9b2-fb384585526b +0f7373d2-3052-4c98-9c04-7074250f44cc true +1016a9db-50e6-4c17-9e93-18a97b67269e +1037da65-9451-48d1-a35d-bfcae76c2c60 +10a7d4aa-af9d-4d03-8d1b-bb53ed123f62 +10ad7a5d-e26a-4d4b-b063-239af96b0e87 +10b85b80-f6fb-4473-a3a6-266f610dcbc6 true +10cd7171-38a4-4276-b911-bcc8092eff7c true +10fef9b1-6bdf-4eef-b6b5-e12504e8fc7d true +1104d152-e95e-4a6a-8225-ab200e4530b4 +11148ea5-efc3-47ad-8672-799331f8e491 true +114ead73-4920-48fd-847d-f9aa1a99cab3 true +11597b54-9453-4ddc-9171-2be17df7a511 +1167e4a5-ce86-48b0-8b96-6d70125e2f9e +11a0c4d2-8908-4c0f-8e51-bf4be1605441 +11af3d82-bb56-464a-9203-8714c270f4a5 +11cc59ea-52cb-41e1-97e4-7ed651037442 true +12254e32-b86c-45dd-93b0-c8cbee619a14 +12257920-12cf-4945-a72f-80551181d7f1 +1266203f-94c9-43ac-af02-a0b8db8a93dc true +12689eb6-84ee-4d28-9c65-3f3e305266f3 +127b14bb-027d-4380-932e-ea34c76592d4 true +128cf2ff-4f97-4039-983b-72e2dd3267aa +1333fbed-c09d-4622-b22c-c67dd32e4583 +133a1365-e43c-4d92-a6f8-8c3947b13b65 +1359fe32-3275-4c1b-ba16-9dc903334044 +137d6ce5-93fb-4201-8bf9-0e97e9595b77 true +13bf741f-7ad6-4a14-908f-cbfe18651381 +13f4a39d-ec9c-43b4-958e-be28a97972e2 +143099ce-8738-4ccf-85fe-63e54943c7f6 +146000e6-9926-43da-9322-de3bf31e8142 +147add3d-eba6-4cea-bbe9-823b44794b9a +14a9ad6b-b7bf-4cdd-972c-bda91e5066bd +14afc7c1-89c8-4740-a932-a76d579e98c3 true +151df048-c17f-4a51-83a9-3de5f99be125 +1520180e-fb4a-4df6-80be-0b3d559c1049 +15268027-ae33-4f5e-9c2c-76327ccdeb44 +155d0003-f37b-45de-b34c-d611c2a2cf35 true +15821e91-ec03-4e76-a909-d2f2a14c3219 +159c1f96-038f-4722-9e36-98f4dfdb6b66 +159c3713-0d0f-499f-8ff6-ecddd44b958a true +15d9442a-2018-4390-beef-55fd90cf6ea3 +15e70015-e207-49dd-93a3-7c0ea5cc456f +1631e3a1-0eb3-4d16-a7e5-b0d8a8a9e630 +16af45f4-69d1-4190-9bca-b309c17bd865 +16be31b9-a434-46e2-a481-459c5584c907 +170e5f14-b29a-4aa8-ba71-4bf381a38d1c +17505b4c-c5e8-4acd-9d10-fb001190a8c5 +1795e970-0ce6-4d90-b198-32567c690869 true +17d74e33-f6c3-4b19-8708-4110a2f55245 +18a57e28-9c23-4e73-9d90-3694f2e373f2 +18c6a217-c947-4e97-a764-c43f33a409fb +18e59ddb-b9b9-401d-9239-0d2cb81936c8 +18ee3e63-a8a2-4004-a313-3e332af8a173 +190f22f4-5fe5-47e5-974a-fbbe63a676c3 +191232f1-58d7-4c4a-93a9-10aee15e5cea +1913a538-60f6-4510-9a76-2466896d2a27 +193a64c2-63ea-4677-9302-b56e3f0531ac true +19a30860-10da-4a1b-abdf-0b03d7b8f370 true +19b01508-db72-4c22-a5b4-4e015a3207f9 +19c965ca-c1ae-4eec-816f-73903fe43aa8 +19d27e58-26a0-48ca-a199-88d48894da5c +1a3ad6e3-cb5e-483b-babc-ba7d34ffacf7 true +1a7b0249-5aa4-4b0b-adb8-2c958e137d54 true +1ac6d3a1-d1a6-4b0c-b405-0aaaaf37c7db +1adf64f4-1fb4-47f9-a8a3-0ebeb3e7f414 +1af4a596-d0c3-4f7c-a217-b83a0755c3b7 true +1b091e60-4c4b-4fb4-b9b8-ee53bccc9b52 +1b251a42-a224-4a0f-9d36-44ecc8902d08 true +1b29b772-b873-447b-8aa0-5dd5e9ee1b80 +1b614d48-0af0-4d09-a6af-4f3af065da14 +1b785bb6-a40c-48e6-a464-f8022ea3cdcb +1bb3d0c2-c6de-42ec-bd60-edc8e3ccd092 +1bdd0326-987d-445b-9514-2ea30d948093 true +1c01b494-d0c1-4a99-b110-9de392bbcf69 true +1c120e92-0385-4120-ac83-1b42191597b8 +1c32d0e8-7312-4821-aede-6745a83ea45b +1c51f4a8-14a6-48ad-9821-c6408dd0cd33 +1c52440e-0063-4170-9cff-a3caaf7c63fb true +1c6dd7c8-4942-498d-a369-65fac6a837d6 +1c79c271-a128-41be-9537-1d744f4971a8 +1ce6a20a-2413-470f-86ca-2dacd8811e72 +1d9b87ab-8b41-4184-9519-fa9822f48aa1 +1da7f725-2fee-4f33-9a89-96253d2449fe +1dcdb77f-b0e5-4b2f-ad4e-d446a6ffa079 +1ddc3c30-fccc-4e5d-a9b2-e09ac4f64dc0 +1e2bcc56-3121-4b1f-b9c7-7e5503b867db +1e5b7df3-5e81-41c5-b096-d9b5409268ae +1e75c4d7-b570-4339-8c0a-06bdfc9bb485 +1e84c68a-4e0e-4980-adea-e46ae2506896 +1ea31cfd-f340-4c68-a547-b76365abd1bd +1f170771-0d08-45db-a38d-da3c539b46b0 +1f182acd-6bb8-4031-aa86-2f3dd83b4cb7 +1f3e4d9e-1fec-4a71-98fd-4912ab64d3d2 +1f6b8ac1-b12c-4eb1-a158-25b15965294c +1f8ad9f7-f796-4900-b10b-365fe442f2e1 +1fb81c48-c8d0-44e1-9b9b-df53ad7d9456 +1fcab6a5-85c8-41b7-a820-fe6e2d84eaba +2031d9a4-3e03-47a2-ab74-6fe7f2fa4ba1 +20d47a7e-f7f1-475b-a9b5-6192f2fce0ca +20ee5488-a11f-418a-86a9-cb981c2fdae5 true +2123f57e-0815-49e1-aa34-994270134f3a +2160f03b-4276-436a-8278-300bedd07f7b +21936507-6a38-4d80-807d-0d7cb5ea0311 +21d2fab0-5092-411b-b752-4859134a7aab true +223cae66-3049-48bf-be7b-cf027c415743 +22440788-7e64-452d-9025-9a25e4d2be60 +2245208e-d17e-4bb7-9bd7-ac75ac0ba134 +22462877-9663-4c06-bb5e-040d76bb144f +2253a94d-e511-42bf-a17c-599ae2d5cf0f +226d433b-9abf-4e98-bf0e-47cb3a37b41c +227d84f4-ec92-464a-b3de-f596866ce825 true +228b1b4e-f5e7-4e33-91fc-b7cbe6979102 +22b95566-7a4c-47b1-92af-7e0ecbfee6fa +230b746a-26de-434a-9774-54db1ad98a7c +2324dacf-f221-4edf-bdf6-924449cc916c true +2381d397-0068-4d6a-88e1-a0257d81a323 true +23831569-6aea-4071-820e-6eb5440acc1f +23b4fe3f-62c7-4439-9364-247ceb9251c1 +23f91a73-1b46-414b-becb-918e84177886 +2439d9a3-6c58-4ced-a82e-27908ed3df94 +2450caca-d74c-4610-ac24-1fae13801eb7 +24d8f04b-f035-4a9b-b03f-75be9e244ceb true +25085ba1-43d7-4a60-82ab-e15c477313b0 +251e7497-4a55-42fc-8e6a-32febded738f true +2534b076-583b-4872-84e3-bc96bdee5ef0 true +2583c0bf-6a7e-4100-a5c1-d249de6ec56d +25da1be8-51c6-41fd-b48d-5227e804ebfa +25e1a428-ab0b-40f5-ac25-1e9899458ec7 +2606b58b-a924-4128-b3a4-5e4b91deb692 true +261583fb-e2d0-44c0-a677-00bd89a3450b +26344e09-20c8-4405-b674-49638169a7a0 true +266c347b-fa2d-4cc1-a2fb-fe1ba20fbc2e +266cf373-c41a-4cc3-8600-c165a4349a6a +26b4001f-2917-4bf5-bcbf-1ce9dc258171 true +26fb6882-7f3e-44da-9100-58057c9aeec8 true +277e09d4-cb89-4748-b9da-39bee94df8b3 +27a75a54-d318-4026-bd63-c80f73899376 +2803a9d2-2555-4bc6-9718-7a9dcabd6ce4 +2803acc2-63bf-49a3-b4fb-0dd0742fb816 +281d5649-179d-4c8c-9a0a-d1159a5df8f1 true +28503262-5aab-44eb-bf51-6fb7534c25ab +2866a0de-36ab-4ec0-a1e1-cce5c697ef85 +286decfc-83d1-42e8-9a2f-880f145c71b3 true +2875fd27-8c5b-46ec-91f0-ef3a7a7727b3 +28aa1696-dde7-40ff-bb7a-6c5b614405dd +28e8966e-ba9b-4499-bc9c-7f51222e15aa +290440da-118e-4e8b-8d0e-d50043c24eac true +292aa324-379d-4b82-9750-4c8e23587f92 +29506f88-302c-4264-81b7-cf762de48615 +29546a71-e5a0-47ae-96da-421275c2cce1 +29676c08-88c7-4b0c-82c9-5f6d7dd51f68 +2974e695-f2e6-477f-91e4-da8eb654bf24 +298120fe-5dbb-4d99-902a-3a6783fc5b6b +298e5099-df2f-4381-8259-b10d0b8c0570 true +299c851f-3566-405f-a217-84c0703d859c +29b17fc8-8e80-4d7d-93dc-bdba1f2cfb9d +29bfab6a-d760-449c-9198-38e8c63574f2 +29d4d79f-8a85-490c-b52d-aaf3bcd9a100 +29e54cc5-b484-4818-9c3e-08074812dc7f +2a0d7eba-2dbf-4566-af0a-c16161a8b189 +2a133e8f-4dd9-49fd-9e3f-5cf122294091 true +2a67b2c1-18cc-45a0-ad53-825c73a3aa15 +2a71ca62-7f39-4c9b-b3de-6dfcf299298a +2a7f0959-9d50-4ccd-88e1-6635f2502905 +2af24d01-6215-4c7b-88b4-6cee45a156f3 true +2b265cf1-0763-4922-a403-e6f644ed95ba +2b41d0b5-97d4-4a80-bf65-b62ac0cb530a +2b4fd497-9f28-4c87-9b13-64f0d00c0cc3 true +2b54e31d-4af9-4071-ad49-60cae644ba39 true +2b60fba0-0143-4f31-8e4c-27236c7f41d8 +2b927027-35f1-488b-bb5b-127a8985cc5e +2bc92150-c490-44da-a925-ebddec5cd953 +2be63080-a881-43df-95b5-e371df93c6bb +2c018134-14c6-425d-8ab4-bc3974402b05 true +2c3b614e-7860-47ad-9320-11c5f45d27b0 true +2d358911-c5eb-480a-a9e6-a6fab52ad7c2 +2d45c298-d386-43ab-b3e0-ba07dc527a68 +2d6cb13b-fdfe-4219-bc13-7de385998bcc +2dbd4567-e6aa-4e1a-996b-af8384f66b37 true +2df38530-e49e-4ff9-93a3-816de29d9bfb +2df89473-f746-4086-a2f3-8ee2d4f63271 +2e0bc41c-fdfa-42ab-abdb-28f13cf2ca20 +2e13cb39-148f-4299-b74a-0a8e10092e9b +2e45b9b5-8d45-4994-aaba-1fab72c77c5d true +2e6b11f5-cede-4152-bfe9-523b030a2bdf +2e8c312f-53b4-4cd3-b6da-653c34945d76 true +2ea2af0e-343f-47af-b0fe-6dea108836be +2eb09ab4-c554-4536-a3ce-2979cfb988c6 +2edee289-e99a-4a53-a067-f83d29db4c2a +2ee72185-ca8f-40e3-b82d-30408dc8c6f2 +2ef91918-bdb6-4a6e-9c39-41d8c513b552 +2f1bbdc4-beaa-4d49-8791-6548094c7291 +2f24078d-9646-40fb-908d-2628d555dcb1 +2f260daf-8fb3-4fb9-a435-670ec8f0180c true +2f30832d-e6b8-4946-8cf4-0d8da5c359c0 true +2f33b981-f22f-4983-b347-f81d7d72c50a +2f3f7f7a-ba31-4c1e-8a23-c7d1eb1f255d true +2f5d7d1b-847e-430c-988a-40028f9f595e +2f8b35f6-2b55-4ff0-8d79-5de7805d6da1 +2fc9f8c2-4a1e-4e8d-8d91-a6c3b3937e8a +2fcfe30d-dc47-4eaf-9df0-7943a2c37fc5 +2feac451-2301-4ed5-8bca-baef62790656 +2febeae3-eb61-4743-8ff9-42547a77a40e +3006707b-dcca-41ce-b138-5d62e6b56984 true +30371578-4ad0-498b-a491-4e194c7466fd +305d5449-57c6-4362-83cf-55b4dae133de +30ba9f80-e595-460a-abf2-b8f46a62e606 +31504825-77aa-4a0f-a634-ca806bd886d3 +3169cf91-33f7-4b7e-b49f-d4b884760685 +319dfae4-0305-4520-8ad1-9679383cb74f +31acc736-076e-426d-8a32-426d91bf6511 +31e26c46-af6e-43a2-8e27-a8342f53e817 +31fd2352-7eb4-4ce8-9dfb-b3d3a1410c82 +322637fc-2d75-4a8f-89a6-a4c963869112 +32596eae-6236-4b3a-99fd-e50856971f59 true +3286c996-861f-4fe3-90a8-86ec826cdf9a +32bed860-5df5-4bbb-8bd1-c95c016561ee true +32d92e2f-d02b-4006-bf16-d1c535948e80 +32f3d9fa-6c64-49e8-a6dc-fb51679753e5 true +330fde18-0a86-4e67-850d-fe1c8b376dab +3322da98-67e0-445f-ba1f-225eb7d297a3 +33333f69-f8a6-4def-a5f2-bab5a26bf054 true +33826788-b600-476b-9843-17fbee2cd849 true +33a0b9dd-c88a-46cd-ba8f-af3d4d6cfc27 +33ac0c39-1552-408b-97ea-a90c9187d61e +33b22ef7-01a8-4e8f-8b5d-9aef37e46896 +33be0567-5c42-451b-bd71-c09f7de30524 true +340dbb93-0888-4f47-b18d-0b86d3e40248 +341634fb-2646-4c69-b57d-b5f00ff4c83d true +3420a3d5-d5c7-402b-a4c5-02e7198c5b7e +342699a7-cb58-4a9f-bb79-306483dfa652 +34335eed-f6c0-4e05-ba6b-1ecaa1fc121c true +346734fc-4798-48e3-8880-a603d52e9ca9 +346a282a-439f-48ac-875a-cc34643d70cc +34a83c04-2ea0-4638-ab6a-0a4bb9a3ad4a +34e44ce8-26fe-48c3-8fe8-5cd8f260aee0 true +34ea288c-f987-476a-a8ed-0ab2c1b92833 +3590f230-1be0-4c7b-afc6-5bffec9933ad true +35cf6113-d23a-476c-b18d-f86fbfb3ea2d +36160c58-889f-4543-bf04-dd29cac9ce19 +361688f0-e909-4c9a-b5d8-10727c9a18fd true +365ca17e-d5b1-41d2-982e-3ca7afdc0a0d +3669a209-82a0-4f9e-8026-43cebad7f3ab +366fa6b3-9595-4624-ae30-20257ef4885a +36957f27-97d1-483a-a8ea-bd5b1484ad12 +36c9ce76-e047-487a-837c-faffdb2676de +36cc6703-7757-4c6b-91a0-a43055c19bb6 +3732940c-2fa8-4301-942a-e58fd8d73421 +373609ea-276f-4b96-9c94-d96ad532246b +37770a6d-bb26-4499-a6e6-2eb399a8ad07 true +3793c189-6e61-4f1f-bb19-a01a306267d7 +379779f8-dbce-4dfd-b18f-ce0485b0d6aa +37e1570f-2c29-418a-acee-6688b6ab4279 +37ea3114-1b65-4c47-9e65-4cbcf339f8c5 +37fa89b6-2f6e-4985-b733-1ee4b2fd59e2 true +381dcabc-52cf-42e9-9624-d4e1270518a9 true +383aa480-8db2-49ed-9542-c738af90f344 +38898dca-756f-4267-aa1d-1fe52a6e4749 true +389079a9-b902-4212-a8c1-7187e818e016 +389a8f10-ad75-4d27-9d11-2c4ba0dc55a6 +38c329c2-4078-450f-ae85-e411b80f30ce +38c7c98e-0372-4892-aea0-ba21ed298958 +38d59ef7-b207-4d55-b74a-a1da3d0c10df +39d3bc28-0ce7-4d1d-9adc-bb9933568809 +39d7db36-7baf-43d7-8a0d-970ae00456d0 true +3a1941c5-c6bd-45b7-b306-d2a1a51c5eab +3a8a0b91-a492-42de-9b64-fc552476d8ff +3aacaf9d-af9b-4220-a836-1d0c3cfd5245 true +3ad5553b-58b8-46c9-8023-e1acb85be08c true +3b4bf6ba-0f27-4228-b6e1-c16dbf82e5cb true +3b7c654f-9a67-4402-a571-a5b0a61559ab +3bae6ece-f05c-4e3d-81a7-64f10f907bb2 +3bb8d5a9-87a6-4155-8f3a-7a0c841672c1 true +3bbe3af0-0f3c-41ee-9c41-70d89b411852 true +3be31b98-5218-44c6-aa92-1e054793d9df +3c496888-2ead-4d5f-afa4-8ab39b57ca03 true +3c504a58-92fd-4b2f-a308-21a9588b1850 +3c8d926d-3156-464b-b385-192257411798 true +3d2c40b9-2ef8-46d7-8215-bb898170902f +3d87abf4-a569-49b5-962b-32c11816a53f +3dac5f75-f71c-4092-b87a-7b8434af633f true +3db4a850-ef45-4fa2-8eb1-b1cba4477d8d +3dc0190f-39b2-4a1f-87d9-1ef41a73fa75 +3e4206fe-8856-46be-98af-d6fecd52db92 true +3e58093d-c387-4a07-85c2-41a519d0c398 +3ebd1cdb-5546-4dea-ba79-a88aef933640 true +3edb8e33-d674-4c25-aa75-7303da0229b9 +3f5c46c8-4a8a-4575-b169-6bfd5d752d98 +3f771434-cfed-4f34-9671-0ef302b5f9e7 +40100d97-ad9a-459b-b044-66568d36c0bd +408159a9-8c38-4cb3-9488-d83331f1762d +40a1dd3f-6ffd-4463-bf71-42057ee61689 +40a75d7a-909f-4657-9ac1-5b56df539129 +40eec0cb-8f50-4f61-92a4-7682c48511f9 +40f3ae1e-4855-4d78-9fe9-c8348058088e +4115b0b4-f738-405b-9351-ae11ba4bd07a +411cbadd-c2d1-4fb3-8ddb-d8bcea3aa176 +41496990-adc5-44d1-96b8-15010bdecc89 true +41a3cf00-0965-4c17-bfb5-247a7da43699 true +41a86c21-2ea1-436a-86a3-9173c7d95d9c +41de6042-e95f-4bca-ba69-f55293248f7c +41ecc0b6-ae98-40e0-a836-9d26070e248c +42187018-d27b-4831-913c-575d07e33c54 +421e31e1-5ffb-4c6e-957c-728b6218d6da +42350e13-ab61-49ef-aea1-802f6b7ea955 +423800b8-5a89-4eec-a12f-451c533fac50 +42895609-81ce-4cef-81db-069efe9a207d +429c5342-c5ae-4c55-98aa-e4f7bf0b4670 +42ac25e5-8828-49bf-bb5c-a6001966c6ff +42c5e47c-ede1-48eb-bc82-2ed835d67495 true +42fe77c3-239a-4afe-ad76-8c221b543e32 +43179e01-e94b-4f83-bb60-60f181388048 true +432bf1ce-0e8b-4621-b998-819fee53f80a +43522eea-96c7-41f6-ad41-1c190cece6aa +4358ee55-cc06-46a0-8b44-6b89d09c380f +4380d762-baa4-4c30-905c-59b33dbd793f true +439dfa4f-917f-40c5-9313-0d5dcf7ecf2f +43bdfaaa-7474-4503-ad93-730d777e6db8 +4416e165-8ac3-43e0-a9e4-6c3e789ffdd2 +44233ad5-4eb9-4709-9c6c-e7797d9c9f1d true +44237e1a-31ca-4635-9ff3-e3c71f587972 +4442a5e3-5793-43ae-92ab-f2d87a70cc0b +446f7fac-2202-4149-9860-fe48335349b3 +44713d16-f673-456a-881b-b0526ee36651 +44b741ee-bdb2-4732-b484-e435854de348 +44dbff22-7cac-4f70-886f-b8ac2dea7130 +44f714c0-88de-43a6-b69a-4d62d8fee57e +4522ef75-31ae-43e5-8e5a-5caf5c83fb35 true +453cd2e7-723b-4e1d-b414-f46f9152edcf +45467cbe-cc21-4822-94f5-82e1203056ad +4546f579-74ab-445c-8619-431f60a6efdd +455f8ebf-b074-4035-915c-91cd15380d72 +459f6da9-0eb8-4b26-85b9-a8f63c587fab +45a306de-3731-40c2-8bd5-2c0fee3b9f99 true +45d45783-5a68-4c06-9b2f-88b7ea1f8347 +461bea3d-934e-46fa-a112-9a761140372a +4672ebd9-08d2-43fc-ba92-ce13b587af3b +4675fa7c-6ac8-4bef-8756-88f757370a10 true +4680ae70-6d17-40fe-b55a-c3152ce8b246 +4687b275-d990-41cd-a146-2715079a8ecd +47014b4d-07f1-4bef-9792-82c027525c0f true +473bf60d-dab0-4531-9903-95dc6680a660 true +475e95a3-6d57-40f3-a0af-9daf1b35618b +478fbabf-520e-48a5-b3a1-a3e535d64345 true +47971ac1-1e72-4788-ba6f-bea608e59076 +47f066d8-abf5-4064-84fd-dfb05649e0fc +4808c4d5-7733-4601-8401-632bf5924c06 +484a19b7-9816-4a66-8f0b-51cb5f146818 +48693756-94c2-434e-ad88-e573e5278a6e +48fae21d-0ac1-4e4a-93b7-ac4ba7c2a606 +49088daf-d19c-4bab-9084-c67f0fba2c90 +491118c7-998f-4d45-8b58-da724ac011e1 +4925b797-e285-4d02-9aed-dfac19b0875b true +492f6c9e-3e1c-4043-aa08-03c610da33a7 true +49466fee-df56-4b9f-898f-96b65318d833 true +495bdeba-9ab9-4756-b4e0-a4aa8b039a02 +496f0696-cee8-425a-81d8-1df8fd17e4bd +49cce532-3ba4-4db6-b518-ea934588bfae +49ee8b11-a7c9-455e-9a9b-835dd617e861 +49f41136-fc63-48a8-8ee2-b7536d3bf0e7 +4a6eb9f9-49a5-4d36-85dd-78512fbe593a +4acf651e-af55-447d-bf0d-9adaef4cf6bf true +4ae0f855-534f-41dc-a4b0-879c771e3379 +4aeca194-6d0d-48ad-8fd4-6aea3be783fd true +4b322f4a-fa06-4509-9783-dbbf12960b77 +4b68a6bb-4fca-45ae-bfb4-b814c67bdac7 true +4b86609f-db97-4a0b-9860-3ce519072521 +4bbf3839-bd13-406d-9f60-d9eb9daa4dcd +4bd9e2b4-e195-4a83-ba1c-478e66a7bb5f true +4be334e3-94a8-4223-846e-39b5877e9ff7 true +4be59cbe-d89e-4abc-aca4-410a7e8cf580 +4be72d80-9418-44de-ab47-e8f2f2b9f2e4 +4c9a28de-ab9a-4e69-9ca6-375512c2f149 +4ca8a902-7ac4-4634-b1df-456175c39d14 +4cef34bd-c677-4647-b940-840a6868cb9c +4d0e9887-c81c-4ea0-9026-41ceb9ec9a51 true +4d45564e-e14c-4289-a637-06193bfcef2e +4d79c0f1-72b2-4e94-addb-9adce3f49c2f +4dad93cd-b188-4750-bdb0-4a5501568d81 +4db059c5-c4d2-440a-84ac-360312e3c590 true +4dba5ae8-2d30-479e-b831-80d39c396280 true +4de45bfc-2a41-4e0d-a0e7-c6caf6ca0f24 true +4df7ae55-1947-4d89-b51a-496b34c4250b true +4dfadd90-3f14-4ee9-abbd-a348a5d6a7d9 +4e0d77a5-782a-4b03-8867-f51a9704368f +4e74bab4-e328-447e-84d9-a29bb7fa1548 +4e8a0b34-71b8-4da8-80f9-f015782b182c +4e9a316e-406b-496b-acf5-6eec80f25ee7 true +4eab3a46-0ea6-431a-8c65-5b5bdee521cc +4ec34b7a-8389-4d01-bca5-06e678089d41 +4f147e12-0e70-4ddb-b4e9-4ea25361f290 true +4f18952b-be4a-4d77-ad4a-05276a6bce1d +4f3f5836-8683-49cb-b5e8-a9881796a202 true +4f62ec59-e478-40ef-8617-e6ed599866f1 true +4fe2d379-ba65-4ee2-a584-f0bc63a8523e +5005cfbc-b57d-442d-976b-34164aeb1977 true +504859eb-2ed0-4184-99f1-79f436fcd692 +5085d62c-dc5e-4f25-8f0c-700e57bb71fb +508de40d-3c1a-4bfc-8713-11dbae730004 true +509d7086-3120-4f61-bc92-4ab924f30ffa true +50a9341a-67f3-4d18-9118-373db23eea3c +50b76e4f-6575-47f6-9e15-a96b58c492f3 true +50dbd55a-e4db-406d-943e-7624d96c554b +50fbe67f-8d29-41a7-8cc6-3df916cd922f true +5105a245-dc96-483d-9a0e-52b39e664d27 +5106bb8d-43a4-4783-97c9-42d707990a2c +51164c4a-fa5e-4246-80e4-a01e1d7707d6 +512a1f5b-9f83-4420-a4e4-978e33432dba +5253d689-c7bd-441c-ab16-3721f1e3b5cb +527721f5-443d-4367-84e8-0f15982e7587 true +529479a1-36f6-4133-bb4e-2f3cf677626d +52c03be3-5715-4edd-9809-ebff7e2ba57c true +52d51fd3-d0c8-4046-9ec3-1a3f523d0b75 +53010db2-bba4-4751-9f12-f51429c9c558 +53339064-e7f3-47b0-a5c6-ce77d50b7489 +537bd7f9-9f4c-4bf1-bc21-9d09dd655a13 +53e5edab-cd6e-42cd-b6e3-a36d57be3685 +5441b3d3-b8e8-4e36-84c5-623e9b199daf +54487624-db9d-43cd-b4a6-0b4d3934f7bb +54627df6-151a-4f3c-9fea-374217afba2b true +5496c32e-97eb-4f6f-a7ee-46bd2123f966 +55054683-a9bc-4c84-b1b9-6a1540e1168f +55449cc5-a88a-4181-9550-5d2bb23a293e +5565df4e-b483-407b-8816-ca9e8ddefc27 +5578c18e-84ae-4f7a-8cb6-ce285e1b1ff4 +55936779-3082-43b8-a1e8-a8329bf19a8c true +55954c84-4a6f-4667-b070-5fa9185fe3d2 +55dc6b95-e01c-4812-a812-2cbf20de74cf +562f66c7-19ed-4d05-a14d-6aaa37e6eb16 +56617f73-b5fd-4e8b-8bd5-467af440f0a9 +569f7616-50c5-47c5-94c8-fa3b3097bc97 +5703a2ef-ccc3-4ca0-b100-a1c567f1dfc4 true +572e67ff-4f0c-49c4-b55a-36a958d3e02f +5742e203-dd0b-43c6-9dfa-304240238737 +574873f1-84ed-4171-a51b-6a2c609e9afa true +5760bdfc-cbd9-4763-841a-bb31a972f551 true +5781b9ed-eeb3-45d3-a364-43cf8ea348f6 +579a0bf9-87da-4887-9700-a5098cd7d720 true +579eb155-01ea-4066-9dae-9ddce5ea2041 true +57d84d96-a1fd-44ec-8460-588b477502a5 true +57fa04a3-a8e7-4699-9115-95957be21dea +57fec813-6327-4290-a2e2-ca74a04cccf8 +588b882b-0bff-427d-8b34-294f2fe55189 +58aa2384-e80d-4083-a173-ba534f152680 true +591075a3-92bb-42b7-a6b7-d761add1ad2f +598288b2-fa85-4bf8-9e01-2d0e86711aa7 +59857d17-d8e0-4932-b92c-97bd36a31d3d +59a25b17-fe55-4592-abfc-0c22e237bbd9 true +59b131b5-2db4-4877-bcc1-2206865df718 +59e29c4a-102b-46a3-a950-3babee7cfccc true +5a0eb3ab-86ce-467f-b389-94f026fe0f7f +5a4bc06f-8ed6-4cbb-93f7-5c5faee7e855 +5a9300fb-d831-4b67-88b2-843d2eca4441 +5b0954c2-a2cd-4013-b866-a6e0474c12c6 +5b2730a8-6a24-4bf7-aacd-f56fd380c696 +5b44153a-1ea5-45a1-9d58-bbc0a474ce1d +5b5f6d2c-1899-474a-afae-a98762630b06 +5b72c7eb-4584-45c0-9039-5fc762b1e50d true +5ba15ba9-7112-45ab-b7e5-353d52af5fe3 +5bb09da7-8ce1-4642-a113-fdd1fbc73897 +5c085687-51d6-4cbe-9b71-0a58718c1d24 +5c17c3d7-a7ba-4756-9991-97bc0b3a46ad +5c48ba78-4e71-441d-b03e-ef305d9e2bde +5cce02c2-3537-4d34-b085-7cf145590292 true +5cd558f0-20d1-40c7-8197-5a41ef97e634 +5cec04b3-e52d-4a6c-af98-6ee5d104b20a true +5cf73976-33ee-44ac-aade-5c64e56d7cd9 +5d54c744-365a-4db7-9675-79d64bdf55a6 +5d5dad82-9b58-4302-a494-02f44c3e94bd +5d790c80-5b87-4802-9b89-02f949846f39 true +5d883f31-f417-4d8b-bdbe-2cf73d1aa4b5 true +5d9e8c9f-8c95-4ca2-9de2-2cf875868ff9 +5ecf975d-fa2b-4c6c-a5e6-fed730c9e035 +5ef7c113-d5bc-486c-9be9-aa39aea779d0 +5f6ad8e2-eed9-4b2d-8697-4e2e742ee1d4 +5f92feba-5842-4894-907b-9ceaf36d0f38 +5fc1d33e-168e-435c-93ae-c619999b6f9b +5ffb50f0-0963-43d0-b032-3e6b2621e1d7 +602f4e55-f69c-4103-bb6e-7d23fd1b06ac true +6042dc41-8cdc-4825-9ab2-bb22af5c5fa6 +60635e6e-3d74-4f56-acb3-be8edc522837 +60959eb0-ed9b-4043-b8a1-0b0f27789642 true +60996e5d-082f-4d1e-8cc5-6cc626974925 +609a5704-81f0-4b67-881c-d00e87b74c0a +6198d258-17e2-4956-8064-1448f4744303 +61e75b9d-46a2-4d74-a0f0-37dc66a09450 +6220b92e-946e-449a-a2bd-3241e6a453a9 +62346d78-6be2-4cfb-850e-6ca1fc3f5e35 +62348772-d9cb-476a-a016-32f16e44cb33 +62ad1938-6235-48d8-aafb-2f773a40c6e1 true +62ccafe7-fbcb-4cbf-8584-224cd58ef421 +62e4244f-3f34-48f4-b865-c8b1c3ad97f5 true +62f342e2-5499-47c8-959c-4d786aa5fd67 true +62f6ce69-77ca-436c-9d40-3eb541853798 +6320229b-25c2-4355-a288-17efb054df6f +633dac04-7a54-476c-8482-9631c351ac26 +639dab1d-3282-4de9-84f7-727d0462f11f +63b2b9b1-aaf2-4539-97f6-aa6815e75f21 +63d6c1e2-ce14-4e89-9bcc-f3e3e0b6fe72 +63e5b7be-525c-4a0b-852f-4cb090178d30 +63e709bb-a56a-452b-a48c-5125a367572a +641068ee-0805-4403-a159-4fdb185cfdad +64115066-e659-4cd0-9a56-fcd43a6df27d true +64148c8b-89e4-4394-b044-27088ab3adc6 true +6433bbab-cad9-4e98-b488-c06e37acf268 true +645572a4-5806-4854-b89c-939463a904f7 +64a2df8b-688c-4368-b672-0406ac7032ad +64c6ac23-d619-44aa-88fc-969376bebfcb true +657aed74-c373-4c45-9de7-b75cdf62577f +65ad0ff6-ba27-4eea-a400-3d96240a5702 +65b9a660-2f5f-4d9b-9f15-5e572d65ee9a +65e87fc6-9232-4ba9-baae-61d7af6e54f4 true +66037242-eaa2-43af-98ed-190c3079833e +667d62a5-160b-4657-be40-beeb787d51d7 +6683a92d-2d09-4875-9403-b977bc2eb37f +66be2f9e-fffe-454e-86ae-75dc713ee934 +678223e6-745c-4d58-888c-b910dbb9039f +67cf1dde-11a3-4aff-a7f2-a8b8288aac8e +67ed7f4f-4873-409b-a04a-b928cb461719 +68059a10-b5eb-41c5-bb44-c56ecdb6baf1 +68249708-6a5c-474d-b1f1-214514fe5dbb +682b9750-e94b-4522-b277-0d572b67e94d true +68935870-c60d-44c5-b57a-000c0a99e9d2 +68a1d32e-915c-417d-98c9-3154ffbc9249 true +68a84b5d-edb4-41ee-82aa-8612ab7b2b38 +68dc301a-6b81-449d-bcf0-7b2f3d3335db true +692cc2c1-44b0-4c47-86b1-2d3c4a5848bb +6938bd7d-06d6-4a9d-8234-9123aa238788 true +697d10ec-d72e-49b1-b2f9-ac8a3be5c856 +698df1c2-7cc5-44cc-ab61-5420d92c598a +699d6871-1043-4581-8dfd-fd1b6e47d518 true +699f0f86-fc10-4f15-9a78-19515e4e4958 +69ae7f71-a967-4c7e-a046-cef77b622f70 +69c3f870-e1a5-4785-8dc4-067a7590b949 +69e71f61-bde7-494c-b65e-346f5972d21a +69fe60c1-d372-41ec-8671-94090b970a7f +6a4adab1-6b87-4e61-a400-dfa4aa4af613 +6a50955b-b259-464b-b3ab-9fa173caeaa5 +6a8a69b4-f6d9-457e-8466-fdf87ac0585c true +6a967445-47ae-46aa-8754-2e48915efc2e true +6aba4aa4-9fe6-4759-9f76-705127be70c0 true +6b8c1da8-2bca-4e16-a04c-a99a04c76d85 +6b9af3c5-cc2a-40af-9264-1c68ea98d031 +6c18b368-8579-42e7-b0da-3500ed15ab82 +6c3be815-1494-4c82-b655-a3e7c92ee945 +6c7689e6-db2a-4544-bf19-0b70c6d74279 +6ca383d2-551e-4c89-b07f-c0faf9b36dcf +6cac250a-d650-4c9d-a896-b38dd7e72955 +6cc22c4d-6014-44f6-a1e9-0b8eaf3f837a +6d2776a9-6add-4222-bb43-64bd4dda65da +6d34e7f6-d885-49f6-b072-6d06f6677a2d +6d564224-4c2f-4bbc-af57-9895ad3b57d6 true +6d5ff978-ef70-4eba-a1db-536985901dce +6d759795-d565-43ce-90d5-91b167b18c10 true +6d813c64-a15b-4e50-af82-c2f18f82a0cb +6d94712f-7565-424b-ad4a-e1789fe2a639 +6d955124-bd23-4958-bad6-b36cdad50a5a true +6d986c2e-b12b-4a6f-ac80-d3bcdaede076 +6e02f522-f081-402b-88f2-38e6622bd006 true +6e362fd8-b0b4-4cff-85c3-6586920627ad +6e750faa-cf8a-4f5e-a02d-1471741cfdab +6ea2fcf1-31dd-46e4-be06-592626e212f5 +6ea7ff7c-35f3-4350-9f9f-104c5182cd0b true +6ead6acc-52c8-4df1-b00e-b46e2e2f0a04 +6ec2fab5-4d76-4c80-ab2d-577b041c1aee +6f550b68-a7cb-4314-ba74-d7d2a6d56d0f +6f63c413-6f3c-48db-990b-fcc749cc6355 +6fb0d554-b14a-4bbb-aa5f-a4d5c082c2dd +6fd78455-d026-412e-ab31-d9e1a02923dd +6fdefa93-f23b-4eab-82d0-1e9ce7c6c17c +70777428-a2a0-4dd6-b048-1ef2a27d9ec6 +7084e394-9ee8-4b0e-a3f5-e8d091ab4fae +70a25de3-45dd-4848-a394-e87d4f6b6930 true +70af4c33-277a-4a9b-8022-60d6bb0ac6aa +7102c0da-2afa-4da2-a79b-a82742e4e3da +713231d3-d739-455e-a913-41d5a1dc8571 true +71f73b19-86e6-494b-8038-46a67396de4b true +7227c5bf-3699-48cf-a0c4-fb8f5d50540d +72619c45-9cb0-4abf-a98e-20cc49a4e4f8 +7287ec50-e64d-418f-a83d-0d3d93981f62 +728f469e-7a39-4a77-8118-944f83c2cf21 +72972472-355f-4538-b69a-23aee8614438 +72baabec-7047-49ff-9e67-338361a06e5f +72dd2d03-e239-4a2c-b509-769886057217 true +73364971-4250-436d-b85e-93df377b2911 +734b53ff-038a-4c79-91e5-c14340b43145 true +73fe21cd-4493-4afb-ba72-376f75bc2469 +7409225f-77a9-474b-889e-38eb4db0da2f +74556136-3dd8-4074-8e26-160ae1b5f780 +748bb216-e420-46e3-9aef-6fe6b2020a74 true +74cfd92e-5da2-4fae-be59-1546f5a22e35 +74dbc21d-e056-4050-8f0f-04194ac43995 +74eca765-7327-44ac-9868-ed01c1dc163d true +751225c7-3a03-40d5-980c-89f6020bc2ca +752045ea-d278-4939-a82f-3ef29dd7616a +752b713a-e049-403f-9d4a-f560ee0d4681 +752f97cd-9398-4830-ad13-38e26e3a8bcb +753ae998-68fb-4917-93ed-484ef1753d9e +75509cde-925c-4645-9a91-c1f244863842 +7569ce07-0cbd-4a30-bab7-d5f482a27883 +75969f3d-bddc-4e9c-8872-a33f108027a2 +75a6eba8-9d1c-4de1-b084-00a5aff10eab +766ee5bf-c1a3-4837-8ee4-0c81e7252c91 true +7690d499-c3d6-4092-89b7-81b3425164be +76a04620-5da7-4914-a5b7-5a8506221f16 +76b1d775-0f28-4bdb-b6cc-a2441e8e17b9 +76cb0eb7-3679-4104-8121-9e01d36de541 +76d885d5-454a-4541-8ec8-61f7972f17f7 +76f4f348-f65f-42da-a338-1e1f28df0ffe +770f3f03-8a4b-4e32-8443-49b14d54ad3b true +7714ce28-a83c-48dd-a84c-ba3565432818 +7728c15e-82ee-48c2-90a4-afa79e048a52 +77293357-c730-4960-a8aa-c25ea0775c3f true +773da43c-fa5a-4a21-85c1-724b9d72bbbb +776cf7db-0b09-48dc-b089-2d24410ed4d0 +77a645b1-5231-4486-8f16-0470ecc3e49f +77a7196b-7391-4a38-89a1-e01b15e9e2a6 +7879670e-7953-4dc8-86ce-88f346cee653 +78a727b9-5bb5-4ed3-aec3-a3f804c79734 true +78bfe231-0661-4502-b18d-28c24b223dce +78edeaed-91f4-4f03-ab2d-38b720e5e6df +7914b992-59e8-4541-8250-427de113802f +794a6b0e-1e0f-4c12-ae52-47b53f385a03 true +7957634e-ec90-46dc-9779-fb000649bd40 true +795d85f9-c2d4-4219-8e06-f6fbcef30e33 true +7968bcd8-e538-443f-b4a6-e41b9766965b true +797ac9d5-9ef4-49bd-af56-1bd0c971467e +797d8c9f-56eb-4487-ae30-5c4dca379bf9 +79813005-389e-4671-850d-b9a5fbc2345d +798cfbc1-a7b2-47cb-91af-43af0ae0119f +79a428bb-83a1-4338-a613-9b732f6feb4d +79c5ee51-42c7-4fc0-8e8b-7ce9acb5133b true +7a281b03-7145-409f-9c5a-c45b6e544a4c true +7a543c90-f6bd-4343-8311-6818c3a9066e +7aa8cd33-8365-4c53-ad1c-e07dcf2f86dc +7ab27dbd-f952-44d9-bd9a-f67d5790fc82 true +7b4255ec-4801-47ee-b12e-4b874c5eec1d +7b8c6a88-3a53-48bd-9b73-bdc3f087e6b7 true +7b9a52de-eb76-473a-a622-4b19d50b6bc9 +7ba6211d-542d-4508-96fc-712328eefc69 +7bb75949-ef13-4934-a19b-798f6a5e7866 +7bdf88dd-7924-4b93-9ff3-d543a170e0d8 +7bf5b1d8-3edd-458a-9730-187ac6738857 true +7c197c69-7872-44cc-93bb-c518b8403da2 true +7c6c40db-df29-4b8c-8881-3f1765e51c2d +7c6e1d99-bc80-4172-b76c-226f6a618b48 true +7c85bdf0-7156-4555-aa37-49e4195ce0e4 +7c94eac0-d189-4107-9443-452d124db20f true +7ce02db8-4ca5-42c9-afe5-b818e42e1767 true +7d4a09ac-d7e0-4675-a9f0-313c73fa7906 +7d83ed97-4c6b-4ba0-9dd2-423f59e8bba2 true +7da3d62d-56ee-4707-b036-2c1b5e111aff true +7de9fea9-9923-4dec-9c83-1cf13b81ca39 +7df8f003-7458-4083-8ef6-20a049526c19 true +7e194149-713e-46fb-ab75-4a7cc2acd94f +7e618ba1-1acc-4c2d-9b80-fee3d12884c9 true +7eb4427c-1b25-405e-8ef1-38fabbfb1a0d +7ebecbcf-c963-4cae-9b1a-8fe44e175008 +7ec8d0b4-41e3-40c7-8f18-fa10b249e023 +7f054bf1-9bff-4ade-8841-ebc67d4299c8 +7f35f61b-9551-45f5-9641-c9a74d7ef110 true +7f8f71fd-602d-4ce0-ad32-211f5e7ca765 +805e5d13-6a31-405f-b43d-3449d73a50d3 +806085ba-3993-48ca-851b-a5dbe0690926 +80aca9f1-6907-4658-b0e4-bf6f53838148 +80e8b1f5-ea5b-4abb-9a06-b51bcce673e6 true +81155341-1ad4-4473-ac14-9257704203e4 true +8118e76c-1974-471c-9cf9-0b31ed231efd +8131895a-c6f1-4b0c-aba6-cc3a52066646 true +814970aa-0552-4357-b958-d572f56421ac +8177f823-7335-4da6-aaf9-354e05299d71 +8197ccde-83c1-4ab3-bf4b-e849667f1f31 +81dd2be8-9b50-4d63-bc4c-22c4dcba127e +81f1e8f4-a335-4910-8a27-151141514c39 +822dee6c-337f-4a5a-a22b-7de9e64d1144 +822e3fd3-ae35-4e89-a5be-feac7c6f62cd +8242d422-fa39-4374-85c7-5d267d68a0a7 +826cf99c-9d92-4cef-b717-d5c16ea7525c true +82e58dcf-ea72-4a50-9eab-a74460d58e4d +82e8a61e-9eb4-4fce-ad43-881202cdfbef +82f09a48-3b8f-4a2b-9067-61b51d85c500 +830df854-5f4b-474e-9aef-e7be6b452a92 +832e240d-da2b-4658-b638-cd2cf2f08912 +83648485-484e-46b6-9a11-64a48fe471f0 +836f63ac-40e4-4170-a9f0-f170c4654db8 true +83b971fa-bf7a-49bc-b1d9-09680e8eeb24 +84a15a42-428d-4092-9f54-235b55b11de3 true +84c41fdc-0d34-4d95-8999-7677e4235e5b true +84c90e57-2348-4843-a557-444e3a82ce43 +84d36e49-fc69-46a1-a5be-e9842e8e192f +84ed43cd-526c-4055-9422-c31dfefa43ef +84f38578-0070-46e8-809a-65c75623829d +8502fd55-eeba-4c91-8274-4f34dfe89977 +857895a5-3a82-45fa-9fc0-00380ef01b47 true +85827568-1a34-42f7-877a-99a2d3a0f5c8 +8591ca33-6c89-4352-adf3-8261ce1bf58e +85ea993c-f6d7-4b7f-836c-3c76206d66c1 +8644762e-70c7-45ba-a0fb-92fdb9e6c73b true +8668b20d-ed68-4d67-91fe-d35c385a3408 +866bd41c-debf-4a53-9108-fd9c0784103c true +86786682-7825-48f0-bbc3-3b1a127aa654 true +86859d13-26a6-429a-a38e-be69f1af9b4d +86946982-ff92-46cf-b6cf-caa45ba4a986 +86bc9f92-f89b-4fb1-affd-093a785ac1db true +86fee3a6-5135-4265-8c8c-04ac00ea8e12 +87061f96-823c-4e0e-9dde-8034682651a8 +87346977-9c76-4d76-b8a7-23ded71369a2 +8746d3f8-1943-4ab0-af49-f7d40aad5cc9 +875be020-8c24-450d-9ddf-f642e60e62f2 +87e5046c-332f-4ab2-b8a9-ab15cfa91fa5 +88274e29-0d4c-41c2-9315-f96c93d44c0d true +886a561a-e64b-44f8-bc77-76724e0c369d +888dfba9-08c4-4048-bb9f-23fe80dfcf5f true +88a3a7ea-728e-4bbb-be44-73093e564cce +88aecc11-264c-4a3e-a2b7-94c99d39fde7 +88cf49a9-ec8a-461c-87bb-003866a4e28e +88db01b2-8bcd-4671-9447-f466279f21f8 +890cdafa-a6ba-47df-9cc6-5ea4d4febcfe +893f9661-bc71-4f65-83de-6483e73bbce3 +8979e8fc-9f34-4305-80fe-122fc619c7dd +89cefdfd-b6d9-41fa-ab28-50563fe44a74 +89da332a-d11f-4f77-977f-6275f337495c true +89ee0500-71a0-47c2-a59a-1b681d9a92fb +8a31ea5f-804f-4b8b-8f91-6ac4bea2c4dd true +8a56157c-ea64-4f80-81db-0c89a9d0b547 +8a5eacbe-4ee5-4e3f-9a0d-d0d1a13ae2ed +8a8432ce-47e9-4fff-b71a-3147ad298fd0 +8ab69964-40a4-4d5e-8d2d-82d07174989c +8ad34764-84db-4613-8483-4e8281f8f984 true +8bbcf675-5176-4df5-bbeb-38603e8d9c42 +8bf02dec-bd39-4a1d-b074-e0ef2167b93a +8bfb6668-fc94-4b2d-9ec2-3385f1780d58 +8c39345a-45b8-44db-b5f3-6b5914b3fd93 +8c492ef6-59bb-4ccd-bc64-48d58d3da475 true +8cda568d-3e48-4e77-afa1-374809ed3c3a +8d671817-97a1-46c9-b23b-e3013f13edb4 +8d98567b-2930-47f8-ad39-759745179c38 +8dcdbdec-4dd9-4928-9e46-4f46fd6df9dc +8e1303b5-0694-4bc3-a0f1-559d51e05364 +8e55e68c-f1ca-468b-b4f7-11c11c2956cd +8e5ebd37-bb67-4c84-b9ec-3422ed7c3b19 true +8e9c8ff0-82b8-4974-91a7-888dce4f7f8d +8ea94f05-b5b6-4aeb-987a-567f627a4216 +8ef5e898-02ca-4617-b966-d5954b2cf403 +8f051932-7f43-4faa-b3b4-c2254756c603 true +8f19178b-b9b8-4858-8f94-c0f1e6eaf2c9 +8f5f16b4-fcef-4a03-98c8-a6aecea249f0 +8f62568e-7778-4729-a454-369f5a3f12f9 +8f62ada0-3878-437b-aeca-d59d658ece1e +8fc9a4e9-9ca1-4144-8cf3-032a5ed6bac2 +8fea3b61-246a-4333-ad04-78348cb59585 +8ff0c7a4-f1c3-400b-bb31-c66df4866cfc +900405a8-53f2-4a6a-bb75-80e9725f3439 +90126aaa-1dde-4136-9be1-4e30e2c1b667 +90e6ec4e-bc73-413b-a07a-e8d3b675c027 +91357869-20f4-4e58-8237-5b028e1f733e +915eb7b9-47d0-4abd-a424-29605ef5f497 +91874305-9f9f-44b1-bac3-28dc0fabd0d2 +91f7217e-5c7c-4462-9f7a-ad7234bddf6d +929edb45-2558-4fe7-b87b-ad4a801a26eb true +92e25da6-dacd-4d0c-9457-751bc135c126 true +9307ddb3-12ef-414f-beb2-62a145d3f63f +933302fd-1717-4cef-852f-0bc28dcfea35 +937de252-6da2-4dae-a80c-8ef39c6eb99b true +93851c17-f570-4055-b6bf-ff928624666d +9389f1c7-bba0-4286-ab0e-73ca1430824b +9391e021-6734-4c01-a6d4-a9e151e58201 true +93c74a74-55b1-4f45-8b7f-3a0442ea5006 +93f749e0-9ee0-4cfc-b970-20f1757aa207 +94b95bc4-3355-4881-93ed-5db3dbcabc26 +94c1cc51-4dc2-4dd2-8a48-b6793675a44c +95accbba-6ae9-4e70-9984-235e7371234c +95ce9659-ea2b-43a9-a6cb-0b85fb4d9a48 +95e68649-5482-4d3b-bc6d-ca77d83531a6 true +95f21d98-e820-4054-970e-ab04c6b0ef69 +960866ac-7fbe-4f4c-8a41-f5c6d756a394 +962aaceb-7115-481d-8496-5ee5d12ac43c true +96526997-d779-49ea-999c-43bd07d250e0 +96a5c8b4-5c9d-4092-9171-03da2422df80 +96df629e-35be-42c9-ad60-2fe43dfba0f1 true +975a8040-5330-47c9-9bb6-13a1d07fd3c9 +979229e7-5022-4a8e-9f23-8673564bbf4e +97bd3c96-d38e-4a46-9e9e-0ff3466b80a6 true +97d8b079-ffd3-4bfe-a1a4-da06038813eb +97f558d6-5624-426b-b21a-6a92c463d004 +98216728-4494-43ee-9af2-930201bcf2fc +9873061d-c9e3-48f8-9a27-6a92255869c6 +98da112b-cca4-4bfb-9b85-32accfe1d295 +98e90113-a97a-40fb-b6e3-dd5f2e646901 true +993d7bc1-e96f-477f-b7e4-ad8d25c59e94 +995d662c-8641-4089-bb52-417a2eaba272 true +996a443e-1ca6-4e05-b1d8-b84455f2412c +99893b11-009f-4ae2-9ce4-c0db476800bb +99929bed-454e-4131-8368-9d152ab1da8a +99b31b91-ec68-4c94-b23f-2b75d9302cea true +9a1f1ac4-3ad5-4ada-80b2-cc31176393a6 +9ab5ad8c-b789-482b-838b-6cdcbd184330 +9acec191-7d26-4519-aac1-b50e00f3a2db +9ad11a61-23e0-4603-90fe-c3001fdc7320 +9b4a1f69-52d8-4c2a-9e8c-072f71c2bd18 +9b876e7b-aa09-4b8f-8c58-a0cab74580b3 true +9b9b76e9-88d4-4440-868a-c5b153c277d9 true +9be9764e-cac0-489a-85e7-9f3c995e7061 +9c3ab93c-6424-4402-bd4f-0f0f7dd2b265 +9c630f43-c0db-42b7-b223-607d5a9a39a5 +9c9cebe1-ea55-48c0-9edf-22033f5ebebd +9cb03b1e-f448-4f94-8c42-7f9271202a1c +9cc11154-43fe-4bbc-a9ee-c5b8e0158d51 +9cece101-e41b-4d77-be20-ae0c0cd2cb9f +9d3c20e2-321f-488d-9472-5756fa061ca0 +9d415448-4c3d-40c3-b910-ee3ff0df9b3c +9d495d7d-837a-4d3b-bb10-608f9abc919e +9ddbf3cc-7d54-4532-a670-5c361df676d4 +9ddc0101-8632-4103-a12b-bf8c5590f624 true +9deb306d-e6ef-4bdb-9a20-c9f5ebc556d5 +9e1b3c33-ef02-48ce-9b60-15b62a9a499e true +9e32e6c5-44d7-45b2-bb87-65e98f8464b0 true +9e3fc42d-d08c-4cb4-93a0-a567c5f6a19f +9e5071df-d3ea-4bd0-83b7-e08ba3fbb896 +9e58de28-a944-4ca2-b53b-b8da836ebd93 +9ea9ede3-8f0d-43c0-9f35-cc9fcfb622e3 +9eac5165-a8e6-4acf-8d52-4ad49e2ba851 true +9ed868fd-baf3-490d-b665-113b9427520d +9f078010-f724-422a-8d8c-8855f2513eec +9f3630e5-1b35-43bb-b53f-c191634ba14b +9fa9a6d4-e6fe-4eba-bbe4-7319b735c63b +a0238799-d0f0-49a9-96b4-33cfc1688279 +a0520e0d-7dc2-4218-9176-32eb842de85f +a05480c6-8b4a-4406-9226-8dce4bbb4d21 +a0eff999-b331-46a2-b8dc-16727efc7580 +a0f58ee1-6e61-41a9-b0c7-5acdb005ac0d +a1007ed7-d907-443d-813a-0bbb0ed84e50 +a1155987-73aa-4d48-b127-5da6d4cb7b02 +a1bd990a-7d22-47af-8258-5d4a05b269a6 +a1e0979c-d57c-4482-9087-88077cf657f9 true +a2224a99-ae98-4018-8651-38f1f145c76b true +a2466ef3-db84-4434-9e07-af11c15c7490 +a253f260-5b6f-40a2-b4cc-d2c66856ffcb +a27b5fe8-3465-4b6f-b63d-cca66626727f +a28cbadf-918a-4831-be0e-9ab6c83133d7 +a2a6355a-2c94-4da3-a707-cc6db6553ce2 +a2bf4c9c-46d8-41c4-8eb3-044e832c78c6 true +a2cb445a-10da-4b47-add1-16584c34d8e3 +a3240817-7453-496f-9d7b-058889858399 +a34042e1-cc97-476e-8564-382d941458b4 +a35cdf18-7bae-4776-8aaa-652bfc135af9 +a3b8ad3e-c982-4023-8977-e9717a33d645 +a3f4de14-3398-4b34-9165-3a24c5742de1 +a3f9fe69-1527-4d04-a51d-ca9f1955d210 +a452f6e1-013f-4745-aa25-e2cd16ca4423 true +a46ec80d-9cd6-49d6-863c-5f5425899445 +a49f38eb-8de7-49e1-b7de-97ab32fb277e +a4b245ca-a50f-4bb2-ad64-1124c31eb166 +a4d85c0a-4687-4454-89be-085b9a2632f9 true +a4fec49a-dc46-496d-86a7-5387c2490c3b true +a500b66b-fab5-4ee9-bda7-11fb6170fff0 +a533451e-9e8e-448e-bc5c-1beace2c397c true +a5a464c0-3b64-4ccc-bdb4-62aac198eddc +a5c68c36-129c-482e-a9eb-023d7ec1ef0d +a5d76237-0ea6-4016-91a1-33463ef1e9e4 +a60eddcd-ef19-4a84-ac8f-9206ed2aeed0 +a618792b-5b12-4545-a3e7-c07313d188f3 +a65e37e5-cf18-488e-8870-e4aeb021ee41 +a674a795-2553-478c-a5e4-6fbde13d7523 +a67aa655-62ae-4905-8042-30fd3ea68944 true +a6a1fe2b-4322-4ea8-9ca9-3bcf288afe36 +a6d86605-7084-4477-8c8a-8cf25c4d1b9b true +a6fbb09b-5b17-49f7-a510-5c1596b424a3 +a717a4a4-b0b2-4aba-af9d-7b431e7883e2 true +a71de636-d8aa-4455-ac06-932ae6d848ee true +a7575150-4148-4b9c-b941-64db8cc61356 +a769b6ce-379a-4f6d-ad06-5df3a58d25ff +a7a4ad54-167b-4cc8-8f88-dcb555c9dfb4 +a7b38940-3cd3-43b1-bba9-6adb6090d1d3 +a82170ad-dc53-4899-a7ee-cdbb50e6a91e +a83fed45-f814-46b9-9fdb-50892a1a1736 true +a8624bea-ae5f-4fd8-b6ed-11b0fbba9254 +a863d462-6836-4f42-a4a7-8925574b76fd +a8c9520b-bfa8-4915-bd66-a6133e1c5ffe +a8f862f6-2e8b-4cec-81ab-eb3be51d71a8 +a906f033-5f9b-4c1e-aaff-287cffc6cc7f true +a93d01a3-5bc6-40c2-9895-64a21fbd6253 true +a9584e1e-deb8-45f9-830c-bef1caeb21c8 +a961ce1f-cf0e-42f7-ada6-f283daeac650 true +a972425d-8c4b-48ab-ae2e-fc7db6661f67 +a9888a76-f2fd-4161-9d15-6126ba398899 +a9b103bc-f890-4d91-9714-8d674aa842fa +aa5fe96b-324a-4010-b128-a5063ce0be77 +aa86bd26-a862-4a03-9d8c-a04ec4c70c0e +aaada565-8a59-4f69-b92a-a6cd9a323a24 true +ab3e98bd-050f-4bfa-b6e9-d829291290a0 true +ab50086c-5cac-4c6b-8e71-6988453228aa +ab52bac3-d430-4700-b1e6-7efe2f5613da true +ab63c859-8b0c-4874-9404-cf7c89a368e8 +ab8aa7ee-f65c-4a29-9995-ef33c4135035 +abcfc7ff-d824-4414-a586-3c33c263a990 +ac139e4b-9d46-460e-92d6-0ac43ce8cb67 +ac5bafbf-2bfe-4dc4-8e13-adb2d80e8dee +ac645c40-0c51-4d77-a5fd-32e619019c60 +accb0b6f-b012-42cd-9374-91355ed49aa8 +acd3b2a0-1a81-4738-9bbc-9ab0c56b0a85 +acd69c03-a3c4-44fa-b3d6-749476309db9 true +ace8301d-b547-4cb2-8311-e27a8980633a true +ad07d661-6987-4928-93a0-e7b902b24ffd +ad2a1d8e-6e19-4d5b-90a7-ab24050735a2 +ad48d6a7-890b-4eba-919c-ff6b63e35304 +adad08de-aeef-42cc-9a2d-b99fab5ab1dd +adbb15f3-b71e-49a7-a954-2f117a93b972 +ade38b82-c9cd-4a9c-a7f6-23130baac87e true +ae13da32-7194-4f04-9fd5-c7f114a2e5b0 true +ae4da701-069d-4ede-b59f-a2524c6529f0 +ae52de8a-deeb-4144-bfd3-52c2cab5fbb0 +ae56f2bc-f7f6-4bc3-ad8a-3b1193e40f71 +aea61272-bb6b-4fab-9817-584a8fa2f55b +aec815b1-23cf-4504-8576-fe03b8798092 +aee86188-c252-4910-98cf-a8d3ca9d029a true +aef2f04d-7449-4088-9ab5-a227d1d7c36d +aef45e8a-6923-4db5-9bb0-18f472f7f0ee +af12db23-5b03-4e68-9127-acc14775690a true +af52f728-fcd9-464a-9e25-28013b84fa1f +af73e4bf-2ba0-44ec-b619-a04f1c13c9a1 true +af77032e-b1e1-42c5-8540-68bcb906f134 true +afd2cfb1-57b0-44ab-a8f5-6edc92bc870b +b0138fc8-736a-433d-831b-0bc39281d67d +b0525467-772d-4d5f-b326-095fe72890e9 +b07bd4e7-6586-49d4-9f43-19d58889743f +b0a1b45a-9dfb-482b-96d4-362cd1ee625a +b0a510f8-2c98-436d-841a-eee086eba241 +b0ad5c48-9538-493c-b5a7-87f8f5ff858a +b0b8b0c1-8184-401e-9346-37422a6f2f81 +b0d0e0b3-acc5-4887-a39e-df765111f99c +b0e1d515-5be7-409a-8c86-3aab8ab6787a +b101ebeb-ddbc-4bb2-b5c9-2bc6a23e0a16 +b109fc82-447e-4d4a-a447-1c7f0569bbc9 +b12f7f41-f8f8-4eb6-b2fa-dd5a18639c11 +b155b9d3-54b8-4906-a564-601bf123f027 true +b18674a0-77ec-4d7a-b563-aad0856ce45e +b2071f60-d582-424d-b873-9ea4c2615442 +b217ba96-19d5-4184-a1ce-890bfdec478a +b243e613-8498-427d-bc20-acc54786ed44 +b2503fbd-507b-44db-b4d0-32700a3ba5f5 +b260cc9d-644b-4e25-a99e-9afc44dc3879 true +b2766451-0904-4ccb-90c3-fa7fcdbe19c4 true +b2b2289a-0762-4846-a4a9-fbd7dc1a3822 true +b2f8bfb5-d8a4-4df9-8724-bbce63e4dcdb +b3394219-debb-4c75-988e-98fef99f0500 +b34ef85b-d2fd-4fbd-bb2f-6c21d737e21a +b376a344-8079-4b95-99a9-d515f9dfa4a3 +b379efa9-29ce-44ad-b653-9cc03ac2bfe6 true +b3861c55-ae00-4319-8a76-03627b8c8bf4 true +b42a782d-79e6-4276-9fa8-744d5d1bea24 true +b444d377-d1b0-4eb3-aca4-c24d3fac8c37 +b4771fd0-758c-413d-bbc2-17e8e2cc79bd +b48e7fa4-1922-4d4b-858f-a4f3bd8810a0 +b4a9bcfc-73ae-4798-a221-bbfe9991e0f0 +b4ce60ee-7591-48eb-81ec-0466d931b2a1 +b4f80b37-afaa-4974-9cd7-f53d788b2be0 +b508dc50-ca89-4b92-a12b-b032d3bf3a5f +b516b7eb-4029-454b-a8ec-9bdede40ffb7 +b55a959a-73e0-4b7c-bb03-88bb22844c09 true +b59c5c8e-7d5f-48bf-b3d8-900e18a64b12 true +b67c717e-f9fc-4ec3-8cdc-ade1c8d1b95f +b6d48a75-7d74-45e6-9fb0-154ca5264744 true +b6ed4e6f-6911-4cd5-92de-f50e8d1916e4 +b7289502-ed78-469e-8b73-04811493a484 +b7597773-8d3f-4ee7-b54a-6c8b60b6d47a +b762619c-c328-4367-99a9-41f10751cedf +b7d87d24-1805-4a12-8f8d-2d14509d5eab +b7f806c7-6ed8-4b7c-bf42-dae336e3f7b3 +b80e31af-6b74-4685-a83e-50c5d7f762fe +b862117b-c899-4938-9df3-6baf43911d1b +b89dd640-c21c-413c-8774-f9a154d897f8 +b9578448-2338-4f49-9d63-5b886a537f07 +b9701217-a4ec-4ef4-84f8-49ae5825b45f true +b97f2f71-17ed-4e3b-8e3c-374241e6faa6 +b982af8c-9e7b-4dba-ba74-76770efffefe +b98be876-6fcc-41e8-ad4f-a78b1661acb1 +b9dc5e38-47af-4d07-8ad1-b0425c5cb386 true +ba047f08-841c-4d5c-8f98-b3e614906d17 true +ba5a0e2c-6e96-44d3-800a-991a81337994 +ba7bf8c1-e6a4-4437-b1ef-cf9e443971d0 +babdc581-4c93-4674-8743-1be4b18ba156 +bb182025-e149-489f-b69f-debf6907da09 true +bb2c53fc-6d97-4747-a60b-8010f62b7f63 +bb770b6e-11d2-457f-858e-2e2c6c86c0bf +bb94d817-e843-416a-bc31-4858001e7976 +bb9c95de-6073-4239-bb5d-023d18344157 +bbcd2900-31df-492c-9d43-e8e0243f7e1b +bbe0b9ec-5a6a-44a9-9770-fbe4117593da +bbf5580d-bef6-4cd7-a231-b6a532d68aed true +bc1bab6f-5d84-4c95-afa7-b4ab39038ced +bc23a56d-f42d-4bdd-b704-07376d4a24af +bc6abddc-4fa4-4ae8-97f4-9c566558b227 true +bc75ec01-01c4-463d-b64b-597c106d4eac +bc929bf3-06ac-408b-a17f-6ecdb28759a7 true +bca14431-d108-4ceb-831b-b6cedb7b1b22 true +bca1f5dd-414a-4570-9f0f-ed733bd170f6 +bcec2bc4-4e04-48cf-b52c-8e1fa2ac3875 +bcf5154c-180c-4366-836d-13aa6bfce980 +bd3b7896-2b3e-4c1a-a2b0-aca1c2ea23c1 +bd687828-857d-440d-836e-5230d3254b1c true +bddfe37f-fb3f-4a24-9ce9-181f250aa0b8 +bde29534-91ef-4817-a1f0-00a5cc9475bd true +be196f96-4e26-4c23-8241-b0a10a39e831 +be1ca606-c535-4751-b52e-c61cfe9b6c73 +be3057cb-9635-4a25-863f-b4e79cda75c3 +be6d80a5-e2df-4e99-9721-80097129bd8c true +be7a093e-d70a-4a23-9db3-bd95aca334a6 +beee46de-ddd6-4fe4-80bc-6f1f4b8fd42a true +bef4846a-ca76-4000-a98a-00a9d327107d +bf88c593-87b8-46d5-808e-8a332c5b50d5 true +c015fb2e-4062-417d-86be-681d98f8a13b true +c0381889-4401-4bc4-9056-641340fa7ec9 +c070e6de-9ae3-42fc-9cf3-023839ad0442 true +c086a87e-ec86-403c-8bc6-37a9b77924f0 true +c095cfbf-cd84-4681-ba67-7b3ed318186b true +c0a2c5cd-58d4-49ca-baa7-d5859abc680b +c1003a14-56be-43d0-9aed-41fb6b048273 +c1370f98-3833-49d3-b58a-21c581ac4ab2 +c14127c7-9943-471c-b5c8-ee9f0bb95bd7 +c1d430d5-32b8-4410-a1b5-6212b2c411ec +c2253c94-aa7a-4e88-8410-e528b08654f2 +c24b807b-174b-4e5f-91d2-93599fb21548 true +c25d3d58-1f6d-4004-85c4-3a162dbf48ec +c286cd77-3c89-446c-a91a-4769aa088be1 +c297cce1-5d10-48da-8ef9-e860ac55f387 +c2a32eca-f03c-4fe3-8c29-3b69c9798b5a true +c34d92cb-7086-40e9-b303-97e204d2ab4a true +c36767f9-a3d8-4f57-a370-4a088ea0b023 +c37a2b82-a495-4d05-8f8b-99a757d0b83d +c391f109-1259-47f6-979b-e72492709b66 +c3aaa4df-ead6-4a84-982d-3c28752e1070 true +c3ab4714-5eda-433e-b461-c1710b769476 +c3de8ca9-c03a-4689-8701-dd524884b5b5 +c3e98495-15f0-4040-b92c-31aa40235dcf +c47474bb-570e-45af-ad38-8f5352ee3774 +c4757dc4-d69a-4e2b-9b8d-61353a5b21e7 +c4e0ccb4-df43-4b20-873d-3a66faab42b1 +c4e31ea7-6ad0-42b7-8579-c3b7fcafa740 +c4e6e6bd-2135-47e8-81ae-7e1d6d5e7482 +c4f82d4f-f53a-4d32-bc78-67aa7786ed1e true +c52dbae3-07a4-4039-b04e-d79a86941c76 true +c52de404-7bbd-4b07-95a0-a66df22a3221 +c533b9cd-e56b-4e32-93b2-1189f37eb646 true +c557b460-3e1e-4ec4-9b4d-ea7bc0c8d64c +c5609af5-9f49-49f1-80ac-b18fccbc9720 true +c5f20b88-cbd1-4283-ad46-5b0bf31fa3ba +c6386600-4824-43b9-b366-4504f4e74d66 +c6442145-3aad-4b57-b2cb-b7eff0699959 +c6700fda-f009-496e-83ec-0532f34bfa92 +c6a0f008-4d97-4319-8099-7e0cb6708438 true +c6a3be9a-b061-4a5f-879c-e948aa895f3a +c6b44473-ebbc-48dc-8a42-44b7da0de840 +c6cb9c06-0c13-4cd7-8d20-ca7b99f7d141 +c761f0d9-c918-41b3-9131-856aad1f5b2b +c7e313a6-4659-4fad-9402-946879a91616 +c7f263ed-22de-479e-909b-f6ceaf5f2eb0 +c80ab964-cf3d-4e89-9232-8e787cb0def5 +c80ec189-5f9e-4049-922e-969d28b3bdde +c837974e-0093-438d-a822-19e97249f0dd true +c895019e-6471-423d-ba15-4f06ed628c7f +c8bbf035-8a01-4c57-934e-988a584c98b9 +c8c936bd-3e6e-4500-bbd5-051659c59309 +c8cb1ae0-6a31-4869-a396-78dd5dc7f302 +c8f123b5-8e57-4c9a-8770-bd4b4c2c064a +c8fd40e1-35fe-4c8b-af6b-730fd6fb5fab true +c92a4616-7215-4ba1-99df-40aea82eedc3 +c9445880-656a-4def-ad1b-2a4200d76c13 +c9647fa4-976a-4d9f-9be4-26b9c71f6eb1 +c98372ea-e9ae-4438-8715-b54ccdcc8f30 +c99c2510-b392-42ee-97e1-16f6a3b50978 +c9cd4daa-907f-490d-b3e5-a43eedd74c32 +ca45fe64-e7c9-48ea-bc02-3bc6a0b58e46 +cab21bd5-4314-4bb1-8b16-0967f8b08757 +cab53124-f25d-4b4a-a238-3d028e788b2e +cac06963-2da1-4935-a6c3-d6152132c7eb +cad24afe-7131-4158-9153-742c9fd16c9b +cb1c6a1f-e659-4fe2-9cfd-f28e776a4f06 +cb5c302b-35a6-4d8d-8d6f-72ad1a3ed1cd +cba58256-d558-48df-83d7-be5939d6dfab +cbafa157-0905-418d-983e-827cc88faa94 true +cbfd98df-8ac2-4497-988b-bc4d1c9c6095 +cc0b8f72-76dd-4eaa-84c1-c343982e18e2 +cc1e584e-6752-4bf3-8c51-ac0192b5df44 +cc372f1d-a83f-438e-8d8e-c346eb03fee6 +cc38b529-3137-4d89-804e-51c6bd60ca97 true +cc3a3e25-87b0-4012-869b-9905b31f8319 true +cc5fd0ac-6ce5-43a6-bb4a-67279156ee8c true +cc7e521d-4fa6-4215-942a-48f84ddf9a49 +cc8ade38-d9f1-4700-aad3-dc866f35d1a1 +cca1c9e8-c68f-4fe0-ae79-693399d8ee31 true +ccb56a74-40db-4d58-b0c4-cd485e8a7fe8 +ccb810a0-28a5-4248-92dd-7259a0fa0c22 +ccbda4c9-3ec5-46c1-a8d6-a215651dfb43 +ccbda933-c3d4-44bc-acfc-fd26bc234bb6 true +ccd5afad-8f2f-4305-b646-26137eff0a0d +ccd69a01-cd72-41e7-87b9-de8454411ee3 true +cce63ba1-abd2-45d1-9ba0-a2cd0fa9b00e +cce6dbc6-3a49-4f8c-b908-e6f11be81ac6 +ccf36cc5-5009-4fbc-8e8e-81f38d3195a7 +cd013d11-8609-41d9-a9c6-7fb33a499f51 +cd09f5d3-c846-41e7-a1ff-aabb55973663 true +cd2c3f78-ddb1-45f7-9939-987e070d9414 true +cd3d18de-96d3-4132-ae73-b80795a53ad0 +cd71ffde-9d7e-4c27-bc22-306f81cb46b7 +cdc96f77-a589-4ae4-85b6-c6d2c89487ba +cdcad0ef-8049-41c2-a8ee-21835323b05d true +cdcae470-3531-4548-a2a9-ba665fca8930 +cdfbf7e0-ad10-4c22-9ee4-888bb6bdd198 +ce1b7543-fee6-4267-a00d-daa5a5ea2861 +ce225925-36ec-4c1c-a327-c64a146bef46 +ce512c4f-3808-4ef6-9853-6f7d14e146f9 +ce7037fc-2614-46f4-a8db-40a2a0f9e3ac true +cea2e600-da22-42b8-aa08-f0e951ff35d5 +ceea3ce9-5ab6-4cbe-99b2-34ca9e6eb468 true +cf17f19b-9f66-4391-bd2c-55384690d9bf +cfced5ea-1e8e-4c85-8318-ef91a041f4e0 true +cfff2bee-7cf4-459f-8690-3638266a07e3 true +d000b381-5ba8-4a5b-84c7-f371e104eeaa +d012051d-65bd-4a59-b4cd-f454d6010888 +d05fad91-9a3d-4d8b-ba99-5a33fcc82081 +d083a1aa-9662-482a-8192-4709f904f16f +d0b22c1a-d0f6-4d83-bd3c-475a3acb0b83 +d0be6a2c-ebc5-405a-a2a5-006a5ea52c76 +d15e2267-a699-4f43-96a3-e98de7ace044 +d168f31c-483c-4340-a2f4-6a60cc8cab30 true +d17dfd97-60aa-4313-bcd7-f48935faa2e8 +d19385b9-bc04-4000-bddf-1d09425a2b4f true +d1ba146a-9503-472e-b668-3686ff275ef2 +d1c2f4a3-8af5-4ad4-bd0c-032b947d3c25 +d21af0cb-c5a1-401d-b605-a2e3ba3f46a1 +d2272949-75dd-4949-88fc-f0f157c83105 +d230ce54-d5fe-4983-9e91-519ce1249322 +d241d156-478f-47a0-989a-96a5fff36780 +d2663716-cdce-4d88-90f8-51fb5d59aa4d +d2a3d703-44e0-4dee-a8bf-9fc0457841ae true +d2c11b5d-93c6-4500-bc4c-8dc50057f362 +d2c4f328-e7a4-4d87-b180-9a0eee13d165 +d2d08017-f955-4e0e-baf0-2d5d37acc304 +d31bee09-f02b-44c5-aa15-a06343e0bf9f true +d355c55f-53b5-407e-8785-33e4e54bb3fe +d37a07ee-5d7b-41d0-bbf1-331277662c52 +d397d6be-6008-47bb-9f72-628fb5c41cb1 true +d3997124-9e16-4df3-8c60-f43f62bbf036 true +d3e6de0a-bfef-438a-b982-4818145651b9 true +d3f2c0c5-1f4c-4156-8cc0-e2d87db500ea true +d44741c1-d58a-495e-9f30-cc0f19fafc4f +d46e0f9c-5dd1-4e88-9a22-96ca436674d3 +d4aa494b-3328-48fa-9873-39be6536a437 +d4dc8d84-20e2-4053-b244-47549e74680c +d524b58b-90df-4d29-b4c8-308ac04dda2b +d56569d3-0a16-4968-9470-93d410916f11 +d5944a91-3bf9-4915-8512-0f1f50a8eb7c true +d5aa4b50-1ac3-48bc-90ee-7ad6bd78a078 true +d5ca4448-871c-4175-801f-38bb43cdb48d +d60c26c8-1b83-4de9-be17-81fca1ef2575 +d6c622c5-3662-4b29-9180-0b4055afa612 +d6f1073c-bf1c-4232-8ee2-5b92f0a32d8e +d702593f-4121-4ac3-bc3f-dae097c7a044 +d702f666-bb61-4d55-9579-e56efc3e0e91 true +d704d649-9e08-4502-a9c5-820f503dac9a true +d7c0876e-3031-4be6-aa2b-bbd8e2bd8884 true +d7eba5f5-103c-4442-9a7f-62c81a9dd9e5 +d7f76663-5195-4de4-bc43-ee61e467fee4 +d8534558-7fea-4dcb-a1fb-2981b725dd61 +d86d32ef-f50e-46d6-b434-fd03bc807cc4 +d8a42c84-6930-4b12-8c1f-9a492d290454 +d8cdd01f-c8b2-46fc-8f69-35da565e1b99 +d909c299-c622-4c39-829f-415621e09eb1 +d94160a6-7943-496d-8b66-172e9ec6804e +d97e5072-19d7-49bb-aaf1-f3a56816f3e3 +d9e59fa9-630b-4d97-aa71-4e03bd6fe1ad +d9ebe2d3-2a6e-4079-8f59-43a327e68ebf +d9fb7469-d50c-4492-8c1c-8bac1ccf81bd +da33cd96-8f15-47f1-aeaa-846234a6b9cd true +da412e67-cea6-4ad9-8c66-75bf7bd9d0f9 true +da438390-6d67-444a-be2e-354a21d30559 +da4c7a8d-c75b-4050-bbac-5edb50e48d4e true +da5de060-6e29-4379-9119-175b394f9bac +da98f3f0-236f-4c41-b123-09f1fdb5bfc6 true +daa0c0a8-91bc-41c5-9627-adad04a7b15b +dabbe6e8-3692-406f-8a2d-f81217eb6964 true +dac01aa1-44bc-4ff5-b123-4868f8f278ef +dac87128-5796-449e-85ea-2c1f8e09ba0b +dad4c0b8-0e02-470c-b7be-6477008d925f true +dae5b7a2-ad95-411c-9721-067d3c578262 +dae62ac4-666c-48f7-8587-ff9330a9e9e6 +dafa1e16-2149-4e76-a693-88d2e7e29a0a +db0a2a90-dc62-4e6e-9a46-88b9a3145ab8 +db16da7a-538a-4270-bd0e-1b4398a922a6 +db1788c0-f515-419f-ada6-834a37168001 +db17c5e1-6d1e-4b0b-a345-8dca770e7097 +db5a6739-388b-453c-99d0-58cfce24d5d5 +db8a9775-faff-49c4-8b35-74e92245f439 +dba78a31-21c5-4600-b18e-3e90237ab40b +dbbc9b03-4d76-4118-8637-185659ce2adb +dbc0cb40-5e33-468e-bc9e-2cf8ed602d01 true +dc2a8c49-ac89-487f-a22c-dc126a5c7ed1 +dc36732e-8054-4d6b-a860-e7c54f04a0bf +dc567aee-5073-493e-bec1-b6b0f4e160d5 +dca15405-893d-4468-84ad-574e54f45c22 true +dca8db58-f563-4a61-b747-94f5ec5bf135 +dd09736a-85a6-4c2c-979b-f2a7027308d1 +dd9a2411-d45c-4385-a6cc-650df658ae9e +ddccfc35-d71d-4e65-afd3-4cf2a21e607f +ddda9bf7-d645-40a2-8aed-eb9efa425d01 +de1c574a-a49c-41d2-b321-849c005c89e0 +de81cdd8-a6ed-4ed7-af7b-6ee027d0d5cf +de9bffc9-c6ee-4ad2-8055-1e0d0e48887d true +dea4f32f-0f51-4bf0-af67-e79b34674fac +dee72459-d0ba-4771-af17-f38f8396adb0 +deee1173-4f31-4784-8c63-d02e2a7b16ba +df471b44-cfeb-43fe-9488-8f85a03952fa +df529c86-f229-404c-8f0c-44e1b29e4e95 true +df54eb2c-59c6-46cf-9cc0-201e94acc07d +df6522a8-552f-4ba1-906c-139f58a0a85e true +df8a0f98-893b-400b-9da4-2514e50c0b24 true +dfa10a30-82d4-433b-9df5-b0d2291378ae true +dfa72495-2737-4fe8-9fcb-c799ab51ddcb +dfb67b9c-64b1-422f-887e-139a8f676fa4 +dfb6cb62-17e3-416b-9214-6ae6f432874a true +dfef1286-acc9-44ee-a736-b9676812235b +dffc6fbb-8592-4f0a-8190-1e64eb2bc634 true +e0010996-fa96-46be-9431-ebbe139c081c +e00ec480-db7a-4aab-84ab-7e5e7184bfdc true +e0ef5017-54ab-469b-9871-f706d4899465 +e0f2d024-4210-4f18-82ce-51755627e13f +e1309aae-d59f-44f0-b630-fc538bab9027 true +e134ed0a-49e0-4a0d-b09b-4e874f422e60 true +e16400e0-a6e6-459a-a16d-361a7b2ffd1d +e1a9f42d-103d-4e01-b98a-e70f98aa1c00 true +e1c6ba82-6bc7-4f15-b71c-c669cb78bcc9 true +e1e4e2e8-1c7b-4c67-bc7a-3940e6670357 true +e1f9bb17-3719-45d6-b4bd-3be88f17e429 true +e1faaf81-1761-4a4f-b01d-c179db8474f3 +e20067e7-f2fb-472b-811a-3d28a08bf31d +e2501541-dbeb-4e28-b4d8-598e963d6158 +e2b04908-769e-4916-bbc4-45ad7db85e02 true +e2eccb3c-0755-49c8-8e82-cc510481ed04 +e2fbc85e-dbc4-422e-9a11-0007d5c194ae +e3784666-7f48-455f-a137-e6262a617ccf +e3c2c69c-bbe5-4678-aa6c-3e5c7ff996f2 +e3c5df86-3d35-4dd8-a081-48632a3b67c8 +e3c94ca7-a487-468d-83f2-8068a592a774 +e3e4b2a1-6c40-4f42-a5b5-17b352209b40 true +e47d814f-5139-4339-a710-fe953efcfa74 +e484c47e-4da7-425b-a636-de512fcbc822 true +e50c31e2-5d52-4d45-9b8f-8ae3fdd26488 +e53c4c4c-976f-4cb9-95af-fb11c264cb72 +e53ec08c-9029-4b50-bc1b-a302878b17e6 +e562454d-7fbe-43a2-8046-bfc5b6ab043b +e5803192-2273-43bf-8acc-945cc13af70f true +e5c7eb1c-238e-4bf8-82bb-205c383f1517 true +e5d9ff02-57a4-4e05-b573-0dbc80e19958 +e5fa00b8-1779-467d-b881-757b6796d8b1 +e5ffe973-b166-4240-b5d5-17eaad746c4d +e6e9da2c-7684-41fb-9123-def8a7cb4dba true +e7098f3c-9dec-4aa9-9554-b3ad9a7d842a true +e73f4c39-965c-47ca-adb5-0544e20d45bc +e757ee7c-a50b-4575-b383-587e6f782bed +e780a3b4-6b8a-40a1-a9ba-5f95725399ea +e78c789e-7b81-49ea-b7e7-d17d8e8925aa +e79745a2-654a-46d6-a886-dd1d21acccf4 +e7b3ee1d-2d5e-4dd6-8ae0-ebddf36aee4d +e7d53c39-4adb-441d-a4e1-50c5f60fd3ef +e81ec213-328e-446b-9c33-ee80bc850d44 true +e83d90d6-c694-47fc-8120-e91546fdda8c true +e8ba3683-aa88-4800-a248-737d39ecb6c6 true +e8f05409-642b-419f-8475-28449c5f9569 true +e952a654-0605-409f-ac45-19d00d4b53c5 +e97e9781-3cb3-44b0-89a9-b704d456fcc2 +e9ab227b-2a64-4c48-a8a0-6c166bf4b970 +ea2f0657-1625-414b-9c7c-bd0b01a12eff true +ea91b2ad-37c8-4a4c-9178-11d7a5e4e75f true +eac7851d-1d61-4a35-9f08-c5da53335f7f +eace08fa-4018-40fa-a7fc-3daf10b2f21d +eae386dd-ef2b-4629-817f-6f8c44ce5817 true +eb2419c3-3b91-4d73-b4be-a40e759ab957 +eb2781af-72a5-4760-a9a7-20d212f56230 +eb511148-31f2-4292-a83f-d16d3be6631e +eba22453-8605-43d3-81f4-f703e58a230d +ebad9165-a532-4846-aff7-5038cd5c694f +ec3d7e48-c9f4-4f64-ada9-7e2891a62d1b +ec462f5c-2aaf-45fa-8c25-6f556349af00 +ec4d4a14-0934-4d28-ad20-64db8fe78d42 true +ec9ccc2d-1e6d-4ebc-b1e5-86d493d19eb4 +ed352845-d1af-40f9-afca-53503a6518d8 true +ed7a65e1-de6f-4c2f-bd47-aa78d0c2189c +edd41c4a-a5f3-4531-b506-d81c04e1ebc6 true +ee004b78-c05d-4c34-a9f3-290a9259d3a3 +ee6749cf-0622-4376-958c-229ebc0f9618 true +ee8905ec-84b9-4fa0-b1b5-0908206b7af9 true +eeaa2e25-739d-443e-ae83-b0ef04e2836f +eed7cb9f-7fe4-4f9d-bb4d-cdaeea6e144f +eeeed70d-3cc8-4b11-9f6e-ab44da414b90 true +ef079a39-9f59-4681-91fa-3d745c5eaec1 +ef10cf22-bdb9-44b5-bc9e-bb777ad8f090 +ef1c5dab-b3a9-444b-b825-8fe3d0248e02 true +ef9360c1-b9b4-4dd9-86de-f996cd22e7a1 +efbe6910-0629-46d6-bcfb-5880a1795742 +eff17621-813f-4e99-a307-118ac9273efc +f018a615-d5ff-4c3e-84f8-d4ebb1f342bc +f04f4b77-a9f2-4ce1-9d0d-6b43d39483f7 +f07f7d83-1fe3-4da4-9c33-86aae3ac67e5 +f08704a2-c1f5-44d5-a3f6-43c5608dc987 +f0a81ab6-10ea-4859-a064-b3b0dc662f24 +f177f59d-ecf0-41a6-b81b-a311eb760976 +f1a1aa8c-3779-4c42-8302-6006dcb95151 +f1b36b35-1bd3-4328-aea9-4c5b3a7c7404 true +f226c31e-37f4-4a4a-aa1f-aa400447a799 +f22fe03d-e1e9-40d5-9ff7-a5bb6e7a2d71 true +f27187d5-f8f0-4188-ae0c-f77ca494a9b3 +f27a59e9-c785-47d7-88a4-de7706bb4f92 +f28962fb-0214-4664-b9c3-7c67d4eb2e8d true +f29f4e3c-81f9-49b1-bc52-47a60a3076bb +f2fd6221-529b-42ca-a060-30db0996a398 true +f3047ad3-3524-4a48-a00f-603a65fa0891 +f310aaca-1c10-41fc-ac49-8430c4c23656 true +f314d140-929d-4ced-bbd9-c7f0204e99eb +f32094e2-ab74-4567-98c7-e35fe529393a +f3338731-d7b9-4c1f-8e3d-e73b5b756c7b +f3deb81d-79ca-4176-9b6d-bd96bb5b7a16 +f3e99f4d-5bbb-4f11-9519-4d3aa15e25cb true +f3fd705f-3f03-4f26-a477-f36e65fe7499 +f4077b1d-84fd-4489-b8b1-bbc9f5b61da2 +f475b875-3df0-49fc-b4f9-10b2ac1c40a6 +f4bba159-e786-4f15-8100-6bff75c03635 +f4e59f5d-12a4-4005-bdae-e157e145c119 +f5042323-b136-4c9d-bf57-0b7b70609495 +f51f7f2d-968a-4630-8e06-d6beb52b9f6b true +f55f863e-ffbf-4eb5-8f76-e2069098adcf +f568f305-55f4-4d1f-849f-6c8f371f5051 +f5dec406-3636-4ac4-894f-1035ef3ef570 +f5e51d8b-bda1-4093-b887-f2355ea171cd true +f5ebecec-331b-4d57-b353-3f50538cae2a +f5ed5cb8-adc0-424c-8371-ffb8e7cc9bc4 +f6514087-db2a-40d8-897a-21b5af7d590e true +f65d9eb9-e3d4-42fd-8d3b-168a6009f6ed true +f66efc52-e910-4d73-a9a7-fddc110c16c0 +f67e345d-7f27-48bf-afe4-00d79c3cf300 +f67f612c-6534-40d1-8e56-c056e886b98d +f6b9b783-c8af-44c2-8443-44b80e972686 true +f6bc9618-f245-4402-b69e-74d424736c4b true +f703fec9-ca54-4363-9ef0-4c4acdc37e0c true +f73ca57b-a00f-4217-9106-aedd26812b3d +f74957d5-eef8-4683-976f-c59ffabdeadc true +f7d2872f-bfc1-496e-ae92-a9d446aa72da true +f7d6ad2c-1f7e-47ef-adfc-16a5d4d0189a +f8043ec1-8313-45f9-a6dc-4cc1e5ad5f54 true +f8673366-8964-4c6c-aa9f-02e0920f21dc +f8683b38-42d3-4f32-a543-034c05dc07f6 +f8816dca-a41e-4ef9-8a17-b40fc7d707ad +f89b5e47-c8c3-4067-99ea-bd22f8932ee1 +f8b6ebd6-8d2a-4592-ad16-06772ad32cc7 +f8ddb5b8-70ff-4d5b-adf0-1752811f7be2 +f8f99d6a-94dc-4fca-8772-d0c3fe887a7f +f912a4cb-2eeb-41f3-a410-0ddc3eab9cdd +f9185eb8-d2e5-47aa-916b-a5ff24612ddf true +f92f93d5-3037-4692-a062-ee84b9961510 +f93aa334-4532-4774-b82c-6d040d3c7156 true +f93e975b-d07d-434d-b227-666e744892c7 +f95dafda-7f63-4ddb-acbf-e363dc901b1b +f95f8351-818a-4cb1-b26a-507c4baced47 true +f9aff2d6-fe39-4d17-90cf-917edf5d2bc8 +f9f5480b-27da-44fd-84a6-1ee4d4a55e36 +fa0ee43e-abc9-44cb-9248-3acc6c40e693 +fa4daa52-528e-45f5-8dd6-fe2a2c9fe8ff true +fa522b1a-3244-4203-966f-546dcfb0c62c true +fa5e8aa4-84e3-44b7-9dfe-326abdf02162 +fa7087f0-0140-4c94-b7c9-185b89c613c8 +fa786b6e-dbf4-4c51-a8c6-3006a029e8ba true +fa80db64-ef07-4b14-aa7d-e93585fd84e2 +faa80ca5-9c2a-40f4-aeec-26d982e663ae +faeaaf02-2ae6-4f5b-b729-61fd56d3946c +faf774dd-13ef-4b09-8cc9-3cf33a562482 true +fb176a2c-4b8f-40d7-bb81-f370c96c3f96 true +fb1df190-d719-42b7-8e9c-d7830f92882f +fb42fd3a-123d-4b2b-8a2f-d7e37af0a675 +fb55aea6-a5e7-4a55-a8df-967f504023f6 true +fb93624f-3d94-4c5d-9aba-10a3eaea0434 +fba16e5e-1b98-484e-8296-43a3995b9f75 +fbacdef8-f2e5-4a1c-8a01-b25442a5af89 true +fc348304-8ef3-4b3d-afcc-a66718315d89 +fc6dd798-66ac-45e2-a663-ea69b976c120 true +fcdd2cbf-917f-4429-a41b-48f8fe2a188b +fd325c37-4113-45e9-9e55-abd6b2ce6dbf +fd694334-1bcc-40f0-863a-017311fbaecb +fd75e2b2-c780-477c-a813-046ccec6fe51 +fd7a8bc2-9005-4230-a4f5-348f4b1f5b16 +fd7ef037-c576-498d-bdef-cec86fdd60b0 +fdaeb95c-9d66-4da9-87cf-eabbdc593007 +fdb161bd-ad0e-47fa-a5bf-f17ece98f28c +fdb49a29-6648-4dc5-bf56-ef948b00d885 +fdbbdafe-ed7e-4bd1-ae67-86430153a738 +fdc10bfc-2b5b-4a23-a93a-eaa85803bbc3 +fdce38d2-ae3a-474f-8024-52279c43b745 +fdfcdb50-716b-4452-a4c8-e03d3b8f89a3 +fe4c4989-ff06-446e-bf01-5c5d8f69f1eb +fe66bca4-eedf-42a9-8def-d29ef3dd01de +fe719bdd-cc9a-4c93-b2c9-982d051fceb1 true +fe7906dc-a882-488e-b576-2ef3486bcf79 +fe7c2db1-c229-4681-9373-e388dccb9e37 +fe7cb896-d81d-4b38-885c-4134ea64020e +fe97c34d-b787-4c3a-a972-821eb42a3442 true +fead13f8-4b89-4f33-950b-ee9254a36c4b true +fead2d6e-2979-4de7-8118-698906954d06 true +ff9df3bf-b8fe-4789-aa34-b205b5143a92 true +ffab80f3-741c-4c67-af21-4585f1c05d2f +ffb89ee2-c3d9-4fd5-b897-a0e0eb692143 +fff8bb82-5151-41f9-b150-801ed4957277 true diff --git a/fhir-server/src/test/resources/responses/ParserTest/lengthOfEncounter.csv b/fhir-server/src/test/resources/responses/ParserTest/lengthOfEncounter.csv deleted file mode 100644 index bbfdbb7687..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/lengthOfEncounter.csv +++ /dev/null @@ -1,217 +0,0 @@ -0134901a-4a69-4b39-92ea-d6f48ec83c6e,true -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,true -0250a217-a663-403b-a708-5d14eadf0c40,true -02b993c0-b358-481c-b0d0-0767387feb9f,true -0364073f-91d8-47a1-b8b0-107c6318a691,true -04945715-8ad5-4d8f-bfda-ae26662a3610,true -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,true -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,true -0af4a77e-892e-4b3f-9110-4c5224782250,true -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,false -102e16c5-4920-4dad-b142-0b280b2aacad,false -11dafd5c-85e7-4275-a147-89a63641e35e,true -12b212d8-bc6e-415a-93b0-594381726668,true -12df1bed-5472-4c48-8e88-2cbb3e5eab33,false -151e3048-66ad-4411-8753-677877e3bf0a,false -16a3408d-c927-4d02-a1ae-cad32419caab,false -16d280a2-c61f-487f-9034-22e884158969,false -17bcf2ea-d921-4715-91c5-6b15226b33d3,true -18ebf5ea-b0a2-4333-9e9c-40217de809ff,false -19502b5a-2031-487d-9d9c-2001f959408b,true -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,false -1db1c71b-9eeb-4a98-abc1-eb699b38510c,false -1f3443ee-d15e-4894-b18e-185cfadfcdea,false -1fd714a6-48b4-425a-99b3-ba5c1a995cce,true -202131ae-78bb-4104-89b0-fb90645760f6,true -20fbcb6e-d793-4b05-8e29-8ff82572b0db,true -21995497-0eb8-4c0b-8c23-e6a829f3d596,true -21c6e0fc-bf47-4f80-b1ff-85b940445bdf,true -224e538d-3622-4f5e-b772-211ed358d8de,false -23780032-f3bb-4b40-af2e-3fa6b1677376,true -24c4dd5b-748b-4165-a0cc-2867b76c2dc2,false -2514cdf0-1216-4c5e-bfa8-0c11c20c6b93,false -274a2e49-9170-4945-bca2-ab6ef4bded75,true -2830e99a-e6b0-411b-a051-7ea1e9f815d1,true -29d4e919-2bd2-44e6-bb8a-380a780f60ff,true -2aff9edd-def2-487a-b435-a162e11a303c,false -2bd766e5-60e4-4469-bb97-54f0503a1eb0,false -2bddd98c-86e3-4ae0-9dc6-87da16ab6267,false -2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5,true -2d2e07ec-e697-4384-a679-867e93740921,false -2de1f829-c9d2-4f22-bf39-536dcb82fc3e,true -2e8eb256-84c9-499a-8bf6-bb39504373e1,true -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,true -2ee2ab26-09fb-4e12-bf21-9fed3b4c38de,true -2fc75f11-2097-4e1e-8390-dbff3e844b7a,false -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,false -308eba53-29fa-489a-97dc-741b077841a7,true -30ae4a13-0cc6-4d12-a5dd-fa9288efcc09,false -31d4c712-75b1-4ecb-9d1a-ccc4b8453826,false -3397a796-894d-409c-97de-a9e6f3f88198,true -359cb842-c25b-429f-ba9b-d52f171e5631,true -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,true -374fd699-6b74-4500-9d60-2473c7e0364f,false -395019b6-84e8-4919-8b8b-ac64e4a6eade,false -39efc9b8-a75f-43a3-98a2-5bdc1c8207d2,true -3ddf46fe-b5be-456d-810f-ea6a7402236d,true -4148ac36-1dcb-4e96-89cd-355e7e8f919b,false -41d26b1c-57b9-408c-b955-bf0ee1db4809,true -4215b5d0-bdd9-4222-a04a-81bb637d60af,false -42a9a333-d09d-4b9e-af96-1f02af26bac3,true -42aa48c7-68cc-4bee-9730-cff29f0e36c5,false -4506aa76-9d0d-40e4-85bc-5735f74522a0,true -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,false -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,true -48cc2275-a478-4ade-b076-cf38e1d16017,true -498f4b18-bd4c-470d-9cde-2fca70ec8964,false -4a464bb5-9373-4f1b-9c03-053c64797114,true -4adcaf72-5241-4877-b61c-f34060576c50,false -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,false -4cd95073-6db3-4eb2-ac4b-0aacb182b352,false -4cfe72fe-21a0-4201-87b6-ef93695d2495,false -4db144d3-a596-4718-a50a-8ac9175ad386,false -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,false -4f14ca7f-5332-4263-a7b2-f0a838380309,false -4f342a71-dec3-403e-970b-e4c21b9eb98b,true -4f3d1e93-a28f-446e-91af-2baf0168b82b,true -50eac25a-3761-4de3-8a69-aae52e658300,true -51241857-a7a4-4517-96e3-21f797581f89,true -522e2abe-ad96-4d1a-b5a5-748faa997531,true -54c750aa-570a-4e8e-9a02-418781923e39,true -55f17f44-287f-41aa-a1bc-d1c0104af36e,true -56999896-e36b-4e63-a6b6-dbb85dfe1936,false -578cc35c-0982-4d72-a729-b304c026f075,true -5abe533b-ae5f-47ad-8746-f60caf7483c0,false -5cedf18a-fc44-4c39-a80f-2106a0d76934,true -5e1fe0f2-4517-462b-8287-7824f9a60f4f,true -5f64131b-d410-449a-9de6-35415919fec5,true -62059efc-7607-41c9-a3ba-70948f6a8a1d,true -634f0889-3a83-484a-bcc8-86f48e333c7b,true -67e5cf95-236b-4f75-abc5-034ca2966448,true -67fc12fc-bb9f-40f1-9051-127a788b3081,false -683714ad-5194-49e7-9b8f-4e306cf68ae1,true -6954c5c2-dd77-490a-9152-f1d78eff913d,true -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,false -6af73cae-2e4e-485f-a74d-6f4307eb3af3,true -6c84348c-5065-4e89-9412-bfda023683f2,false -6d47a2fe-3645-4c5c-bebe-1b822eff197f,false -6e187f47-91a1-4217-a185-31b6f01db225,true -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,true -721e4027-a080-40d8-bc4a-43cd33477611,true -72788e7f-1bf1-40b5-a1f1-27c669dc7278,true -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,true -749babeb-6883-4bd4-92a5-bec52769071c,true -75a09d4f-eef2-4404-a386-dfcb408ec9ed,true -777aa5f2-2a09-4aed-92ea-912694e28b48,true -79be3b69-23d8-4408-8f01-68d993e63b6c,true -7ac56d5a-32a7-4b40-a956-356e3006faed,false -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,false -7e7322a6-7912-4101-b3be-d134245a0626,false -7ea43fc6-b1f7-46be-a308-5ecb275a1081,false -7f410064-638a-4477-85c4-97fc2bb14a49,false -8089a9ac-9e71-4487-a231-f720e4bc8d3e,false -836b2e59-fb97-4014-ab0c-d5a5dc750969,true -85481b3f-c75e-40cd-bacd-b0afb79e893c,true -85c997bf-63d9-4ebb-8e0b-320de3dddc6c,true -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,false -86415df5-7e47-4637-9e09-aaad9e7628d1,true -8743e69b-17aa-44ff-b8fa-4f582665efc6,false -87b04a97-1d33-4548-aec9-3d2856070702,false -88b16960-bfff-41d0-81e4-9a4630834a00,true -89f260bb-68b4-4dec-91f4-d52e673e0ff7,false -8a1908f7-47cc-4f82-859c-781a193e9901,false -8bc7af32-e5ad-4849-ba4d-b446db833ab4,true -8be81657-8ba6-4ec5-8ff9-4809367096ea,false -8dc2ce26-aec7-43c0-9771-350af4257ad8,true -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,false -8f76d729-9f74-4cfd-be2a-8b033b568e18,true -900ce9fe-02d3-476b-902a-9467767ecdcf,false -922e2443-9cc5-421f-8310-9cd23f6e9f2d,false -96b2282d-1384-4cfb-9958-f009fb501626,true -97b42d67-8691-433d-8a31-dada076162ae,true -984067fc-3dfc-47b7-8ee0-4d9b27d43860,true -990c21ab-433b-4920-873e-f9dfc7103d9d,false -99fc3558-79f3-4d14-9aa1-ff63f621ecd9,false -9b2eb632-6f1a-4e97-912b-3c8378a1b11c,true -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,true -9bc516d4-7c82-4340-a90c-bb493f96fbe4,false -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,false -9ed8703e-3b40-424c-9e98-b3b404051f99,false -a1228f20-7c50-4d3a-b88c-2d277ca79d79,false -a221198d-000e-497b-8b70-bb4d37f7bbe1,true -a2c3ee1d-ec35-4b26-9bab-12de3f47d604,false -a6790cbd-8349-432e-a976-5dfc07451203,true -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,false -a6cb423a-7571-46df-83e2-ccc6820adb36,true -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,true -acb2329d-b1c3-45c3-ad28-91a09aa521e1,false -add6d754-a075-4693-a33a-c061c9a368ff,true -ae7ddaef-4911-418c-8401-d978617e145b,true -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,false -b00df815-7528-4967-bfe2-311130d91c21,true -b07fb98d-2da4-423a-83b4-7a673de93096,true -b0d337c5-3555-4817-ba59-4ceea5ad8a91,true -b1ccd55a-6b88-4240-b20c-ca893f36e23b,false -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,true -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,false -b6900c21-d310-4fb4-8b8f-176a09c91989,true -b7772635-1801-48e4-a442-f4aaa6860544,true -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,false -bac374c0-50e8-4a5c-947b-82a8e9a747a9,false -baf48725-f0f9-4357-a71d-b1104910deae,true -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,false -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,false -bf895c48-1d52-4780-8e9a-532d69356d28,true -bfc93cb7-e53a-49f7-b86e-81d6a13cfdea,true -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,true -c3ec1f2e-1411-41e4-8370-acd5a8f68cf4,false -c57d51a7-45de-41b9-92fc-efd0634effaf,false -c5c6eed5-aec6-4b8a-b689-adbb219c2267,false -c892b1d7-7485-407d-8883-54fb7fecebc1,false -c90aae7e-5704-4e13-8794-41ab377193bd,false -ca3e0486-fd6b-4a13-a741-3a47760b412d,false -ca7f269d-3794-4994-8c46-d8e9f2aa6378,false -cc56af6d-25c6-480e-9767-da02a55551da,false -cdb8493c-e3b0-447f-b16b-e58dd64660f3,false -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,true -cfcbe34a-edc5-4442-bc05-5927fa00336b,true -d05461d6-13bf-402c-890d-db6404933f7c,false -d080c116-681a-494a-a928-45924109b49d,false -d535b213-2abc-438f-aa6a-c06476d60b09,false -d6988116-3c63-44f8-9f94-9e9d51cc5a37,true -d79728e1-8834-4f4a-8edc-ce18aca18be6,true -d7a48a26-7731-4046-bf22-78f0b8084eb9,false -d90676d2-ce74-4867-a00f-6dbffa7c00be,true -d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2,false -db79f75d-af3c-4f82-b4e5-9b5d26598eef,false -def5fd23-2b74-4c64-85e9-fac27e626bb7,true -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,false -e0dfbc08-45ad-4a81-b648-7e65c066f673,true -e1040d58-53bc-4467-8101-ca53b772cede,true -e1c2ad9f-6f61-4f16-805a-2f405b63659a,true -e2387a81-5ff5-4daf-b2e8-881998d0d917,true -e26ae29a-213a-4f79-98c2-cc81cf2f451d,false -e363eb67-64c7-440f-93fd-5c8787c69a85,false -e4358f28-62a8-4e06-b2bd-cb00d3310349,false -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,false -e8c41168-a093-40eb-8baa-6802d24f554a,false -e8eba267-fecb-477e-9625-771fbcfc3cba,true -ede7745a-8f3e-4e20-9f16-33756be7ab6c,false -ee24bf89-81a3-4259-a382-86917f3829d4,false -f1293c5e-c997-4ba9-8bfd-438e7a840dc8,false -f18b08bd-40da-43c1-87ec-4ba23542635f,false -f19eefca-c1ad-4860-bdda-4674a631d464,false -f28931e5-1f35-4f9f-a02e-78398735ea67,false -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,false -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,true -f3aa491a-4dbd-4436-b674-18b2177dcf18,false -f3c2f842-6aa3-42c9-a0f3-895c40456868,true -f3eb0c38-2571-44e7-be39-732eb52cf1df,true -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,true -f77a8561-5adc-452a-ac9a-76273b6a6678,true -f883f2ca-d523-4c9f-86b2-0add137901de,false -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,false -fc771883-3bd6-46d9-9626-a130e1b902de,false -fcab9efe-fb05-42d6-b366-ce6db1cc4093,true -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,false -fce20464-ff98-4051-8714-6b9584e52740,true diff --git a/fhir-server/src/test/resources/responses/ParserTest/lengthOfEncounter.tsv b/fhir-server/src/test/resources/responses/ParserTest/lengthOfEncounter.tsv new file mode 100644 index 0000000000..7cfb41b4ef --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/lengthOfEncounter.tsv @@ -0,0 +1,217 @@ +0134901a-4a69-4b39-92ea-d6f48ec83c6e true +01ac1476-0c9c-4cd7-82a6-4ff526018e9a true +0250a217-a663-403b-a708-5d14eadf0c40 true +02b993c0-b358-481c-b0d0-0767387feb9f true +0364073f-91d8-47a1-b8b0-107c6318a691 true +04945715-8ad5-4d8f-bfda-ae26662a3610 true +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 true +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 true +0af4a77e-892e-4b3f-9110-4c5224782250 true +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd false +102e16c5-4920-4dad-b142-0b280b2aacad false +11dafd5c-85e7-4275-a147-89a63641e35e true +12b212d8-bc6e-415a-93b0-594381726668 true +12df1bed-5472-4c48-8e88-2cbb3e5eab33 false +151e3048-66ad-4411-8753-677877e3bf0a false +16a3408d-c927-4d02-a1ae-cad32419caab false +16d280a2-c61f-487f-9034-22e884158969 false +17bcf2ea-d921-4715-91c5-6b15226b33d3 true +18ebf5ea-b0a2-4333-9e9c-40217de809ff false +19502b5a-2031-487d-9d9c-2001f959408b true +1cb88e7a-3db6-4a88-9b68-b0e1492114bc false +1db1c71b-9eeb-4a98-abc1-eb699b38510c false +1f3443ee-d15e-4894-b18e-185cfadfcdea false +1fd714a6-48b4-425a-99b3-ba5c1a995cce true +202131ae-78bb-4104-89b0-fb90645760f6 true +20fbcb6e-d793-4b05-8e29-8ff82572b0db true +21995497-0eb8-4c0b-8c23-e6a829f3d596 true +21c6e0fc-bf47-4f80-b1ff-85b940445bdf true +224e538d-3622-4f5e-b772-211ed358d8de false +23780032-f3bb-4b40-af2e-3fa6b1677376 true +24c4dd5b-748b-4165-a0cc-2867b76c2dc2 false +2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 false +274a2e49-9170-4945-bca2-ab6ef4bded75 true +2830e99a-e6b0-411b-a051-7ea1e9f815d1 true +29d4e919-2bd2-44e6-bb8a-380a780f60ff true +2aff9edd-def2-487a-b435-a162e11a303c false +2bd766e5-60e4-4469-bb97-54f0503a1eb0 false +2bddd98c-86e3-4ae0-9dc6-87da16ab6267 false +2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5 true +2d2e07ec-e697-4384-a679-867e93740921 false +2de1f829-c9d2-4f22-bf39-536dcb82fc3e true +2e8eb256-84c9-499a-8bf6-bb39504373e1 true +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f true +2ee2ab26-09fb-4e12-bf21-9fed3b4c38de true +2fc75f11-2097-4e1e-8390-dbff3e844b7a false +2ffd6142-b75b-406f-bc06-cdb1c02eaefc false +308eba53-29fa-489a-97dc-741b077841a7 true +30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 false +31d4c712-75b1-4ecb-9d1a-ccc4b8453826 false +3397a796-894d-409c-97de-a9e6f3f88198 true +359cb842-c25b-429f-ba9b-d52f171e5631 true +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 true +374fd699-6b74-4500-9d60-2473c7e0364f false +395019b6-84e8-4919-8b8b-ac64e4a6eade false +39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 true +3ddf46fe-b5be-456d-810f-ea6a7402236d true +4148ac36-1dcb-4e96-89cd-355e7e8f919b false +41d26b1c-57b9-408c-b955-bf0ee1db4809 true +4215b5d0-bdd9-4222-a04a-81bb637d60af false +42a9a333-d09d-4b9e-af96-1f02af26bac3 true +42aa48c7-68cc-4bee-9730-cff29f0e36c5 false +4506aa76-9d0d-40e4-85bc-5735f74522a0 true +45b25ced-6eed-4fca-8ec1-b7d32ea13efe false +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 true +48cc2275-a478-4ade-b076-cf38e1d16017 true +498f4b18-bd4c-470d-9cde-2fca70ec8964 false +4a464bb5-9373-4f1b-9c03-053c64797114 true +4adcaf72-5241-4877-b61c-f34060576c50 false +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 false +4cd95073-6db3-4eb2-ac4b-0aacb182b352 false +4cfe72fe-21a0-4201-87b6-ef93695d2495 false +4db144d3-a596-4718-a50a-8ac9175ad386 false +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 false +4f14ca7f-5332-4263-a7b2-f0a838380309 false +4f342a71-dec3-403e-970b-e4c21b9eb98b true +4f3d1e93-a28f-446e-91af-2baf0168b82b true +50eac25a-3761-4de3-8a69-aae52e658300 true +51241857-a7a4-4517-96e3-21f797581f89 true +522e2abe-ad96-4d1a-b5a5-748faa997531 true +54c750aa-570a-4e8e-9a02-418781923e39 true +55f17f44-287f-41aa-a1bc-d1c0104af36e true +56999896-e36b-4e63-a6b6-dbb85dfe1936 false +578cc35c-0982-4d72-a729-b304c026f075 true +5abe533b-ae5f-47ad-8746-f60caf7483c0 false +5cedf18a-fc44-4c39-a80f-2106a0d76934 true +5e1fe0f2-4517-462b-8287-7824f9a60f4f true +5f64131b-d410-449a-9de6-35415919fec5 true +62059efc-7607-41c9-a3ba-70948f6a8a1d true +634f0889-3a83-484a-bcc8-86f48e333c7b true +67e5cf95-236b-4f75-abc5-034ca2966448 true +67fc12fc-bb9f-40f1-9051-127a788b3081 false +683714ad-5194-49e7-9b8f-4e306cf68ae1 true +6954c5c2-dd77-490a-9152-f1d78eff913d true +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 false +6af73cae-2e4e-485f-a74d-6f4307eb3af3 true +6c84348c-5065-4e89-9412-bfda023683f2 false +6d47a2fe-3645-4c5c-bebe-1b822eff197f false +6e187f47-91a1-4217-a185-31b6f01db225 true +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 true +721e4027-a080-40d8-bc4a-43cd33477611 true +72788e7f-1bf1-40b5-a1f1-27c669dc7278 true +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e true +749babeb-6883-4bd4-92a5-bec52769071c true +75a09d4f-eef2-4404-a386-dfcb408ec9ed true +777aa5f2-2a09-4aed-92ea-912694e28b48 true +79be3b69-23d8-4408-8f01-68d993e63b6c true +7ac56d5a-32a7-4b40-a956-356e3006faed false +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 false +7e7322a6-7912-4101-b3be-d134245a0626 false +7ea43fc6-b1f7-46be-a308-5ecb275a1081 false +7f410064-638a-4477-85c4-97fc2bb14a49 false +8089a9ac-9e71-4487-a231-f720e4bc8d3e false +836b2e59-fb97-4014-ab0c-d5a5dc750969 true +85481b3f-c75e-40cd-bacd-b0afb79e893c true +85c997bf-63d9-4ebb-8e0b-320de3dddc6c true +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b false +86415df5-7e47-4637-9e09-aaad9e7628d1 true +8743e69b-17aa-44ff-b8fa-4f582665efc6 false +87b04a97-1d33-4548-aec9-3d2856070702 false +88b16960-bfff-41d0-81e4-9a4630834a00 true +89f260bb-68b4-4dec-91f4-d52e673e0ff7 false +8a1908f7-47cc-4f82-859c-781a193e9901 false +8bc7af32-e5ad-4849-ba4d-b446db833ab4 true +8be81657-8ba6-4ec5-8ff9-4809367096ea false +8dc2ce26-aec7-43c0-9771-350af4257ad8 true +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 false +8f76d729-9f74-4cfd-be2a-8b033b568e18 true +900ce9fe-02d3-476b-902a-9467767ecdcf false +922e2443-9cc5-421f-8310-9cd23f6e9f2d false +96b2282d-1384-4cfb-9958-f009fb501626 true +97b42d67-8691-433d-8a31-dada076162ae true +984067fc-3dfc-47b7-8ee0-4d9b27d43860 true +990c21ab-433b-4920-873e-f9dfc7103d9d false +99fc3558-79f3-4d14-9aa1-ff63f621ecd9 false +9b2eb632-6f1a-4e97-912b-3c8378a1b11c true +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 true +9bc516d4-7c82-4340-a90c-bb493f96fbe4 false +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 false +9ed8703e-3b40-424c-9e98-b3b404051f99 false +a1228f20-7c50-4d3a-b88c-2d277ca79d79 false +a221198d-000e-497b-8b70-bb4d37f7bbe1 true +a2c3ee1d-ec35-4b26-9bab-12de3f47d604 false +a6790cbd-8349-432e-a976-5dfc07451203 true +a6b084fb-ada7-480a-9adc-f180d4eb1e2a false +a6cb423a-7571-46df-83e2-ccc6820adb36 true +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 true +acb2329d-b1c3-45c3-ad28-91a09aa521e1 false +add6d754-a075-4693-a33a-c061c9a368ff true +ae7ddaef-4911-418c-8401-d978617e145b true +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 false +b00df815-7528-4967-bfe2-311130d91c21 true +b07fb98d-2da4-423a-83b4-7a673de93096 true +b0d337c5-3555-4817-ba59-4ceea5ad8a91 true +b1ccd55a-6b88-4240-b20c-ca893f36e23b false +b1d4dc41-07ae-44db-ae17-1df86c5a62cf true +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa false +b6900c21-d310-4fb4-8b8f-176a09c91989 true +b7772635-1801-48e4-a442-f4aaa6860544 true +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 false +bac374c0-50e8-4a5c-947b-82a8e9a747a9 false +baf48725-f0f9-4357-a71d-b1104910deae true +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 false +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb false +bf895c48-1d52-4780-8e9a-532d69356d28 true +bfc93cb7-e53a-49f7-b86e-81d6a13cfdea true +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a true +c3ec1f2e-1411-41e4-8370-acd5a8f68cf4 false +c57d51a7-45de-41b9-92fc-efd0634effaf false +c5c6eed5-aec6-4b8a-b689-adbb219c2267 false +c892b1d7-7485-407d-8883-54fb7fecebc1 false +c90aae7e-5704-4e13-8794-41ab377193bd false +ca3e0486-fd6b-4a13-a741-3a47760b412d false +ca7f269d-3794-4994-8c46-d8e9f2aa6378 false +cc56af6d-25c6-480e-9767-da02a55551da false +cdb8493c-e3b0-447f-b16b-e58dd64660f3 false +cf67a8d4-48e2-4dc9-a521-6aabcff0330b true +cfcbe34a-edc5-4442-bc05-5927fa00336b true +d05461d6-13bf-402c-890d-db6404933f7c false +d080c116-681a-494a-a928-45924109b49d false +d535b213-2abc-438f-aa6a-c06476d60b09 false +d6988116-3c63-44f8-9f94-9e9d51cc5a37 true +d79728e1-8834-4f4a-8edc-ce18aca18be6 true +d7a48a26-7731-4046-bf22-78f0b8084eb9 false +d90676d2-ce74-4867-a00f-6dbffa7c00be true +d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2 false +db79f75d-af3c-4f82-b4e5-9b5d26598eef false +def5fd23-2b74-4c64-85e9-fac27e626bb7 true +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 false +e0dfbc08-45ad-4a81-b648-7e65c066f673 true +e1040d58-53bc-4467-8101-ca53b772cede true +e1c2ad9f-6f61-4f16-805a-2f405b63659a true +e2387a81-5ff5-4daf-b2e8-881998d0d917 true +e26ae29a-213a-4f79-98c2-cc81cf2f451d false +e363eb67-64c7-440f-93fd-5c8787c69a85 false +e4358f28-62a8-4e06-b2bd-cb00d3310349 false +e7f68d35-ae55-4fa4-b0be-0672a5a7186a false +e8c41168-a093-40eb-8baa-6802d24f554a false +e8eba267-fecb-477e-9625-771fbcfc3cba true +ede7745a-8f3e-4e20-9f16-33756be7ab6c false +ee24bf89-81a3-4259-a382-86917f3829d4 false +f1293c5e-c997-4ba9-8bfd-438e7a840dc8 false +f18b08bd-40da-43c1-87ec-4ba23542635f false +f19eefca-c1ad-4860-bdda-4674a631d464 false +f28931e5-1f35-4f9f-a02e-78398735ea67 false +f29330d0-5b32-4f75-b558-a1c7f05c0b4a false +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c true +f3aa491a-4dbd-4436-b674-18b2177dcf18 false +f3c2f842-6aa3-42c9-a0f3-895c40456868 true +f3eb0c38-2571-44e7-be39-732eb52cf1df true +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 true +f77a8561-5adc-452a-ac9a-76273b6a6678 true +f883f2ca-d523-4c9f-86b2-0add137901de false +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c false +fc771883-3bd6-46d9-9626-a130e1b902de false +fcab9efe-fb05-42d6-b366-ce6db1cc4093 true +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 false +fce20464-ff98-4051-8714-6b9584e52740 true diff --git a/fhir-server/src/test/resources/responses/ParserTest/testAggregationFollowingNestedWhere.csv b/fhir-server/src/test/resources/responses/ParserTest/testAggregationFollowingNestedWhere.csv deleted file mode 100644 index f20ac5b250..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testAggregationFollowingNestedWhere.csv +++ /dev/null @@ -1,9 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -9360820c-8602-4335-8b50-c88d627a0c20,female -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" diff --git a/fhir-server/src/test/resources/responses/ParserTest/testAggregationFollowingNestedWhere.tsv b/fhir-server/src/test/resources/responses/ParserTest/testAggregationFollowingNestedWhere.tsv new file mode 100644 index 0000000000..421f232c97 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testAggregationFollowingNestedWhere.tsv @@ -0,0 +1,9 @@ +121503c8-9564-4b48-9086-a22df717948e +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 female +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testBooleanOperatorWithLeftLiteral.csv b/fhir-server/src/test/resources/responses/ParserTest/testBooleanOperatorWithLeftLiteral.csv deleted file mode 100644 index 406939796a..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testBooleanOperatorWithLeftLiteral.csv +++ /dev/null @@ -1,9 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,false -2b36c1e2-bbe1-45ae-8124-4adad2677702,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,true -9360820c-8602-4335-8b50-c88d627a0c20,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -beff242e-580b-47c0-9844-c1a68c36c5bf,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testBooleanOperatorWithLeftLiteral.tsv b/fhir-server/src/test/resources/responses/ParserTest/testBooleanOperatorWithLeftLiteral.tsv new file mode 100644 index 0000000000..22919842a9 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testBooleanOperatorWithLeftLiteral.tsv @@ -0,0 +1,9 @@ +121503c8-9564-4b48-9086-a22df717948e false +2b36c1e2-bbe1-45ae-8124-4adad2677702 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d true +9360820c-8602-4335-8b50-c88d627a0c20 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +beff242e-580b-47c0-9844-c1a68c36c5bf false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperator.csv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperator.csv deleted file mode 100644 index 682c2ede9f..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperator.csv +++ /dev/null @@ -1,23 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,Gleichner915 -121503c8-9564-4b48-9086-a22df717948e,Ophelia894 -2b36c1e2-bbe1-45ae-8124-4adad2677702,Ulibarri312 -2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,Heller342 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Moyna -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Burchard -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Arianne -9360820c-8602-4335-8b50-c88d627a0c20,Oberbrunner298 -9360820c-8602-4335-8b50-c88d627a0c20,Karina848 -9360820c-8602-4335-8b50-c88d627a0c20,Wuckert783 -9360820c-8602-4335-8b50-c88d627a0c20,Karina848 -a7eb2ce7-1075-426c-addd-957b861b0e55,Botsford977 -a7eb2ce7-1075-426c-addd-957b861b0e55,Shirley182 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,MacGyver246 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Gilberto712 -beff242e-580b-47c0-9844-c1a68c36c5bf,Towne435 -beff242e-580b-47c0-9844-c1a68c36c5bf,Guy979 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Ebert178 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Su690 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperator.tsv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperator.tsv new file mode 100644 index 0000000000..6dc5162e15 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperator.tsv @@ -0,0 +1,23 @@ +121503c8-9564-4b48-9086-a22df717948e Gleichner915 +121503c8-9564-4b48-9086-a22df717948e Ophelia894 +2b36c1e2-bbe1-45ae-8124-4adad2677702 Ulibarri312 +2b36c1e2-bbe1-45ae-8124-4adad2677702 Pedro316 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 Heller342 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 Cherryl901 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Krajcik437 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Seymour882 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Moyna +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Burchard +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Arianne +9360820c-8602-4335-8b50-c88d627a0c20 Oberbrunner298 +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 +9360820c-8602-4335-8b50-c88d627a0c20 Wuckert783 +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 +a7eb2ce7-1075-426c-addd-957b861b0e55 Botsford977 +a7eb2ce7-1075-426c-addd-957b861b0e55 Shirley182 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 MacGyver246 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 Gilberto712 +beff242e-580b-47c0-9844-c1a68c36c5bf Towne435 +beff242e-580b-47c0-9844-c1a68c36c5bf Guy979 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 Ebert178 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 Su690 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithCodingLiterals.csv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithCodingLiterals.csv deleted file mode 100644 index 99ab3a98d9..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithCodingLiterals.csv +++ /dev/null @@ -1,9 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,false -2b36c1e2-bbe1-45ae-8124-4adad2677702,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -9360820c-8602-4335-8b50-c88d627a0c20,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -beff242e-580b-47c0-9844-c1a68c36c5bf,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithCodingLiterals.tsv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithCodingLiterals.tsv new file mode 100644 index 0000000000..43d9028dc5 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithCodingLiterals.tsv @@ -0,0 +1,9 @@ +121503c8-9564-4b48-9086-a22df717948e false +2b36c1e2-bbe1-45ae-8124-4adad2677702 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +9360820c-8602-4335-8b50-c88d627a0c20 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +beff242e-580b-47c0-9844-c1a68c36c5bf false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithComplexTypeAndNull.csv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithComplexTypeAndNull.csv deleted file mode 100644 index ed97a9aacb..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithComplexTypeAndNull.csv +++ /dev/null @@ -1,19 +0,0 @@ -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Seymour882 -beff242e-580b-47c0-9844-c1a68c36c5bf,Guy979 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Su690 -2b36c1e2-bbe1-45ae-8124-4adad2677702,Pedro316 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Gilberto712 -a7eb2ce7-1075-426c-addd-957b861b0e55,Shirley182 -121503c8-9564-4b48-9086-a22df717948e,Ophelia894 -9360820c-8602-4335-8b50-c88d627a0c20,Karina848 -9360820c-8602-4335-8b50-c88d627a0c20,Karina848 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -121503c8-9564-4b48-9086-a22df717948e,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithComplexTypeAndNull.tsv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithComplexTypeAndNull.tsv new file mode 100644 index 0000000000..e7f6ec916c --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithComplexTypeAndNull.tsv @@ -0,0 +1,19 @@ +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Seymour882 +beff242e-580b-47c0-9844-c1a68c36c5bf Guy979 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 Su690 +2b36c1e2-bbe1-45ae-8124-4adad2677702 Pedro316 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 Gilberto712 +a7eb2ce7-1075-426c-addd-957b861b0e55 Shirley182 +121503c8-9564-4b48-9086-a22df717948e Ophelia894 +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 Cherryl901 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +a7eb2ce7-1075-426c-addd-957b861b0e55 +121503c8-9564-4b48-9086-a22df717948e +9360820c-8602-4335-8b50-c88d627a0c20 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithDifferentlyTypedStringPaths.csv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithDifferentlyTypedStringPaths.csv deleted file mode 100644 index c9c4777c2c..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithDifferentlyTypedStringPaths.csv +++ /dev/null @@ -1,142 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,http://snomed.info/sct -121503c8-9564-4b48-9086-a22df717948e,http://snomed.info/sct -121503c8-9564-4b48-9086-a22df717948e,http://snomed.info/sct -121503c8-9564-4b48-9086-a22df717948e,http://snomed.info/sct -121503c8-9564-4b48-9086-a22df717948e,http://snomed.info/sct -121503c8-9564-4b48-9086-a22df717948e,http://snomed.info/sct -121503c8-9564-4b48-9086-a22df717948e,http://snomed.info/sct -121503c8-9564-4b48-9086-a22df717948e,http://snomed.info/sct -121503c8-9564-4b48-9086-a22df717948e,http://snomed.info/sct -121503c8-9564-4b48-9086-a22df717948e,http://snomed.info/sct -2b36c1e2-bbe1-45ae-8124-4adad2677702,http://snomed.info/sct -2b36c1e2-bbe1-45ae-8124-4adad2677702,http://snomed.info/sct -2b36c1e2-bbe1-45ae-8124-4adad2677702,http://snomed.info/sct -7001ad9c-34d2-4eb5-8165-5fdc2147f469,http://snomed.info/sct -7001ad9c-34d2-4eb5-8165-5fdc2147f469,http://snomed.info/sct -7001ad9c-34d2-4eb5-8165-5fdc2147f469,http://snomed.info/sct -7001ad9c-34d2-4eb5-8165-5fdc2147f469,http://snomed.info/sct -7001ad9c-34d2-4eb5-8165-5fdc2147f469,http://snomed.info/sct -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://snomed.info/sct -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://snomed.info/sct -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://snomed.info/sct -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://snomed.info/sct -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://snomed.info/sct -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://snomed.info/sct -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://snomed.info/sct -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -9360820c-8602-4335-8b50-c88d627a0c20,http://snomed.info/sct -a7eb2ce7-1075-426c-addd-957b861b0e55,http://snomed.info/sct -a7eb2ce7-1075-426c-addd-957b861b0e55,http://snomed.info/sct -a7eb2ce7-1075-426c-addd-957b861b0e55,http://snomed.info/sct -a7eb2ce7-1075-426c-addd-957b861b0e55,http://snomed.info/sct -a7eb2ce7-1075-426c-addd-957b861b0e55,http://snomed.info/sct -a7eb2ce7-1075-426c-addd-957b861b0e55,http://snomed.info/sct -a7eb2ce7-1075-426c-addd-957b861b0e55,http://snomed.info/sct -a7eb2ce7-1075-426c-addd-957b861b0e55,http://snomed.info/sct -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://snomed.info/sct -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://snomed.info/sct -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://snomed.info/sct -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://snomed.info/sct -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://snomed.info/sct -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://snomed.info/sct -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://snomed.info/sct -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://snomed.info/sct -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://snomed.info/sct -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://snomed.info/sct -beff242e-580b-47c0-9844-c1a68c36c5bf,http://snomed.info/sct -beff242e-580b-47c0-9844-c1a68c36c5bf,http://snomed.info/sct -beff242e-580b-47c0-9844-c1a68c36c5bf,http://snomed.info/sct -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://snomed.info/sct -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://snomed.info/sct -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://snomed.info/sct -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://snomed.info/sct -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://snomed.info/sct -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://snomed.info/sct -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://snomed.info/sct -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://snomed.info/sct -121503c8-9564-4b48-9086-a22df717948e,15777000 -121503c8-9564-4b48-9086-a22df717948e,271737000 -121503c8-9564-4b48-9086-a22df717948e,444814009 -121503c8-9564-4b48-9086-a22df717948e,10509002 -121503c8-9564-4b48-9086-a22df717948e,444470001 -121503c8-9564-4b48-9086-a22df717948e,68496003 -121503c8-9564-4b48-9086-a22df717948e,195662009 -121503c8-9564-4b48-9086-a22df717948e,713197008 -121503c8-9564-4b48-9086-a22df717948e,363406005 -121503c8-9564-4b48-9086-a22df717948e,403190006 -2b36c1e2-bbe1-45ae-8124-4adad2677702,65363002 -2b36c1e2-bbe1-45ae-8124-4adad2677702,38341003 -2b36c1e2-bbe1-45ae-8124-4adad2677702,10509002 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,40055000 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,38341003 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,162864005 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,38822007 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,70704007 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,44054006 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,271737000 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,302870006 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,237602007 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,368581000119106 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,444814009 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,444814009 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,195662009 -9360820c-8602-4335-8b50-c88d627a0c20,38341003 -9360820c-8602-4335-8b50-c88d627a0c20,162864005 -9360820c-8602-4335-8b50-c88d627a0c20,19169002 -9360820c-8602-4335-8b50-c88d627a0c20,72892002 -9360820c-8602-4335-8b50-c88d627a0c20,444814009 -9360820c-8602-4335-8b50-c88d627a0c20,444814009 -9360820c-8602-4335-8b50-c88d627a0c20,10509002 -9360820c-8602-4335-8b50-c88d627a0c20,444814009 -9360820c-8602-4335-8b50-c88d627a0c20,72892002 -9360820c-8602-4335-8b50-c88d627a0c20,156073000 -9360820c-8602-4335-8b50-c88d627a0c20,6072007 -9360820c-8602-4335-8b50-c88d627a0c20,236077008 -9360820c-8602-4335-8b50-c88d627a0c20,94260004 -9360820c-8602-4335-8b50-c88d627a0c20,55822004 -9360820c-8602-4335-8b50-c88d627a0c20,15777000 -9360820c-8602-4335-8b50-c88d627a0c20,444814009 -a7eb2ce7-1075-426c-addd-957b861b0e55,367498001 -a7eb2ce7-1075-426c-addd-957b861b0e55,162864005 -a7eb2ce7-1075-426c-addd-957b861b0e55,15777000 -a7eb2ce7-1075-426c-addd-957b861b0e55,271737000 -a7eb2ce7-1075-426c-addd-957b861b0e55,33737001 -a7eb2ce7-1075-426c-addd-957b861b0e55,444814009 -a7eb2ce7-1075-426c-addd-957b861b0e55,195662009 -a7eb2ce7-1075-426c-addd-957b861b0e55,444814009 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,403190006 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,10509002 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,65363002 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,284551006 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,65363002 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,10509002 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,53741008 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,22298006 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,22298006 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,399211009 -beff242e-580b-47c0-9844-c1a68c36c5bf,444814009 -beff242e-580b-47c0-9844-c1a68c36c5bf,444814009 -beff242e-580b-47c0-9844-c1a68c36c5bf,444814009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,15777000 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,19169002 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,271737000 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,195662009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,87433001 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,195662009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,444814009 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,195662009 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithDifferentlyTypedStringPaths.tsv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithDifferentlyTypedStringPaths.tsv new file mode 100644 index 0000000000..2781f9191d --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithDifferentlyTypedStringPaths.tsv @@ -0,0 +1,142 @@ +121503c8-9564-4b48-9086-a22df717948e http://snomed.info/sct +121503c8-9564-4b48-9086-a22df717948e http://snomed.info/sct +121503c8-9564-4b48-9086-a22df717948e http://snomed.info/sct +121503c8-9564-4b48-9086-a22df717948e http://snomed.info/sct +121503c8-9564-4b48-9086-a22df717948e http://snomed.info/sct +121503c8-9564-4b48-9086-a22df717948e http://snomed.info/sct +121503c8-9564-4b48-9086-a22df717948e http://snomed.info/sct +121503c8-9564-4b48-9086-a22df717948e http://snomed.info/sct +121503c8-9564-4b48-9086-a22df717948e http://snomed.info/sct +121503c8-9564-4b48-9086-a22df717948e http://snomed.info/sct +2b36c1e2-bbe1-45ae-8124-4adad2677702 http://snomed.info/sct +2b36c1e2-bbe1-45ae-8124-4adad2677702 http://snomed.info/sct +2b36c1e2-bbe1-45ae-8124-4adad2677702 http://snomed.info/sct +7001ad9c-34d2-4eb5-8165-5fdc2147f469 http://snomed.info/sct +7001ad9c-34d2-4eb5-8165-5fdc2147f469 http://snomed.info/sct +7001ad9c-34d2-4eb5-8165-5fdc2147f469 http://snomed.info/sct +7001ad9c-34d2-4eb5-8165-5fdc2147f469 http://snomed.info/sct +7001ad9c-34d2-4eb5-8165-5fdc2147f469 http://snomed.info/sct +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://snomed.info/sct +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://snomed.info/sct +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://snomed.info/sct +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://snomed.info/sct +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://snomed.info/sct +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://snomed.info/sct +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://snomed.info/sct +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +9360820c-8602-4335-8b50-c88d627a0c20 http://snomed.info/sct +a7eb2ce7-1075-426c-addd-957b861b0e55 http://snomed.info/sct +a7eb2ce7-1075-426c-addd-957b861b0e55 http://snomed.info/sct +a7eb2ce7-1075-426c-addd-957b861b0e55 http://snomed.info/sct +a7eb2ce7-1075-426c-addd-957b861b0e55 http://snomed.info/sct +a7eb2ce7-1075-426c-addd-957b861b0e55 http://snomed.info/sct +a7eb2ce7-1075-426c-addd-957b861b0e55 http://snomed.info/sct +a7eb2ce7-1075-426c-addd-957b861b0e55 http://snomed.info/sct +a7eb2ce7-1075-426c-addd-957b861b0e55 http://snomed.info/sct +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://snomed.info/sct +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://snomed.info/sct +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://snomed.info/sct +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://snomed.info/sct +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://snomed.info/sct +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://snomed.info/sct +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://snomed.info/sct +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://snomed.info/sct +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://snomed.info/sct +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://snomed.info/sct +beff242e-580b-47c0-9844-c1a68c36c5bf http://snomed.info/sct +beff242e-580b-47c0-9844-c1a68c36c5bf http://snomed.info/sct +beff242e-580b-47c0-9844-c1a68c36c5bf http://snomed.info/sct +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://snomed.info/sct +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://snomed.info/sct +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://snomed.info/sct +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://snomed.info/sct +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://snomed.info/sct +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://snomed.info/sct +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://snomed.info/sct +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://snomed.info/sct +121503c8-9564-4b48-9086-a22df717948e 15777000 +121503c8-9564-4b48-9086-a22df717948e 271737000 +121503c8-9564-4b48-9086-a22df717948e 444814009 +121503c8-9564-4b48-9086-a22df717948e 10509002 +121503c8-9564-4b48-9086-a22df717948e 444470001 +121503c8-9564-4b48-9086-a22df717948e 68496003 +121503c8-9564-4b48-9086-a22df717948e 195662009 +121503c8-9564-4b48-9086-a22df717948e 713197008 +121503c8-9564-4b48-9086-a22df717948e 363406005 +121503c8-9564-4b48-9086-a22df717948e 403190006 +2b36c1e2-bbe1-45ae-8124-4adad2677702 65363002 +2b36c1e2-bbe1-45ae-8124-4adad2677702 38341003 +2b36c1e2-bbe1-45ae-8124-4adad2677702 10509002 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 40055000 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 38341003 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 162864005 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 38822007 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 70704007 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 44054006 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 271737000 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 302870006 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 237602007 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 368581000119106 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 444814009 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 195662009 +9360820c-8602-4335-8b50-c88d627a0c20 38341003 +9360820c-8602-4335-8b50-c88d627a0c20 162864005 +9360820c-8602-4335-8b50-c88d627a0c20 19169002 +9360820c-8602-4335-8b50-c88d627a0c20 72892002 +9360820c-8602-4335-8b50-c88d627a0c20 444814009 +9360820c-8602-4335-8b50-c88d627a0c20 444814009 +9360820c-8602-4335-8b50-c88d627a0c20 10509002 +9360820c-8602-4335-8b50-c88d627a0c20 444814009 +9360820c-8602-4335-8b50-c88d627a0c20 72892002 +9360820c-8602-4335-8b50-c88d627a0c20 156073000 +9360820c-8602-4335-8b50-c88d627a0c20 6072007 +9360820c-8602-4335-8b50-c88d627a0c20 236077008 +9360820c-8602-4335-8b50-c88d627a0c20 94260004 +9360820c-8602-4335-8b50-c88d627a0c20 55822004 +9360820c-8602-4335-8b50-c88d627a0c20 15777000 +9360820c-8602-4335-8b50-c88d627a0c20 444814009 +a7eb2ce7-1075-426c-addd-957b861b0e55 367498001 +a7eb2ce7-1075-426c-addd-957b861b0e55 162864005 +a7eb2ce7-1075-426c-addd-957b861b0e55 15777000 +a7eb2ce7-1075-426c-addd-957b861b0e55 271737000 +a7eb2ce7-1075-426c-addd-957b861b0e55 33737001 +a7eb2ce7-1075-426c-addd-957b861b0e55 444814009 +a7eb2ce7-1075-426c-addd-957b861b0e55 195662009 +a7eb2ce7-1075-426c-addd-957b861b0e55 444814009 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 403190006 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 10509002 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 65363002 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 284551006 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 65363002 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 10509002 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 53741008 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 22298006 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 22298006 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 399211009 +beff242e-580b-47c0-9844-c1a68c36c5bf 444814009 +beff242e-580b-47c0-9844-c1a68c36c5bf 444814009 +beff242e-580b-47c0-9844-c1a68c36c5bf 444814009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 15777000 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 19169002 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 271737000 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 87433001 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 195662009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 444814009 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 195662009 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithResourcePaths.csv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithResourcePaths.csv deleted file mode 100644 index 69058d53fb..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithResourcePaths.csv +++ /dev/null @@ -1,142 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,06e446da-6d66-4d97-a457-4abf2a0d2e24 -121503c8-9564-4b48-9086-a22df717948e,248e951c-e0ce-4e4b-afae-6273fd109c9d -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,80787532-559e-4999-ac25-75c462ce4ef1 -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,e87fbe4b-74ef-44e4-8e36-70b2abb48895 -121503c8-9564-4b48-9086-a22df717948e,f41b6458-2358-43e7-ae34-d2a0fbe083d8 -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,171ad058-c082-46ab-b9cc-d7bba6e4e3cd -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,aa5a32b3-3993-4eb5-afce-489d0e7413cf -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,f1fd855c-802c-417a-ab7c-14a3c9daafc6 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,a16f311d-d6dc-486e-8eb0-7cd53a5333ee -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,bb9c4fc1-795a-4492-b065-1f497fe18bb2 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,e620d7ee-6cfe-4f04-ba06-1d0b39f7624d -9360820c-8602-4335-8b50-c88d627a0c20,1cea8432-f8a0-4746-ab5c-8f195fc07c55 -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,29386b12-9af9-4c21-9f82-858998b388bb -9360820c-8602-4335-8b50-c88d627a0c20,4dd7824b-4b41-4402-bc42-de016e1bfcd9 -9360820c-8602-4335-8b50-c88d627a0c20,549d3ef5-a05e-4b8e-acba-3d70f26a82f6 -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,6794fbaf-e715-4e22-bafa-b7e594550ff7 -9360820c-8602-4335-8b50-c88d627a0c20,77fc5f45-e51d-41e2-8356-daa27ebd8268 -9360820c-8602-4335-8b50-c88d627a0c20,8922ff1b-4bc5-41b4-8512-5aad1517e2eb -9360820c-8602-4335-8b50-c88d627a0c20,be4b757d-70f1-464c-bb29-6f9a0f6cb05e -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,258191b8-2b42-4473-9ba2-be0ba3561767 -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,8ddb74fc-46d5-4e94-a0ee-6761b292ae95 -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,e35f5823-4533-49e5-9652-79d733be6bef -a7eb2ce7-1075-426c-addd-957b861b0e55,eaf99c09-419d-4162-8417-5a9d7e042cd4 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,01fac610-c309-46f8-9a0f-6b0f55c9915a -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,abecd476-3911-4a6b-a1c6-b8ecea2fe63e -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,d12274a5-9e03-4c78-ae3e-6cc27e91d737 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,4025ceb3-04a6-41fb-8569-16a8dcce7ccc -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,5378726d-7f2b-4c83-9762-eaf385915fa7 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,5ef84858-0480-4a34-8f43-fc962fe627b2 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,ad679e19-3e17-4c67-8b0d-dd4f7d862207 -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,46d69851-eca3-42e2-b142-88c5578f6cff -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,9e598086-27bb-4e50-8988-9a40eb3c178f -121503c8-9564-4b48-9086-a22df717948e,b8eccdce-7261-4402-9aa0-7360ae6bf53b -121503c8-9564-4b48-9086-a22df717948e,d554ba19-e081-4979-9c0c-a72cd6e5e8ea -121503c8-9564-4b48-9086-a22df717948e,df98a2ea-8129-4d1a-9a9f-13a0292f6d1d -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,badbd940-9839-4472-aa66-3382d8823c80 -2b36c1e2-bbe1-45ae-8124-4adad2677702,db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,5300158c-92cb-40fe-bc14-a449d7b3c1c5 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,eb373264-da60-4e98-af7c-e3021fdd8d4b -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,031f1c86-16ec-42e1-a155-81456e778fed -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,061521f0-7f7e-41a5-ad35-b30fe958dea7 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8f3ad6ad-a457-484e-a455-f0711e77b2ba -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,1dcedab1-cf3c-46dc-859b-cc3fb63e8375 -9360820c-8602-4335-8b50-c88d627a0c20,251ddff9-1588-4790-8f40-cd6ab1d9b7fc -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,55cde383-2eb1-42d9-b5c6-698d6eade389 -9360820c-8602-4335-8b50-c88d627a0c20,66b38727-96ea-43ad-bff7-5f4df5d779a9 -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,c39927f7-d23a-475c-b325-c1de4b51e280 -9360820c-8602-4335-8b50-c88d627a0c20,c892bf2f-a7bc-407e-9508-c778a9b8761c -9360820c-8602-4335-8b50-c88d627a0c20,eaee8845-e4ed-42e1-9098-99b3f7e14dc3 -9360820c-8602-4335-8b50-c88d627a0c20,f1dd0d5f-c410-484b-984c-2ba38cee79ba -a7eb2ce7-1075-426c-addd-957b861b0e55,1146cf52-573f-4667-97c3-abf235f9a83b -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,31925a68-7a48-412c-b4f8-aef92498dda1 -a7eb2ce7-1075-426c-addd-957b861b0e55,38171e22-b35d-4161-be15-80243be37b99 -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,b77b04ef-5eda-418c-a6fb-528a2d0a171a -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,1452c50c-b0b9-472c-afb7-2f9e5a3c4717 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,21372b39-2be0-4d0d-85c4-5d7e250d8f78 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,31393667-dd7f-4c94-90c3-6cde75c67d3e -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,39170d67-8205-4636-84d7-d8575115d14c -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,da8db730-f051-42d0-a2db-a3577d96e9bd -beff242e-580b-47c0-9844-c1a68c36c5bf,0447de38-1f2a-411c-9fd2-a9c62ab1f221 -beff242e-580b-47c0-9844-c1a68c36c5bf,7ed34fb1-0bbf-4c41-a101-1316ec483aa7 -beff242e-580b-47c0-9844-c1a68c36c5bf,c879c300-7fdf-4b53-aa6a-a2b4a266b30c -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,475461a2-3bd2-43fd-a5aa-7ce424203ae8 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,6464e20b-55ec-4685-be3e-55bc3b9602d4 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,7d18555a-54c9-4c0a-bf3b-6305d0392a2a -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithResourcePaths.tsv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithResourcePaths.tsv new file mode 100644 index 0000000000..d4dd2d0902 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithResourcePaths.tsv @@ -0,0 +1,142 @@ +121503c8-9564-4b48-9086-a22df717948e 06e446da-6d66-4d97-a457-4abf2a0d2e24 +121503c8-9564-4b48-9086-a22df717948e 248e951c-e0ce-4e4b-afae-6273fd109c9d +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 80787532-559e-4999-ac25-75c462ce4ef1 +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e e87fbe4b-74ef-44e4-8e36-70b2abb48895 +121503c8-9564-4b48-9086-a22df717948e f41b6458-2358-43e7-ae34-d2a0fbe083d8 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 171ad058-c082-46ab-b9cc-d7bba6e4e3cd +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 aa5a32b3-3993-4eb5-afce-489d0e7413cf +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 f1fd855c-802c-417a-ab7c-14a3c9daafc6 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d a16f311d-d6dc-486e-8eb0-7cd53a5333ee +8ee183e2-b3c0-4151-be94-b945d6aa8c6d b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc +8ee183e2-b3c0-4151-be94-b945d6aa8c6d bb9c4fc1-795a-4492-b065-1f497fe18bb2 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d e620d7ee-6cfe-4f04-ba06-1d0b39f7624d +9360820c-8602-4335-8b50-c88d627a0c20 1cea8432-f8a0-4746-ab5c-8f195fc07c55 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 29386b12-9af9-4c21-9f82-858998b388bb +9360820c-8602-4335-8b50-c88d627a0c20 4dd7824b-4b41-4402-bc42-de016e1bfcd9 +9360820c-8602-4335-8b50-c88d627a0c20 549d3ef5-a05e-4b8e-acba-3d70f26a82f6 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 6794fbaf-e715-4e22-bafa-b7e594550ff7 +9360820c-8602-4335-8b50-c88d627a0c20 77fc5f45-e51d-41e2-8356-daa27ebd8268 +9360820c-8602-4335-8b50-c88d627a0c20 8922ff1b-4bc5-41b4-8512-5aad1517e2eb +9360820c-8602-4335-8b50-c88d627a0c20 be4b757d-70f1-464c-bb29-6f9a0f6cb05e +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 258191b8-2b42-4473-9ba2-be0ba3561767 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 8ddb74fc-46d5-4e94-a0ee-6761b292ae95 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 e35f5823-4533-49e5-9652-79d733be6bef +a7eb2ce7-1075-426c-addd-957b861b0e55 eaf99c09-419d-4162-8417-5a9d7e042cd4 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 01fac610-c309-46f8-9a0f-6b0f55c9915a +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 abecd476-3911-4a6b-a1c6-b8ecea2fe63e +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 d12274a5-9e03-4c78-ae3e-6cc27e91d737 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 4025ceb3-04a6-41fb-8569-16a8dcce7ccc +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 5378726d-7f2b-4c83-9762-eaf385915fa7 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 5ef84858-0480-4a34-8f43-fc962fe627b2 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 ad679e19-3e17-4c67-8b0d-dd4f7d862207 +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 46d69851-eca3-42e2-b142-88c5578f6cff +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 9e598086-27bb-4e50-8988-9a40eb3c178f +121503c8-9564-4b48-9086-a22df717948e b8eccdce-7261-4402-9aa0-7360ae6bf53b +121503c8-9564-4b48-9086-a22df717948e d554ba19-e081-4979-9c0c-a72cd6e5e8ea +121503c8-9564-4b48-9086-a22df717948e df98a2ea-8129-4d1a-9a9f-13a0292f6d1d +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +2b36c1e2-bbe1-45ae-8124-4adad2677702 badbd940-9839-4472-aa66-3382d8823c80 +2b36c1e2-bbe1-45ae-8124-4adad2677702 db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 5300158c-92cb-40fe-bc14-a449d7b3c1c5 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 eb373264-da60-4e98-af7c-e3021fdd8d4b +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 031f1c86-16ec-42e1-a155-81456e778fed +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 061521f0-7f7e-41a5-ad35-b30fe958dea7 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8f3ad6ad-a457-484e-a455-f0711e77b2ba +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 1dcedab1-cf3c-46dc-859b-cc3fb63e8375 +9360820c-8602-4335-8b50-c88d627a0c20 251ddff9-1588-4790-8f40-cd6ab1d9b7fc +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 55cde383-2eb1-42d9-b5c6-698d6eade389 +9360820c-8602-4335-8b50-c88d627a0c20 66b38727-96ea-43ad-bff7-5f4df5d779a9 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 c39927f7-d23a-475c-b325-c1de4b51e280 +9360820c-8602-4335-8b50-c88d627a0c20 c892bf2f-a7bc-407e-9508-c778a9b8761c +9360820c-8602-4335-8b50-c88d627a0c20 eaee8845-e4ed-42e1-9098-99b3f7e14dc3 +9360820c-8602-4335-8b50-c88d627a0c20 f1dd0d5f-c410-484b-984c-2ba38cee79ba +a7eb2ce7-1075-426c-addd-957b861b0e55 1146cf52-573f-4667-97c3-abf235f9a83b +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 31925a68-7a48-412c-b4f8-aef92498dda1 +a7eb2ce7-1075-426c-addd-957b861b0e55 38171e22-b35d-4161-be15-80243be37b99 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 b77b04ef-5eda-418c-a6fb-528a2d0a171a +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 1452c50c-b0b9-472c-afb7-2f9e5a3c4717 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 21372b39-2be0-4d0d-85c4-5d7e250d8f78 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 31393667-dd7f-4c94-90c3-6cde75c67d3e +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 39170d67-8205-4636-84d7-d8575115d14c +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 da8db730-f051-42d0-a2db-a3577d96e9bd +beff242e-580b-47c0-9844-c1a68c36c5bf 0447de38-1f2a-411c-9fd2-a9c62ab1f221 +beff242e-580b-47c0-9844-c1a68c36c5bf 7ed34fb1-0bbf-4c41-a101-1316ec483aa7 +beff242e-580b-47c0-9844-c1a68c36c5bf c879c300-7fdf-4b53-aa6a-a2b4a266b30c +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 475461a2-3bd2-43fd-a5aa-7ce424203ae8 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 6464e20b-55ec-4685-be3e-55bc3b9602d4 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 7d18555a-54c9-4c0a-bf3b-6305d0392a2a +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoLiterals.csv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoLiterals.csv deleted file mode 100644 index d356d91dcd..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoLiterals.csv +++ /dev/null @@ -1,18 +0,0 @@ -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,1 -beff242e-580b-47c0-9844-c1a68c36c5bf,1 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,1 -2b36c1e2-bbe1-45ae-8124-4adad2677702,1 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,1 -a7eb2ce7-1075-426c-addd-957b861b0e55,1 -121503c8-9564-4b48-9086-a22df717948e,1 -9360820c-8602-4335-8b50-c88d627a0c20,1 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,1 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,2 -beff242e-580b-47c0-9844-c1a68c36c5bf,2 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,2 -2b36c1e2-bbe1-45ae-8124-4adad2677702,2 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,2 -a7eb2ce7-1075-426c-addd-957b861b0e55,2 -121503c8-9564-4b48-9086-a22df717948e,2 -9360820c-8602-4335-8b50-c88d627a0c20,2 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,2 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoLiterals.tsv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoLiterals.tsv new file mode 100644 index 0000000000..c637dd191b --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoLiterals.tsv @@ -0,0 +1,18 @@ +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 1 +beff242e-580b-47c0-9844-c1a68c36c5bf 1 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 1 +2b36c1e2-bbe1-45ae-8124-4adad2677702 1 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 1 +a7eb2ce7-1075-426c-addd-957b861b0e55 1 +121503c8-9564-4b48-9086-a22df717948e 1 +9360820c-8602-4335-8b50-c88d627a0c20 1 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 1 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 2 +beff242e-580b-47c0-9844-c1a68c36c5bf 2 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 2 +2b36c1e2-bbe1-45ae-8124-4adad2677702 2 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 2 +a7eb2ce7-1075-426c-addd-957b861b0e55 2 +121503c8-9564-4b48-9086-a22df717948e 2 +9360820c-8602-4335-8b50-c88d627a0c20 2 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 2 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoUntypedResourcePaths.csv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoUntypedResourcePaths.csv deleted file mode 100644 index f74f89ccf9..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoUntypedResourcePaths.csv +++ /dev/null @@ -1,174 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -121503c8-9564-4b48-9086-a22df717948e,121503c8-9564-4b48-9086-a22df717948e -2b36c1e2-bbe1-45ae-8124-4adad2677702,2b36c1e2-bbe1-45ae-8124-4adad2677702 -2b36c1e2-bbe1-45ae-8124-4adad2677702,2b36c1e2-bbe1-45ae-8124-4adad2677702 -2b36c1e2-bbe1-45ae-8124-4adad2677702,2b36c1e2-bbe1-45ae-8124-4adad2677702 -2b36c1e2-bbe1-45ae-8124-4adad2677702,2b36c1e2-bbe1-45ae-8124-4adad2677702 -2b36c1e2-bbe1-45ae-8124-4adad2677702,2b36c1e2-bbe1-45ae-8124-4adad2677702 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,7001ad9c-34d2-4eb5-8165-5fdc2147f469 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,7001ad9c-34d2-4eb5-8165-5fdc2147f469 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,7001ad9c-34d2-4eb5-8165-5fdc2147f469 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,7001ad9c-34d2-4eb5-8165-5fdc2147f469 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,7001ad9c-34d2-4eb5-8165-5fdc2147f469 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,7001ad9c-34d2-4eb5-8165-5fdc2147f469 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,7001ad9c-34d2-4eb5-8165-5fdc2147f469 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,7001ad9c-34d2-4eb5-8165-5fdc2147f469 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,7001ad9c-34d2-4eb5-8165-5fdc2147f469 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,7001ad9c-34d2-4eb5-8165-5fdc2147f469 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,7001ad9c-34d2-4eb5-8165-5fdc2147f469 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,8ee183e2-b3c0-4151-be94-b945d6aa8c6d -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -9360820c-8602-4335-8b50-c88d627a0c20,9360820c-8602-4335-8b50-c88d627a0c20 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -a7eb2ce7-1075-426c-addd-957b861b0e55,a7eb2ce7-1075-426c-addd-957b861b0e55 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 -beff242e-580b-47c0-9844-c1a68c36c5bf,beff242e-580b-47c0-9844-c1a68c36c5bf -beff242e-580b-47c0-9844-c1a68c36c5bf,beff242e-580b-47c0-9844-c1a68c36c5bf -beff242e-580b-47c0-9844-c1a68c36c5bf,beff242e-580b-47c0-9844-c1a68c36c5bf -beff242e-580b-47c0-9844-c1a68c36c5bf,beff242e-580b-47c0-9844-c1a68c36c5bf -beff242e-580b-47c0-9844-c1a68c36c5bf,beff242e-580b-47c0-9844-c1a68c36c5bf -beff242e-580b-47c0-9844-c1a68c36c5bf,beff242e-580b-47c0-9844-c1a68c36c5bf -beff242e-580b-47c0-9844-c1a68c36c5bf,beff242e-580b-47c0-9844-c1a68c36c5bf -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoUntypedResourcePaths.tsv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoUntypedResourcePaths.tsv new file mode 100644 index 0000000000..31d226faaa --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithTwoUntypedResourcePaths.tsv @@ -0,0 +1,174 @@ +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 121503c8-9564-4b48-9086-a22df717948e +2b36c1e2-bbe1-45ae-8124-4adad2677702 2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 9360820c-8602-4335-8b50-c88d627a0c20 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithWhereFunction.csv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithWhereFunction.csv deleted file mode 100644 index 27765c9e59..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithWhereFunction.csv +++ /dev/null @@ -1,9 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,1959-09-27 -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" diff --git a/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithWhereFunction.tsv b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithWhereFunction.tsv new file mode 100644 index 0000000000..948f42576b --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testCombineOperatorWithWhereFunction.tsv @@ -0,0 +1,9 @@ +121503c8-9564-4b48-9086-a22df717948e 1959-09-27 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testComplexExtensionsOnComplexPath.csv b/fhir-server/src/test/resources/responses/ParserTest/testComplexExtensionsOnComplexPath.csv deleted file mode 100644 index d333ebdc1a..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testComplexExtensionsOnComplexPath.csv +++ /dev/null @@ -1,13 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,42.409270 -121503c8-9564-4b48-9086-a22df717948e,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,42.364178 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -9360820c-8602-4335-8b50-c88d627a0c20,42.347444 -9360820c-8602-4335-8b50-c88d627a0c20,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,42.391383 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" diff --git a/fhir-server/src/test/resources/responses/ParserTest/testComplexExtensionsOnComplexPath.tsv b/fhir-server/src/test/resources/responses/ParserTest/testComplexExtensionsOnComplexPath.tsv new file mode 100644 index 0000000000..74f0381c27 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testComplexExtensionsOnComplexPath.tsv @@ -0,0 +1,13 @@ +121503c8-9564-4b48-9086-a22df717948e 42.409270 +121503c8-9564-4b48-9086-a22df717948e +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 42.364178 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 42.347444 +9360820c-8602-4335-8b50-c88d627a0c20 +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 42.391383 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithLanguage.csv b/fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithLanguage.csv deleted file mode 100644 index a2c3f15c92..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithLanguage.csv +++ /dev/null @@ -1,71 +0,0 @@ -01fac610-c309-46f8-9a0f-6b0f55c9915a, -031f1c86-16ec-42e1-a155-81456e778fed,Viral sinusitis : disorder -0447de38-1f2a-411c-9fd2-a9c62ab1f221,Viral sinusitis : disorder -061521f0-7f7e-41a5-ad35-b30fe958dea7,Acute viral pharyngitis : disorder -06e446da-6d66-4d97-a457-4abf2a0d2e24, -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,Acute viral pharyngitis : disorder -1146cf52-573f-4667-97c3-abf235f9a83b, -1452c50c-b0b9-472c-afb7-2f9e5a3c4717, -171ad058-c082-46ab-b9cc-d7bba6e4e3cd, -1cea8432-f8a0-4746-ab5c-8f195fc07c55, -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,Viral sinusitis : disorder -21372b39-2be0-4d0d-85c4-5d7e250d8f78, -248e951c-e0ce-4e4b-afae-6273fd109c9d, -251ddff9-1588-4790-8f40-cd6ab1d9b7fc, -258191b8-2b42-4473-9ba2-be0ba3561767, -29386b12-9af9-4c21-9f82-858998b388bb, -31393667-dd7f-4c94-90c3-6cde75c67d3e, -31925a68-7a48-412c-b4f8-aef92498dda1,Viral sinusitis : disorder -38171e22-b35d-4161-be15-80243be37b99,Acute viral pharyngitis : disorder -39170d67-8205-4636-84d7-d8575115d14c, -4025ceb3-04a6-41fb-8569-16a8dcce7ccc, -46d69851-eca3-42e2-b142-88c5578f6cff,Viral sinusitis : disorder -475461a2-3bd2-43fd-a5aa-7ce424203ae8,Viral sinusitis : disorder -4dd7824b-4b41-4402-bc42-de016e1bfcd9, -5300158c-92cb-40fe-bc14-a449d7b3c1c5, -5378726d-7f2b-4c83-9762-eaf385915fa7, -549d3ef5-a05e-4b8e-acba-3d70f26a82f6, -55cde383-2eb1-42d9-b5c6-698d6eade389,Viral sinusitis : disorder -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325, -5ef84858-0480-4a34-8f43-fc962fe627b2, -6464e20b-55ec-4685-be3e-55bc3b9602d4,Acute viral pharyngitis : disorder -66b38727-96ea-43ad-bff7-5f4df5d779a9,Viral sinusitis : disorder -6794fbaf-e715-4e22-bafa-b7e594550ff7, -77fc5f45-e51d-41e2-8356-daa27ebd8268, -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,Acute viral pharyngitis : disorder -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,Viral sinusitis : disorder -80787532-559e-4999-ac25-75c462ce4ef1, -8922ff1b-4bc5-41b4-8512-5aad1517e2eb, -8ddb74fc-46d5-4e94-a0ee-6761b292ae95, -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6, -8f3ad6ad-a457-484e-a455-f0711e77b2ba,Viral sinusitis : disorder -9e598086-27bb-4e50-8988-9a40eb3c178f,Acute viral pharyngitis : disorder -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc, -a16f311d-d6dc-486e-8eb0-7cd53a5333ee, -aa5a32b3-3993-4eb5-afce-489d0e7413cf, -abecd476-3911-4a6b-a1c6-b8ecea2fe63e, -ad679e19-3e17-4c67-8b0d-dd4f7d862207, -b77b04ef-5eda-418c-a6fb-528a2d0a171a,Viral sinusitis : disorder -b8eccdce-7261-4402-9aa0-7360ae6bf53b, -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc, -badbd940-9839-4472-aa66-3382d8823c80, -bb9c4fc1-795a-4492-b065-1f497fe18bb2, -be4b757d-70f1-464c-bb29-6f9a0f6cb05e, -c39927f7-d23a-475c-b325-c1de4b51e280, -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,Viral sinusitis : disorder -c892bf2f-a7bc-407e-9508-c778a9b8761c, -d12274a5-9e03-4c78-ae3e-6cc27e91d737, -d554ba19-e081-4979-9c0c-a72cd6e5e8ea, -da8db730-f051-42d0-a2db-a3577d96e9bd, -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82, -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d, -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76, -e35f5823-4533-49e5-9652-79d733be6bef, -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d, -e87fbe4b-74ef-44e4-8e36-70b2abb48895, -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,Viral sinusitis : disorder -eaf99c09-419d-4162-8417-5a9d7e042cd4, -eb373264-da60-4e98-af7c-e3021fdd8d4b, -f1dd0d5f-c410-484b-984c-2ba38cee79ba, -f1fd855c-802c-417a-ab7c-14a3c9daafc6, -f41b6458-2358-43e7-ae34-d2a0fbe083d8, diff --git a/fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithLanguage.tsv b/fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithLanguage.tsv new file mode 100644 index 0000000000..610517722a --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithLanguage.tsv @@ -0,0 +1,71 @@ +01fac610-c309-46f8-9a0f-6b0f55c9915a +031f1c86-16ec-42e1-a155-81456e778fed Viral sinusitis : disorder +0447de38-1f2a-411c-9fd2-a9c62ab1f221 Viral sinusitis : disorder +061521f0-7f7e-41a5-ad35-b30fe958dea7 Acute viral pharyngitis : disorder +06e446da-6d66-4d97-a457-4abf2a0d2e24 +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 Acute viral pharyngitis : disorder +1146cf52-573f-4667-97c3-abf235f9a83b +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 +171ad058-c082-46ab-b9cc-d7bba6e4e3cd +1cea8432-f8a0-4746-ab5c-8f195fc07c55 +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 Viral sinusitis : disorder +21372b39-2be0-4d0d-85c4-5d7e250d8f78 +248e951c-e0ce-4e4b-afae-6273fd109c9d +251ddff9-1588-4790-8f40-cd6ab1d9b7fc +258191b8-2b42-4473-9ba2-be0ba3561767 +29386b12-9af9-4c21-9f82-858998b388bb +31393667-dd7f-4c94-90c3-6cde75c67d3e +31925a68-7a48-412c-b4f8-aef92498dda1 Viral sinusitis : disorder +38171e22-b35d-4161-be15-80243be37b99 Acute viral pharyngitis : disorder +39170d67-8205-4636-84d7-d8575115d14c +4025ceb3-04a6-41fb-8569-16a8dcce7ccc +46d69851-eca3-42e2-b142-88c5578f6cff Viral sinusitis : disorder +475461a2-3bd2-43fd-a5aa-7ce424203ae8 Viral sinusitis : disorder +4dd7824b-4b41-4402-bc42-de016e1bfcd9 +5300158c-92cb-40fe-bc14-a449d7b3c1c5 +5378726d-7f2b-4c83-9762-eaf385915fa7 +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 +55cde383-2eb1-42d9-b5c6-698d6eade389 Viral sinusitis : disorder +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 +5ef84858-0480-4a34-8f43-fc962fe627b2 +6464e20b-55ec-4685-be3e-55bc3b9602d4 Acute viral pharyngitis : disorder +66b38727-96ea-43ad-bff7-5f4df5d779a9 Viral sinusitis : disorder +6794fbaf-e715-4e22-bafa-b7e594550ff7 +77fc5f45-e51d-41e2-8356-daa27ebd8268 +7d18555a-54c9-4c0a-bf3b-6305d0392a2a Acute viral pharyngitis : disorder +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 Viral sinusitis : disorder +80787532-559e-4999-ac25-75c462ce4ef1 +8922ff1b-4bc5-41b4-8512-5aad1517e2eb +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 +8f3ad6ad-a457-484e-a455-f0711e77b2ba Viral sinusitis : disorder +9e598086-27bb-4e50-8988-9a40eb3c178f Acute viral pharyngitis : disorder +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc +a16f311d-d6dc-486e-8eb0-7cd53a5333ee +aa5a32b3-3993-4eb5-afce-489d0e7413cf +abecd476-3911-4a6b-a1c6-b8ecea2fe63e +ad679e19-3e17-4c67-8b0d-dd4f7d862207 +b77b04ef-5eda-418c-a6fb-528a2d0a171a Viral sinusitis : disorder +b8eccdce-7261-4402-9aa0-7360ae6bf53b +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc +badbd940-9839-4472-aa66-3382d8823c80 +bb9c4fc1-795a-4492-b065-1f497fe18bb2 +be4b757d-70f1-464c-bb29-6f9a0f6cb05e +c39927f7-d23a-475c-b325-c1de4b51e280 +c879c300-7fdf-4b53-aa6a-a2b4a266b30c Viral sinusitis : disorder +c892bf2f-a7bc-407e-9508-c778a9b8761c +d12274a5-9e03-4c78-ae3e-6cc27e91d737 +d554ba19-e081-4979-9c0c-a72cd6e5e8ea +da8db730-f051-42d0-a2db-a3577d96e9bd +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 +e35f5823-4533-49e5-9652-79d733be6bef +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d +e87fbe4b-74ef-44e4-8e36-70b2abb48895 +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 Viral sinusitis : disorder +eaf99c09-419d-4162-8417-5a9d7e042cd4 +eb373264-da60-4e98-af7c-e3021fdd8d4b +f1dd0d5f-c410-484b-984c-2ba38cee79ba +f1fd855c-802c-417a-ab7c-14a3c9daafc6 +f41b6458-2358-43e7-ae34-d2a0fbe083d8 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithNoLanguage.csv b/fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithNoLanguage.csv deleted file mode 100644 index 7fb6b425b0..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithNoLanguage.csv +++ /dev/null @@ -1,84 +0,0 @@ -01fac610-c309-46f8-9a0f-6b0f55c9915a, -031f1c86-16ec-42e1-a155-81456e778fed,Viral sinusitis -031f1c86-16ec-42e1-a155-81456e778fed,Wirusowe zapalenie zatok -0447de38-1f2a-411c-9fd2-a9c62ab1f221,Viral sinusitis -0447de38-1f2a-411c-9fd2-a9c62ab1f221,Wirusowe zapalenie zatok -061521f0-7f7e-41a5-ad35-b30fe958dea7,Acute viral pharyngitis -06e446da-6d66-4d97-a457-4abf2a0d2e24, -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,Acute viral pharyngitis -1146cf52-573f-4667-97c3-abf235f9a83b, -1452c50c-b0b9-472c-afb7-2f9e5a3c4717, -171ad058-c082-46ab-b9cc-d7bba6e4e3cd, -1cea8432-f8a0-4746-ab5c-8f195fc07c55, -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,Viral sinusitis -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,Wirusowe zapalenie zatok -21372b39-2be0-4d0d-85c4-5d7e250d8f78, -248e951c-e0ce-4e4b-afae-6273fd109c9d, -251ddff9-1588-4790-8f40-cd6ab1d9b7fc, -258191b8-2b42-4473-9ba2-be0ba3561767, -29386b12-9af9-4c21-9f82-858998b388bb, -31393667-dd7f-4c94-90c3-6cde75c67d3e, -31925a68-7a48-412c-b4f8-aef92498dda1,Viral sinusitis -31925a68-7a48-412c-b4f8-aef92498dda1,Wirusowe zapalenie zatok -38171e22-b35d-4161-be15-80243be37b99,Acute viral pharyngitis -39170d67-8205-4636-84d7-d8575115d14c, -4025ceb3-04a6-41fb-8569-16a8dcce7ccc, -46d69851-eca3-42e2-b142-88c5578f6cff,Viral sinusitis -46d69851-eca3-42e2-b142-88c5578f6cff,Wirusowe zapalenie zatok -475461a2-3bd2-43fd-a5aa-7ce424203ae8,Viral sinusitis -475461a2-3bd2-43fd-a5aa-7ce424203ae8,Wirusowe zapalenie zatok -4dd7824b-4b41-4402-bc42-de016e1bfcd9, -5300158c-92cb-40fe-bc14-a449d7b3c1c5, -5378726d-7f2b-4c83-9762-eaf385915fa7, -549d3ef5-a05e-4b8e-acba-3d70f26a82f6, -55cde383-2eb1-42d9-b5c6-698d6eade389,Viral sinusitis -55cde383-2eb1-42d9-b5c6-698d6eade389,Wirusowe zapalenie zatok -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325, -5ef84858-0480-4a34-8f43-fc962fe627b2, -6464e20b-55ec-4685-be3e-55bc3b9602d4,Acute viral pharyngitis -66b38727-96ea-43ad-bff7-5f4df5d779a9,Viral sinusitis -66b38727-96ea-43ad-bff7-5f4df5d779a9,Wirusowe zapalenie zatok -6794fbaf-e715-4e22-bafa-b7e594550ff7, -77fc5f45-e51d-41e2-8356-daa27ebd8268, -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,Acute viral pharyngitis -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,Viral sinusitis -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,Wirusowe zapalenie zatok -80787532-559e-4999-ac25-75c462ce4ef1, -8922ff1b-4bc5-41b4-8512-5aad1517e2eb, -8ddb74fc-46d5-4e94-a0ee-6761b292ae95, -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6, -8f3ad6ad-a457-484e-a455-f0711e77b2ba,Viral sinusitis -8f3ad6ad-a457-484e-a455-f0711e77b2ba,Wirusowe zapalenie zatok -9e598086-27bb-4e50-8988-9a40eb3c178f,Acute viral pharyngitis -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc, -a16f311d-d6dc-486e-8eb0-7cd53a5333ee, -aa5a32b3-3993-4eb5-afce-489d0e7413cf, -abecd476-3911-4a6b-a1c6-b8ecea2fe63e, -ad679e19-3e17-4c67-8b0d-dd4f7d862207, -b77b04ef-5eda-418c-a6fb-528a2d0a171a,Viral sinusitis -b77b04ef-5eda-418c-a6fb-528a2d0a171a,Wirusowe zapalenie zatok -b8eccdce-7261-4402-9aa0-7360ae6bf53b, -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc, -badbd940-9839-4472-aa66-3382d8823c80, -bb9c4fc1-795a-4492-b065-1f497fe18bb2, -be4b757d-70f1-464c-bb29-6f9a0f6cb05e, -c39927f7-d23a-475c-b325-c1de4b51e280, -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,Viral sinusitis -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,Wirusowe zapalenie zatok -c892bf2f-a7bc-407e-9508-c778a9b8761c, -d12274a5-9e03-4c78-ae3e-6cc27e91d737, -d554ba19-e081-4979-9c0c-a72cd6e5e8ea, -da8db730-f051-42d0-a2db-a3577d96e9bd, -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82, -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d, -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76, -e35f5823-4533-49e5-9652-79d733be6bef, -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d, -e87fbe4b-74ef-44e4-8e36-70b2abb48895, -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,Viral sinusitis -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,Wirusowe zapalenie zatok -eaf99c09-419d-4162-8417-5a9d7e042cd4, -eb373264-da60-4e98-af7c-e3021fdd8d4b, -f1dd0d5f-c410-484b-984c-2ba38cee79ba, -f1fd855c-802c-417a-ab7c-14a3c9daafc6, -f41b6458-2358-43e7-ae34-d2a0fbe083d8, diff --git a/fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithNoLanguage.tsv b/fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithNoLanguage.tsv new file mode 100644 index 0000000000..cfd384e454 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testDesignationFunctionWithNoLanguage.tsv @@ -0,0 +1,84 @@ +01fac610-c309-46f8-9a0f-6b0f55c9915a +031f1c86-16ec-42e1-a155-81456e778fed Viral sinusitis +031f1c86-16ec-42e1-a155-81456e778fed Wirusowe zapalenie zatok +0447de38-1f2a-411c-9fd2-a9c62ab1f221 Viral sinusitis +0447de38-1f2a-411c-9fd2-a9c62ab1f221 Wirusowe zapalenie zatok +061521f0-7f7e-41a5-ad35-b30fe958dea7 Acute viral pharyngitis +06e446da-6d66-4d97-a457-4abf2a0d2e24 +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 Acute viral pharyngitis +1146cf52-573f-4667-97c3-abf235f9a83b +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 +171ad058-c082-46ab-b9cc-d7bba6e4e3cd +1cea8432-f8a0-4746-ab5c-8f195fc07c55 +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 Viral sinusitis +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 Wirusowe zapalenie zatok +21372b39-2be0-4d0d-85c4-5d7e250d8f78 +248e951c-e0ce-4e4b-afae-6273fd109c9d +251ddff9-1588-4790-8f40-cd6ab1d9b7fc +258191b8-2b42-4473-9ba2-be0ba3561767 +29386b12-9af9-4c21-9f82-858998b388bb +31393667-dd7f-4c94-90c3-6cde75c67d3e +31925a68-7a48-412c-b4f8-aef92498dda1 Viral sinusitis +31925a68-7a48-412c-b4f8-aef92498dda1 Wirusowe zapalenie zatok +38171e22-b35d-4161-be15-80243be37b99 Acute viral pharyngitis +39170d67-8205-4636-84d7-d8575115d14c +4025ceb3-04a6-41fb-8569-16a8dcce7ccc +46d69851-eca3-42e2-b142-88c5578f6cff Viral sinusitis +46d69851-eca3-42e2-b142-88c5578f6cff Wirusowe zapalenie zatok +475461a2-3bd2-43fd-a5aa-7ce424203ae8 Viral sinusitis +475461a2-3bd2-43fd-a5aa-7ce424203ae8 Wirusowe zapalenie zatok +4dd7824b-4b41-4402-bc42-de016e1bfcd9 +5300158c-92cb-40fe-bc14-a449d7b3c1c5 +5378726d-7f2b-4c83-9762-eaf385915fa7 +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 +55cde383-2eb1-42d9-b5c6-698d6eade389 Viral sinusitis +55cde383-2eb1-42d9-b5c6-698d6eade389 Wirusowe zapalenie zatok +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 +5ef84858-0480-4a34-8f43-fc962fe627b2 +6464e20b-55ec-4685-be3e-55bc3b9602d4 Acute viral pharyngitis +66b38727-96ea-43ad-bff7-5f4df5d779a9 Viral sinusitis +66b38727-96ea-43ad-bff7-5f4df5d779a9 Wirusowe zapalenie zatok +6794fbaf-e715-4e22-bafa-b7e594550ff7 +77fc5f45-e51d-41e2-8356-daa27ebd8268 +7d18555a-54c9-4c0a-bf3b-6305d0392a2a Acute viral pharyngitis +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 Viral sinusitis +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 Wirusowe zapalenie zatok +80787532-559e-4999-ac25-75c462ce4ef1 +8922ff1b-4bc5-41b4-8512-5aad1517e2eb +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 +8f3ad6ad-a457-484e-a455-f0711e77b2ba Viral sinusitis +8f3ad6ad-a457-484e-a455-f0711e77b2ba Wirusowe zapalenie zatok +9e598086-27bb-4e50-8988-9a40eb3c178f Acute viral pharyngitis +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc +a16f311d-d6dc-486e-8eb0-7cd53a5333ee +aa5a32b3-3993-4eb5-afce-489d0e7413cf +abecd476-3911-4a6b-a1c6-b8ecea2fe63e +ad679e19-3e17-4c67-8b0d-dd4f7d862207 +b77b04ef-5eda-418c-a6fb-528a2d0a171a Viral sinusitis +b77b04ef-5eda-418c-a6fb-528a2d0a171a Wirusowe zapalenie zatok +b8eccdce-7261-4402-9aa0-7360ae6bf53b +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc +badbd940-9839-4472-aa66-3382d8823c80 +bb9c4fc1-795a-4492-b065-1f497fe18bb2 +be4b757d-70f1-464c-bb29-6f9a0f6cb05e +c39927f7-d23a-475c-b325-c1de4b51e280 +c879c300-7fdf-4b53-aa6a-a2b4a266b30c Viral sinusitis +c879c300-7fdf-4b53-aa6a-a2b4a266b30c Wirusowe zapalenie zatok +c892bf2f-a7bc-407e-9508-c778a9b8761c +d12274a5-9e03-4c78-ae3e-6cc27e91d737 +d554ba19-e081-4979-9c0c-a72cd6e5e8ea +da8db730-f051-42d0-a2db-a3577d96e9bd +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 +e35f5823-4533-49e5-9652-79d733be6bef +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d +e87fbe4b-74ef-44e4-8e36-70b2abb48895 +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 Viral sinusitis +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 Wirusowe zapalenie zatok +eaf99c09-419d-4162-8417-5a9d7e042cd4 +eb373264-da60-4e98-af7c-e3021fdd8d4b +f1dd0d5f-c410-484b-984c-2ba38cee79ba +f1fd855c-802c-417a-ab7c-14a3c9daafc6 +f41b6458-2358-43e7-ae34-d2a0fbe083d8 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testDisplayFunction.csv b/fhir-server/src/test/resources/responses/ParserTest/testDisplayFunction.csv deleted file mode 100644 index 684798b40e..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testDisplayFunction.csv +++ /dev/null @@ -1,71 +0,0 @@ -01fac610-c309-46f8-9a0f-6b0f55c9915a, -031f1c86-16ec-42e1-a155-81456e778fed,Viral sinusitis (disorder) -0447de38-1f2a-411c-9fd2-a9c62ab1f221,Viral sinusitis (disorder) -061521f0-7f7e-41a5-ad35-b30fe958dea7,Acute viral pharyngitis (disorder) -06e446da-6d66-4d97-a457-4abf2a0d2e24, -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,Acute viral pharyngitis (disorder) -1146cf52-573f-4667-97c3-abf235f9a83b, -1452c50c-b0b9-472c-afb7-2f9e5a3c4717, -171ad058-c082-46ab-b9cc-d7bba6e4e3cd, -1cea8432-f8a0-4746-ab5c-8f195fc07c55, -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,Viral sinusitis (disorder) -21372b39-2be0-4d0d-85c4-5d7e250d8f78, -248e951c-e0ce-4e4b-afae-6273fd109c9d, -251ddff9-1588-4790-8f40-cd6ab1d9b7fc, -258191b8-2b42-4473-9ba2-be0ba3561767, -29386b12-9af9-4c21-9f82-858998b388bb, -31393667-dd7f-4c94-90c3-6cde75c67d3e, -31925a68-7a48-412c-b4f8-aef92498dda1,Viral sinusitis (disorder) -38171e22-b35d-4161-be15-80243be37b99,Acute viral pharyngitis (disorder) -39170d67-8205-4636-84d7-d8575115d14c, -4025ceb3-04a6-41fb-8569-16a8dcce7ccc, -46d69851-eca3-42e2-b142-88c5578f6cff,Viral sinusitis (disorder) -475461a2-3bd2-43fd-a5aa-7ce424203ae8,Viral sinusitis (disorder) -4dd7824b-4b41-4402-bc42-de016e1bfcd9, -5300158c-92cb-40fe-bc14-a449d7b3c1c5, -5378726d-7f2b-4c83-9762-eaf385915fa7, -549d3ef5-a05e-4b8e-acba-3d70f26a82f6, -55cde383-2eb1-42d9-b5c6-698d6eade389,Viral sinusitis (disorder) -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325, -5ef84858-0480-4a34-8f43-fc962fe627b2, -6464e20b-55ec-4685-be3e-55bc3b9602d4,Acute viral pharyngitis (disorder) -66b38727-96ea-43ad-bff7-5f4df5d779a9,Viral sinusitis (disorder) -6794fbaf-e715-4e22-bafa-b7e594550ff7, -77fc5f45-e51d-41e2-8356-daa27ebd8268, -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,Acute viral pharyngitis (disorder) -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,Viral sinusitis (disorder) -80787532-559e-4999-ac25-75c462ce4ef1, -8922ff1b-4bc5-41b4-8512-5aad1517e2eb, -8ddb74fc-46d5-4e94-a0ee-6761b292ae95, -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6, -8f3ad6ad-a457-484e-a455-f0711e77b2ba,Viral sinusitis (disorder) -9e598086-27bb-4e50-8988-9a40eb3c178f,Acute viral pharyngitis (disorder) -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc, -a16f311d-d6dc-486e-8eb0-7cd53a5333ee, -aa5a32b3-3993-4eb5-afce-489d0e7413cf, -abecd476-3911-4a6b-a1c6-b8ecea2fe63e, -ad679e19-3e17-4c67-8b0d-dd4f7d862207, -b77b04ef-5eda-418c-a6fb-528a2d0a171a,Viral sinusitis (disorder) -b8eccdce-7261-4402-9aa0-7360ae6bf53b, -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc, -badbd940-9839-4472-aa66-3382d8823c80, -bb9c4fc1-795a-4492-b065-1f497fe18bb2, -be4b757d-70f1-464c-bb29-6f9a0f6cb05e, -c39927f7-d23a-475c-b325-c1de4b51e280, -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,Viral sinusitis (disorder) -c892bf2f-a7bc-407e-9508-c778a9b8761c, -d12274a5-9e03-4c78-ae3e-6cc27e91d737, -d554ba19-e081-4979-9c0c-a72cd6e5e8ea, -da8db730-f051-42d0-a2db-a3577d96e9bd, -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82, -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d, -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76, -e35f5823-4533-49e5-9652-79d733be6bef, -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d, -e87fbe4b-74ef-44e4-8e36-70b2abb48895, -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,Viral sinusitis (disorder) -eaf99c09-419d-4162-8417-5a9d7e042cd4, -eb373264-da60-4e98-af7c-e3021fdd8d4b, -f1dd0d5f-c410-484b-984c-2ba38cee79ba, -f1fd855c-802c-417a-ab7c-14a3c9daafc6, -f41b6458-2358-43e7-ae34-d2a0fbe083d8, diff --git a/fhir-server/src/test/resources/responses/ParserTest/testDisplayFunction.tsv b/fhir-server/src/test/resources/responses/ParserTest/testDisplayFunction.tsv new file mode 100644 index 0000000000..e3780bd6b6 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testDisplayFunction.tsv @@ -0,0 +1,71 @@ +01fac610-c309-46f8-9a0f-6b0f55c9915a +031f1c86-16ec-42e1-a155-81456e778fed Viral sinusitis (disorder) +0447de38-1f2a-411c-9fd2-a9c62ab1f221 Viral sinusitis (disorder) +061521f0-7f7e-41a5-ad35-b30fe958dea7 Acute viral pharyngitis (disorder) +06e446da-6d66-4d97-a457-4abf2a0d2e24 +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 Acute viral pharyngitis (disorder) +1146cf52-573f-4667-97c3-abf235f9a83b +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 +171ad058-c082-46ab-b9cc-d7bba6e4e3cd +1cea8432-f8a0-4746-ab5c-8f195fc07c55 +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 Viral sinusitis (disorder) +21372b39-2be0-4d0d-85c4-5d7e250d8f78 +248e951c-e0ce-4e4b-afae-6273fd109c9d +251ddff9-1588-4790-8f40-cd6ab1d9b7fc +258191b8-2b42-4473-9ba2-be0ba3561767 +29386b12-9af9-4c21-9f82-858998b388bb +31393667-dd7f-4c94-90c3-6cde75c67d3e +31925a68-7a48-412c-b4f8-aef92498dda1 Viral sinusitis (disorder) +38171e22-b35d-4161-be15-80243be37b99 Acute viral pharyngitis (disorder) +39170d67-8205-4636-84d7-d8575115d14c +4025ceb3-04a6-41fb-8569-16a8dcce7ccc +46d69851-eca3-42e2-b142-88c5578f6cff Viral sinusitis (disorder) +475461a2-3bd2-43fd-a5aa-7ce424203ae8 Viral sinusitis (disorder) +4dd7824b-4b41-4402-bc42-de016e1bfcd9 +5300158c-92cb-40fe-bc14-a449d7b3c1c5 +5378726d-7f2b-4c83-9762-eaf385915fa7 +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 +55cde383-2eb1-42d9-b5c6-698d6eade389 Viral sinusitis (disorder) +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 +5ef84858-0480-4a34-8f43-fc962fe627b2 +6464e20b-55ec-4685-be3e-55bc3b9602d4 Acute viral pharyngitis (disorder) +66b38727-96ea-43ad-bff7-5f4df5d779a9 Viral sinusitis (disorder) +6794fbaf-e715-4e22-bafa-b7e594550ff7 +77fc5f45-e51d-41e2-8356-daa27ebd8268 +7d18555a-54c9-4c0a-bf3b-6305d0392a2a Acute viral pharyngitis (disorder) +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 Viral sinusitis (disorder) +80787532-559e-4999-ac25-75c462ce4ef1 +8922ff1b-4bc5-41b4-8512-5aad1517e2eb +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 +8f3ad6ad-a457-484e-a455-f0711e77b2ba Viral sinusitis (disorder) +9e598086-27bb-4e50-8988-9a40eb3c178f Acute viral pharyngitis (disorder) +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc +a16f311d-d6dc-486e-8eb0-7cd53a5333ee +aa5a32b3-3993-4eb5-afce-489d0e7413cf +abecd476-3911-4a6b-a1c6-b8ecea2fe63e +ad679e19-3e17-4c67-8b0d-dd4f7d862207 +b77b04ef-5eda-418c-a6fb-528a2d0a171a Viral sinusitis (disorder) +b8eccdce-7261-4402-9aa0-7360ae6bf53b +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc +badbd940-9839-4472-aa66-3382d8823c80 +bb9c4fc1-795a-4492-b065-1f497fe18bb2 +be4b757d-70f1-464c-bb29-6f9a0f6cb05e +c39927f7-d23a-475c-b325-c1de4b51e280 +c879c300-7fdf-4b53-aa6a-a2b4a266b30c Viral sinusitis (disorder) +c892bf2f-a7bc-407e-9508-c778a9b8761c +d12274a5-9e03-4c78-ae3e-6cc27e91d737 +d554ba19-e081-4979-9c0c-a72cd6e5e8ea +da8db730-f051-42d0-a2db-a3577d96e9bd +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 +e35f5823-4533-49e5-9652-79d733be6bef +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d +e87fbe4b-74ef-44e4-8e36-70b2abb48895 +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 Viral sinusitis (disorder) +eaf99c09-419d-4162-8417-5a9d7e042cd4 +eb373264-da60-4e98-af7c-e3021fdd8d4b +f1dd0d5f-c410-484b-984c-2ba38cee79ba +f1fd855c-802c-417a-ab7c-14a3c9daafc6 +f41b6458-2358-43e7-ae34-d2a0fbe083d8 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunction.csv b/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunction.csv deleted file mode 100644 index 228049b378..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunction.csv +++ /dev/null @@ -1,63 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,Waneta858 Bailey598 -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,María del Carmen27 Lozano749 -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,Claudia969 Schaden604 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Onie555 Tremblay80 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,Rosann381 Dietrich576 -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,Leo278 Dare640 -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Amiee221 Dach178 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,Germaine912 Berge125 -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Idella49 Monahan736 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" diff --git a/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunction.tsv b/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunction.tsv new file mode 100644 index 0000000000..43425804de --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunction.tsv @@ -0,0 +1,63 @@ +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e Waneta858 Bailey598 +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 María del Carmen27 Lozano749 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 Claudia969 Schaden604 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Onie555 Tremblay80 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 Rosann381 Dietrich576 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 Leo278 Dare640 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 Amiee221 Dach178 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf Germaine912 Berge125 +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 Idella49 Monahan736 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionInWhere.csv b/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionInWhere.csv deleted file mode 100644 index 047cc10a8c..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionInWhere.csv +++ /dev/null @@ -1,9 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Boston diff --git a/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionInWhere.tsv b/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionInWhere.tsv new file mode 100644 index 0000000000..deea5ccbed --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionInWhere.tsv @@ -0,0 +1,9 @@ +121503c8-9564-4b48-9086-a22df717948e +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 Boston diff --git a/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionOnTranslateResult.csv b/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionOnTranslateResult.csv deleted file mode 100644 index 9a71e1f4e4..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionOnTranslateResult.csv +++ /dev/null @@ -1,96 +0,0 @@ -01fac610-c309-46f8-9a0f-6b0f55c9915a,"" -031f1c86-16ec-42e1-a155-81456e778fed,"" -031f1c86-16ec-42e1-a155-81456e778fed,"" -0447de38-1f2a-411c-9fd2-a9c62ab1f221,"" -0447de38-1f2a-411c-9fd2-a9c62ab1f221,"" -061521f0-7f7e-41a5-ad35-b30fe958dea7,"" -061521f0-7f7e-41a5-ad35-b30fe958dea7,"" -061521f0-7f7e-41a5-ad35-b30fe958dea7,"" -06e446da-6d66-4d97-a457-4abf2a0d2e24,"" -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,"" -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,"" -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,"" -1146cf52-573f-4667-97c3-abf235f9a83b,"" -1452c50c-b0b9-472c-afb7-2f9e5a3c4717,"" -171ad058-c082-46ab-b9cc-d7bba6e4e3cd,"" -1cea8432-f8a0-4746-ab5c-8f195fc07c55,"" -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,"" -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,"" -21372b39-2be0-4d0d-85c4-5d7e250d8f78,"" -248e951c-e0ce-4e4b-afae-6273fd109c9d,"" -251ddff9-1588-4790-8f40-cd6ab1d9b7fc,"" -258191b8-2b42-4473-9ba2-be0ba3561767,"" -29386b12-9af9-4c21-9f82-858998b388bb,"" -31393667-dd7f-4c94-90c3-6cde75c67d3e,"" -31925a68-7a48-412c-b4f8-aef92498dda1,"" -31925a68-7a48-412c-b4f8-aef92498dda1,"" -38171e22-b35d-4161-be15-80243be37b99,"" -38171e22-b35d-4161-be15-80243be37b99,"" -38171e22-b35d-4161-be15-80243be37b99,"" -39170d67-8205-4636-84d7-d8575115d14c,"" -4025ceb3-04a6-41fb-8569-16a8dcce7ccc,"" -46d69851-eca3-42e2-b142-88c5578f6cff,"" -46d69851-eca3-42e2-b142-88c5578f6cff,"" -475461a2-3bd2-43fd-a5aa-7ce424203ae8,"" -475461a2-3bd2-43fd-a5aa-7ce424203ae8,"" -4dd7824b-4b41-4402-bc42-de016e1bfcd9,"" -5300158c-92cb-40fe-bc14-a449d7b3c1c5,"" -5378726d-7f2b-4c83-9762-eaf385915fa7,"" -549d3ef5-a05e-4b8e-acba-3d70f26a82f6,"" -55cde383-2eb1-42d9-b5c6-698d6eade389,"" -55cde383-2eb1-42d9-b5c6-698d6eade389,"" -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325,"" -5ef84858-0480-4a34-8f43-fc962fe627b2,"" -6464e20b-55ec-4685-be3e-55bc3b9602d4,"" -6464e20b-55ec-4685-be3e-55bc3b9602d4,"" -6464e20b-55ec-4685-be3e-55bc3b9602d4,"" -66b38727-96ea-43ad-bff7-5f4df5d779a9,"" -66b38727-96ea-43ad-bff7-5f4df5d779a9,"" -6794fbaf-e715-4e22-bafa-b7e594550ff7,"" -77fc5f45-e51d-41e2-8356-daa27ebd8268,"" -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,"" -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,"" -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,"" -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,"" -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,"" -80787532-559e-4999-ac25-75c462ce4ef1,"" -8922ff1b-4bc5-41b4-8512-5aad1517e2eb,"" -8ddb74fc-46d5-4e94-a0ee-6761b292ae95,"" -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6,"" -8f3ad6ad-a457-484e-a455-f0711e77b2ba,"" -8f3ad6ad-a457-484e-a455-f0711e77b2ba,"" -9e598086-27bb-4e50-8988-9a40eb3c178f,"" -9e598086-27bb-4e50-8988-9a40eb3c178f,"" -9e598086-27bb-4e50-8988-9a40eb3c178f,"" -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc,"" -a16f311d-d6dc-486e-8eb0-7cd53a5333ee,"" -aa5a32b3-3993-4eb5-afce-489d0e7413cf,"" -abecd476-3911-4a6b-a1c6-b8ecea2fe63e,"" -ad679e19-3e17-4c67-8b0d-dd4f7d862207,"" -b77b04ef-5eda-418c-a6fb-528a2d0a171a,"" -b77b04ef-5eda-418c-a6fb-528a2d0a171a,"" -b8eccdce-7261-4402-9aa0-7360ae6bf53b,"" -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc,"" -badbd940-9839-4472-aa66-3382d8823c80,"" -bb9c4fc1-795a-4492-b065-1f497fe18bb2,"" -be4b757d-70f1-464c-bb29-6f9a0f6cb05e,"" -c39927f7-d23a-475c-b325-c1de4b51e280,"" -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,"" -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,"" -c892bf2f-a7bc-407e-9508-c778a9b8761c,"" -d12274a5-9e03-4c78-ae3e-6cc27e91d737,"" -d554ba19-e081-4979-9c0c-a72cd6e5e8ea,"" -da8db730-f051-42d0-a2db-a3577d96e9bd,"" -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82,"" -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d,"" -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76,"" -e35f5823-4533-49e5-9652-79d733be6bef,"" -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d,"" -e87fbe4b-74ef-44e4-8e36-70b2abb48895,"" -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,"" -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,"" -eaf99c09-419d-4162-8417-5a9d7e042cd4,"" -eb373264-da60-4e98-af7c-e3021fdd8d4b,"" -f1dd0d5f-c410-484b-984c-2ba38cee79ba,"" -f1fd855c-802c-417a-ab7c-14a3c9daafc6,"" -f41b6458-2358-43e7-ae34-d2a0fbe083d8,"" diff --git a/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionOnTranslateResult.tsv b/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionOnTranslateResult.tsv new file mode 100644 index 0000000000..1f7ab10687 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testExtensionFunctionOnTranslateResult.tsv @@ -0,0 +1,96 @@ +01fac610-c309-46f8-9a0f-6b0f55c9915a +031f1c86-16ec-42e1-a155-81456e778fed +031f1c86-16ec-42e1-a155-81456e778fed +0447de38-1f2a-411c-9fd2-a9c62ab1f221 +0447de38-1f2a-411c-9fd2-a9c62ab1f221 +061521f0-7f7e-41a5-ad35-b30fe958dea7 +061521f0-7f7e-41a5-ad35-b30fe958dea7 +061521f0-7f7e-41a5-ad35-b30fe958dea7 +06e446da-6d66-4d97-a457-4abf2a0d2e24 +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 +1146cf52-573f-4667-97c3-abf235f9a83b +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 +171ad058-c082-46ab-b9cc-d7bba6e4e3cd +1cea8432-f8a0-4746-ab5c-8f195fc07c55 +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 +21372b39-2be0-4d0d-85c4-5d7e250d8f78 +248e951c-e0ce-4e4b-afae-6273fd109c9d +251ddff9-1588-4790-8f40-cd6ab1d9b7fc +258191b8-2b42-4473-9ba2-be0ba3561767 +29386b12-9af9-4c21-9f82-858998b388bb +31393667-dd7f-4c94-90c3-6cde75c67d3e +31925a68-7a48-412c-b4f8-aef92498dda1 +31925a68-7a48-412c-b4f8-aef92498dda1 +38171e22-b35d-4161-be15-80243be37b99 +38171e22-b35d-4161-be15-80243be37b99 +38171e22-b35d-4161-be15-80243be37b99 +39170d67-8205-4636-84d7-d8575115d14c +4025ceb3-04a6-41fb-8569-16a8dcce7ccc +46d69851-eca3-42e2-b142-88c5578f6cff +46d69851-eca3-42e2-b142-88c5578f6cff +475461a2-3bd2-43fd-a5aa-7ce424203ae8 +475461a2-3bd2-43fd-a5aa-7ce424203ae8 +4dd7824b-4b41-4402-bc42-de016e1bfcd9 +5300158c-92cb-40fe-bc14-a449d7b3c1c5 +5378726d-7f2b-4c83-9762-eaf385915fa7 +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 +55cde383-2eb1-42d9-b5c6-698d6eade389 +55cde383-2eb1-42d9-b5c6-698d6eade389 +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 +5ef84858-0480-4a34-8f43-fc962fe627b2 +6464e20b-55ec-4685-be3e-55bc3b9602d4 +6464e20b-55ec-4685-be3e-55bc3b9602d4 +6464e20b-55ec-4685-be3e-55bc3b9602d4 +66b38727-96ea-43ad-bff7-5f4df5d779a9 +66b38727-96ea-43ad-bff7-5f4df5d779a9 +6794fbaf-e715-4e22-bafa-b7e594550ff7 +77fc5f45-e51d-41e2-8356-daa27ebd8268 +7d18555a-54c9-4c0a-bf3b-6305d0392a2a +7d18555a-54c9-4c0a-bf3b-6305d0392a2a +7d18555a-54c9-4c0a-bf3b-6305d0392a2a +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 +80787532-559e-4999-ac25-75c462ce4ef1 +8922ff1b-4bc5-41b4-8512-5aad1517e2eb +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 +8f3ad6ad-a457-484e-a455-f0711e77b2ba +8f3ad6ad-a457-484e-a455-f0711e77b2ba +9e598086-27bb-4e50-8988-9a40eb3c178f +9e598086-27bb-4e50-8988-9a40eb3c178f +9e598086-27bb-4e50-8988-9a40eb3c178f +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc +a16f311d-d6dc-486e-8eb0-7cd53a5333ee +aa5a32b3-3993-4eb5-afce-489d0e7413cf +abecd476-3911-4a6b-a1c6-b8ecea2fe63e +ad679e19-3e17-4c67-8b0d-dd4f7d862207 +b77b04ef-5eda-418c-a6fb-528a2d0a171a +b77b04ef-5eda-418c-a6fb-528a2d0a171a +b8eccdce-7261-4402-9aa0-7360ae6bf53b +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc +badbd940-9839-4472-aa66-3382d8823c80 +bb9c4fc1-795a-4492-b065-1f497fe18bb2 +be4b757d-70f1-464c-bb29-6f9a0f6cb05e +c39927f7-d23a-475c-b325-c1de4b51e280 +c879c300-7fdf-4b53-aa6a-a2b4a266b30c +c879c300-7fdf-4b53-aa6a-a2b4a266b30c +c892bf2f-a7bc-407e-9508-c778a9b8761c +d12274a5-9e03-4c78-ae3e-6cc27e91d737 +d554ba19-e081-4979-9c0c-a72cd6e5e8ea +da8db730-f051-42d0-a2db-a3577d96e9bd +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 +e35f5823-4533-49e5-9652-79d733be6bef +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d +e87fbe4b-74ef-44e4-8e36-70b2abb48895 +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 +eaf99c09-419d-4162-8417-5a9d7e042cd4 +eb373264-da60-4e98-af7c-e3021fdd8d4b +f1dd0d5f-c410-484b-984c-2ba38cee79ba +f1fd855c-802c-417a-ab7c-14a3c9daafc6 +f41b6458-2358-43e7-ae34-d2a0fbe083d8 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testExtensionsCurrentResource.csv b/fhir-server/src/test/resources/responses/ParserTest/testExtensionsCurrentResource.csv deleted file mode 100644 index c7eac3366e..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testExtensionsCurrentResource.csv +++ /dev/null @@ -1,497 +0,0 @@ -01fac610-c309-46f8-9a0f-6b0f55c9915a,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -01fac610-c309-46f8-9a0f-6b0f55c9915a,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -01fac610-c309-46f8-9a0f-6b0f55c9915a,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -01fac610-c309-46f8-9a0f-6b0f55c9915a,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -01fac610-c309-46f8-9a0f-6b0f55c9915a,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -01fac610-c309-46f8-9a0f-6b0f55c9915a,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -01fac610-c309-46f8-9a0f-6b0f55c9915a,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -031f1c86-16ec-42e1-a155-81456e778fed,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -031f1c86-16ec-42e1-a155-81456e778fed,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -031f1c86-16ec-42e1-a155-81456e778fed,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -031f1c86-16ec-42e1-a155-81456e778fed,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -031f1c86-16ec-42e1-a155-81456e778fed,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -031f1c86-16ec-42e1-a155-81456e778fed,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -031f1c86-16ec-42e1-a155-81456e778fed,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -0447de38-1f2a-411c-9fd2-a9c62ab1f221,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -0447de38-1f2a-411c-9fd2-a9c62ab1f221,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -0447de38-1f2a-411c-9fd2-a9c62ab1f221,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -0447de38-1f2a-411c-9fd2-a9c62ab1f221,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -0447de38-1f2a-411c-9fd2-a9c62ab1f221,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -0447de38-1f2a-411c-9fd2-a9c62ab1f221,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -0447de38-1f2a-411c-9fd2-a9c62ab1f221,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -061521f0-7f7e-41a5-ad35-b30fe958dea7,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -061521f0-7f7e-41a5-ad35-b30fe958dea7,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -061521f0-7f7e-41a5-ad35-b30fe958dea7,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -061521f0-7f7e-41a5-ad35-b30fe958dea7,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -061521f0-7f7e-41a5-ad35-b30fe958dea7,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -061521f0-7f7e-41a5-ad35-b30fe958dea7,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -061521f0-7f7e-41a5-ad35-b30fe958dea7,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -06e446da-6d66-4d97-a457-4abf2a0d2e24,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -06e446da-6d66-4d97-a457-4abf2a0d2e24,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -06e446da-6d66-4d97-a457-4abf2a0d2e24,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -06e446da-6d66-4d97-a457-4abf2a0d2e24,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -06e446da-6d66-4d97-a457-4abf2a0d2e24,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -06e446da-6d66-4d97-a457-4abf2a0d2e24,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -06e446da-6d66-4d97-a457-4abf2a0d2e24,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -1146cf52-573f-4667-97c3-abf235f9a83b,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -1146cf52-573f-4667-97c3-abf235f9a83b,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -1146cf52-573f-4667-97c3-abf235f9a83b,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -1146cf52-573f-4667-97c3-abf235f9a83b,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -1146cf52-573f-4667-97c3-abf235f9a83b,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -1146cf52-573f-4667-97c3-abf235f9a83b,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -1146cf52-573f-4667-97c3-abf235f9a83b,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -1452c50c-b0b9-472c-afb7-2f9e5a3c4717,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -1452c50c-b0b9-472c-afb7-2f9e5a3c4717,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -1452c50c-b0b9-472c-afb7-2f9e5a3c4717,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -1452c50c-b0b9-472c-afb7-2f9e5a3c4717,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -1452c50c-b0b9-472c-afb7-2f9e5a3c4717,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -1452c50c-b0b9-472c-afb7-2f9e5a3c4717,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -1452c50c-b0b9-472c-afb7-2f9e5a3c4717,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -171ad058-c082-46ab-b9cc-d7bba6e4e3cd,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -171ad058-c082-46ab-b9cc-d7bba6e4e3cd,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -171ad058-c082-46ab-b9cc-d7bba6e4e3cd,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -171ad058-c082-46ab-b9cc-d7bba6e4e3cd,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -171ad058-c082-46ab-b9cc-d7bba6e4e3cd,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -171ad058-c082-46ab-b9cc-d7bba6e4e3cd,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -171ad058-c082-46ab-b9cc-d7bba6e4e3cd,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -1cea8432-f8a0-4746-ab5c-8f195fc07c55,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -1cea8432-f8a0-4746-ab5c-8f195fc07c55,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -1cea8432-f8a0-4746-ab5c-8f195fc07c55,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -1cea8432-f8a0-4746-ab5c-8f195fc07c55,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -1cea8432-f8a0-4746-ab5c-8f195fc07c55,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -1cea8432-f8a0-4746-ab5c-8f195fc07c55,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -1cea8432-f8a0-4746-ab5c-8f195fc07c55,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -21372b39-2be0-4d0d-85c4-5d7e250d8f78,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -21372b39-2be0-4d0d-85c4-5d7e250d8f78,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -21372b39-2be0-4d0d-85c4-5d7e250d8f78,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -21372b39-2be0-4d0d-85c4-5d7e250d8f78,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -21372b39-2be0-4d0d-85c4-5d7e250d8f78,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -21372b39-2be0-4d0d-85c4-5d7e250d8f78,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -21372b39-2be0-4d0d-85c4-5d7e250d8f78,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -248e951c-e0ce-4e4b-afae-6273fd109c9d,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -248e951c-e0ce-4e4b-afae-6273fd109c9d,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -248e951c-e0ce-4e4b-afae-6273fd109c9d,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -248e951c-e0ce-4e4b-afae-6273fd109c9d,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -248e951c-e0ce-4e4b-afae-6273fd109c9d,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -248e951c-e0ce-4e4b-afae-6273fd109c9d,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -248e951c-e0ce-4e4b-afae-6273fd109c9d,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -251ddff9-1588-4790-8f40-cd6ab1d9b7fc,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -251ddff9-1588-4790-8f40-cd6ab1d9b7fc,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -251ddff9-1588-4790-8f40-cd6ab1d9b7fc,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -251ddff9-1588-4790-8f40-cd6ab1d9b7fc,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -251ddff9-1588-4790-8f40-cd6ab1d9b7fc,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -251ddff9-1588-4790-8f40-cd6ab1d9b7fc,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -251ddff9-1588-4790-8f40-cd6ab1d9b7fc,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -258191b8-2b42-4473-9ba2-be0ba3561767,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -258191b8-2b42-4473-9ba2-be0ba3561767,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -258191b8-2b42-4473-9ba2-be0ba3561767,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -258191b8-2b42-4473-9ba2-be0ba3561767,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -258191b8-2b42-4473-9ba2-be0ba3561767,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -258191b8-2b42-4473-9ba2-be0ba3561767,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -258191b8-2b42-4473-9ba2-be0ba3561767,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -29386b12-9af9-4c21-9f82-858998b388bb,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -29386b12-9af9-4c21-9f82-858998b388bb,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -29386b12-9af9-4c21-9f82-858998b388bb,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -29386b12-9af9-4c21-9f82-858998b388bb,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -29386b12-9af9-4c21-9f82-858998b388bb,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -29386b12-9af9-4c21-9f82-858998b388bb,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -29386b12-9af9-4c21-9f82-858998b388bb,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -31393667-dd7f-4c94-90c3-6cde75c67d3e,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -31393667-dd7f-4c94-90c3-6cde75c67d3e,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -31393667-dd7f-4c94-90c3-6cde75c67d3e,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -31393667-dd7f-4c94-90c3-6cde75c67d3e,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -31393667-dd7f-4c94-90c3-6cde75c67d3e,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -31393667-dd7f-4c94-90c3-6cde75c67d3e,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -31393667-dd7f-4c94-90c3-6cde75c67d3e,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -31925a68-7a48-412c-b4f8-aef92498dda1,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -31925a68-7a48-412c-b4f8-aef92498dda1,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -31925a68-7a48-412c-b4f8-aef92498dda1,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -31925a68-7a48-412c-b4f8-aef92498dda1,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -31925a68-7a48-412c-b4f8-aef92498dda1,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -31925a68-7a48-412c-b4f8-aef92498dda1,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -31925a68-7a48-412c-b4f8-aef92498dda1,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -38171e22-b35d-4161-be15-80243be37b99,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -38171e22-b35d-4161-be15-80243be37b99,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -38171e22-b35d-4161-be15-80243be37b99,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -38171e22-b35d-4161-be15-80243be37b99,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -38171e22-b35d-4161-be15-80243be37b99,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -38171e22-b35d-4161-be15-80243be37b99,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -38171e22-b35d-4161-be15-80243be37b99,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -39170d67-8205-4636-84d7-d8575115d14c,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -39170d67-8205-4636-84d7-d8575115d14c,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -39170d67-8205-4636-84d7-d8575115d14c,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -39170d67-8205-4636-84d7-d8575115d14c,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -39170d67-8205-4636-84d7-d8575115d14c,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -39170d67-8205-4636-84d7-d8575115d14c,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -39170d67-8205-4636-84d7-d8575115d14c,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -4025ceb3-04a6-41fb-8569-16a8dcce7ccc,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -4025ceb3-04a6-41fb-8569-16a8dcce7ccc,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -4025ceb3-04a6-41fb-8569-16a8dcce7ccc,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -4025ceb3-04a6-41fb-8569-16a8dcce7ccc,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -4025ceb3-04a6-41fb-8569-16a8dcce7ccc,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -4025ceb3-04a6-41fb-8569-16a8dcce7ccc,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -4025ceb3-04a6-41fb-8569-16a8dcce7ccc,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -46d69851-eca3-42e2-b142-88c5578f6cff,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -46d69851-eca3-42e2-b142-88c5578f6cff,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -46d69851-eca3-42e2-b142-88c5578f6cff,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -46d69851-eca3-42e2-b142-88c5578f6cff,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -46d69851-eca3-42e2-b142-88c5578f6cff,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -46d69851-eca3-42e2-b142-88c5578f6cff,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -46d69851-eca3-42e2-b142-88c5578f6cff,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -475461a2-3bd2-43fd-a5aa-7ce424203ae8,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -475461a2-3bd2-43fd-a5aa-7ce424203ae8,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -475461a2-3bd2-43fd-a5aa-7ce424203ae8,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -475461a2-3bd2-43fd-a5aa-7ce424203ae8,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -475461a2-3bd2-43fd-a5aa-7ce424203ae8,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -475461a2-3bd2-43fd-a5aa-7ce424203ae8,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -475461a2-3bd2-43fd-a5aa-7ce424203ae8,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -4dd7824b-4b41-4402-bc42-de016e1bfcd9,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -4dd7824b-4b41-4402-bc42-de016e1bfcd9,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -4dd7824b-4b41-4402-bc42-de016e1bfcd9,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -4dd7824b-4b41-4402-bc42-de016e1bfcd9,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -4dd7824b-4b41-4402-bc42-de016e1bfcd9,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -4dd7824b-4b41-4402-bc42-de016e1bfcd9,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -4dd7824b-4b41-4402-bc42-de016e1bfcd9,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -5300158c-92cb-40fe-bc14-a449d7b3c1c5,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -5300158c-92cb-40fe-bc14-a449d7b3c1c5,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -5300158c-92cb-40fe-bc14-a449d7b3c1c5,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -5300158c-92cb-40fe-bc14-a449d7b3c1c5,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -5300158c-92cb-40fe-bc14-a449d7b3c1c5,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -5300158c-92cb-40fe-bc14-a449d7b3c1c5,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -5300158c-92cb-40fe-bc14-a449d7b3c1c5,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -5378726d-7f2b-4c83-9762-eaf385915fa7,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -5378726d-7f2b-4c83-9762-eaf385915fa7,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -5378726d-7f2b-4c83-9762-eaf385915fa7,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -5378726d-7f2b-4c83-9762-eaf385915fa7,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -5378726d-7f2b-4c83-9762-eaf385915fa7,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -5378726d-7f2b-4c83-9762-eaf385915fa7,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -5378726d-7f2b-4c83-9762-eaf385915fa7,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -549d3ef5-a05e-4b8e-acba-3d70f26a82f6,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -549d3ef5-a05e-4b8e-acba-3d70f26a82f6,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -549d3ef5-a05e-4b8e-acba-3d70f26a82f6,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -549d3ef5-a05e-4b8e-acba-3d70f26a82f6,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -549d3ef5-a05e-4b8e-acba-3d70f26a82f6,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -549d3ef5-a05e-4b8e-acba-3d70f26a82f6,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -549d3ef5-a05e-4b8e-acba-3d70f26a82f6,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -55cde383-2eb1-42d9-b5c6-698d6eade389,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -55cde383-2eb1-42d9-b5c6-698d6eade389,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -55cde383-2eb1-42d9-b5c6-698d6eade389,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -55cde383-2eb1-42d9-b5c6-698d6eade389,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -55cde383-2eb1-42d9-b5c6-698d6eade389,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -55cde383-2eb1-42d9-b5c6-698d6eade389,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -55cde383-2eb1-42d9-b5c6-698d6eade389,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -5ef84858-0480-4a34-8f43-fc962fe627b2,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -5ef84858-0480-4a34-8f43-fc962fe627b2,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -5ef84858-0480-4a34-8f43-fc962fe627b2,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -5ef84858-0480-4a34-8f43-fc962fe627b2,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -5ef84858-0480-4a34-8f43-fc962fe627b2,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -5ef84858-0480-4a34-8f43-fc962fe627b2,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -5ef84858-0480-4a34-8f43-fc962fe627b2,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -6464e20b-55ec-4685-be3e-55bc3b9602d4,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -6464e20b-55ec-4685-be3e-55bc3b9602d4,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -6464e20b-55ec-4685-be3e-55bc3b9602d4,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -6464e20b-55ec-4685-be3e-55bc3b9602d4,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -6464e20b-55ec-4685-be3e-55bc3b9602d4,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -6464e20b-55ec-4685-be3e-55bc3b9602d4,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -6464e20b-55ec-4685-be3e-55bc3b9602d4,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -66b38727-96ea-43ad-bff7-5f4df5d779a9,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -66b38727-96ea-43ad-bff7-5f4df5d779a9,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -66b38727-96ea-43ad-bff7-5f4df5d779a9,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -66b38727-96ea-43ad-bff7-5f4df5d779a9,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -66b38727-96ea-43ad-bff7-5f4df5d779a9,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -66b38727-96ea-43ad-bff7-5f4df5d779a9,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -66b38727-96ea-43ad-bff7-5f4df5d779a9,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -6794fbaf-e715-4e22-bafa-b7e594550ff7,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -6794fbaf-e715-4e22-bafa-b7e594550ff7,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -6794fbaf-e715-4e22-bafa-b7e594550ff7,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -6794fbaf-e715-4e22-bafa-b7e594550ff7,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -6794fbaf-e715-4e22-bafa-b7e594550ff7,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -6794fbaf-e715-4e22-bafa-b7e594550ff7,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -6794fbaf-e715-4e22-bafa-b7e594550ff7,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -77fc5f45-e51d-41e2-8356-daa27ebd8268,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -77fc5f45-e51d-41e2-8356-daa27ebd8268,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -77fc5f45-e51d-41e2-8356-daa27ebd8268,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -77fc5f45-e51d-41e2-8356-daa27ebd8268,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -77fc5f45-e51d-41e2-8356-daa27ebd8268,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -77fc5f45-e51d-41e2-8356-daa27ebd8268,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -77fc5f45-e51d-41e2-8356-daa27ebd8268,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -80787532-559e-4999-ac25-75c462ce4ef1,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -80787532-559e-4999-ac25-75c462ce4ef1,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -80787532-559e-4999-ac25-75c462ce4ef1,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -80787532-559e-4999-ac25-75c462ce4ef1,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -80787532-559e-4999-ac25-75c462ce4ef1,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -80787532-559e-4999-ac25-75c462ce4ef1,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -80787532-559e-4999-ac25-75c462ce4ef1,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -8922ff1b-4bc5-41b4-8512-5aad1517e2eb,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -8922ff1b-4bc5-41b4-8512-5aad1517e2eb,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -8922ff1b-4bc5-41b4-8512-5aad1517e2eb,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -8922ff1b-4bc5-41b4-8512-5aad1517e2eb,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -8922ff1b-4bc5-41b4-8512-5aad1517e2eb,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -8922ff1b-4bc5-41b4-8512-5aad1517e2eb,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -8922ff1b-4bc5-41b4-8512-5aad1517e2eb,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -8ddb74fc-46d5-4e94-a0ee-6761b292ae95,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -8ddb74fc-46d5-4e94-a0ee-6761b292ae95,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -8ddb74fc-46d5-4e94-a0ee-6761b292ae95,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -8ddb74fc-46d5-4e94-a0ee-6761b292ae95,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -8ddb74fc-46d5-4e94-a0ee-6761b292ae95,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -8ddb74fc-46d5-4e94-a0ee-6761b292ae95,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -8ddb74fc-46d5-4e94-a0ee-6761b292ae95,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -8f3ad6ad-a457-484e-a455-f0711e77b2ba,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -8f3ad6ad-a457-484e-a455-f0711e77b2ba,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -8f3ad6ad-a457-484e-a455-f0711e77b2ba,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -8f3ad6ad-a457-484e-a455-f0711e77b2ba,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -8f3ad6ad-a457-484e-a455-f0711e77b2ba,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -8f3ad6ad-a457-484e-a455-f0711e77b2ba,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -8f3ad6ad-a457-484e-a455-f0711e77b2ba,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -9e598086-27bb-4e50-8988-9a40eb3c178f,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -9e598086-27bb-4e50-8988-9a40eb3c178f,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -9e598086-27bb-4e50-8988-9a40eb3c178f,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -9e598086-27bb-4e50-8988-9a40eb3c178f,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -9e598086-27bb-4e50-8988-9a40eb3c178f,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -9e598086-27bb-4e50-8988-9a40eb3c178f,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -9e598086-27bb-4e50-8988-9a40eb3c178f,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -a16f311d-d6dc-486e-8eb0-7cd53a5333ee,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -a16f311d-d6dc-486e-8eb0-7cd53a5333ee,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -a16f311d-d6dc-486e-8eb0-7cd53a5333ee,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -a16f311d-d6dc-486e-8eb0-7cd53a5333ee,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -a16f311d-d6dc-486e-8eb0-7cd53a5333ee,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -a16f311d-d6dc-486e-8eb0-7cd53a5333ee,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -a16f311d-d6dc-486e-8eb0-7cd53a5333ee,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -aa5a32b3-3993-4eb5-afce-489d0e7413cf,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -aa5a32b3-3993-4eb5-afce-489d0e7413cf,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -aa5a32b3-3993-4eb5-afce-489d0e7413cf,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -aa5a32b3-3993-4eb5-afce-489d0e7413cf,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -aa5a32b3-3993-4eb5-afce-489d0e7413cf,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -aa5a32b3-3993-4eb5-afce-489d0e7413cf,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -aa5a32b3-3993-4eb5-afce-489d0e7413cf,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -abecd476-3911-4a6b-a1c6-b8ecea2fe63e,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -abecd476-3911-4a6b-a1c6-b8ecea2fe63e,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -abecd476-3911-4a6b-a1c6-b8ecea2fe63e,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -abecd476-3911-4a6b-a1c6-b8ecea2fe63e,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -abecd476-3911-4a6b-a1c6-b8ecea2fe63e,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -abecd476-3911-4a6b-a1c6-b8ecea2fe63e,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -abecd476-3911-4a6b-a1c6-b8ecea2fe63e,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -ad679e19-3e17-4c67-8b0d-dd4f7d862207,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -ad679e19-3e17-4c67-8b0d-dd4f7d862207,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -ad679e19-3e17-4c67-8b0d-dd4f7d862207,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -ad679e19-3e17-4c67-8b0d-dd4f7d862207,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -ad679e19-3e17-4c67-8b0d-dd4f7d862207,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -ad679e19-3e17-4c67-8b0d-dd4f7d862207,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -ad679e19-3e17-4c67-8b0d-dd4f7d862207,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -b77b04ef-5eda-418c-a6fb-528a2d0a171a,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -b77b04ef-5eda-418c-a6fb-528a2d0a171a,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -b77b04ef-5eda-418c-a6fb-528a2d0a171a,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -b77b04ef-5eda-418c-a6fb-528a2d0a171a,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -b77b04ef-5eda-418c-a6fb-528a2d0a171a,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -b77b04ef-5eda-418c-a6fb-528a2d0a171a,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -b77b04ef-5eda-418c-a6fb-528a2d0a171a,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -b8eccdce-7261-4402-9aa0-7360ae6bf53b,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -b8eccdce-7261-4402-9aa0-7360ae6bf53b,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -b8eccdce-7261-4402-9aa0-7360ae6bf53b,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -b8eccdce-7261-4402-9aa0-7360ae6bf53b,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -b8eccdce-7261-4402-9aa0-7360ae6bf53b,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -b8eccdce-7261-4402-9aa0-7360ae6bf53b,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -b8eccdce-7261-4402-9aa0-7360ae6bf53b,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -badbd940-9839-4472-aa66-3382d8823c80,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -badbd940-9839-4472-aa66-3382d8823c80,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -badbd940-9839-4472-aa66-3382d8823c80,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -badbd940-9839-4472-aa66-3382d8823c80,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -badbd940-9839-4472-aa66-3382d8823c80,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -badbd940-9839-4472-aa66-3382d8823c80,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -badbd940-9839-4472-aa66-3382d8823c80,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -bb9c4fc1-795a-4492-b065-1f497fe18bb2,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -bb9c4fc1-795a-4492-b065-1f497fe18bb2,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -bb9c4fc1-795a-4492-b065-1f497fe18bb2,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -bb9c4fc1-795a-4492-b065-1f497fe18bb2,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -bb9c4fc1-795a-4492-b065-1f497fe18bb2,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -bb9c4fc1-795a-4492-b065-1f497fe18bb2,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -bb9c4fc1-795a-4492-b065-1f497fe18bb2,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -be4b757d-70f1-464c-bb29-6f9a0f6cb05e,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -be4b757d-70f1-464c-bb29-6f9a0f6cb05e,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -be4b757d-70f1-464c-bb29-6f9a0f6cb05e,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -be4b757d-70f1-464c-bb29-6f9a0f6cb05e,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -be4b757d-70f1-464c-bb29-6f9a0f6cb05e,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -be4b757d-70f1-464c-bb29-6f9a0f6cb05e,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -be4b757d-70f1-464c-bb29-6f9a0f6cb05e,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -c39927f7-d23a-475c-b325-c1de4b51e280,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -c39927f7-d23a-475c-b325-c1de4b51e280,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -c39927f7-d23a-475c-b325-c1de4b51e280,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -c39927f7-d23a-475c-b325-c1de4b51e280,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -c39927f7-d23a-475c-b325-c1de4b51e280,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -c39927f7-d23a-475c-b325-c1de4b51e280,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -c39927f7-d23a-475c-b325-c1de4b51e280,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -c892bf2f-a7bc-407e-9508-c778a9b8761c,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -c892bf2f-a7bc-407e-9508-c778a9b8761c,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -c892bf2f-a7bc-407e-9508-c778a9b8761c,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -c892bf2f-a7bc-407e-9508-c778a9b8761c,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -c892bf2f-a7bc-407e-9508-c778a9b8761c,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -c892bf2f-a7bc-407e-9508-c778a9b8761c,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -c892bf2f-a7bc-407e-9508-c778a9b8761c,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -d12274a5-9e03-4c78-ae3e-6cc27e91d737,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -d12274a5-9e03-4c78-ae3e-6cc27e91d737,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -d12274a5-9e03-4c78-ae3e-6cc27e91d737,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -d12274a5-9e03-4c78-ae3e-6cc27e91d737,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -d12274a5-9e03-4c78-ae3e-6cc27e91d737,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -d12274a5-9e03-4c78-ae3e-6cc27e91d737,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -d12274a5-9e03-4c78-ae3e-6cc27e91d737,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -d554ba19-e081-4979-9c0c-a72cd6e5e8ea,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -d554ba19-e081-4979-9c0c-a72cd6e5e8ea,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -d554ba19-e081-4979-9c0c-a72cd6e5e8ea,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -d554ba19-e081-4979-9c0c-a72cd6e5e8ea,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -d554ba19-e081-4979-9c0c-a72cd6e5e8ea,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -d554ba19-e081-4979-9c0c-a72cd6e5e8ea,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -d554ba19-e081-4979-9c0c-a72cd6e5e8ea,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -da8db730-f051-42d0-a2db-a3577d96e9bd,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -da8db730-f051-42d0-a2db-a3577d96e9bd,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -da8db730-f051-42d0-a2db-a3577d96e9bd,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -da8db730-f051-42d0-a2db-a3577d96e9bd,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -da8db730-f051-42d0-a2db-a3577d96e9bd,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -da8db730-f051-42d0-a2db-a3577d96e9bd,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -da8db730-f051-42d0-a2db-a3577d96e9bd,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -e35f5823-4533-49e5-9652-79d733be6bef,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -e35f5823-4533-49e5-9652-79d733be6bef,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -e35f5823-4533-49e5-9652-79d733be6bef,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -e35f5823-4533-49e5-9652-79d733be6bef,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -e35f5823-4533-49e5-9652-79d733be6bef,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -e35f5823-4533-49e5-9652-79d733be6bef,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -e35f5823-4533-49e5-9652-79d733be6bef,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -e87fbe4b-74ef-44e4-8e36-70b2abb48895,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -e87fbe4b-74ef-44e4-8e36-70b2abb48895,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -e87fbe4b-74ef-44e4-8e36-70b2abb48895,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -e87fbe4b-74ef-44e4-8e36-70b2abb48895,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -e87fbe4b-74ef-44e4-8e36-70b2abb48895,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -e87fbe4b-74ef-44e4-8e36-70b2abb48895,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -e87fbe4b-74ef-44e4-8e36-70b2abb48895,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -eaf99c09-419d-4162-8417-5a9d7e042cd4,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -eaf99c09-419d-4162-8417-5a9d7e042cd4,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -eaf99c09-419d-4162-8417-5a9d7e042cd4,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -eaf99c09-419d-4162-8417-5a9d7e042cd4,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -eaf99c09-419d-4162-8417-5a9d7e042cd4,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -eaf99c09-419d-4162-8417-5a9d7e042cd4,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -eaf99c09-419d-4162-8417-5a9d7e042cd4,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -eb373264-da60-4e98-af7c-e3021fdd8d4b,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -eb373264-da60-4e98-af7c-e3021fdd8d4b,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -eb373264-da60-4e98-af7c-e3021fdd8d4b,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -eb373264-da60-4e98-af7c-e3021fdd8d4b,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -eb373264-da60-4e98-af7c-e3021fdd8d4b,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -eb373264-da60-4e98-af7c-e3021fdd8d4b,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -eb373264-da60-4e98-af7c-e3021fdd8d4b,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -f1dd0d5f-c410-484b-984c-2ba38cee79ba,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -f1dd0d5f-c410-484b-984c-2ba38cee79ba,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -f1dd0d5f-c410-484b-984c-2ba38cee79ba,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -f1dd0d5f-c410-484b-984c-2ba38cee79ba,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -f1dd0d5f-c410-484b-984c-2ba38cee79ba,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -f1dd0d5f-c410-484b-984c-2ba38cee79ba,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -f1dd0d5f-c410-484b-984c-2ba38cee79ba,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -f1fd855c-802c-417a-ab7c-14a3c9daafc6,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -f1fd855c-802c-417a-ab7c-14a3c9daafc6,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -f1fd855c-802c-417a-ab7c-14a3c9daafc6,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -f1fd855c-802c-417a-ab7c-14a3c9daafc6,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -f1fd855c-802c-417a-ab7c-14a3c9daafc6,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -f1fd855c-802c-417a-ab7c-14a3c9daafc6,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -f1fd855c-802c-417a-ab7c-14a3c9daafc6,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -f41b6458-2358-43e7-ae34-d2a0fbe083d8,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -f41b6458-2358-43e7-ae34-d2a0fbe083d8,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -f41b6458-2358-43e7-ae34-d2a0fbe083d8,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -f41b6458-2358-43e7-ae34-d2a0fbe083d8,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -f41b6458-2358-43e7-ae34-d2a0fbe083d8,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -f41b6458-2358-43e7-ae34-d2a0fbe083d8,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -f41b6458-2358-43e7-ae34-d2a0fbe083d8,http://synthetichealth.github.io/synthea/quality-adjusted-life-years diff --git a/fhir-server/src/test/resources/responses/ParserTest/testExtensionsCurrentResource.tsv b/fhir-server/src/test/resources/responses/ParserTest/testExtensionsCurrentResource.tsv new file mode 100644 index 0000000000..a1a17d32d4 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testExtensionsCurrentResource.tsv @@ -0,0 +1,497 @@ +01fac610-c309-46f8-9a0f-6b0f55c9915a http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +01fac610-c309-46f8-9a0f-6b0f55c9915a http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +01fac610-c309-46f8-9a0f-6b0f55c9915a http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +01fac610-c309-46f8-9a0f-6b0f55c9915a http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +01fac610-c309-46f8-9a0f-6b0f55c9915a http://hl7.org/fhir/StructureDefinition/patient-birthPlace +01fac610-c309-46f8-9a0f-6b0f55c9915a http://synthetichealth.github.io/synthea/disability-adjusted-life-years +01fac610-c309-46f8-9a0f-6b0f55c9915a http://synthetichealth.github.io/synthea/quality-adjusted-life-years +031f1c86-16ec-42e1-a155-81456e778fed http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +031f1c86-16ec-42e1-a155-81456e778fed http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +031f1c86-16ec-42e1-a155-81456e778fed http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +031f1c86-16ec-42e1-a155-81456e778fed http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +031f1c86-16ec-42e1-a155-81456e778fed http://hl7.org/fhir/StructureDefinition/patient-birthPlace +031f1c86-16ec-42e1-a155-81456e778fed http://synthetichealth.github.io/synthea/disability-adjusted-life-years +031f1c86-16ec-42e1-a155-81456e778fed http://synthetichealth.github.io/synthea/quality-adjusted-life-years +0447de38-1f2a-411c-9fd2-a9c62ab1f221 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +0447de38-1f2a-411c-9fd2-a9c62ab1f221 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +0447de38-1f2a-411c-9fd2-a9c62ab1f221 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +0447de38-1f2a-411c-9fd2-a9c62ab1f221 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +0447de38-1f2a-411c-9fd2-a9c62ab1f221 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +0447de38-1f2a-411c-9fd2-a9c62ab1f221 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +0447de38-1f2a-411c-9fd2-a9c62ab1f221 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +061521f0-7f7e-41a5-ad35-b30fe958dea7 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +061521f0-7f7e-41a5-ad35-b30fe958dea7 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +061521f0-7f7e-41a5-ad35-b30fe958dea7 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +061521f0-7f7e-41a5-ad35-b30fe958dea7 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +061521f0-7f7e-41a5-ad35-b30fe958dea7 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +061521f0-7f7e-41a5-ad35-b30fe958dea7 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +061521f0-7f7e-41a5-ad35-b30fe958dea7 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +06e446da-6d66-4d97-a457-4abf2a0d2e24 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +06e446da-6d66-4d97-a457-4abf2a0d2e24 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +06e446da-6d66-4d97-a457-4abf2a0d2e24 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +06e446da-6d66-4d97-a457-4abf2a0d2e24 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +06e446da-6d66-4d97-a457-4abf2a0d2e24 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +06e446da-6d66-4d97-a457-4abf2a0d2e24 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +06e446da-6d66-4d97-a457-4abf2a0d2e24 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +1146cf52-573f-4667-97c3-abf235f9a83b http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +1146cf52-573f-4667-97c3-abf235f9a83b http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +1146cf52-573f-4667-97c3-abf235f9a83b http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +1146cf52-573f-4667-97c3-abf235f9a83b http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +1146cf52-573f-4667-97c3-abf235f9a83b http://hl7.org/fhir/StructureDefinition/patient-birthPlace +1146cf52-573f-4667-97c3-abf235f9a83b http://synthetichealth.github.io/synthea/disability-adjusted-life-years +1146cf52-573f-4667-97c3-abf235f9a83b http://synthetichealth.github.io/synthea/quality-adjusted-life-years +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +171ad058-c082-46ab-b9cc-d7bba6e4e3cd http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +171ad058-c082-46ab-b9cc-d7bba6e4e3cd http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +171ad058-c082-46ab-b9cc-d7bba6e4e3cd http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +171ad058-c082-46ab-b9cc-d7bba6e4e3cd http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +171ad058-c082-46ab-b9cc-d7bba6e4e3cd http://hl7.org/fhir/StructureDefinition/patient-birthPlace +171ad058-c082-46ab-b9cc-d7bba6e4e3cd http://synthetichealth.github.io/synthea/disability-adjusted-life-years +171ad058-c082-46ab-b9cc-d7bba6e4e3cd http://synthetichealth.github.io/synthea/quality-adjusted-life-years +1cea8432-f8a0-4746-ab5c-8f195fc07c55 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +1cea8432-f8a0-4746-ab5c-8f195fc07c55 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +1cea8432-f8a0-4746-ab5c-8f195fc07c55 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +1cea8432-f8a0-4746-ab5c-8f195fc07c55 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +1cea8432-f8a0-4746-ab5c-8f195fc07c55 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +1cea8432-f8a0-4746-ab5c-8f195fc07c55 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +1cea8432-f8a0-4746-ab5c-8f195fc07c55 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +21372b39-2be0-4d0d-85c4-5d7e250d8f78 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +21372b39-2be0-4d0d-85c4-5d7e250d8f78 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +21372b39-2be0-4d0d-85c4-5d7e250d8f78 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +21372b39-2be0-4d0d-85c4-5d7e250d8f78 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +21372b39-2be0-4d0d-85c4-5d7e250d8f78 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +21372b39-2be0-4d0d-85c4-5d7e250d8f78 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +21372b39-2be0-4d0d-85c4-5d7e250d8f78 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +248e951c-e0ce-4e4b-afae-6273fd109c9d http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +248e951c-e0ce-4e4b-afae-6273fd109c9d http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +248e951c-e0ce-4e4b-afae-6273fd109c9d http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +248e951c-e0ce-4e4b-afae-6273fd109c9d http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +248e951c-e0ce-4e4b-afae-6273fd109c9d http://hl7.org/fhir/StructureDefinition/patient-birthPlace +248e951c-e0ce-4e4b-afae-6273fd109c9d http://synthetichealth.github.io/synthea/disability-adjusted-life-years +248e951c-e0ce-4e4b-afae-6273fd109c9d http://synthetichealth.github.io/synthea/quality-adjusted-life-years +251ddff9-1588-4790-8f40-cd6ab1d9b7fc http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +251ddff9-1588-4790-8f40-cd6ab1d9b7fc http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +251ddff9-1588-4790-8f40-cd6ab1d9b7fc http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +251ddff9-1588-4790-8f40-cd6ab1d9b7fc http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +251ddff9-1588-4790-8f40-cd6ab1d9b7fc http://hl7.org/fhir/StructureDefinition/patient-birthPlace +251ddff9-1588-4790-8f40-cd6ab1d9b7fc http://synthetichealth.github.io/synthea/disability-adjusted-life-years +251ddff9-1588-4790-8f40-cd6ab1d9b7fc http://synthetichealth.github.io/synthea/quality-adjusted-life-years +258191b8-2b42-4473-9ba2-be0ba3561767 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +258191b8-2b42-4473-9ba2-be0ba3561767 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +258191b8-2b42-4473-9ba2-be0ba3561767 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +258191b8-2b42-4473-9ba2-be0ba3561767 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +258191b8-2b42-4473-9ba2-be0ba3561767 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +258191b8-2b42-4473-9ba2-be0ba3561767 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +258191b8-2b42-4473-9ba2-be0ba3561767 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +29386b12-9af9-4c21-9f82-858998b388bb http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +29386b12-9af9-4c21-9f82-858998b388bb http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +29386b12-9af9-4c21-9f82-858998b388bb http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +29386b12-9af9-4c21-9f82-858998b388bb http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +29386b12-9af9-4c21-9f82-858998b388bb http://hl7.org/fhir/StructureDefinition/patient-birthPlace +29386b12-9af9-4c21-9f82-858998b388bb http://synthetichealth.github.io/synthea/disability-adjusted-life-years +29386b12-9af9-4c21-9f82-858998b388bb http://synthetichealth.github.io/synthea/quality-adjusted-life-years +31393667-dd7f-4c94-90c3-6cde75c67d3e http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +31393667-dd7f-4c94-90c3-6cde75c67d3e http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +31393667-dd7f-4c94-90c3-6cde75c67d3e http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +31393667-dd7f-4c94-90c3-6cde75c67d3e http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +31393667-dd7f-4c94-90c3-6cde75c67d3e http://hl7.org/fhir/StructureDefinition/patient-birthPlace +31393667-dd7f-4c94-90c3-6cde75c67d3e http://synthetichealth.github.io/synthea/disability-adjusted-life-years +31393667-dd7f-4c94-90c3-6cde75c67d3e http://synthetichealth.github.io/synthea/quality-adjusted-life-years +31925a68-7a48-412c-b4f8-aef92498dda1 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +31925a68-7a48-412c-b4f8-aef92498dda1 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +31925a68-7a48-412c-b4f8-aef92498dda1 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +31925a68-7a48-412c-b4f8-aef92498dda1 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +31925a68-7a48-412c-b4f8-aef92498dda1 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +31925a68-7a48-412c-b4f8-aef92498dda1 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +31925a68-7a48-412c-b4f8-aef92498dda1 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +38171e22-b35d-4161-be15-80243be37b99 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +38171e22-b35d-4161-be15-80243be37b99 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +38171e22-b35d-4161-be15-80243be37b99 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +38171e22-b35d-4161-be15-80243be37b99 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +38171e22-b35d-4161-be15-80243be37b99 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +38171e22-b35d-4161-be15-80243be37b99 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +38171e22-b35d-4161-be15-80243be37b99 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +39170d67-8205-4636-84d7-d8575115d14c http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +39170d67-8205-4636-84d7-d8575115d14c http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +39170d67-8205-4636-84d7-d8575115d14c http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +39170d67-8205-4636-84d7-d8575115d14c http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +39170d67-8205-4636-84d7-d8575115d14c http://hl7.org/fhir/StructureDefinition/patient-birthPlace +39170d67-8205-4636-84d7-d8575115d14c http://synthetichealth.github.io/synthea/disability-adjusted-life-years +39170d67-8205-4636-84d7-d8575115d14c http://synthetichealth.github.io/synthea/quality-adjusted-life-years +4025ceb3-04a6-41fb-8569-16a8dcce7ccc http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +4025ceb3-04a6-41fb-8569-16a8dcce7ccc http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +4025ceb3-04a6-41fb-8569-16a8dcce7ccc http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +4025ceb3-04a6-41fb-8569-16a8dcce7ccc http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +4025ceb3-04a6-41fb-8569-16a8dcce7ccc http://hl7.org/fhir/StructureDefinition/patient-birthPlace +4025ceb3-04a6-41fb-8569-16a8dcce7ccc http://synthetichealth.github.io/synthea/disability-adjusted-life-years +4025ceb3-04a6-41fb-8569-16a8dcce7ccc http://synthetichealth.github.io/synthea/quality-adjusted-life-years +46d69851-eca3-42e2-b142-88c5578f6cff http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +46d69851-eca3-42e2-b142-88c5578f6cff http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +46d69851-eca3-42e2-b142-88c5578f6cff http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +46d69851-eca3-42e2-b142-88c5578f6cff http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +46d69851-eca3-42e2-b142-88c5578f6cff http://hl7.org/fhir/StructureDefinition/patient-birthPlace +46d69851-eca3-42e2-b142-88c5578f6cff http://synthetichealth.github.io/synthea/disability-adjusted-life-years +46d69851-eca3-42e2-b142-88c5578f6cff http://synthetichealth.github.io/synthea/quality-adjusted-life-years +475461a2-3bd2-43fd-a5aa-7ce424203ae8 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +475461a2-3bd2-43fd-a5aa-7ce424203ae8 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +475461a2-3bd2-43fd-a5aa-7ce424203ae8 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +475461a2-3bd2-43fd-a5aa-7ce424203ae8 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +475461a2-3bd2-43fd-a5aa-7ce424203ae8 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +475461a2-3bd2-43fd-a5aa-7ce424203ae8 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +475461a2-3bd2-43fd-a5aa-7ce424203ae8 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +4dd7824b-4b41-4402-bc42-de016e1bfcd9 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +4dd7824b-4b41-4402-bc42-de016e1bfcd9 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +4dd7824b-4b41-4402-bc42-de016e1bfcd9 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +4dd7824b-4b41-4402-bc42-de016e1bfcd9 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +4dd7824b-4b41-4402-bc42-de016e1bfcd9 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +4dd7824b-4b41-4402-bc42-de016e1bfcd9 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +4dd7824b-4b41-4402-bc42-de016e1bfcd9 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +5300158c-92cb-40fe-bc14-a449d7b3c1c5 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +5300158c-92cb-40fe-bc14-a449d7b3c1c5 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +5300158c-92cb-40fe-bc14-a449d7b3c1c5 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +5300158c-92cb-40fe-bc14-a449d7b3c1c5 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +5300158c-92cb-40fe-bc14-a449d7b3c1c5 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +5300158c-92cb-40fe-bc14-a449d7b3c1c5 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +5300158c-92cb-40fe-bc14-a449d7b3c1c5 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +5378726d-7f2b-4c83-9762-eaf385915fa7 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +5378726d-7f2b-4c83-9762-eaf385915fa7 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +5378726d-7f2b-4c83-9762-eaf385915fa7 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +5378726d-7f2b-4c83-9762-eaf385915fa7 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +5378726d-7f2b-4c83-9762-eaf385915fa7 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +5378726d-7f2b-4c83-9762-eaf385915fa7 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +5378726d-7f2b-4c83-9762-eaf385915fa7 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +55cde383-2eb1-42d9-b5c6-698d6eade389 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +55cde383-2eb1-42d9-b5c6-698d6eade389 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +55cde383-2eb1-42d9-b5c6-698d6eade389 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +55cde383-2eb1-42d9-b5c6-698d6eade389 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +55cde383-2eb1-42d9-b5c6-698d6eade389 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +55cde383-2eb1-42d9-b5c6-698d6eade389 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +55cde383-2eb1-42d9-b5c6-698d6eade389 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +5ef84858-0480-4a34-8f43-fc962fe627b2 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +5ef84858-0480-4a34-8f43-fc962fe627b2 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +5ef84858-0480-4a34-8f43-fc962fe627b2 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +5ef84858-0480-4a34-8f43-fc962fe627b2 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +5ef84858-0480-4a34-8f43-fc962fe627b2 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +5ef84858-0480-4a34-8f43-fc962fe627b2 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +5ef84858-0480-4a34-8f43-fc962fe627b2 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +6464e20b-55ec-4685-be3e-55bc3b9602d4 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +6464e20b-55ec-4685-be3e-55bc3b9602d4 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +6464e20b-55ec-4685-be3e-55bc3b9602d4 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +6464e20b-55ec-4685-be3e-55bc3b9602d4 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +6464e20b-55ec-4685-be3e-55bc3b9602d4 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +6464e20b-55ec-4685-be3e-55bc3b9602d4 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +6464e20b-55ec-4685-be3e-55bc3b9602d4 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +66b38727-96ea-43ad-bff7-5f4df5d779a9 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +66b38727-96ea-43ad-bff7-5f4df5d779a9 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +66b38727-96ea-43ad-bff7-5f4df5d779a9 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +66b38727-96ea-43ad-bff7-5f4df5d779a9 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +66b38727-96ea-43ad-bff7-5f4df5d779a9 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +66b38727-96ea-43ad-bff7-5f4df5d779a9 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +66b38727-96ea-43ad-bff7-5f4df5d779a9 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +6794fbaf-e715-4e22-bafa-b7e594550ff7 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +6794fbaf-e715-4e22-bafa-b7e594550ff7 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +6794fbaf-e715-4e22-bafa-b7e594550ff7 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +6794fbaf-e715-4e22-bafa-b7e594550ff7 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +6794fbaf-e715-4e22-bafa-b7e594550ff7 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +6794fbaf-e715-4e22-bafa-b7e594550ff7 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +6794fbaf-e715-4e22-bafa-b7e594550ff7 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +77fc5f45-e51d-41e2-8356-daa27ebd8268 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +77fc5f45-e51d-41e2-8356-daa27ebd8268 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +77fc5f45-e51d-41e2-8356-daa27ebd8268 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +77fc5f45-e51d-41e2-8356-daa27ebd8268 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +77fc5f45-e51d-41e2-8356-daa27ebd8268 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +77fc5f45-e51d-41e2-8356-daa27ebd8268 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +77fc5f45-e51d-41e2-8356-daa27ebd8268 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +7d18555a-54c9-4c0a-bf3b-6305d0392a2a http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +7d18555a-54c9-4c0a-bf3b-6305d0392a2a http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +7d18555a-54c9-4c0a-bf3b-6305d0392a2a http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +7d18555a-54c9-4c0a-bf3b-6305d0392a2a http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +7d18555a-54c9-4c0a-bf3b-6305d0392a2a http://hl7.org/fhir/StructureDefinition/patient-birthPlace +7d18555a-54c9-4c0a-bf3b-6305d0392a2a http://synthetichealth.github.io/synthea/disability-adjusted-life-years +7d18555a-54c9-4c0a-bf3b-6305d0392a2a http://synthetichealth.github.io/synthea/quality-adjusted-life-years +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +80787532-559e-4999-ac25-75c462ce4ef1 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +80787532-559e-4999-ac25-75c462ce4ef1 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +80787532-559e-4999-ac25-75c462ce4ef1 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +80787532-559e-4999-ac25-75c462ce4ef1 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +80787532-559e-4999-ac25-75c462ce4ef1 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +80787532-559e-4999-ac25-75c462ce4ef1 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +80787532-559e-4999-ac25-75c462ce4ef1 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +8922ff1b-4bc5-41b4-8512-5aad1517e2eb http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +8922ff1b-4bc5-41b4-8512-5aad1517e2eb http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +8922ff1b-4bc5-41b4-8512-5aad1517e2eb http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +8922ff1b-4bc5-41b4-8512-5aad1517e2eb http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +8922ff1b-4bc5-41b4-8512-5aad1517e2eb http://hl7.org/fhir/StructureDefinition/patient-birthPlace +8922ff1b-4bc5-41b4-8512-5aad1517e2eb http://synthetichealth.github.io/synthea/disability-adjusted-life-years +8922ff1b-4bc5-41b4-8512-5aad1517e2eb http://synthetichealth.github.io/synthea/quality-adjusted-life-years +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +8f3ad6ad-a457-484e-a455-f0711e77b2ba http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +8f3ad6ad-a457-484e-a455-f0711e77b2ba http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +8f3ad6ad-a457-484e-a455-f0711e77b2ba http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +8f3ad6ad-a457-484e-a455-f0711e77b2ba http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +8f3ad6ad-a457-484e-a455-f0711e77b2ba http://hl7.org/fhir/StructureDefinition/patient-birthPlace +8f3ad6ad-a457-484e-a455-f0711e77b2ba http://synthetichealth.github.io/synthea/disability-adjusted-life-years +8f3ad6ad-a457-484e-a455-f0711e77b2ba http://synthetichealth.github.io/synthea/quality-adjusted-life-years +9e598086-27bb-4e50-8988-9a40eb3c178f http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +9e598086-27bb-4e50-8988-9a40eb3c178f http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +9e598086-27bb-4e50-8988-9a40eb3c178f http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +9e598086-27bb-4e50-8988-9a40eb3c178f http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +9e598086-27bb-4e50-8988-9a40eb3c178f http://hl7.org/fhir/StructureDefinition/patient-birthPlace +9e598086-27bb-4e50-8988-9a40eb3c178f http://synthetichealth.github.io/synthea/disability-adjusted-life-years +9e598086-27bb-4e50-8988-9a40eb3c178f http://synthetichealth.github.io/synthea/quality-adjusted-life-years +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc http://hl7.org/fhir/StructureDefinition/patient-birthPlace +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc http://synthetichealth.github.io/synthea/disability-adjusted-life-years +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc http://synthetichealth.github.io/synthea/quality-adjusted-life-years +a16f311d-d6dc-486e-8eb0-7cd53a5333ee http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +a16f311d-d6dc-486e-8eb0-7cd53a5333ee http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +a16f311d-d6dc-486e-8eb0-7cd53a5333ee http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +a16f311d-d6dc-486e-8eb0-7cd53a5333ee http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +a16f311d-d6dc-486e-8eb0-7cd53a5333ee http://hl7.org/fhir/StructureDefinition/patient-birthPlace +a16f311d-d6dc-486e-8eb0-7cd53a5333ee http://synthetichealth.github.io/synthea/disability-adjusted-life-years +a16f311d-d6dc-486e-8eb0-7cd53a5333ee http://synthetichealth.github.io/synthea/quality-adjusted-life-years +aa5a32b3-3993-4eb5-afce-489d0e7413cf http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +aa5a32b3-3993-4eb5-afce-489d0e7413cf http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +aa5a32b3-3993-4eb5-afce-489d0e7413cf http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +aa5a32b3-3993-4eb5-afce-489d0e7413cf http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +aa5a32b3-3993-4eb5-afce-489d0e7413cf http://hl7.org/fhir/StructureDefinition/patient-birthPlace +aa5a32b3-3993-4eb5-afce-489d0e7413cf http://synthetichealth.github.io/synthea/disability-adjusted-life-years +aa5a32b3-3993-4eb5-afce-489d0e7413cf http://synthetichealth.github.io/synthea/quality-adjusted-life-years +abecd476-3911-4a6b-a1c6-b8ecea2fe63e http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +abecd476-3911-4a6b-a1c6-b8ecea2fe63e http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +abecd476-3911-4a6b-a1c6-b8ecea2fe63e http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +abecd476-3911-4a6b-a1c6-b8ecea2fe63e http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +abecd476-3911-4a6b-a1c6-b8ecea2fe63e http://hl7.org/fhir/StructureDefinition/patient-birthPlace +abecd476-3911-4a6b-a1c6-b8ecea2fe63e http://synthetichealth.github.io/synthea/disability-adjusted-life-years +abecd476-3911-4a6b-a1c6-b8ecea2fe63e http://synthetichealth.github.io/synthea/quality-adjusted-life-years +ad679e19-3e17-4c67-8b0d-dd4f7d862207 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +ad679e19-3e17-4c67-8b0d-dd4f7d862207 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +ad679e19-3e17-4c67-8b0d-dd4f7d862207 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +ad679e19-3e17-4c67-8b0d-dd4f7d862207 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +ad679e19-3e17-4c67-8b0d-dd4f7d862207 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +ad679e19-3e17-4c67-8b0d-dd4f7d862207 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +ad679e19-3e17-4c67-8b0d-dd4f7d862207 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +b77b04ef-5eda-418c-a6fb-528a2d0a171a http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +b77b04ef-5eda-418c-a6fb-528a2d0a171a http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +b77b04ef-5eda-418c-a6fb-528a2d0a171a http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +b77b04ef-5eda-418c-a6fb-528a2d0a171a http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +b77b04ef-5eda-418c-a6fb-528a2d0a171a http://hl7.org/fhir/StructureDefinition/patient-birthPlace +b77b04ef-5eda-418c-a6fb-528a2d0a171a http://synthetichealth.github.io/synthea/disability-adjusted-life-years +b77b04ef-5eda-418c-a6fb-528a2d0a171a http://synthetichealth.github.io/synthea/quality-adjusted-life-years +b8eccdce-7261-4402-9aa0-7360ae6bf53b http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +b8eccdce-7261-4402-9aa0-7360ae6bf53b http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +b8eccdce-7261-4402-9aa0-7360ae6bf53b http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +b8eccdce-7261-4402-9aa0-7360ae6bf53b http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +b8eccdce-7261-4402-9aa0-7360ae6bf53b http://hl7.org/fhir/StructureDefinition/patient-birthPlace +b8eccdce-7261-4402-9aa0-7360ae6bf53b http://synthetichealth.github.io/synthea/disability-adjusted-life-years +b8eccdce-7261-4402-9aa0-7360ae6bf53b http://synthetichealth.github.io/synthea/quality-adjusted-life-years +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc http://hl7.org/fhir/StructureDefinition/patient-birthPlace +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc http://synthetichealth.github.io/synthea/disability-adjusted-life-years +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc http://synthetichealth.github.io/synthea/quality-adjusted-life-years +badbd940-9839-4472-aa66-3382d8823c80 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +badbd940-9839-4472-aa66-3382d8823c80 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +badbd940-9839-4472-aa66-3382d8823c80 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +badbd940-9839-4472-aa66-3382d8823c80 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +badbd940-9839-4472-aa66-3382d8823c80 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +badbd940-9839-4472-aa66-3382d8823c80 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +badbd940-9839-4472-aa66-3382d8823c80 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +bb9c4fc1-795a-4492-b065-1f497fe18bb2 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +bb9c4fc1-795a-4492-b065-1f497fe18bb2 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +bb9c4fc1-795a-4492-b065-1f497fe18bb2 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +bb9c4fc1-795a-4492-b065-1f497fe18bb2 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +bb9c4fc1-795a-4492-b065-1f497fe18bb2 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +bb9c4fc1-795a-4492-b065-1f497fe18bb2 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +bb9c4fc1-795a-4492-b065-1f497fe18bb2 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +be4b757d-70f1-464c-bb29-6f9a0f6cb05e http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +be4b757d-70f1-464c-bb29-6f9a0f6cb05e http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +be4b757d-70f1-464c-bb29-6f9a0f6cb05e http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +be4b757d-70f1-464c-bb29-6f9a0f6cb05e http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +be4b757d-70f1-464c-bb29-6f9a0f6cb05e http://hl7.org/fhir/StructureDefinition/patient-birthPlace +be4b757d-70f1-464c-bb29-6f9a0f6cb05e http://synthetichealth.github.io/synthea/disability-adjusted-life-years +be4b757d-70f1-464c-bb29-6f9a0f6cb05e http://synthetichealth.github.io/synthea/quality-adjusted-life-years +c39927f7-d23a-475c-b325-c1de4b51e280 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +c39927f7-d23a-475c-b325-c1de4b51e280 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +c39927f7-d23a-475c-b325-c1de4b51e280 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +c39927f7-d23a-475c-b325-c1de4b51e280 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +c39927f7-d23a-475c-b325-c1de4b51e280 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +c39927f7-d23a-475c-b325-c1de4b51e280 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +c39927f7-d23a-475c-b325-c1de4b51e280 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +c879c300-7fdf-4b53-aa6a-a2b4a266b30c http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +c879c300-7fdf-4b53-aa6a-a2b4a266b30c http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +c879c300-7fdf-4b53-aa6a-a2b4a266b30c http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +c879c300-7fdf-4b53-aa6a-a2b4a266b30c http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +c879c300-7fdf-4b53-aa6a-a2b4a266b30c http://hl7.org/fhir/StructureDefinition/patient-birthPlace +c879c300-7fdf-4b53-aa6a-a2b4a266b30c http://synthetichealth.github.io/synthea/disability-adjusted-life-years +c879c300-7fdf-4b53-aa6a-a2b4a266b30c http://synthetichealth.github.io/synthea/quality-adjusted-life-years +c892bf2f-a7bc-407e-9508-c778a9b8761c http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +c892bf2f-a7bc-407e-9508-c778a9b8761c http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +c892bf2f-a7bc-407e-9508-c778a9b8761c http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +c892bf2f-a7bc-407e-9508-c778a9b8761c http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +c892bf2f-a7bc-407e-9508-c778a9b8761c http://hl7.org/fhir/StructureDefinition/patient-birthPlace +c892bf2f-a7bc-407e-9508-c778a9b8761c http://synthetichealth.github.io/synthea/disability-adjusted-life-years +c892bf2f-a7bc-407e-9508-c778a9b8761c http://synthetichealth.github.io/synthea/quality-adjusted-life-years +d12274a5-9e03-4c78-ae3e-6cc27e91d737 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +d12274a5-9e03-4c78-ae3e-6cc27e91d737 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +d12274a5-9e03-4c78-ae3e-6cc27e91d737 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +d12274a5-9e03-4c78-ae3e-6cc27e91d737 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +d12274a5-9e03-4c78-ae3e-6cc27e91d737 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +d12274a5-9e03-4c78-ae3e-6cc27e91d737 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +d12274a5-9e03-4c78-ae3e-6cc27e91d737 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +d554ba19-e081-4979-9c0c-a72cd6e5e8ea http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +d554ba19-e081-4979-9c0c-a72cd6e5e8ea http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +d554ba19-e081-4979-9c0c-a72cd6e5e8ea http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +d554ba19-e081-4979-9c0c-a72cd6e5e8ea http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +d554ba19-e081-4979-9c0c-a72cd6e5e8ea http://hl7.org/fhir/StructureDefinition/patient-birthPlace +d554ba19-e081-4979-9c0c-a72cd6e5e8ea http://synthetichealth.github.io/synthea/disability-adjusted-life-years +d554ba19-e081-4979-9c0c-a72cd6e5e8ea http://synthetichealth.github.io/synthea/quality-adjusted-life-years +da8db730-f051-42d0-a2db-a3577d96e9bd http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +da8db730-f051-42d0-a2db-a3577d96e9bd http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +da8db730-f051-42d0-a2db-a3577d96e9bd http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +da8db730-f051-42d0-a2db-a3577d96e9bd http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +da8db730-f051-42d0-a2db-a3577d96e9bd http://hl7.org/fhir/StructureDefinition/patient-birthPlace +da8db730-f051-42d0-a2db-a3577d96e9bd http://synthetichealth.github.io/synthea/disability-adjusted-life-years +da8db730-f051-42d0-a2db-a3577d96e9bd http://synthetichealth.github.io/synthea/quality-adjusted-life-years +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d http://hl7.org/fhir/StructureDefinition/patient-birthPlace +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d http://synthetichealth.github.io/synthea/disability-adjusted-life-years +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d http://synthetichealth.github.io/synthea/quality-adjusted-life-years +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +e35f5823-4533-49e5-9652-79d733be6bef http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +e35f5823-4533-49e5-9652-79d733be6bef http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +e35f5823-4533-49e5-9652-79d733be6bef http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +e35f5823-4533-49e5-9652-79d733be6bef http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +e35f5823-4533-49e5-9652-79d733be6bef http://hl7.org/fhir/StructureDefinition/patient-birthPlace +e35f5823-4533-49e5-9652-79d733be6bef http://synthetichealth.github.io/synthea/disability-adjusted-life-years +e35f5823-4533-49e5-9652-79d733be6bef http://synthetichealth.github.io/synthea/quality-adjusted-life-years +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d http://hl7.org/fhir/StructureDefinition/patient-birthPlace +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d http://synthetichealth.github.io/synthea/disability-adjusted-life-years +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d http://synthetichealth.github.io/synthea/quality-adjusted-life-years +e87fbe4b-74ef-44e4-8e36-70b2abb48895 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +e87fbe4b-74ef-44e4-8e36-70b2abb48895 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +e87fbe4b-74ef-44e4-8e36-70b2abb48895 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +e87fbe4b-74ef-44e4-8e36-70b2abb48895 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +e87fbe4b-74ef-44e4-8e36-70b2abb48895 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +e87fbe4b-74ef-44e4-8e36-70b2abb48895 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +e87fbe4b-74ef-44e4-8e36-70b2abb48895 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +eaf99c09-419d-4162-8417-5a9d7e042cd4 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +eaf99c09-419d-4162-8417-5a9d7e042cd4 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +eaf99c09-419d-4162-8417-5a9d7e042cd4 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +eaf99c09-419d-4162-8417-5a9d7e042cd4 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +eaf99c09-419d-4162-8417-5a9d7e042cd4 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +eaf99c09-419d-4162-8417-5a9d7e042cd4 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +eaf99c09-419d-4162-8417-5a9d7e042cd4 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +eb373264-da60-4e98-af7c-e3021fdd8d4b http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +eb373264-da60-4e98-af7c-e3021fdd8d4b http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +eb373264-da60-4e98-af7c-e3021fdd8d4b http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +eb373264-da60-4e98-af7c-e3021fdd8d4b http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +eb373264-da60-4e98-af7c-e3021fdd8d4b http://hl7.org/fhir/StructureDefinition/patient-birthPlace +eb373264-da60-4e98-af7c-e3021fdd8d4b http://synthetichealth.github.io/synthea/disability-adjusted-life-years +eb373264-da60-4e98-af7c-e3021fdd8d4b http://synthetichealth.github.io/synthea/quality-adjusted-life-years +f1dd0d5f-c410-484b-984c-2ba38cee79ba http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +f1dd0d5f-c410-484b-984c-2ba38cee79ba http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +f1dd0d5f-c410-484b-984c-2ba38cee79ba http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +f1dd0d5f-c410-484b-984c-2ba38cee79ba http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +f1dd0d5f-c410-484b-984c-2ba38cee79ba http://hl7.org/fhir/StructureDefinition/patient-birthPlace +f1dd0d5f-c410-484b-984c-2ba38cee79ba http://synthetichealth.github.io/synthea/disability-adjusted-life-years +f1dd0d5f-c410-484b-984c-2ba38cee79ba http://synthetichealth.github.io/synthea/quality-adjusted-life-years +f1fd855c-802c-417a-ab7c-14a3c9daafc6 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +f1fd855c-802c-417a-ab7c-14a3c9daafc6 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +f1fd855c-802c-417a-ab7c-14a3c9daafc6 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +f1fd855c-802c-417a-ab7c-14a3c9daafc6 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +f1fd855c-802c-417a-ab7c-14a3c9daafc6 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +f1fd855c-802c-417a-ab7c-14a3c9daafc6 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +f1fd855c-802c-417a-ab7c-14a3c9daafc6 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +f41b6458-2358-43e7-ae34-d2a0fbe083d8 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +f41b6458-2358-43e7-ae34-d2a0fbe083d8 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +f41b6458-2358-43e7-ae34-d2a0fbe083d8 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +f41b6458-2358-43e7-ae34-d2a0fbe083d8 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +f41b6458-2358-43e7-ae34-d2a0fbe083d8 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +f41b6458-2358-43e7-ae34-d2a0fbe083d8 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +f41b6458-2358-43e7-ae34-d2a0fbe083d8 http://synthetichealth.github.io/synthea/quality-adjusted-life-years diff --git a/fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnElements.csv b/fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnElements.csv deleted file mode 100644 index a29da5bb44..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnElements.csv +++ /dev/null @@ -1,9 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,http://hl7.org/fhir/StructureDefinition/geolocation -2b36c1e2-bbe1-45ae-8124-4adad2677702,http://hl7.org/fhir/StructureDefinition/geolocation -7001ad9c-34d2-4eb5-8165-5fdc2147f469,http://hl7.org/fhir/StructureDefinition/geolocation -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://hl7.org/fhir/StructureDefinition/geolocation -9360820c-8602-4335-8b50-c88d627a0c20,http://hl7.org/fhir/StructureDefinition/geolocation -a7eb2ce7-1075-426c-addd-957b861b0e55,http://hl7.org/fhir/StructureDefinition/geolocation -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://hl7.org/fhir/StructureDefinition/geolocation -beff242e-580b-47c0-9844-c1a68c36c5bf,http://hl7.org/fhir/StructureDefinition/geolocation -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://hl7.org/fhir/StructureDefinition/geolocation diff --git a/fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnElements.tsv b/fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnElements.tsv new file mode 100644 index 0000000000..e7da71936f --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnElements.tsv @@ -0,0 +1,9 @@ +121503c8-9564-4b48-9086-a22df717948e http://hl7.org/fhir/StructureDefinition/geolocation +2b36c1e2-bbe1-45ae-8124-4adad2677702 http://hl7.org/fhir/StructureDefinition/geolocation +7001ad9c-34d2-4eb5-8165-5fdc2147f469 http://hl7.org/fhir/StructureDefinition/geolocation +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://hl7.org/fhir/StructureDefinition/geolocation +9360820c-8602-4335-8b50-c88d627a0c20 http://hl7.org/fhir/StructureDefinition/geolocation +a7eb2ce7-1075-426c-addd-957b861b0e55 http://hl7.org/fhir/StructureDefinition/geolocation +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://hl7.org/fhir/StructureDefinition/geolocation +beff242e-580b-47c0-9844-c1a68c36c5bf http://hl7.org/fhir/StructureDefinition/geolocation +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://hl7.org/fhir/StructureDefinition/geolocation diff --git a/fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnResources.csv b/fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnResources.csv deleted file mode 100644 index a6c98a9e90..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnResources.csv +++ /dev/null @@ -1,63 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -121503c8-9564-4b48-9086-a22df717948e,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -121503c8-9564-4b48-9086-a22df717948e,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -121503c8-9564-4b48-9086-a22df717948e,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -121503c8-9564-4b48-9086-a22df717948e,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -121503c8-9564-4b48-9086-a22df717948e,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -121503c8-9564-4b48-9086-a22df717948e,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -2b36c1e2-bbe1-45ae-8124-4adad2677702,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -2b36c1e2-bbe1-45ae-8124-4adad2677702,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -2b36c1e2-bbe1-45ae-8124-4adad2677702,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -2b36c1e2-bbe1-45ae-8124-4adad2677702,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -2b36c1e2-bbe1-45ae-8124-4adad2677702,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -2b36c1e2-bbe1-45ae-8124-4adad2677702,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -2b36c1e2-bbe1-45ae-8124-4adad2677702,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -7001ad9c-34d2-4eb5-8165-5fdc2147f469,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -7001ad9c-34d2-4eb5-8165-5fdc2147f469,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -7001ad9c-34d2-4eb5-8165-5fdc2147f469,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -7001ad9c-34d2-4eb5-8165-5fdc2147f469,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -7001ad9c-34d2-4eb5-8165-5fdc2147f469,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -7001ad9c-34d2-4eb5-8165-5fdc2147f469,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -7001ad9c-34d2-4eb5-8165-5fdc2147f469,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -9360820c-8602-4335-8b50-c88d627a0c20,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -9360820c-8602-4335-8b50-c88d627a0c20,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -9360820c-8602-4335-8b50-c88d627a0c20,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -9360820c-8602-4335-8b50-c88d627a0c20,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -9360820c-8602-4335-8b50-c88d627a0c20,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -9360820c-8602-4335-8b50-c88d627a0c20,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -9360820c-8602-4335-8b50-c88d627a0c20,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -a7eb2ce7-1075-426c-addd-957b861b0e55,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -a7eb2ce7-1075-426c-addd-957b861b0e55,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -a7eb2ce7-1075-426c-addd-957b861b0e55,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -a7eb2ce7-1075-426c-addd-957b861b0e55,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -a7eb2ce7-1075-426c-addd-957b861b0e55,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -a7eb2ce7-1075-426c-addd-957b861b0e55,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -a7eb2ce7-1075-426c-addd-957b861b0e55,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -beff242e-580b-47c0-9844-c1a68c36c5bf,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -beff242e-580b-47c0-9844-c1a68c36c5bf,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -beff242e-580b-47c0-9844-c1a68c36c5bf,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -beff242e-580b-47c0-9844-c1a68c36c5bf,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -beff242e-580b-47c0-9844-c1a68c36c5bf,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -beff242e-580b-47c0-9844-c1a68c36c5bf,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -beff242e-580b-47c0-9844-c1a68c36c5bf,http://synthetichealth.github.io/synthea/quality-adjusted-life-years -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://hl7.org/fhir/us/core/StructureDefinition/us-core-race -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://hl7.org/fhir/StructureDefinition/patient-birthPlace -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://synthetichealth.github.io/synthea/disability-adjusted-life-years -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,http://synthetichealth.github.io/synthea/quality-adjusted-life-years diff --git a/fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnResources.tsv b/fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnResources.tsv new file mode 100644 index 0000000000..c3dd166223 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testExtensionsOnResources.tsv @@ -0,0 +1,63 @@ +121503c8-9564-4b48-9086-a22df717948e http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +121503c8-9564-4b48-9086-a22df717948e http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +121503c8-9564-4b48-9086-a22df717948e http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +121503c8-9564-4b48-9086-a22df717948e http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +121503c8-9564-4b48-9086-a22df717948e http://hl7.org/fhir/StructureDefinition/patient-birthPlace +121503c8-9564-4b48-9086-a22df717948e http://synthetichealth.github.io/synthea/disability-adjusted-life-years +121503c8-9564-4b48-9086-a22df717948e http://synthetichealth.github.io/synthea/quality-adjusted-life-years +2b36c1e2-bbe1-45ae-8124-4adad2677702 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +2b36c1e2-bbe1-45ae-8124-4adad2677702 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +2b36c1e2-bbe1-45ae-8124-4adad2677702 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +2b36c1e2-bbe1-45ae-8124-4adad2677702 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +2b36c1e2-bbe1-45ae-8124-4adad2677702 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +2b36c1e2-bbe1-45ae-8124-4adad2677702 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +2b36c1e2-bbe1-45ae-8124-4adad2677702 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +7001ad9c-34d2-4eb5-8165-5fdc2147f469 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +7001ad9c-34d2-4eb5-8165-5fdc2147f469 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +7001ad9c-34d2-4eb5-8165-5fdc2147f469 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +7001ad9c-34d2-4eb5-8165-5fdc2147f469 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +7001ad9c-34d2-4eb5-8165-5fdc2147f469 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +7001ad9c-34d2-4eb5-8165-5fdc2147f469 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +7001ad9c-34d2-4eb5-8165-5fdc2147f469 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://hl7.org/fhir/StructureDefinition/patient-birthPlace +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://synthetichealth.github.io/synthea/disability-adjusted-life-years +8ee183e2-b3c0-4151-be94-b945d6aa8c6d http://synthetichealth.github.io/synthea/quality-adjusted-life-years +9360820c-8602-4335-8b50-c88d627a0c20 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +9360820c-8602-4335-8b50-c88d627a0c20 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +9360820c-8602-4335-8b50-c88d627a0c20 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +9360820c-8602-4335-8b50-c88d627a0c20 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +9360820c-8602-4335-8b50-c88d627a0c20 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +9360820c-8602-4335-8b50-c88d627a0c20 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +9360820c-8602-4335-8b50-c88d627a0c20 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +a7eb2ce7-1075-426c-addd-957b861b0e55 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +a7eb2ce7-1075-426c-addd-957b861b0e55 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +a7eb2ce7-1075-426c-addd-957b861b0e55 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +a7eb2ce7-1075-426c-addd-957b861b0e55 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +a7eb2ce7-1075-426c-addd-957b861b0e55 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +a7eb2ce7-1075-426c-addd-957b861b0e55 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +a7eb2ce7-1075-426c-addd-957b861b0e55 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 http://synthetichealth.github.io/synthea/quality-adjusted-life-years +beff242e-580b-47c0-9844-c1a68c36c5bf http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +beff242e-580b-47c0-9844-c1a68c36c5bf http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +beff242e-580b-47c0-9844-c1a68c36c5bf http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +beff242e-580b-47c0-9844-c1a68c36c5bf http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +beff242e-580b-47c0-9844-c1a68c36c5bf http://hl7.org/fhir/StructureDefinition/patient-birthPlace +beff242e-580b-47c0-9844-c1a68c36c5bf http://synthetichealth.github.io/synthea/disability-adjusted-life-years +beff242e-580b-47c0-9844-c1a68c36c5bf http://synthetichealth.github.io/synthea/quality-adjusted-life-years +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://hl7.org/fhir/us/core/StructureDefinition/us-core-race +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://hl7.org/fhir/StructureDefinition/patient-birthPlace +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://synthetichealth.github.io/synthea/disability-adjusted-life-years +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 http://synthetichealth.github.io/synthea/quality-adjusted-life-years diff --git a/fhir-server/src/test/resources/responses/ParserTest/testIfFunction.csv b/fhir-server/src/test/resources/responses/ParserTest/testIfFunction.csv deleted file mode 100644 index d4c1ec4a3d..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testIfFunction.csv +++ /dev/null @@ -1,9 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,Not male -2b36c1e2-bbe1-45ae-8124-4adad2677702,Male -7001ad9c-34d2-4eb5-8165-5fdc2147f469,Not male -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Male -9360820c-8602-4335-8b50-c88d627a0c20,Not male -a7eb2ce7-1075-426c-addd-957b861b0e55,Male -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,Male -beff242e-580b-47c0-9844-c1a68c36c5bf,Male -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Not male diff --git a/fhir-server/src/test/resources/responses/ParserTest/testIfFunction.tsv b/fhir-server/src/test/resources/responses/ParserTest/testIfFunction.tsv new file mode 100644 index 0000000000..0891ec2ae3 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testIfFunction.tsv @@ -0,0 +1,9 @@ +121503c8-9564-4b48-9086-a22df717948e Not male +2b36c1e2-bbe1-45ae-8124-4adad2677702 Male +7001ad9c-34d2-4eb5-8165-5fdc2147f469 Not male +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Male +9360820c-8602-4335-8b50-c88d627a0c20 Not male +a7eb2ce7-1075-426c-addd-957b861b0e55 Male +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 Male +beff242e-580b-47c0-9844-c1a68c36c5bf Male +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 Not male diff --git a/fhir-server/src/test/resources/responses/ParserTest/testIfFunctionWithComplexTypeResult.csv b/fhir-server/src/test/resources/responses/ParserTest/testIfFunctionWithComplexTypeResult.csv deleted file mode 100644 index 9736057e5b..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testIfFunctionWithComplexTypeResult.csv +++ /dev/null @@ -1,10 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,Ophelia894 -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,Cherryl901 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -9360820c-8602-4335-8b50-c88d627a0c20,Karina848 -9360820c-8602-4335-8b50-c88d627a0c20,Karina848 -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Su690 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testIfFunctionWithComplexTypeResult.tsv b/fhir-server/src/test/resources/responses/ParserTest/testIfFunctionWithComplexTypeResult.tsv new file mode 100644 index 0000000000..0634ea6c04 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testIfFunctionWithComplexTypeResult.tsv @@ -0,0 +1,10 @@ +121503c8-9564-4b48-9086-a22df717948e Ophelia894 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 Cherryl901 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 +9360820c-8602-4335-8b50-c88d627a0c20 Karina848 +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 Su690 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testIifWithNullLiteral.csv b/fhir-server/src/test/resources/responses/ParserTest/testIifWithNullLiteral.csv deleted file mode 100644 index d8988d9173..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testIifWithNullLiteral.csv +++ /dev/null @@ -1,9 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,1998-12-26 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,1970-11-22 -9360820c-8602-4335-8b50-c88d627a0c20,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,1957-06-06 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,1957-06-06 -beff242e-580b-47c0-9844-c1a68c36c5bf,1983-09-06 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" diff --git a/fhir-server/src/test/resources/responses/ParserTest/testIifWithNullLiteral.tsv b/fhir-server/src/test/resources/responses/ParserTest/testIifWithNullLiteral.tsv new file mode 100644 index 0000000000..970318a9cf --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testIifWithNullLiteral.tsv @@ -0,0 +1,9 @@ +121503c8-9564-4b48-9086-a22df717948e +2b36c1e2-bbe1-45ae-8124-4adad2677702 1998-12-26 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 1970-11-22 +9360820c-8602-4335-8b50-c88d627a0c20 +a7eb2ce7-1075-426c-addd-957b861b0e55 1957-06-06 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 1957-06-06 +beff242e-580b-47c0-9844-c1a68c36c5bf 1983-09-06 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testNestedExtensions.csv b/fhir-server/src/test/resources/responses/ParserTest/testNestedExtensions.csv deleted file mode 100644 index 0ede2043f1..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testNestedExtensions.csv +++ /dev/null @@ -1,81 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,ombCategory -121503c8-9564-4b48-9086-a22df717948e,text -121503c8-9564-4b48-9086-a22df717948e,ombCategory -121503c8-9564-4b48-9086-a22df717948e,text -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,ombCategory -2b36c1e2-bbe1-45ae-8124-4adad2677702,text -2b36c1e2-bbe1-45ae-8124-4adad2677702,ombCategory -2b36c1e2-bbe1-45ae-8124-4adad2677702,text -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,ombCategory -7001ad9c-34d2-4eb5-8165-5fdc2147f469,text -7001ad9c-34d2-4eb5-8165-5fdc2147f469,ombCategory -7001ad9c-34d2-4eb5-8165-5fdc2147f469,text -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,ombCategory -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,text -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,ombCategory -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,text -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -9360820c-8602-4335-8b50-c88d627a0c20,ombCategory -9360820c-8602-4335-8b50-c88d627a0c20,text -9360820c-8602-4335-8b50-c88d627a0c20,ombCategory -9360820c-8602-4335-8b50-c88d627a0c20,text -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,ombCategory -a7eb2ce7-1075-426c-addd-957b861b0e55,text -a7eb2ce7-1075-426c-addd-957b861b0e55,ombCategory -a7eb2ce7-1075-426c-addd-957b861b0e55,text -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,ombCategory -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,text -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,ombCategory -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,text -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,ombCategory -beff242e-580b-47c0-9844-c1a68c36c5bf,text -beff242e-580b-47c0-9844-c1a68c36c5bf,ombCategory -beff242e-580b-47c0-9844-c1a68c36c5bf,text -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,ombCategory -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,text -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,ombCategory -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,text -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" diff --git a/fhir-server/src/test/resources/responses/ParserTest/testNestedExtensions.tsv b/fhir-server/src/test/resources/responses/ParserTest/testNestedExtensions.tsv new file mode 100644 index 0000000000..604f9c8fb8 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testNestedExtensions.tsv @@ -0,0 +1,81 @@ +121503c8-9564-4b48-9086-a22df717948e ombCategory +121503c8-9564-4b48-9086-a22df717948e text +121503c8-9564-4b48-9086-a22df717948e ombCategory +121503c8-9564-4b48-9086-a22df717948e text +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +2b36c1e2-bbe1-45ae-8124-4adad2677702 ombCategory +2b36c1e2-bbe1-45ae-8124-4adad2677702 text +2b36c1e2-bbe1-45ae-8124-4adad2677702 ombCategory +2b36c1e2-bbe1-45ae-8124-4adad2677702 text +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 ombCategory +7001ad9c-34d2-4eb5-8165-5fdc2147f469 text +7001ad9c-34d2-4eb5-8165-5fdc2147f469 ombCategory +7001ad9c-34d2-4eb5-8165-5fdc2147f469 text +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d ombCategory +8ee183e2-b3c0-4151-be94-b945d6aa8c6d text +8ee183e2-b3c0-4151-be94-b945d6aa8c6d ombCategory +8ee183e2-b3c0-4151-be94-b945d6aa8c6d text +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 ombCategory +9360820c-8602-4335-8b50-c88d627a0c20 text +9360820c-8602-4335-8b50-c88d627a0c20 ombCategory +9360820c-8602-4335-8b50-c88d627a0c20 text +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +a7eb2ce7-1075-426c-addd-957b861b0e55 ombCategory +a7eb2ce7-1075-426c-addd-957b861b0e55 text +a7eb2ce7-1075-426c-addd-957b861b0e55 ombCategory +a7eb2ce7-1075-426c-addd-957b861b0e55 text +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 ombCategory +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 text +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 ombCategory +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 text +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf ombCategory +beff242e-580b-47c0-9844-c1a68c36c5bf text +beff242e-580b-47c0-9844-c1a68c36c5bf ombCategory +beff242e-580b-47c0-9844-c1a68c36c5bf text +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 ombCategory +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 text +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 ombCategory +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 text +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testNestedWhereWithAggregationOnElement.csv b/fhir-server/src/test/resources/responses/ParserTest/testNestedWhereWithAggregationOnElement.csv deleted file mode 100644 index 0fbf4d50fd..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testNestedWhereWithAggregationOnElement.csv +++ /dev/null @@ -1,10 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,Wuckert783 -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" diff --git a/fhir-server/src/test/resources/responses/ParserTest/testNestedWhereWithAggregationOnElement.tsv b/fhir-server/src/test/resources/responses/ParserTest/testNestedWhereWithAggregationOnElement.tsv new file mode 100644 index 0000000000..e9d826f4d4 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testNestedWhereWithAggregationOnElement.tsv @@ -0,0 +1,10 @@ +121503c8-9564-4b48-9086-a22df717948e +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 Wuckert783 +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testNotFunction.csv b/fhir-server/src/test/resources/responses/ParserTest/testNotFunction.csv deleted file mode 100644 index 5b30a75161..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testNotFunction.csv +++ /dev/null @@ -1,9 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,true -2b36c1e2-bbe1-45ae-8124-4adad2677702,true -7001ad9c-34d2-4eb5-8165-5fdc2147f469,true -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,true -9360820c-8602-4335-8b50-c88d627a0c20,true -a7eb2ce7-1075-426c-addd-957b861b0e55,true -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,true -beff242e-580b-47c0-9844-c1a68c36c5bf,true -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testNotFunction.tsv b/fhir-server/src/test/resources/responses/ParserTest/testNotFunction.tsv new file mode 100644 index 0000000000..97e63dcfca --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testNotFunction.tsv @@ -0,0 +1,9 @@ +121503c8-9564-4b48-9086-a22df717948e true +2b36c1e2-bbe1-45ae-8124-4adad2677702 true +7001ad9c-34d2-4eb5-8165-5fdc2147f469 true +8ee183e2-b3c0-4151-be94-b945d6aa8c6d true +9360820c-8602-4335-8b50-c88d627a0c20 true +a7eb2ce7-1075-426c-addd-957b861b0e55 true +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 true +beff242e-580b-47c0-9844-c1a68c36c5bf true +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testPropertyFunctionWithCodingType.csv b/fhir-server/src/test/resources/responses/ParserTest/testPropertyFunctionWithCodingType.csv deleted file mode 100644 index 4004cb5ae2..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testPropertyFunctionWithCodingType.csv +++ /dev/null @@ -1,77 +0,0 @@ -01fac610-c309-46f8-9a0f-6b0f55c9915a, -031f1c86-16ec-42e1-a155-81456e778fed,284551006 -0447de38-1f2a-411c-9fd2-a9c62ab1f221,284551006 -061521f0-7f7e-41a5-ad35-b30fe958dea7,40055000 -061521f0-7f7e-41a5-ad35-b30fe958dea7,403190006 -06e446da-6d66-4d97-a457-4abf2a0d2e24, -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,40055000 -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,403190006 -1146cf52-573f-4667-97c3-abf235f9a83b, -1452c50c-b0b9-472c-afb7-2f9e5a3c4717, -171ad058-c082-46ab-b9cc-d7bba6e4e3cd, -1cea8432-f8a0-4746-ab5c-8f195fc07c55, -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,284551006 -21372b39-2be0-4d0d-85c4-5d7e250d8f78, -248e951c-e0ce-4e4b-afae-6273fd109c9d, -251ddff9-1588-4790-8f40-cd6ab1d9b7fc, -258191b8-2b42-4473-9ba2-be0ba3561767, -29386b12-9af9-4c21-9f82-858998b388bb, -31393667-dd7f-4c94-90c3-6cde75c67d3e, -31925a68-7a48-412c-b4f8-aef92498dda1,284551006 -38171e22-b35d-4161-be15-80243be37b99,40055000 -38171e22-b35d-4161-be15-80243be37b99,403190006 -39170d67-8205-4636-84d7-d8575115d14c, -4025ceb3-04a6-41fb-8569-16a8dcce7ccc, -46d69851-eca3-42e2-b142-88c5578f6cff,284551006 -475461a2-3bd2-43fd-a5aa-7ce424203ae8,284551006 -4dd7824b-4b41-4402-bc42-de016e1bfcd9, -5300158c-92cb-40fe-bc14-a449d7b3c1c5, -5378726d-7f2b-4c83-9762-eaf385915fa7, -549d3ef5-a05e-4b8e-acba-3d70f26a82f6, -55cde383-2eb1-42d9-b5c6-698d6eade389,284551006 -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325, -5ef84858-0480-4a34-8f43-fc962fe627b2, -6464e20b-55ec-4685-be3e-55bc3b9602d4,40055000 -6464e20b-55ec-4685-be3e-55bc3b9602d4,403190006 -66b38727-96ea-43ad-bff7-5f4df5d779a9,284551006 -6794fbaf-e715-4e22-bafa-b7e594550ff7, -77fc5f45-e51d-41e2-8356-daa27ebd8268, -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,40055000 -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,403190006 -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,284551006 -80787532-559e-4999-ac25-75c462ce4ef1, -8922ff1b-4bc5-41b4-8512-5aad1517e2eb, -8ddb74fc-46d5-4e94-a0ee-6761b292ae95, -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6, -8f3ad6ad-a457-484e-a455-f0711e77b2ba,284551006 -9e598086-27bb-4e50-8988-9a40eb3c178f,40055000 -9e598086-27bb-4e50-8988-9a40eb3c178f,403190006 -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc, -a16f311d-d6dc-486e-8eb0-7cd53a5333ee, -aa5a32b3-3993-4eb5-afce-489d0e7413cf, -abecd476-3911-4a6b-a1c6-b8ecea2fe63e, -ad679e19-3e17-4c67-8b0d-dd4f7d862207, -b77b04ef-5eda-418c-a6fb-528a2d0a171a,284551006 -b8eccdce-7261-4402-9aa0-7360ae6bf53b, -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc, -badbd940-9839-4472-aa66-3382d8823c80, -bb9c4fc1-795a-4492-b065-1f497fe18bb2, -be4b757d-70f1-464c-bb29-6f9a0f6cb05e, -c39927f7-d23a-475c-b325-c1de4b51e280, -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,284551006 -c892bf2f-a7bc-407e-9508-c778a9b8761c, -d12274a5-9e03-4c78-ae3e-6cc27e91d737, -d554ba19-e081-4979-9c0c-a72cd6e5e8ea, -da8db730-f051-42d0-a2db-a3577d96e9bd, -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82, -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d, -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76, -e35f5823-4533-49e5-9652-79d733be6bef, -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d, -e87fbe4b-74ef-44e4-8e36-70b2abb48895, -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,284551006 -eaf99c09-419d-4162-8417-5a9d7e042cd4, -eb373264-da60-4e98-af7c-e3021fdd8d4b, -f1dd0d5f-c410-484b-984c-2ba38cee79ba, -f1fd855c-802c-417a-ab7c-14a3c9daafc6, -f41b6458-2358-43e7-ae34-d2a0fbe083d8, diff --git a/fhir-server/src/test/resources/responses/ParserTest/testPropertyFunctionWithCodingType.tsv b/fhir-server/src/test/resources/responses/ParserTest/testPropertyFunctionWithCodingType.tsv new file mode 100644 index 0000000000..4f9e1ae10f --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testPropertyFunctionWithCodingType.tsv @@ -0,0 +1,77 @@ +01fac610-c309-46f8-9a0f-6b0f55c9915a +031f1c86-16ec-42e1-a155-81456e778fed 284551006 +0447de38-1f2a-411c-9fd2-a9c62ab1f221 284551006 +061521f0-7f7e-41a5-ad35-b30fe958dea7 40055000 +061521f0-7f7e-41a5-ad35-b30fe958dea7 403190006 +06e446da-6d66-4d97-a457-4abf2a0d2e24 +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 40055000 +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 403190006 +1146cf52-573f-4667-97c3-abf235f9a83b +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 +171ad058-c082-46ab-b9cc-d7bba6e4e3cd +1cea8432-f8a0-4746-ab5c-8f195fc07c55 +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 284551006 +21372b39-2be0-4d0d-85c4-5d7e250d8f78 +248e951c-e0ce-4e4b-afae-6273fd109c9d +251ddff9-1588-4790-8f40-cd6ab1d9b7fc +258191b8-2b42-4473-9ba2-be0ba3561767 +29386b12-9af9-4c21-9f82-858998b388bb +31393667-dd7f-4c94-90c3-6cde75c67d3e +31925a68-7a48-412c-b4f8-aef92498dda1 284551006 +38171e22-b35d-4161-be15-80243be37b99 40055000 +38171e22-b35d-4161-be15-80243be37b99 403190006 +39170d67-8205-4636-84d7-d8575115d14c +4025ceb3-04a6-41fb-8569-16a8dcce7ccc +46d69851-eca3-42e2-b142-88c5578f6cff 284551006 +475461a2-3bd2-43fd-a5aa-7ce424203ae8 284551006 +4dd7824b-4b41-4402-bc42-de016e1bfcd9 +5300158c-92cb-40fe-bc14-a449d7b3c1c5 +5378726d-7f2b-4c83-9762-eaf385915fa7 +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 +55cde383-2eb1-42d9-b5c6-698d6eade389 284551006 +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 +5ef84858-0480-4a34-8f43-fc962fe627b2 +6464e20b-55ec-4685-be3e-55bc3b9602d4 40055000 +6464e20b-55ec-4685-be3e-55bc3b9602d4 403190006 +66b38727-96ea-43ad-bff7-5f4df5d779a9 284551006 +6794fbaf-e715-4e22-bafa-b7e594550ff7 +77fc5f45-e51d-41e2-8356-daa27ebd8268 +7d18555a-54c9-4c0a-bf3b-6305d0392a2a 40055000 +7d18555a-54c9-4c0a-bf3b-6305d0392a2a 403190006 +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 284551006 +80787532-559e-4999-ac25-75c462ce4ef1 +8922ff1b-4bc5-41b4-8512-5aad1517e2eb +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 +8f3ad6ad-a457-484e-a455-f0711e77b2ba 284551006 +9e598086-27bb-4e50-8988-9a40eb3c178f 40055000 +9e598086-27bb-4e50-8988-9a40eb3c178f 403190006 +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc +a16f311d-d6dc-486e-8eb0-7cd53a5333ee +aa5a32b3-3993-4eb5-afce-489d0e7413cf +abecd476-3911-4a6b-a1c6-b8ecea2fe63e +ad679e19-3e17-4c67-8b0d-dd4f7d862207 +b77b04ef-5eda-418c-a6fb-528a2d0a171a 284551006 +b8eccdce-7261-4402-9aa0-7360ae6bf53b +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc +badbd940-9839-4472-aa66-3382d8823c80 +bb9c4fc1-795a-4492-b065-1f497fe18bb2 +be4b757d-70f1-464c-bb29-6f9a0f6cb05e +c39927f7-d23a-475c-b325-c1de4b51e280 +c879c300-7fdf-4b53-aa6a-a2b4a266b30c 284551006 +c892bf2f-a7bc-407e-9508-c778a9b8761c +d12274a5-9e03-4c78-ae3e-6cc27e91d737 +d554ba19-e081-4979-9c0c-a72cd6e5e8ea +da8db730-f051-42d0-a2db-a3577d96e9bd +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 +e35f5823-4533-49e5-9652-79d733be6bef +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d +e87fbe4b-74ef-44e4-8e36-70b2abb48895 +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 284551006 +eaf99c09-419d-4162-8417-5a9d7e042cd4 +eb373264-da60-4e98-af7c-e3021fdd8d4b +f1dd0d5f-c410-484b-984c-2ba38cee79ba +f1fd855c-802c-417a-ab7c-14a3c9daafc6 +f41b6458-2358-43e7-ae34-d2a0fbe083d8 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionSubtractionAndEquality.csv b/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionSubtractionAndEquality.csv deleted file mode 100644 index 3079631f8e..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionSubtractionAndEquality.csv +++ /dev/null @@ -1,9 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,true -2b36c1e2-bbe1-45ae-8124-4adad2677702, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,true -9360820c-8602-4335-8b50-c88d627a0c20,true -a7eb2ce7-1075-426c-addd-957b861b0e55,true -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -beff242e-580b-47c0-9844-c1a68c36c5bf, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,true diff --git a/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionSubtractionAndEquality.tsv b/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionSubtractionAndEquality.tsv new file mode 100644 index 0000000000..5ff3950cdb --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionSubtractionAndEquality.tsv @@ -0,0 +1,9 @@ +121503c8-9564-4b48-9086-a22df717948e true +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d true +9360820c-8602-4335-8b50-c88d627a0c20 true +a7eb2ce7-1075-426c-addd-957b861b0e55 true +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 true diff --git a/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_code.csv b/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_code.csv deleted file mode 100644 index 6f23efbb9d..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_code.csv +++ /dev/null @@ -1,9 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,m-3 -2b36c1e2-bbe1-45ae-8124-4adad2677702, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,m-3 -9360820c-8602-4335-8b50-c88d627a0c20,m-3 -a7eb2ce7-1075-426c-addd-957b861b0e55,m-3 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -beff242e-580b-47c0-9844-c1a68c36c5bf, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,m-3 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_code.tsv b/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_code.tsv new file mode 100644 index 0000000000..c2946a4bff --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_code.tsv @@ -0,0 +1,9 @@ +121503c8-9564-4b48-9086-a22df717948e m-3 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d m-3 +9360820c-8602-4335-8b50-c88d627a0c20 m-3 +a7eb2ce7-1075-426c-addd-957b861b0e55 m-3 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 m-3 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_value.csv b/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_value.csv deleted file mode 100644 index d804ee2487..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_value.csv +++ /dev/null @@ -1,9 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,85389546411730050624531900 -9360820c-8602-4335-8b50-c88d627a0c20,83016978156556952635728000 -a7eb2ce7-1075-426c-addd-957b861b0e55, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -beff242e-580b-47c0-9844-c1a68c36c5bf, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,85425566063603919179553900 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_value.tsv b/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_value.tsv new file mode 100644 index 0000000000..acec64897d --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testQuantityAdditionWithOverflow_value.tsv @@ -0,0 +1,9 @@ +121503c8-9564-4b48-9086-a22df717948e +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 85389546411730050624531900 +9360820c-8602-4335-8b50-c88d627a0c20 83016978156556952635728000 +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 85425566063603919179553900 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testQuantityMultiplicationAndDivision.csv b/fhir-server/src/test/resources/responses/ParserTest/testQuantityMultiplicationAndDivision.csv deleted file mode 100644 index 08b66438c0..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testQuantityMultiplicationAndDivision.csv +++ /dev/null @@ -1,9 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,2.000000 -2b36c1e2-bbe1-45ae-8124-4adad2677702,2.000000 -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -9360820c-8602-4335-8b50-c88d627a0c20, -a7eb2ce7-1075-426c-addd-957b861b0e55, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,2.000000 -beff242e-580b-47c0-9844-c1a68c36c5bf, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, diff --git a/fhir-server/src/test/resources/responses/ParserTest/testQuantityMultiplicationAndDivision.tsv b/fhir-server/src/test/resources/responses/ParserTest/testQuantityMultiplicationAndDivision.tsv new file mode 100644 index 0000000000..587c663cf1 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testQuantityMultiplicationAndDivision.tsv @@ -0,0 +1,9 @@ +121503c8-9564-4b48-9086-a22df717948e 2.000000 +2b36c1e2-bbe1-45ae-8124-4adad2677702 2.000000 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 2.000000 +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testQueryWithExternalConstantInWhere.csv b/fhir-server/src/test/resources/responses/ParserTest/testQueryWithExternalConstantInWhere.csv deleted file mode 100644 index eb098e72a9..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testQueryWithExternalConstantInWhere.csv +++ /dev/null @@ -1,10 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,Gleichner915 -2b36c1e2-bbe1-45ae-8124-4adad2677702,Ulibarri312 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,Heller342 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Krajcik437 -9360820c-8602-4335-8b50-c88d627a0c20,Oberbrunner298 -9360820c-8602-4335-8b50-c88d627a0c20,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,Botsford977 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,MacGyver246 -beff242e-580b-47c0-9844-c1a68c36c5bf,Towne435 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,Ebert178 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testQueryWithExternalConstantInWhere.tsv b/fhir-server/src/test/resources/responses/ParserTest/testQueryWithExternalConstantInWhere.tsv new file mode 100644 index 0000000000..d6fd6d08d9 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testQueryWithExternalConstantInWhere.tsv @@ -0,0 +1,10 @@ +121503c8-9564-4b48-9086-a22df717948e Gleichner915 +2b36c1e2-bbe1-45ae-8124-4adad2677702 Ulibarri312 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 Heller342 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Krajcik437 +9360820c-8602-4335-8b50-c88d627a0c20 Oberbrunner298 +9360820c-8602-4335-8b50-c88d627a0c20 +a7eb2ce7-1075-426c-addd-957b861b0e55 Botsford977 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 MacGyver246 +beff242e-580b-47c0-9844-c1a68c36c5bf Towne435 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 Ebert178 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReference.csv b/fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReference.csv deleted file mode 100644 index 9bd1b08b0c..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReference.csv +++ /dev/null @@ -1,217 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,Hemoglobin A1c total in Blood < 7.0 -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -beff242e-580b-47c0-9844-c1a68c36c5bf, -beff242e-580b-47c0-9844-c1a68c36c5bf, -beff242e-580b-47c0-9844-c1a68c36c5bf, -beff242e-580b-47c0-9844-c1a68c36c5bf, -beff242e-580b-47c0-9844-c1a68c36c5bf, -beff242e-580b-47c0-9844-c1a68c36c5bf, -beff242e-580b-47c0-9844-c1a68c36c5bf, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, diff --git a/fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReference.tsv b/fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReference.tsv new file mode 100644 index 0000000000..4ee8755ee7 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReference.tsv @@ -0,0 +1,217 @@ +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d Hemoglobin A1c total in Blood < 7.0 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReferenceWithWrongType.csv b/fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReferenceWithWrongType.csv deleted file mode 100644 index 799e0d7cfd..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReferenceWithWrongType.csv +++ /dev/null @@ -1,217 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -121503c8-9564-4b48-9086-a22df717948e, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -2b36c1e2-bbe1-45ae-8124-4adad2677702, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -7001ad9c-34d2-4eb5-8165-5fdc2147f469, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -8ee183e2-b3c0-4151-be94-b945d6aa8c6d, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -9360820c-8602-4335-8b50-c88d627a0c20, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -a7eb2ce7-1075-426c-addd-957b861b0e55, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9, -beff242e-580b-47c0-9844-c1a68c36c5bf, -beff242e-580b-47c0-9844-c1a68c36c5bf, -beff242e-580b-47c0-9844-c1a68c36c5bf, -beff242e-580b-47c0-9844-c1a68c36c5bf, -beff242e-580b-47c0-9844-c1a68c36c5bf, -beff242e-580b-47c0-9844-c1a68c36c5bf, -beff242e-580b-47c0-9844-c1a68c36c5bf, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, -e62e52ae-2d75-4070-a0ae-3cc78d35ed08, diff --git a/fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReferenceWithWrongType.tsv b/fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReferenceWithWrongType.tsv new file mode 100644 index 0000000000..78bd0df1d6 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testResolutionOfExtensionReferenceWithWrongType.tsv @@ -0,0 +1,217 @@ +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingMonomorphicResolve.csv b/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingMonomorphicResolve.csv deleted file mode 100644 index 6ee5afd19e..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingMonomorphicResolve.csv +++ /dev/null @@ -1,3519 +0,0 @@ -0134901a-4a69-4b39-92ea-d6f48ec83c6e,fcab9efe-fb05-42d6-b366-ce6db1cc4093 -0134901a-4a69-4b39-92ea-d6f48ec83c6e,f1293c5e-c997-4ba9-8bfd-438e7a840dc8 -0134901a-4a69-4b39-92ea-d6f48ec83c6e,e4358f28-62a8-4e06-b2bd-cb00d3310349 -0134901a-4a69-4b39-92ea-d6f48ec83c6e,8f76d729-9f74-4cfd-be2a-8b033b568e18 -0134901a-4a69-4b39-92ea-d6f48ec83c6e,39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 -0134901a-4a69-4b39-92ea-d6f48ec83c6e,31d4c712-75b1-4ecb-9d1a-ccc4b8453826 -0134901a-4a69-4b39-92ea-d6f48ec83c6e,24c4dd5b-748b-4165-a0cc-2867b76c2dc2 -0134901a-4a69-4b39-92ea-d6f48ec83c6e,16d280a2-c61f-487f-9034-22e884158969 -0134901a-4a69-4b39-92ea-d6f48ec83c6e,0134901a-4a69-4b39-92ea-d6f48ec83c6e -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,d90676d2-ce74-4867-a00f-6dbffa7c00be -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,cc56af6d-25c6-480e-9767-da02a55551da -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,baf48725-f0f9-4357-a71d-b1104910deae -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,9ed8703e-3b40-424c-9e98-b3b404051f99 -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,8be81657-8ba6-4ec5-8ff9-4809367096ea -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,7f410064-638a-4477-85c4-97fc2bb14a49 -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,5cedf18a-fc44-4c39-a80f-2106a0d76934 -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,4f342a71-dec3-403e-970b-e4c21b9eb98b -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,4db144d3-a596-4718-a50a-8ac9175ad386 -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,4215b5d0-bdd9-4222-a04a-81bb637d60af -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,29d4e919-2bd2-44e6-bb8a-380a780f60ff -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,102e16c5-4920-4dad-b142-0b280b2aacad -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -0250a217-a663-403b-a708-5d14eadf0c40,a2c3ee1d-ec35-4b26-9bab-12de3f47d604 -0250a217-a663-403b-a708-5d14eadf0c40,86415df5-7e47-4637-9e09-aaad9e7628d1 -0250a217-a663-403b-a708-5d14eadf0c40,749babeb-6883-4bd4-92a5-bec52769071c -0250a217-a663-403b-a708-5d14eadf0c40,578cc35c-0982-4d72-a729-b304c026f075 -0250a217-a663-403b-a708-5d14eadf0c40,0250a217-a663-403b-a708-5d14eadf0c40 -02b993c0-b358-481c-b0d0-0767387feb9f,fce20464-ff98-4051-8714-6b9584e52740 -02b993c0-b358-481c-b0d0-0767387feb9f,f3eb0c38-2571-44e7-be39-732eb52cf1df -02b993c0-b358-481c-b0d0-0767387feb9f,f3c2f842-6aa3-42c9-a0f3-895c40456868 -02b993c0-b358-481c-b0d0-0767387feb9f,ede7745a-8f3e-4e20-9f16-33756be7ab6c -02b993c0-b358-481c-b0d0-0767387feb9f,ca3e0486-fd6b-4a13-a741-3a47760b412d -02b993c0-b358-481c-b0d0-0767387feb9f,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -02b993c0-b358-481c-b0d0-0767387feb9f,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -02b993c0-b358-481c-b0d0-0767387feb9f,a221198d-000e-497b-8b70-bb4d37f7bbe1 -02b993c0-b358-481c-b0d0-0767387feb9f,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -02b993c0-b358-481c-b0d0-0767387feb9f,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -02b993c0-b358-481c-b0d0-0767387feb9f,88b16960-bfff-41d0-81e4-9a4630834a00 -02b993c0-b358-481c-b0d0-0767387feb9f,522e2abe-ad96-4d1a-b5a5-748faa997531 -02b993c0-b358-481c-b0d0-0767387feb9f,4cfe72fe-21a0-4201-87b6-ef93695d2495 -02b993c0-b358-481c-b0d0-0767387feb9f,498f4b18-bd4c-470d-9cde-2fca70ec8964 -02b993c0-b358-481c-b0d0-0767387feb9f,202131ae-78bb-4104-89b0-fb90645760f6 -02b993c0-b358-481c-b0d0-0767387feb9f,11dafd5c-85e7-4275-a147-89a63641e35e -02b993c0-b358-481c-b0d0-0767387feb9f,02b993c0-b358-481c-b0d0-0767387feb9f -0364073f-91d8-47a1-b8b0-107c6318a691,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -0364073f-91d8-47a1-b8b0-107c6318a691,e8eba267-fecb-477e-9625-771fbcfc3cba -0364073f-91d8-47a1-b8b0-107c6318a691,e8c41168-a093-40eb-8baa-6802d24f554a -0364073f-91d8-47a1-b8b0-107c6318a691,e26ae29a-213a-4f79-98c2-cc81cf2f451d -0364073f-91d8-47a1-b8b0-107c6318a691,e0dfbc08-45ad-4a81-b648-7e65c066f673 -0364073f-91d8-47a1-b8b0-107c6318a691,d535b213-2abc-438f-aa6a-c06476d60b09 -0364073f-91d8-47a1-b8b0-107c6318a691,c892b1d7-7485-407d-8883-54fb7fecebc1 -0364073f-91d8-47a1-b8b0-107c6318a691,b6900c21-d310-4fb4-8b8f-176a09c91989 -0364073f-91d8-47a1-b8b0-107c6318a691,b00df815-7528-4967-bfe2-311130d91c21 -0364073f-91d8-47a1-b8b0-107c6318a691,add6d754-a075-4693-a33a-c061c9a368ff -0364073f-91d8-47a1-b8b0-107c6318a691,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -0364073f-91d8-47a1-b8b0-107c6318a691,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -0364073f-91d8-47a1-b8b0-107c6318a691,8a1908f7-47cc-4f82-859c-781a193e9901 -0364073f-91d8-47a1-b8b0-107c6318a691,8743e69b-17aa-44ff-b8fa-4f582665efc6 -0364073f-91d8-47a1-b8b0-107c6318a691,721e4027-a080-40d8-bc4a-43cd33477611 -0364073f-91d8-47a1-b8b0-107c6318a691,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -0364073f-91d8-47a1-b8b0-107c6318a691,54c750aa-570a-4e8e-9a02-418781923e39 -0364073f-91d8-47a1-b8b0-107c6318a691,50eac25a-3761-4de3-8a69-aae52e658300 -0364073f-91d8-47a1-b8b0-107c6318a691,4a464bb5-9373-4f1b-9c03-053c64797114 -0364073f-91d8-47a1-b8b0-107c6318a691,4506aa76-9d0d-40e4-85bc-5735f74522a0 -0364073f-91d8-47a1-b8b0-107c6318a691,42a9a333-d09d-4b9e-af96-1f02af26bac3 -0364073f-91d8-47a1-b8b0-107c6318a691,2fc75f11-2097-4e1e-8390-dbff3e844b7a -0364073f-91d8-47a1-b8b0-107c6318a691,274a2e49-9170-4945-bca2-ab6ef4bded75 -0364073f-91d8-47a1-b8b0-107c6318a691,0364073f-91d8-47a1-b8b0-107c6318a691 -04945715-8ad5-4d8f-bfda-ae26662a3610,b0d337c5-3555-4817-ba59-4ceea5ad8a91 -04945715-8ad5-4d8f-bfda-ae26662a3610,79be3b69-23d8-4408-8f01-68d993e63b6c -04945715-8ad5-4d8f-bfda-ae26662a3610,04945715-8ad5-4d8f-bfda-ae26662a3610 -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,f3aa491a-4dbd-4436-b674-18b2177dcf18 -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,e1040d58-53bc-4467-8101-ca53b772cede -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,d05461d6-13bf-402c-890d-db6404933f7c -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,b7772635-1801-48e4-a442-f4aaa6860544 -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,b07fb98d-2da4-423a-83b4-7a673de93096 -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,97b42d67-8691-433d-8a31-dada076162ae -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,720f7ed6-7b2e-41dc-a0e0-724a3332aa24 -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,2ffd6142-b75b-406f-bc06-cdb1c02eaefc -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,224e538d-3622-4f5e-b772-211ed358d8de -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,16a3408d-c927-4d02-a1ae-cad32419caab -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,04d88eb4-fb1b-4fa1-802a-5624f4c61b32 -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,e1c2ad9f-6f61-4f16-805a-2f405b63659a -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,d6988116-3c63-44f8-9f94-9e9d51cc5a37 -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,cfcbe34a-edc5-4442-bc05-5927fa00336b -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,c90aae7e-5704-4e13-8794-41ab377193bd -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,bf895c48-1d52-4780-8e9a-532d69356d28 -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,ae9e663e-8c15-43c9-8a88-52b61cbf07a9 -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,56999896-e36b-4e63-a6b6-dbb85dfe1936 -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,51241857-a7a4-4517-96e3-21f797581f89 -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,4cd95073-6db3-4eb2-ac4b-0aacb182b352 -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,4148ac36-1dcb-4e96-89cd-355e7e8f919b -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,2e8eb256-84c9-499a-8bf6-bb39504373e1 -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 -0af4a77e-892e-4b3f-9110-4c5224782250,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -0af4a77e-892e-4b3f-9110-4c5224782250,ee24bf89-81a3-4259-a382-86917f3829d4 -0af4a77e-892e-4b3f-9110-4c5224782250,e363eb67-64c7-440f-93fd-5c8787c69a85 -0af4a77e-892e-4b3f-9110-4c5224782250,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -0af4a77e-892e-4b3f-9110-4c5224782250,db79f75d-af3c-4f82-b4e5-9b5d26598eef -0af4a77e-892e-4b3f-9110-4c5224782250,d7a48a26-7731-4046-bf22-78f0b8084eb9 -0af4a77e-892e-4b3f-9110-4c5224782250,d080c116-681a-494a-a928-45924109b49d -0af4a77e-892e-4b3f-9110-4c5224782250,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -0af4a77e-892e-4b3f-9110-4c5224782250,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -0af4a77e-892e-4b3f-9110-4c5224782250,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -0af4a77e-892e-4b3f-9110-4c5224782250,990c21ab-433b-4920-873e-f9dfc7103d9d -0af4a77e-892e-4b3f-9110-4c5224782250,922e2443-9cc5-421f-8310-9cd23f6e9f2d -0af4a77e-892e-4b3f-9110-4c5224782250,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -0af4a77e-892e-4b3f-9110-4c5224782250,8089a9ac-9e71-4487-a231-f720e4bc8d3e -0af4a77e-892e-4b3f-9110-4c5224782250,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -0af4a77e-892e-4b3f-9110-4c5224782250,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -0af4a77e-892e-4b3f-9110-4c5224782250,6d47a2fe-3645-4c5c-bebe-1b822eff197f -0af4a77e-892e-4b3f-9110-4c5224782250,67fc12fc-bb9f-40f1-9051-127a788b3081 -0af4a77e-892e-4b3f-9110-4c5224782250,634f0889-3a83-484a-bcc8-86f48e333c7b -0af4a77e-892e-4b3f-9110-4c5224782250,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -0af4a77e-892e-4b3f-9110-4c5224782250,4adcaf72-5241-4877-b61c-f34060576c50 -0af4a77e-892e-4b3f-9110-4c5224782250,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -0af4a77e-892e-4b3f-9110-4c5224782250,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -0af4a77e-892e-4b3f-9110-4c5224782250,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -0af4a77e-892e-4b3f-9110-4c5224782250,374fd699-6b74-4500-9d60-2473c7e0364f -0af4a77e-892e-4b3f-9110-4c5224782250,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -0af4a77e-892e-4b3f-9110-4c5224782250,308eba53-29fa-489a-97dc-741b077841a7 -0af4a77e-892e-4b3f-9110-4c5224782250,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -0af4a77e-892e-4b3f-9110-4c5224782250,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -0af4a77e-892e-4b3f-9110-4c5224782250,21995497-0eb8-4c0b-8c23-e6a829f3d596 -0af4a77e-892e-4b3f-9110-4c5224782250,18ebf5ea-b0a2-4333-9e9c-40217de809ff -0af4a77e-892e-4b3f-9110-4c5224782250,151e3048-66ad-4411-8753-677877e3bf0a -0af4a77e-892e-4b3f-9110-4c5224782250,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -0af4a77e-892e-4b3f-9110-4c5224782250,0af4a77e-892e-4b3f-9110-4c5224782250 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,ee24bf89-81a3-4259-a382-86917f3829d4 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,e363eb67-64c7-440f-93fd-5c8787c69a85 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,db79f75d-af3c-4f82-b4e5-9b5d26598eef -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,d7a48a26-7731-4046-bf22-78f0b8084eb9 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,d080c116-681a-494a-a928-45924109b49d -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,990c21ab-433b-4920-873e-f9dfc7103d9d -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,922e2443-9cc5-421f-8310-9cd23f6e9f2d -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,8089a9ac-9e71-4487-a231-f720e4bc8d3e -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,6d47a2fe-3645-4c5c-bebe-1b822eff197f -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,67fc12fc-bb9f-40f1-9051-127a788b3081 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,634f0889-3a83-484a-bcc8-86f48e333c7b -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,4adcaf72-5241-4877-b61c-f34060576c50 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,374fd699-6b74-4500-9d60-2473c7e0364f -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,308eba53-29fa-489a-97dc-741b077841a7 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,21995497-0eb8-4c0b-8c23-e6a829f3d596 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,18ebf5ea-b0a2-4333-9e9c-40217de809ff -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,151e3048-66ad-4411-8753-677877e3bf0a -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,0af4a77e-892e-4b3f-9110-4c5224782250 -102e16c5-4920-4dad-b142-0b280b2aacad,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -102e16c5-4920-4dad-b142-0b280b2aacad,d90676d2-ce74-4867-a00f-6dbffa7c00be -102e16c5-4920-4dad-b142-0b280b2aacad,cc56af6d-25c6-480e-9767-da02a55551da -102e16c5-4920-4dad-b142-0b280b2aacad,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -102e16c5-4920-4dad-b142-0b280b2aacad,baf48725-f0f9-4357-a71d-b1104910deae -102e16c5-4920-4dad-b142-0b280b2aacad,9ed8703e-3b40-424c-9e98-b3b404051f99 -102e16c5-4920-4dad-b142-0b280b2aacad,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -102e16c5-4920-4dad-b142-0b280b2aacad,8be81657-8ba6-4ec5-8ff9-4809367096ea -102e16c5-4920-4dad-b142-0b280b2aacad,7f410064-638a-4477-85c4-97fc2bb14a49 -102e16c5-4920-4dad-b142-0b280b2aacad,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -102e16c5-4920-4dad-b142-0b280b2aacad,5cedf18a-fc44-4c39-a80f-2106a0d76934 -102e16c5-4920-4dad-b142-0b280b2aacad,4f342a71-dec3-403e-970b-e4c21b9eb98b -102e16c5-4920-4dad-b142-0b280b2aacad,4db144d3-a596-4718-a50a-8ac9175ad386 -102e16c5-4920-4dad-b142-0b280b2aacad,4215b5d0-bdd9-4222-a04a-81bb637d60af -102e16c5-4920-4dad-b142-0b280b2aacad,29d4e919-2bd2-44e6-bb8a-380a780f60ff -102e16c5-4920-4dad-b142-0b280b2aacad,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -102e16c5-4920-4dad-b142-0b280b2aacad,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -102e16c5-4920-4dad-b142-0b280b2aacad,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -102e16c5-4920-4dad-b142-0b280b2aacad,102e16c5-4920-4dad-b142-0b280b2aacad -102e16c5-4920-4dad-b142-0b280b2aacad,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -11dafd5c-85e7-4275-a147-89a63641e35e,fce20464-ff98-4051-8714-6b9584e52740 -11dafd5c-85e7-4275-a147-89a63641e35e,f3eb0c38-2571-44e7-be39-732eb52cf1df -11dafd5c-85e7-4275-a147-89a63641e35e,f3c2f842-6aa3-42c9-a0f3-895c40456868 -11dafd5c-85e7-4275-a147-89a63641e35e,ede7745a-8f3e-4e20-9f16-33756be7ab6c -11dafd5c-85e7-4275-a147-89a63641e35e,ca3e0486-fd6b-4a13-a741-3a47760b412d -11dafd5c-85e7-4275-a147-89a63641e35e,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -11dafd5c-85e7-4275-a147-89a63641e35e,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -11dafd5c-85e7-4275-a147-89a63641e35e,a221198d-000e-497b-8b70-bb4d37f7bbe1 -11dafd5c-85e7-4275-a147-89a63641e35e,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -11dafd5c-85e7-4275-a147-89a63641e35e,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -11dafd5c-85e7-4275-a147-89a63641e35e,88b16960-bfff-41d0-81e4-9a4630834a00 -11dafd5c-85e7-4275-a147-89a63641e35e,522e2abe-ad96-4d1a-b5a5-748faa997531 -11dafd5c-85e7-4275-a147-89a63641e35e,4cfe72fe-21a0-4201-87b6-ef93695d2495 -11dafd5c-85e7-4275-a147-89a63641e35e,498f4b18-bd4c-470d-9cde-2fca70ec8964 -11dafd5c-85e7-4275-a147-89a63641e35e,202131ae-78bb-4104-89b0-fb90645760f6 -11dafd5c-85e7-4275-a147-89a63641e35e,11dafd5c-85e7-4275-a147-89a63641e35e -11dafd5c-85e7-4275-a147-89a63641e35e,02b993c0-b358-481c-b0d0-0767387feb9f -12b212d8-bc6e-415a-93b0-594381726668,bac374c0-50e8-4a5c-947b-82a8e9a747a9 -12b212d8-bc6e-415a-93b0-594381726668,8bc7af32-e5ad-4849-ba4d-b446db833ab4 -12b212d8-bc6e-415a-93b0-594381726668,7ea43fc6-b1f7-46be-a308-5ecb275a1081 -12b212d8-bc6e-415a-93b0-594381726668,2de1f829-c9d2-4f22-bf39-536dcb82fc3e -12b212d8-bc6e-415a-93b0-594381726668,2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 -12b212d8-bc6e-415a-93b0-594381726668,23780032-f3bb-4b40-af2e-3fa6b1677376 -12b212d8-bc6e-415a-93b0-594381726668,21c6e0fc-bf47-4f80-b1ff-85b940445bdf -12b212d8-bc6e-415a-93b0-594381726668,12b212d8-bc6e-415a-93b0-594381726668 -12df1bed-5472-4c48-8e88-2cbb3e5eab33,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -12df1bed-5472-4c48-8e88-2cbb3e5eab33,d90676d2-ce74-4867-a00f-6dbffa7c00be -12df1bed-5472-4c48-8e88-2cbb3e5eab33,cc56af6d-25c6-480e-9767-da02a55551da -12df1bed-5472-4c48-8e88-2cbb3e5eab33,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -12df1bed-5472-4c48-8e88-2cbb3e5eab33,baf48725-f0f9-4357-a71d-b1104910deae -12df1bed-5472-4c48-8e88-2cbb3e5eab33,9ed8703e-3b40-424c-9e98-b3b404051f99 -12df1bed-5472-4c48-8e88-2cbb3e5eab33,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -12df1bed-5472-4c48-8e88-2cbb3e5eab33,8be81657-8ba6-4ec5-8ff9-4809367096ea -12df1bed-5472-4c48-8e88-2cbb3e5eab33,7f410064-638a-4477-85c4-97fc2bb14a49 -12df1bed-5472-4c48-8e88-2cbb3e5eab33,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -12df1bed-5472-4c48-8e88-2cbb3e5eab33,5cedf18a-fc44-4c39-a80f-2106a0d76934 -12df1bed-5472-4c48-8e88-2cbb3e5eab33,4f342a71-dec3-403e-970b-e4c21b9eb98b -12df1bed-5472-4c48-8e88-2cbb3e5eab33,4db144d3-a596-4718-a50a-8ac9175ad386 -12df1bed-5472-4c48-8e88-2cbb3e5eab33,4215b5d0-bdd9-4222-a04a-81bb637d60af -12df1bed-5472-4c48-8e88-2cbb3e5eab33,29d4e919-2bd2-44e6-bb8a-380a780f60ff -12df1bed-5472-4c48-8e88-2cbb3e5eab33,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -12df1bed-5472-4c48-8e88-2cbb3e5eab33,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -12df1bed-5472-4c48-8e88-2cbb3e5eab33,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -12df1bed-5472-4c48-8e88-2cbb3e5eab33,102e16c5-4920-4dad-b142-0b280b2aacad -12df1bed-5472-4c48-8e88-2cbb3e5eab33,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -151e3048-66ad-4411-8753-677877e3bf0a,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -151e3048-66ad-4411-8753-677877e3bf0a,ee24bf89-81a3-4259-a382-86917f3829d4 -151e3048-66ad-4411-8753-677877e3bf0a,e363eb67-64c7-440f-93fd-5c8787c69a85 -151e3048-66ad-4411-8753-677877e3bf0a,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -151e3048-66ad-4411-8753-677877e3bf0a,db79f75d-af3c-4f82-b4e5-9b5d26598eef -151e3048-66ad-4411-8753-677877e3bf0a,d7a48a26-7731-4046-bf22-78f0b8084eb9 -151e3048-66ad-4411-8753-677877e3bf0a,d080c116-681a-494a-a928-45924109b49d -151e3048-66ad-4411-8753-677877e3bf0a,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -151e3048-66ad-4411-8753-677877e3bf0a,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -151e3048-66ad-4411-8753-677877e3bf0a,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -151e3048-66ad-4411-8753-677877e3bf0a,990c21ab-433b-4920-873e-f9dfc7103d9d -151e3048-66ad-4411-8753-677877e3bf0a,922e2443-9cc5-421f-8310-9cd23f6e9f2d -151e3048-66ad-4411-8753-677877e3bf0a,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -151e3048-66ad-4411-8753-677877e3bf0a,8089a9ac-9e71-4487-a231-f720e4bc8d3e -151e3048-66ad-4411-8753-677877e3bf0a,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -151e3048-66ad-4411-8753-677877e3bf0a,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -151e3048-66ad-4411-8753-677877e3bf0a,6d47a2fe-3645-4c5c-bebe-1b822eff197f -151e3048-66ad-4411-8753-677877e3bf0a,67fc12fc-bb9f-40f1-9051-127a788b3081 -151e3048-66ad-4411-8753-677877e3bf0a,634f0889-3a83-484a-bcc8-86f48e333c7b -151e3048-66ad-4411-8753-677877e3bf0a,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -151e3048-66ad-4411-8753-677877e3bf0a,4adcaf72-5241-4877-b61c-f34060576c50 -151e3048-66ad-4411-8753-677877e3bf0a,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -151e3048-66ad-4411-8753-677877e3bf0a,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -151e3048-66ad-4411-8753-677877e3bf0a,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -151e3048-66ad-4411-8753-677877e3bf0a,374fd699-6b74-4500-9d60-2473c7e0364f -151e3048-66ad-4411-8753-677877e3bf0a,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -151e3048-66ad-4411-8753-677877e3bf0a,308eba53-29fa-489a-97dc-741b077841a7 -151e3048-66ad-4411-8753-677877e3bf0a,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -151e3048-66ad-4411-8753-677877e3bf0a,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -151e3048-66ad-4411-8753-677877e3bf0a,21995497-0eb8-4c0b-8c23-e6a829f3d596 -151e3048-66ad-4411-8753-677877e3bf0a,18ebf5ea-b0a2-4333-9e9c-40217de809ff -151e3048-66ad-4411-8753-677877e3bf0a,151e3048-66ad-4411-8753-677877e3bf0a -151e3048-66ad-4411-8753-677877e3bf0a,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -151e3048-66ad-4411-8753-677877e3bf0a,0af4a77e-892e-4b3f-9110-4c5224782250 -16a3408d-c927-4d02-a1ae-cad32419caab,f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 -16a3408d-c927-4d02-a1ae-cad32419caab,f3aa491a-4dbd-4436-b674-18b2177dcf18 -16a3408d-c927-4d02-a1ae-cad32419caab,e1040d58-53bc-4467-8101-ca53b772cede -16a3408d-c927-4d02-a1ae-cad32419caab,d05461d6-13bf-402c-890d-db6404933f7c -16a3408d-c927-4d02-a1ae-cad32419caab,b7772635-1801-48e4-a442-f4aaa6860544 -16a3408d-c927-4d02-a1ae-cad32419caab,b07fb98d-2da4-423a-83b4-7a673de93096 -16a3408d-c927-4d02-a1ae-cad32419caab,97b42d67-8691-433d-8a31-dada076162ae -16a3408d-c927-4d02-a1ae-cad32419caab,720f7ed6-7b2e-41dc-a0e0-724a3332aa24 -16a3408d-c927-4d02-a1ae-cad32419caab,4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 -16a3408d-c927-4d02-a1ae-cad32419caab,2ffd6142-b75b-406f-bc06-cdb1c02eaefc -16a3408d-c927-4d02-a1ae-cad32419caab,224e538d-3622-4f5e-b772-211ed358d8de -16a3408d-c927-4d02-a1ae-cad32419caab,16a3408d-c927-4d02-a1ae-cad32419caab -16a3408d-c927-4d02-a1ae-cad32419caab,04d88eb4-fb1b-4fa1-802a-5624f4c61b32 -16d280a2-c61f-487f-9034-22e884158969,fcab9efe-fb05-42d6-b366-ce6db1cc4093 -16d280a2-c61f-487f-9034-22e884158969,f1293c5e-c997-4ba9-8bfd-438e7a840dc8 -16d280a2-c61f-487f-9034-22e884158969,e4358f28-62a8-4e06-b2bd-cb00d3310349 -16d280a2-c61f-487f-9034-22e884158969,8f76d729-9f74-4cfd-be2a-8b033b568e18 -16d280a2-c61f-487f-9034-22e884158969,39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 -16d280a2-c61f-487f-9034-22e884158969,31d4c712-75b1-4ecb-9d1a-ccc4b8453826 -16d280a2-c61f-487f-9034-22e884158969,24c4dd5b-748b-4165-a0cc-2867b76c2dc2 -16d280a2-c61f-487f-9034-22e884158969,16d280a2-c61f-487f-9034-22e884158969 -16d280a2-c61f-487f-9034-22e884158969,0134901a-4a69-4b39-92ea-d6f48ec83c6e -17bcf2ea-d921-4715-91c5-6b15226b33d3,def5fd23-2b74-4c64-85e9-fac27e626bb7 -17bcf2ea-d921-4715-91c5-6b15226b33d3,96b2282d-1384-4cfb-9958-f009fb501626 -17bcf2ea-d921-4715-91c5-6b15226b33d3,17bcf2ea-d921-4715-91c5-6b15226b33d3 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -18ebf5ea-b0a2-4333-9e9c-40217de809ff,ee24bf89-81a3-4259-a382-86917f3829d4 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,e363eb67-64c7-440f-93fd-5c8787c69a85 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,db79f75d-af3c-4f82-b4e5-9b5d26598eef -18ebf5ea-b0a2-4333-9e9c-40217de809ff,d7a48a26-7731-4046-bf22-78f0b8084eb9 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,d080c116-681a-494a-a928-45924109b49d -18ebf5ea-b0a2-4333-9e9c-40217de809ff,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -18ebf5ea-b0a2-4333-9e9c-40217de809ff,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -18ebf5ea-b0a2-4333-9e9c-40217de809ff,990c21ab-433b-4920-873e-f9dfc7103d9d -18ebf5ea-b0a2-4333-9e9c-40217de809ff,922e2443-9cc5-421f-8310-9cd23f6e9f2d -18ebf5ea-b0a2-4333-9e9c-40217de809ff,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,8089a9ac-9e71-4487-a231-f720e4bc8d3e -18ebf5ea-b0a2-4333-9e9c-40217de809ff,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -18ebf5ea-b0a2-4333-9e9c-40217de809ff,6d47a2fe-3645-4c5c-bebe-1b822eff197f -18ebf5ea-b0a2-4333-9e9c-40217de809ff,67fc12fc-bb9f-40f1-9051-127a788b3081 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,634f0889-3a83-484a-bcc8-86f48e333c7b -18ebf5ea-b0a2-4333-9e9c-40217de809ff,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,4adcaf72-5241-4877-b61c-f34060576c50 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -18ebf5ea-b0a2-4333-9e9c-40217de809ff,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,374fd699-6b74-4500-9d60-2473c7e0364f -18ebf5ea-b0a2-4333-9e9c-40217de809ff,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,308eba53-29fa-489a-97dc-741b077841a7 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -18ebf5ea-b0a2-4333-9e9c-40217de809ff,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,21995497-0eb8-4c0b-8c23-e6a829f3d596 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,18ebf5ea-b0a2-4333-9e9c-40217de809ff -18ebf5ea-b0a2-4333-9e9c-40217de809ff,151e3048-66ad-4411-8753-677877e3bf0a -18ebf5ea-b0a2-4333-9e9c-40217de809ff,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -18ebf5ea-b0a2-4333-9e9c-40217de809ff,0af4a77e-892e-4b3f-9110-4c5224782250 -19502b5a-2031-487d-9d9c-2001f959408b,fc771883-3bd6-46d9-9626-a130e1b902de -19502b5a-2031-487d-9d9c-2001f959408b,e2387a81-5ff5-4daf-b2e8-881998d0d917 -19502b5a-2031-487d-9d9c-2001f959408b,b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa -19502b5a-2031-487d-9d9c-2001f959408b,b1d4dc41-07ae-44db-ae17-1df86c5a62cf -19502b5a-2031-487d-9d9c-2001f959408b,67e5cf95-236b-4f75-abc5-034ca2966448 -19502b5a-2031-487d-9d9c-2001f959408b,48cc2275-a478-4ade-b076-cf38e1d16017 -19502b5a-2031-487d-9d9c-2001f959408b,41d26b1c-57b9-408c-b955-bf0ee1db4809 -19502b5a-2031-487d-9d9c-2001f959408b,3ddf46fe-b5be-456d-810f-ea6a7402236d -19502b5a-2031-487d-9d9c-2001f959408b,3397a796-894d-409c-97de-a9e6f3f88198 -19502b5a-2031-487d-9d9c-2001f959408b,19502b5a-2031-487d-9d9c-2001f959408b -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,d90676d2-ce74-4867-a00f-6dbffa7c00be -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,cc56af6d-25c6-480e-9767-da02a55551da -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,baf48725-f0f9-4357-a71d-b1104910deae -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,9ed8703e-3b40-424c-9e98-b3b404051f99 -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,8be81657-8ba6-4ec5-8ff9-4809367096ea -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,7f410064-638a-4477-85c4-97fc2bb14a49 -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,5cedf18a-fc44-4c39-a80f-2106a0d76934 -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,4f342a71-dec3-403e-970b-e4c21b9eb98b -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,4db144d3-a596-4718-a50a-8ac9175ad386 -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,4215b5d0-bdd9-4222-a04a-81bb637d60af -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,29d4e919-2bd2-44e6-bb8a-380a780f60ff -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,102e16c5-4920-4dad-b142-0b280b2aacad -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -1db1c71b-9eeb-4a98-abc1-eb699b38510c,fcc916dd-5e4f-4bf7-9aef-c906a36d7301 -1db1c71b-9eeb-4a98-abc1-eb699b38510c,c57d51a7-45de-41b9-92fc-efd0634effaf -1db1c71b-9eeb-4a98-abc1-eb699b38510c,ae7ddaef-4911-418c-8401-d978617e145b -1db1c71b-9eeb-4a98-abc1-eb699b38510c,acb2329d-b1c3-45c3-ad28-91a09aa521e1 -1db1c71b-9eeb-4a98-abc1-eb699b38510c,a89c0c42-c19f-4d7f-a15e-d04e043c92f6 -1db1c71b-9eeb-4a98-abc1-eb699b38510c,836b2e59-fb97-4014-ab0c-d5a5dc750969 -1db1c71b-9eeb-4a98-abc1-eb699b38510c,683714ad-5194-49e7-9b8f-4e306cf68ae1 -1db1c71b-9eeb-4a98-abc1-eb699b38510c,5f64131b-d410-449a-9de6-35415919fec5 -1db1c71b-9eeb-4a98-abc1-eb699b38510c,4f3d1e93-a28f-446e-91af-2baf0168b82b -1db1c71b-9eeb-4a98-abc1-eb699b38510c,1db1c71b-9eeb-4a98-abc1-eb699b38510c -1f3443ee-d15e-4894-b18e-185cfadfcdea,f19eefca-c1ad-4860-bdda-4674a631d464 -1f3443ee-d15e-4894-b18e-185cfadfcdea,f18b08bd-40da-43c1-87ec-4ba23542635f -1f3443ee-d15e-4894-b18e-185cfadfcdea,b1ccd55a-6b88-4240-b20c-ca893f36e23b -1f3443ee-d15e-4894-b18e-185cfadfcdea,900ce9fe-02d3-476b-902a-9467767ecdcf -1f3443ee-d15e-4894-b18e-185cfadfcdea,8dc2ce26-aec7-43c0-9771-350af4257ad8 -1f3443ee-d15e-4894-b18e-185cfadfcdea,1f3443ee-d15e-4894-b18e-185cfadfcdea -1fd714a6-48b4-425a-99b3-ba5c1a995cce,f77a8561-5adc-452a-ac9a-76273b6a6678 -1fd714a6-48b4-425a-99b3-ba5c1a995cce,d79728e1-8834-4f4a-8edc-ce18aca18be6 -1fd714a6-48b4-425a-99b3-ba5c1a995cce,cf67a8d4-48e2-4dc9-a521-6aabcff0330b -1fd714a6-48b4-425a-99b3-ba5c1a995cce,bb1a7816-4798-4f58-9ce4-c5bd3af682a8 -1fd714a6-48b4-425a-99b3-ba5c1a995cce,b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 -1fd714a6-48b4-425a-99b3-ba5c1a995cce,a6cb423a-7571-46df-83e2-ccc6820adb36 -1fd714a6-48b4-425a-99b3-ba5c1a995cce,7e7322a6-7912-4101-b3be-d134245a0626 -1fd714a6-48b4-425a-99b3-ba5c1a995cce,777aa5f2-2a09-4aed-92ea-912694e28b48 -1fd714a6-48b4-425a-99b3-ba5c1a995cce,6e187f47-91a1-4217-a185-31b6f01db225 -1fd714a6-48b4-425a-99b3-ba5c1a995cce,62059efc-7607-41c9-a3ba-70948f6a8a1d -1fd714a6-48b4-425a-99b3-ba5c1a995cce,2d2e07ec-e697-4384-a679-867e93740921 -1fd714a6-48b4-425a-99b3-ba5c1a995cce,20fbcb6e-d793-4b05-8e29-8ff82572b0db -1fd714a6-48b4-425a-99b3-ba5c1a995cce,1fd714a6-48b4-425a-99b3-ba5c1a995cce -202131ae-78bb-4104-89b0-fb90645760f6,fce20464-ff98-4051-8714-6b9584e52740 -202131ae-78bb-4104-89b0-fb90645760f6,f3eb0c38-2571-44e7-be39-732eb52cf1df -202131ae-78bb-4104-89b0-fb90645760f6,f3c2f842-6aa3-42c9-a0f3-895c40456868 -202131ae-78bb-4104-89b0-fb90645760f6,ede7745a-8f3e-4e20-9f16-33756be7ab6c -202131ae-78bb-4104-89b0-fb90645760f6,ca3e0486-fd6b-4a13-a741-3a47760b412d -202131ae-78bb-4104-89b0-fb90645760f6,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -202131ae-78bb-4104-89b0-fb90645760f6,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -202131ae-78bb-4104-89b0-fb90645760f6,a221198d-000e-497b-8b70-bb4d37f7bbe1 -202131ae-78bb-4104-89b0-fb90645760f6,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -202131ae-78bb-4104-89b0-fb90645760f6,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -202131ae-78bb-4104-89b0-fb90645760f6,88b16960-bfff-41d0-81e4-9a4630834a00 -202131ae-78bb-4104-89b0-fb90645760f6,522e2abe-ad96-4d1a-b5a5-748faa997531 -202131ae-78bb-4104-89b0-fb90645760f6,4cfe72fe-21a0-4201-87b6-ef93695d2495 -202131ae-78bb-4104-89b0-fb90645760f6,498f4b18-bd4c-470d-9cde-2fca70ec8964 -202131ae-78bb-4104-89b0-fb90645760f6,202131ae-78bb-4104-89b0-fb90645760f6 -202131ae-78bb-4104-89b0-fb90645760f6,11dafd5c-85e7-4275-a147-89a63641e35e -202131ae-78bb-4104-89b0-fb90645760f6,02b993c0-b358-481c-b0d0-0767387feb9f -20fbcb6e-d793-4b05-8e29-8ff82572b0db,f77a8561-5adc-452a-ac9a-76273b6a6678 -20fbcb6e-d793-4b05-8e29-8ff82572b0db,d79728e1-8834-4f4a-8edc-ce18aca18be6 -20fbcb6e-d793-4b05-8e29-8ff82572b0db,cf67a8d4-48e2-4dc9-a521-6aabcff0330b -20fbcb6e-d793-4b05-8e29-8ff82572b0db,bb1a7816-4798-4f58-9ce4-c5bd3af682a8 -20fbcb6e-d793-4b05-8e29-8ff82572b0db,b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 -20fbcb6e-d793-4b05-8e29-8ff82572b0db,a6cb423a-7571-46df-83e2-ccc6820adb36 -20fbcb6e-d793-4b05-8e29-8ff82572b0db,7e7322a6-7912-4101-b3be-d134245a0626 -20fbcb6e-d793-4b05-8e29-8ff82572b0db,777aa5f2-2a09-4aed-92ea-912694e28b48 -20fbcb6e-d793-4b05-8e29-8ff82572b0db,6e187f47-91a1-4217-a185-31b6f01db225 -20fbcb6e-d793-4b05-8e29-8ff82572b0db,62059efc-7607-41c9-a3ba-70948f6a8a1d -20fbcb6e-d793-4b05-8e29-8ff82572b0db,2d2e07ec-e697-4384-a679-867e93740921 -20fbcb6e-d793-4b05-8e29-8ff82572b0db,20fbcb6e-d793-4b05-8e29-8ff82572b0db -20fbcb6e-d793-4b05-8e29-8ff82572b0db,1fd714a6-48b4-425a-99b3-ba5c1a995cce -21995497-0eb8-4c0b-8c23-e6a829f3d596,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -21995497-0eb8-4c0b-8c23-e6a829f3d596,ee24bf89-81a3-4259-a382-86917f3829d4 -21995497-0eb8-4c0b-8c23-e6a829f3d596,e363eb67-64c7-440f-93fd-5c8787c69a85 -21995497-0eb8-4c0b-8c23-e6a829f3d596,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -21995497-0eb8-4c0b-8c23-e6a829f3d596,db79f75d-af3c-4f82-b4e5-9b5d26598eef -21995497-0eb8-4c0b-8c23-e6a829f3d596,d7a48a26-7731-4046-bf22-78f0b8084eb9 -21995497-0eb8-4c0b-8c23-e6a829f3d596,d080c116-681a-494a-a928-45924109b49d -21995497-0eb8-4c0b-8c23-e6a829f3d596,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -21995497-0eb8-4c0b-8c23-e6a829f3d596,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -21995497-0eb8-4c0b-8c23-e6a829f3d596,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -21995497-0eb8-4c0b-8c23-e6a829f3d596,990c21ab-433b-4920-873e-f9dfc7103d9d -21995497-0eb8-4c0b-8c23-e6a829f3d596,922e2443-9cc5-421f-8310-9cd23f6e9f2d -21995497-0eb8-4c0b-8c23-e6a829f3d596,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -21995497-0eb8-4c0b-8c23-e6a829f3d596,8089a9ac-9e71-4487-a231-f720e4bc8d3e -21995497-0eb8-4c0b-8c23-e6a829f3d596,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -21995497-0eb8-4c0b-8c23-e6a829f3d596,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -21995497-0eb8-4c0b-8c23-e6a829f3d596,6d47a2fe-3645-4c5c-bebe-1b822eff197f -21995497-0eb8-4c0b-8c23-e6a829f3d596,67fc12fc-bb9f-40f1-9051-127a788b3081 -21995497-0eb8-4c0b-8c23-e6a829f3d596,634f0889-3a83-484a-bcc8-86f48e333c7b -21995497-0eb8-4c0b-8c23-e6a829f3d596,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -21995497-0eb8-4c0b-8c23-e6a829f3d596,4adcaf72-5241-4877-b61c-f34060576c50 -21995497-0eb8-4c0b-8c23-e6a829f3d596,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -21995497-0eb8-4c0b-8c23-e6a829f3d596,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -21995497-0eb8-4c0b-8c23-e6a829f3d596,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -21995497-0eb8-4c0b-8c23-e6a829f3d596,374fd699-6b74-4500-9d60-2473c7e0364f -21995497-0eb8-4c0b-8c23-e6a829f3d596,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -21995497-0eb8-4c0b-8c23-e6a829f3d596,308eba53-29fa-489a-97dc-741b077841a7 -21995497-0eb8-4c0b-8c23-e6a829f3d596,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -21995497-0eb8-4c0b-8c23-e6a829f3d596,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -21995497-0eb8-4c0b-8c23-e6a829f3d596,21995497-0eb8-4c0b-8c23-e6a829f3d596 -21995497-0eb8-4c0b-8c23-e6a829f3d596,18ebf5ea-b0a2-4333-9e9c-40217de809ff -21995497-0eb8-4c0b-8c23-e6a829f3d596,151e3048-66ad-4411-8753-677877e3bf0a -21995497-0eb8-4c0b-8c23-e6a829f3d596,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -21995497-0eb8-4c0b-8c23-e6a829f3d596,0af4a77e-892e-4b3f-9110-4c5224782250 -21c6e0fc-bf47-4f80-b1ff-85b940445bdf,bac374c0-50e8-4a5c-947b-82a8e9a747a9 -21c6e0fc-bf47-4f80-b1ff-85b940445bdf,8bc7af32-e5ad-4849-ba4d-b446db833ab4 -21c6e0fc-bf47-4f80-b1ff-85b940445bdf,7ea43fc6-b1f7-46be-a308-5ecb275a1081 -21c6e0fc-bf47-4f80-b1ff-85b940445bdf,2de1f829-c9d2-4f22-bf39-536dcb82fc3e -21c6e0fc-bf47-4f80-b1ff-85b940445bdf,2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 -21c6e0fc-bf47-4f80-b1ff-85b940445bdf,23780032-f3bb-4b40-af2e-3fa6b1677376 -21c6e0fc-bf47-4f80-b1ff-85b940445bdf,21c6e0fc-bf47-4f80-b1ff-85b940445bdf -21c6e0fc-bf47-4f80-b1ff-85b940445bdf,12b212d8-bc6e-415a-93b0-594381726668 -224e538d-3622-4f5e-b772-211ed358d8de,f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 -224e538d-3622-4f5e-b772-211ed358d8de,f3aa491a-4dbd-4436-b674-18b2177dcf18 -224e538d-3622-4f5e-b772-211ed358d8de,e1040d58-53bc-4467-8101-ca53b772cede -224e538d-3622-4f5e-b772-211ed358d8de,d05461d6-13bf-402c-890d-db6404933f7c -224e538d-3622-4f5e-b772-211ed358d8de,b7772635-1801-48e4-a442-f4aaa6860544 -224e538d-3622-4f5e-b772-211ed358d8de,b07fb98d-2da4-423a-83b4-7a673de93096 -224e538d-3622-4f5e-b772-211ed358d8de,97b42d67-8691-433d-8a31-dada076162ae -224e538d-3622-4f5e-b772-211ed358d8de,720f7ed6-7b2e-41dc-a0e0-724a3332aa24 -224e538d-3622-4f5e-b772-211ed358d8de,4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 -224e538d-3622-4f5e-b772-211ed358d8de,2ffd6142-b75b-406f-bc06-cdb1c02eaefc -224e538d-3622-4f5e-b772-211ed358d8de,224e538d-3622-4f5e-b772-211ed358d8de -224e538d-3622-4f5e-b772-211ed358d8de,16a3408d-c927-4d02-a1ae-cad32419caab -224e538d-3622-4f5e-b772-211ed358d8de,04d88eb4-fb1b-4fa1-802a-5624f4c61b32 -23780032-f3bb-4b40-af2e-3fa6b1677376,bac374c0-50e8-4a5c-947b-82a8e9a747a9 -23780032-f3bb-4b40-af2e-3fa6b1677376,8bc7af32-e5ad-4849-ba4d-b446db833ab4 -23780032-f3bb-4b40-af2e-3fa6b1677376,7ea43fc6-b1f7-46be-a308-5ecb275a1081 -23780032-f3bb-4b40-af2e-3fa6b1677376,2de1f829-c9d2-4f22-bf39-536dcb82fc3e -23780032-f3bb-4b40-af2e-3fa6b1677376,2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 -23780032-f3bb-4b40-af2e-3fa6b1677376,23780032-f3bb-4b40-af2e-3fa6b1677376 -23780032-f3bb-4b40-af2e-3fa6b1677376,21c6e0fc-bf47-4f80-b1ff-85b940445bdf -23780032-f3bb-4b40-af2e-3fa6b1677376,12b212d8-bc6e-415a-93b0-594381726668 -24c4dd5b-748b-4165-a0cc-2867b76c2dc2,fcab9efe-fb05-42d6-b366-ce6db1cc4093 -24c4dd5b-748b-4165-a0cc-2867b76c2dc2,f1293c5e-c997-4ba9-8bfd-438e7a840dc8 -24c4dd5b-748b-4165-a0cc-2867b76c2dc2,e4358f28-62a8-4e06-b2bd-cb00d3310349 -24c4dd5b-748b-4165-a0cc-2867b76c2dc2,8f76d729-9f74-4cfd-be2a-8b033b568e18 -24c4dd5b-748b-4165-a0cc-2867b76c2dc2,39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 -24c4dd5b-748b-4165-a0cc-2867b76c2dc2,31d4c712-75b1-4ecb-9d1a-ccc4b8453826 -24c4dd5b-748b-4165-a0cc-2867b76c2dc2,24c4dd5b-748b-4165-a0cc-2867b76c2dc2 -24c4dd5b-748b-4165-a0cc-2867b76c2dc2,16d280a2-c61f-487f-9034-22e884158969 -24c4dd5b-748b-4165-a0cc-2867b76c2dc2,0134901a-4a69-4b39-92ea-d6f48ec83c6e -2514cdf0-1216-4c5e-bfa8-0c11c20c6b93,bac374c0-50e8-4a5c-947b-82a8e9a747a9 -2514cdf0-1216-4c5e-bfa8-0c11c20c6b93,8bc7af32-e5ad-4849-ba4d-b446db833ab4 -2514cdf0-1216-4c5e-bfa8-0c11c20c6b93,7ea43fc6-b1f7-46be-a308-5ecb275a1081 -2514cdf0-1216-4c5e-bfa8-0c11c20c6b93,2de1f829-c9d2-4f22-bf39-536dcb82fc3e -2514cdf0-1216-4c5e-bfa8-0c11c20c6b93,2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 -2514cdf0-1216-4c5e-bfa8-0c11c20c6b93,23780032-f3bb-4b40-af2e-3fa6b1677376 -2514cdf0-1216-4c5e-bfa8-0c11c20c6b93,21c6e0fc-bf47-4f80-b1ff-85b940445bdf -2514cdf0-1216-4c5e-bfa8-0c11c20c6b93,12b212d8-bc6e-415a-93b0-594381726668 -274a2e49-9170-4945-bca2-ab6ef4bded75,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -274a2e49-9170-4945-bca2-ab6ef4bded75,e8eba267-fecb-477e-9625-771fbcfc3cba -274a2e49-9170-4945-bca2-ab6ef4bded75,e8c41168-a093-40eb-8baa-6802d24f554a -274a2e49-9170-4945-bca2-ab6ef4bded75,e26ae29a-213a-4f79-98c2-cc81cf2f451d -274a2e49-9170-4945-bca2-ab6ef4bded75,e0dfbc08-45ad-4a81-b648-7e65c066f673 -274a2e49-9170-4945-bca2-ab6ef4bded75,d535b213-2abc-438f-aa6a-c06476d60b09 -274a2e49-9170-4945-bca2-ab6ef4bded75,c892b1d7-7485-407d-8883-54fb7fecebc1 -274a2e49-9170-4945-bca2-ab6ef4bded75,b6900c21-d310-4fb4-8b8f-176a09c91989 -274a2e49-9170-4945-bca2-ab6ef4bded75,b00df815-7528-4967-bfe2-311130d91c21 -274a2e49-9170-4945-bca2-ab6ef4bded75,add6d754-a075-4693-a33a-c061c9a368ff -274a2e49-9170-4945-bca2-ab6ef4bded75,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -274a2e49-9170-4945-bca2-ab6ef4bded75,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -274a2e49-9170-4945-bca2-ab6ef4bded75,8a1908f7-47cc-4f82-859c-781a193e9901 -274a2e49-9170-4945-bca2-ab6ef4bded75,8743e69b-17aa-44ff-b8fa-4f582665efc6 -274a2e49-9170-4945-bca2-ab6ef4bded75,721e4027-a080-40d8-bc4a-43cd33477611 -274a2e49-9170-4945-bca2-ab6ef4bded75,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -274a2e49-9170-4945-bca2-ab6ef4bded75,54c750aa-570a-4e8e-9a02-418781923e39 -274a2e49-9170-4945-bca2-ab6ef4bded75,50eac25a-3761-4de3-8a69-aae52e658300 -274a2e49-9170-4945-bca2-ab6ef4bded75,4a464bb5-9373-4f1b-9c03-053c64797114 -274a2e49-9170-4945-bca2-ab6ef4bded75,4506aa76-9d0d-40e4-85bc-5735f74522a0 -274a2e49-9170-4945-bca2-ab6ef4bded75,42a9a333-d09d-4b9e-af96-1f02af26bac3 -274a2e49-9170-4945-bca2-ab6ef4bded75,2fc75f11-2097-4e1e-8390-dbff3e844b7a -274a2e49-9170-4945-bca2-ab6ef4bded75,274a2e49-9170-4945-bca2-ab6ef4bded75 -274a2e49-9170-4945-bca2-ab6ef4bded75,0364073f-91d8-47a1-b8b0-107c6318a691 -2830e99a-e6b0-411b-a051-7ea1e9f815d1,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -2830e99a-e6b0-411b-a051-7ea1e9f815d1,d90676d2-ce74-4867-a00f-6dbffa7c00be -2830e99a-e6b0-411b-a051-7ea1e9f815d1,cc56af6d-25c6-480e-9767-da02a55551da -2830e99a-e6b0-411b-a051-7ea1e9f815d1,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -2830e99a-e6b0-411b-a051-7ea1e9f815d1,baf48725-f0f9-4357-a71d-b1104910deae -2830e99a-e6b0-411b-a051-7ea1e9f815d1,9ed8703e-3b40-424c-9e98-b3b404051f99 -2830e99a-e6b0-411b-a051-7ea1e9f815d1,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -2830e99a-e6b0-411b-a051-7ea1e9f815d1,8be81657-8ba6-4ec5-8ff9-4809367096ea -2830e99a-e6b0-411b-a051-7ea1e9f815d1,7f410064-638a-4477-85c4-97fc2bb14a49 -2830e99a-e6b0-411b-a051-7ea1e9f815d1,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -2830e99a-e6b0-411b-a051-7ea1e9f815d1,5cedf18a-fc44-4c39-a80f-2106a0d76934 -2830e99a-e6b0-411b-a051-7ea1e9f815d1,4f342a71-dec3-403e-970b-e4c21b9eb98b -2830e99a-e6b0-411b-a051-7ea1e9f815d1,4db144d3-a596-4718-a50a-8ac9175ad386 -2830e99a-e6b0-411b-a051-7ea1e9f815d1,4215b5d0-bdd9-4222-a04a-81bb637d60af -2830e99a-e6b0-411b-a051-7ea1e9f815d1,29d4e919-2bd2-44e6-bb8a-380a780f60ff -2830e99a-e6b0-411b-a051-7ea1e9f815d1,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -2830e99a-e6b0-411b-a051-7ea1e9f815d1,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -2830e99a-e6b0-411b-a051-7ea1e9f815d1,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -2830e99a-e6b0-411b-a051-7ea1e9f815d1,102e16c5-4920-4dad-b142-0b280b2aacad -2830e99a-e6b0-411b-a051-7ea1e9f815d1,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -29d4e919-2bd2-44e6-bb8a-380a780f60ff,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -29d4e919-2bd2-44e6-bb8a-380a780f60ff,d90676d2-ce74-4867-a00f-6dbffa7c00be -29d4e919-2bd2-44e6-bb8a-380a780f60ff,cc56af6d-25c6-480e-9767-da02a55551da -29d4e919-2bd2-44e6-bb8a-380a780f60ff,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -29d4e919-2bd2-44e6-bb8a-380a780f60ff,baf48725-f0f9-4357-a71d-b1104910deae -29d4e919-2bd2-44e6-bb8a-380a780f60ff,9ed8703e-3b40-424c-9e98-b3b404051f99 -29d4e919-2bd2-44e6-bb8a-380a780f60ff,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -29d4e919-2bd2-44e6-bb8a-380a780f60ff,8be81657-8ba6-4ec5-8ff9-4809367096ea -29d4e919-2bd2-44e6-bb8a-380a780f60ff,7f410064-638a-4477-85c4-97fc2bb14a49 -29d4e919-2bd2-44e6-bb8a-380a780f60ff,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -29d4e919-2bd2-44e6-bb8a-380a780f60ff,5cedf18a-fc44-4c39-a80f-2106a0d76934 -29d4e919-2bd2-44e6-bb8a-380a780f60ff,4f342a71-dec3-403e-970b-e4c21b9eb98b -29d4e919-2bd2-44e6-bb8a-380a780f60ff,4db144d3-a596-4718-a50a-8ac9175ad386 -29d4e919-2bd2-44e6-bb8a-380a780f60ff,4215b5d0-bdd9-4222-a04a-81bb637d60af -29d4e919-2bd2-44e6-bb8a-380a780f60ff,29d4e919-2bd2-44e6-bb8a-380a780f60ff -29d4e919-2bd2-44e6-bb8a-380a780f60ff,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -29d4e919-2bd2-44e6-bb8a-380a780f60ff,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -29d4e919-2bd2-44e6-bb8a-380a780f60ff,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -29d4e919-2bd2-44e6-bb8a-380a780f60ff,102e16c5-4920-4dad-b142-0b280b2aacad -29d4e919-2bd2-44e6-bb8a-380a780f60ff,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -2aff9edd-def2-487a-b435-a162e11a303c,99fc3558-79f3-4d14-9aa1-ff63f621ecd9 -2aff9edd-def2-487a-b435-a162e11a303c,2aff9edd-def2-487a-b435-a162e11a303c -2bd766e5-60e4-4469-bb97-54f0503a1eb0,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -2bd766e5-60e4-4469-bb97-54f0503a1eb0,ee24bf89-81a3-4259-a382-86917f3829d4 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,e363eb67-64c7-440f-93fd-5c8787c69a85 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,db79f75d-af3c-4f82-b4e5-9b5d26598eef -2bd766e5-60e4-4469-bb97-54f0503a1eb0,d7a48a26-7731-4046-bf22-78f0b8084eb9 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,d080c116-681a-494a-a928-45924109b49d -2bd766e5-60e4-4469-bb97-54f0503a1eb0,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -2bd766e5-60e4-4469-bb97-54f0503a1eb0,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -2bd766e5-60e4-4469-bb97-54f0503a1eb0,990c21ab-433b-4920-873e-f9dfc7103d9d -2bd766e5-60e4-4469-bb97-54f0503a1eb0,922e2443-9cc5-421f-8310-9cd23f6e9f2d -2bd766e5-60e4-4469-bb97-54f0503a1eb0,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,8089a9ac-9e71-4487-a231-f720e4bc8d3e -2bd766e5-60e4-4469-bb97-54f0503a1eb0,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -2bd766e5-60e4-4469-bb97-54f0503a1eb0,6d47a2fe-3645-4c5c-bebe-1b822eff197f -2bd766e5-60e4-4469-bb97-54f0503a1eb0,67fc12fc-bb9f-40f1-9051-127a788b3081 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,634f0889-3a83-484a-bcc8-86f48e333c7b -2bd766e5-60e4-4469-bb97-54f0503a1eb0,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,4adcaf72-5241-4877-b61c-f34060576c50 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -2bd766e5-60e4-4469-bb97-54f0503a1eb0,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,374fd699-6b74-4500-9d60-2473c7e0364f -2bd766e5-60e4-4469-bb97-54f0503a1eb0,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,308eba53-29fa-489a-97dc-741b077841a7 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -2bd766e5-60e4-4469-bb97-54f0503a1eb0,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,21995497-0eb8-4c0b-8c23-e6a829f3d596 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,18ebf5ea-b0a2-4333-9e9c-40217de809ff -2bd766e5-60e4-4469-bb97-54f0503a1eb0,151e3048-66ad-4411-8753-677877e3bf0a -2bd766e5-60e4-4469-bb97-54f0503a1eb0,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -2bd766e5-60e4-4469-bb97-54f0503a1eb0,0af4a77e-892e-4b3f-9110-4c5224782250 -2bddd98c-86e3-4ae0-9dc6-87da16ab6267,c3ec1f2e-1411-41e4-8370-acd5a8f68cf4 -2bddd98c-86e3-4ae0-9dc6-87da16ab6267,87b04a97-1d33-4548-aec9-3d2856070702 -2bddd98c-86e3-4ae0-9dc6-87da16ab6267,2bddd98c-86e3-4ae0-9dc6-87da16ab6267 -2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5,984067fc-3dfc-47b7-8ee0-4d9b27d43860 -2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5,55f17f44-287f-41aa-a1bc-d1c0104af36e -2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5,2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5 -2d2e07ec-e697-4384-a679-867e93740921,f77a8561-5adc-452a-ac9a-76273b6a6678 -2d2e07ec-e697-4384-a679-867e93740921,d79728e1-8834-4f4a-8edc-ce18aca18be6 -2d2e07ec-e697-4384-a679-867e93740921,cf67a8d4-48e2-4dc9-a521-6aabcff0330b -2d2e07ec-e697-4384-a679-867e93740921,bb1a7816-4798-4f58-9ce4-c5bd3af682a8 -2d2e07ec-e697-4384-a679-867e93740921,b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 -2d2e07ec-e697-4384-a679-867e93740921,a6cb423a-7571-46df-83e2-ccc6820adb36 -2d2e07ec-e697-4384-a679-867e93740921,7e7322a6-7912-4101-b3be-d134245a0626 -2d2e07ec-e697-4384-a679-867e93740921,777aa5f2-2a09-4aed-92ea-912694e28b48 -2d2e07ec-e697-4384-a679-867e93740921,6e187f47-91a1-4217-a185-31b6f01db225 -2d2e07ec-e697-4384-a679-867e93740921,62059efc-7607-41c9-a3ba-70948f6a8a1d -2d2e07ec-e697-4384-a679-867e93740921,2d2e07ec-e697-4384-a679-867e93740921 -2d2e07ec-e697-4384-a679-867e93740921,20fbcb6e-d793-4b05-8e29-8ff82572b0db -2d2e07ec-e697-4384-a679-867e93740921,1fd714a6-48b4-425a-99b3-ba5c1a995cce -2de1f829-c9d2-4f22-bf39-536dcb82fc3e,bac374c0-50e8-4a5c-947b-82a8e9a747a9 -2de1f829-c9d2-4f22-bf39-536dcb82fc3e,8bc7af32-e5ad-4849-ba4d-b446db833ab4 -2de1f829-c9d2-4f22-bf39-536dcb82fc3e,7ea43fc6-b1f7-46be-a308-5ecb275a1081 -2de1f829-c9d2-4f22-bf39-536dcb82fc3e,2de1f829-c9d2-4f22-bf39-536dcb82fc3e -2de1f829-c9d2-4f22-bf39-536dcb82fc3e,2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 -2de1f829-c9d2-4f22-bf39-536dcb82fc3e,23780032-f3bb-4b40-af2e-3fa6b1677376 -2de1f829-c9d2-4f22-bf39-536dcb82fc3e,21c6e0fc-bf47-4f80-b1ff-85b940445bdf -2de1f829-c9d2-4f22-bf39-536dcb82fc3e,12b212d8-bc6e-415a-93b0-594381726668 -2e8eb256-84c9-499a-8bf6-bb39504373e1,e1c2ad9f-6f61-4f16-805a-2f405b63659a -2e8eb256-84c9-499a-8bf6-bb39504373e1,d6988116-3c63-44f8-9f94-9e9d51cc5a37 -2e8eb256-84c9-499a-8bf6-bb39504373e1,cfcbe34a-edc5-4442-bc05-5927fa00336b -2e8eb256-84c9-499a-8bf6-bb39504373e1,c90aae7e-5704-4e13-8794-41ab377193bd -2e8eb256-84c9-499a-8bf6-bb39504373e1,bf895c48-1d52-4780-8e9a-532d69356d28 -2e8eb256-84c9-499a-8bf6-bb39504373e1,ae9e663e-8c15-43c9-8a88-52b61cbf07a9 -2e8eb256-84c9-499a-8bf6-bb39504373e1,56999896-e36b-4e63-a6b6-dbb85dfe1936 -2e8eb256-84c9-499a-8bf6-bb39504373e1,51241857-a7a4-4517-96e3-21f797581f89 -2e8eb256-84c9-499a-8bf6-bb39504373e1,4cd95073-6db3-4eb2-ac4b-0aacb182b352 -2e8eb256-84c9-499a-8bf6-bb39504373e1,4148ac36-1dcb-4e96-89cd-355e7e8f919b -2e8eb256-84c9-499a-8bf6-bb39504373e1,2e8eb256-84c9-499a-8bf6-bb39504373e1 -2e8eb256-84c9-499a-8bf6-bb39504373e1,07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,ee24bf89-81a3-4259-a382-86917f3829d4 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,e363eb67-64c7-440f-93fd-5c8787c69a85 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,db79f75d-af3c-4f82-b4e5-9b5d26598eef -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,d7a48a26-7731-4046-bf22-78f0b8084eb9 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,d080c116-681a-494a-a928-45924109b49d -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,990c21ab-433b-4920-873e-f9dfc7103d9d -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,922e2443-9cc5-421f-8310-9cd23f6e9f2d -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,8089a9ac-9e71-4487-a231-f720e4bc8d3e -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,6d47a2fe-3645-4c5c-bebe-1b822eff197f -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,67fc12fc-bb9f-40f1-9051-127a788b3081 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,634f0889-3a83-484a-bcc8-86f48e333c7b -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,4adcaf72-5241-4877-b61c-f34060576c50 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,374fd699-6b74-4500-9d60-2473c7e0364f -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,308eba53-29fa-489a-97dc-741b077841a7 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,21995497-0eb8-4c0b-8c23-e6a829f3d596 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,18ebf5ea-b0a2-4333-9e9c-40217de809ff -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,151e3048-66ad-4411-8753-677877e3bf0a -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,0af4a77e-892e-4b3f-9110-4c5224782250 -2ee2ab26-09fb-4e12-bf21-9fed3b4c38de,bfc93cb7-e53a-49f7-b86e-81d6a13cfdea -2ee2ab26-09fb-4e12-bf21-9fed3b4c38de,a6790cbd-8349-432e-a976-5dfc07451203 -2ee2ab26-09fb-4e12-bf21-9fed3b4c38de,9b2eb632-6f1a-4e97-912b-3c8378a1b11c -2ee2ab26-09fb-4e12-bf21-9fed3b4c38de,85c997bf-63d9-4ebb-8e0b-320de3dddc6c -2ee2ab26-09fb-4e12-bf21-9fed3b4c38de,85481b3f-c75e-40cd-bacd-b0afb79e893c -2ee2ab26-09fb-4e12-bf21-9fed3b4c38de,75a09d4f-eef2-4404-a386-dfcb408ec9ed -2ee2ab26-09fb-4e12-bf21-9fed3b4c38de,30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 -2ee2ab26-09fb-4e12-bf21-9fed3b4c38de,2ee2ab26-09fb-4e12-bf21-9fed3b4c38de -2fc75f11-2097-4e1e-8390-dbff3e844b7a,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -2fc75f11-2097-4e1e-8390-dbff3e844b7a,e8eba267-fecb-477e-9625-771fbcfc3cba -2fc75f11-2097-4e1e-8390-dbff3e844b7a,e8c41168-a093-40eb-8baa-6802d24f554a -2fc75f11-2097-4e1e-8390-dbff3e844b7a,e26ae29a-213a-4f79-98c2-cc81cf2f451d -2fc75f11-2097-4e1e-8390-dbff3e844b7a,e0dfbc08-45ad-4a81-b648-7e65c066f673 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,d535b213-2abc-438f-aa6a-c06476d60b09 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,c892b1d7-7485-407d-8883-54fb7fecebc1 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,b6900c21-d310-4fb4-8b8f-176a09c91989 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,b00df815-7528-4967-bfe2-311130d91c21 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,add6d754-a075-4693-a33a-c061c9a368ff -2fc75f11-2097-4e1e-8390-dbff3e844b7a,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,8a1908f7-47cc-4f82-859c-781a193e9901 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,8743e69b-17aa-44ff-b8fa-4f582665efc6 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,721e4027-a080-40d8-bc4a-43cd33477611 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,54c750aa-570a-4e8e-9a02-418781923e39 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,50eac25a-3761-4de3-8a69-aae52e658300 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,4a464bb5-9373-4f1b-9c03-053c64797114 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,4506aa76-9d0d-40e4-85bc-5735f74522a0 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,42a9a333-d09d-4b9e-af96-1f02af26bac3 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,2fc75f11-2097-4e1e-8390-dbff3e844b7a -2fc75f11-2097-4e1e-8390-dbff3e844b7a,274a2e49-9170-4945-bca2-ab6ef4bded75 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,0364073f-91d8-47a1-b8b0-107c6318a691 -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,f3aa491a-4dbd-4436-b674-18b2177dcf18 -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,e1040d58-53bc-4467-8101-ca53b772cede -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,d05461d6-13bf-402c-890d-db6404933f7c -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,b7772635-1801-48e4-a442-f4aaa6860544 -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,b07fb98d-2da4-423a-83b4-7a673de93096 -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,97b42d67-8691-433d-8a31-dada076162ae -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,720f7ed6-7b2e-41dc-a0e0-724a3332aa24 -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,2ffd6142-b75b-406f-bc06-cdb1c02eaefc -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,224e538d-3622-4f5e-b772-211ed358d8de -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,16a3408d-c927-4d02-a1ae-cad32419caab -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,04d88eb4-fb1b-4fa1-802a-5624f4c61b32 -308eba53-29fa-489a-97dc-741b077841a7,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -308eba53-29fa-489a-97dc-741b077841a7,ee24bf89-81a3-4259-a382-86917f3829d4 -308eba53-29fa-489a-97dc-741b077841a7,e363eb67-64c7-440f-93fd-5c8787c69a85 -308eba53-29fa-489a-97dc-741b077841a7,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -308eba53-29fa-489a-97dc-741b077841a7,db79f75d-af3c-4f82-b4e5-9b5d26598eef -308eba53-29fa-489a-97dc-741b077841a7,d7a48a26-7731-4046-bf22-78f0b8084eb9 -308eba53-29fa-489a-97dc-741b077841a7,d080c116-681a-494a-a928-45924109b49d -308eba53-29fa-489a-97dc-741b077841a7,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -308eba53-29fa-489a-97dc-741b077841a7,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -308eba53-29fa-489a-97dc-741b077841a7,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -308eba53-29fa-489a-97dc-741b077841a7,990c21ab-433b-4920-873e-f9dfc7103d9d -308eba53-29fa-489a-97dc-741b077841a7,922e2443-9cc5-421f-8310-9cd23f6e9f2d -308eba53-29fa-489a-97dc-741b077841a7,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -308eba53-29fa-489a-97dc-741b077841a7,8089a9ac-9e71-4487-a231-f720e4bc8d3e -308eba53-29fa-489a-97dc-741b077841a7,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -308eba53-29fa-489a-97dc-741b077841a7,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -308eba53-29fa-489a-97dc-741b077841a7,6d47a2fe-3645-4c5c-bebe-1b822eff197f -308eba53-29fa-489a-97dc-741b077841a7,67fc12fc-bb9f-40f1-9051-127a788b3081 -308eba53-29fa-489a-97dc-741b077841a7,634f0889-3a83-484a-bcc8-86f48e333c7b -308eba53-29fa-489a-97dc-741b077841a7,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -308eba53-29fa-489a-97dc-741b077841a7,4adcaf72-5241-4877-b61c-f34060576c50 -308eba53-29fa-489a-97dc-741b077841a7,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -308eba53-29fa-489a-97dc-741b077841a7,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -308eba53-29fa-489a-97dc-741b077841a7,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -308eba53-29fa-489a-97dc-741b077841a7,374fd699-6b74-4500-9d60-2473c7e0364f -308eba53-29fa-489a-97dc-741b077841a7,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -308eba53-29fa-489a-97dc-741b077841a7,308eba53-29fa-489a-97dc-741b077841a7 -308eba53-29fa-489a-97dc-741b077841a7,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -308eba53-29fa-489a-97dc-741b077841a7,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -308eba53-29fa-489a-97dc-741b077841a7,21995497-0eb8-4c0b-8c23-e6a829f3d596 -308eba53-29fa-489a-97dc-741b077841a7,18ebf5ea-b0a2-4333-9e9c-40217de809ff -308eba53-29fa-489a-97dc-741b077841a7,151e3048-66ad-4411-8753-677877e3bf0a -308eba53-29fa-489a-97dc-741b077841a7,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -308eba53-29fa-489a-97dc-741b077841a7,0af4a77e-892e-4b3f-9110-4c5224782250 -30ae4a13-0cc6-4d12-a5dd-fa9288efcc09,bfc93cb7-e53a-49f7-b86e-81d6a13cfdea -30ae4a13-0cc6-4d12-a5dd-fa9288efcc09,a6790cbd-8349-432e-a976-5dfc07451203 -30ae4a13-0cc6-4d12-a5dd-fa9288efcc09,9b2eb632-6f1a-4e97-912b-3c8378a1b11c -30ae4a13-0cc6-4d12-a5dd-fa9288efcc09,85c997bf-63d9-4ebb-8e0b-320de3dddc6c -30ae4a13-0cc6-4d12-a5dd-fa9288efcc09,85481b3f-c75e-40cd-bacd-b0afb79e893c -30ae4a13-0cc6-4d12-a5dd-fa9288efcc09,75a09d4f-eef2-4404-a386-dfcb408ec9ed -30ae4a13-0cc6-4d12-a5dd-fa9288efcc09,30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 -30ae4a13-0cc6-4d12-a5dd-fa9288efcc09,2ee2ab26-09fb-4e12-bf21-9fed3b4c38de -31d4c712-75b1-4ecb-9d1a-ccc4b8453826,fcab9efe-fb05-42d6-b366-ce6db1cc4093 -31d4c712-75b1-4ecb-9d1a-ccc4b8453826,f1293c5e-c997-4ba9-8bfd-438e7a840dc8 -31d4c712-75b1-4ecb-9d1a-ccc4b8453826,e4358f28-62a8-4e06-b2bd-cb00d3310349 -31d4c712-75b1-4ecb-9d1a-ccc4b8453826,8f76d729-9f74-4cfd-be2a-8b033b568e18 -31d4c712-75b1-4ecb-9d1a-ccc4b8453826,39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 -31d4c712-75b1-4ecb-9d1a-ccc4b8453826,31d4c712-75b1-4ecb-9d1a-ccc4b8453826 -31d4c712-75b1-4ecb-9d1a-ccc4b8453826,24c4dd5b-748b-4165-a0cc-2867b76c2dc2 -31d4c712-75b1-4ecb-9d1a-ccc4b8453826,16d280a2-c61f-487f-9034-22e884158969 -31d4c712-75b1-4ecb-9d1a-ccc4b8453826,0134901a-4a69-4b39-92ea-d6f48ec83c6e -3397a796-894d-409c-97de-a9e6f3f88198,fc771883-3bd6-46d9-9626-a130e1b902de -3397a796-894d-409c-97de-a9e6f3f88198,e2387a81-5ff5-4daf-b2e8-881998d0d917 -3397a796-894d-409c-97de-a9e6f3f88198,b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa -3397a796-894d-409c-97de-a9e6f3f88198,b1d4dc41-07ae-44db-ae17-1df86c5a62cf -3397a796-894d-409c-97de-a9e6f3f88198,67e5cf95-236b-4f75-abc5-034ca2966448 -3397a796-894d-409c-97de-a9e6f3f88198,48cc2275-a478-4ade-b076-cf38e1d16017 -3397a796-894d-409c-97de-a9e6f3f88198,41d26b1c-57b9-408c-b955-bf0ee1db4809 -3397a796-894d-409c-97de-a9e6f3f88198,3ddf46fe-b5be-456d-810f-ea6a7402236d -3397a796-894d-409c-97de-a9e6f3f88198,3397a796-894d-409c-97de-a9e6f3f88198 -3397a796-894d-409c-97de-a9e6f3f88198,19502b5a-2031-487d-9d9c-2001f959408b -359cb842-c25b-429f-ba9b-d52f171e5631,f883f2ca-d523-4c9f-86b2-0add137901de -359cb842-c25b-429f-ba9b-d52f171e5631,f28931e5-1f35-4f9f-a02e-78398735ea67 -359cb842-c25b-429f-ba9b-d52f171e5631,e7f68d35-ae55-4fa4-b0be-0672a5a7186a -359cb842-c25b-429f-ba9b-d52f171e5631,85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b -359cb842-c25b-429f-ba9b-d52f171e5631,7ac56d5a-32a7-4b40-a956-356e3006faed -359cb842-c25b-429f-ba9b-d52f171e5631,72788e7f-1bf1-40b5-a1f1-27c669dc7278 -359cb842-c25b-429f-ba9b-d52f171e5631,6c84348c-5065-4e89-9412-bfda023683f2 -359cb842-c25b-429f-ba9b-d52f171e5631,5abe533b-ae5f-47ad-8746-f60caf7483c0 -359cb842-c25b-429f-ba9b-d52f171e5631,4f14ca7f-5332-4263-a7b2-f0a838380309 -359cb842-c25b-429f-ba9b-d52f171e5631,359cb842-c25b-429f-ba9b-d52f171e5631 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,ee24bf89-81a3-4259-a382-86917f3829d4 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,e363eb67-64c7-440f-93fd-5c8787c69a85 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,db79f75d-af3c-4f82-b4e5-9b5d26598eef -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,d7a48a26-7731-4046-bf22-78f0b8084eb9 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,d080c116-681a-494a-a928-45924109b49d -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,990c21ab-433b-4920-873e-f9dfc7103d9d -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,922e2443-9cc5-421f-8310-9cd23f6e9f2d -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,8089a9ac-9e71-4487-a231-f720e4bc8d3e -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,6d47a2fe-3645-4c5c-bebe-1b822eff197f -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,67fc12fc-bb9f-40f1-9051-127a788b3081 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,634f0889-3a83-484a-bcc8-86f48e333c7b -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,4adcaf72-5241-4877-b61c-f34060576c50 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,374fd699-6b74-4500-9d60-2473c7e0364f -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,308eba53-29fa-489a-97dc-741b077841a7 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,21995497-0eb8-4c0b-8c23-e6a829f3d596 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,18ebf5ea-b0a2-4333-9e9c-40217de809ff -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,151e3048-66ad-4411-8753-677877e3bf0a -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,0af4a77e-892e-4b3f-9110-4c5224782250 -374fd699-6b74-4500-9d60-2473c7e0364f,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -374fd699-6b74-4500-9d60-2473c7e0364f,ee24bf89-81a3-4259-a382-86917f3829d4 -374fd699-6b74-4500-9d60-2473c7e0364f,e363eb67-64c7-440f-93fd-5c8787c69a85 -374fd699-6b74-4500-9d60-2473c7e0364f,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -374fd699-6b74-4500-9d60-2473c7e0364f,db79f75d-af3c-4f82-b4e5-9b5d26598eef -374fd699-6b74-4500-9d60-2473c7e0364f,d7a48a26-7731-4046-bf22-78f0b8084eb9 -374fd699-6b74-4500-9d60-2473c7e0364f,d080c116-681a-494a-a928-45924109b49d -374fd699-6b74-4500-9d60-2473c7e0364f,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -374fd699-6b74-4500-9d60-2473c7e0364f,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -374fd699-6b74-4500-9d60-2473c7e0364f,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -374fd699-6b74-4500-9d60-2473c7e0364f,990c21ab-433b-4920-873e-f9dfc7103d9d -374fd699-6b74-4500-9d60-2473c7e0364f,922e2443-9cc5-421f-8310-9cd23f6e9f2d -374fd699-6b74-4500-9d60-2473c7e0364f,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -374fd699-6b74-4500-9d60-2473c7e0364f,8089a9ac-9e71-4487-a231-f720e4bc8d3e -374fd699-6b74-4500-9d60-2473c7e0364f,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -374fd699-6b74-4500-9d60-2473c7e0364f,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -374fd699-6b74-4500-9d60-2473c7e0364f,6d47a2fe-3645-4c5c-bebe-1b822eff197f -374fd699-6b74-4500-9d60-2473c7e0364f,67fc12fc-bb9f-40f1-9051-127a788b3081 -374fd699-6b74-4500-9d60-2473c7e0364f,634f0889-3a83-484a-bcc8-86f48e333c7b -374fd699-6b74-4500-9d60-2473c7e0364f,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -374fd699-6b74-4500-9d60-2473c7e0364f,4adcaf72-5241-4877-b61c-f34060576c50 -374fd699-6b74-4500-9d60-2473c7e0364f,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -374fd699-6b74-4500-9d60-2473c7e0364f,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -374fd699-6b74-4500-9d60-2473c7e0364f,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -374fd699-6b74-4500-9d60-2473c7e0364f,374fd699-6b74-4500-9d60-2473c7e0364f -374fd699-6b74-4500-9d60-2473c7e0364f,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -374fd699-6b74-4500-9d60-2473c7e0364f,308eba53-29fa-489a-97dc-741b077841a7 -374fd699-6b74-4500-9d60-2473c7e0364f,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -374fd699-6b74-4500-9d60-2473c7e0364f,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -374fd699-6b74-4500-9d60-2473c7e0364f,21995497-0eb8-4c0b-8c23-e6a829f3d596 -374fd699-6b74-4500-9d60-2473c7e0364f,18ebf5ea-b0a2-4333-9e9c-40217de809ff -374fd699-6b74-4500-9d60-2473c7e0364f,151e3048-66ad-4411-8753-677877e3bf0a -374fd699-6b74-4500-9d60-2473c7e0364f,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -374fd699-6b74-4500-9d60-2473c7e0364f,0af4a77e-892e-4b3f-9110-4c5224782250 -395019b6-84e8-4919-8b8b-ac64e4a6eade,d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2 -395019b6-84e8-4919-8b8b-ac64e4a6eade,395019b6-84e8-4919-8b8b-ac64e4a6eade -39efc9b8-a75f-43a3-98a2-5bdc1c8207d2,fcab9efe-fb05-42d6-b366-ce6db1cc4093 -39efc9b8-a75f-43a3-98a2-5bdc1c8207d2,f1293c5e-c997-4ba9-8bfd-438e7a840dc8 -39efc9b8-a75f-43a3-98a2-5bdc1c8207d2,e4358f28-62a8-4e06-b2bd-cb00d3310349 -39efc9b8-a75f-43a3-98a2-5bdc1c8207d2,8f76d729-9f74-4cfd-be2a-8b033b568e18 -39efc9b8-a75f-43a3-98a2-5bdc1c8207d2,39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 -39efc9b8-a75f-43a3-98a2-5bdc1c8207d2,31d4c712-75b1-4ecb-9d1a-ccc4b8453826 -39efc9b8-a75f-43a3-98a2-5bdc1c8207d2,24c4dd5b-748b-4165-a0cc-2867b76c2dc2 -39efc9b8-a75f-43a3-98a2-5bdc1c8207d2,16d280a2-c61f-487f-9034-22e884158969 -39efc9b8-a75f-43a3-98a2-5bdc1c8207d2,0134901a-4a69-4b39-92ea-d6f48ec83c6e -3ddf46fe-b5be-456d-810f-ea6a7402236d,fc771883-3bd6-46d9-9626-a130e1b902de -3ddf46fe-b5be-456d-810f-ea6a7402236d,e2387a81-5ff5-4daf-b2e8-881998d0d917 -3ddf46fe-b5be-456d-810f-ea6a7402236d,b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa -3ddf46fe-b5be-456d-810f-ea6a7402236d,b1d4dc41-07ae-44db-ae17-1df86c5a62cf -3ddf46fe-b5be-456d-810f-ea6a7402236d,67e5cf95-236b-4f75-abc5-034ca2966448 -3ddf46fe-b5be-456d-810f-ea6a7402236d,48cc2275-a478-4ade-b076-cf38e1d16017 -3ddf46fe-b5be-456d-810f-ea6a7402236d,41d26b1c-57b9-408c-b955-bf0ee1db4809 -3ddf46fe-b5be-456d-810f-ea6a7402236d,3ddf46fe-b5be-456d-810f-ea6a7402236d -3ddf46fe-b5be-456d-810f-ea6a7402236d,3397a796-894d-409c-97de-a9e6f3f88198 -3ddf46fe-b5be-456d-810f-ea6a7402236d,19502b5a-2031-487d-9d9c-2001f959408b -4148ac36-1dcb-4e96-89cd-355e7e8f919b,e1c2ad9f-6f61-4f16-805a-2f405b63659a -4148ac36-1dcb-4e96-89cd-355e7e8f919b,d6988116-3c63-44f8-9f94-9e9d51cc5a37 -4148ac36-1dcb-4e96-89cd-355e7e8f919b,cfcbe34a-edc5-4442-bc05-5927fa00336b -4148ac36-1dcb-4e96-89cd-355e7e8f919b,c90aae7e-5704-4e13-8794-41ab377193bd -4148ac36-1dcb-4e96-89cd-355e7e8f919b,bf895c48-1d52-4780-8e9a-532d69356d28 -4148ac36-1dcb-4e96-89cd-355e7e8f919b,ae9e663e-8c15-43c9-8a88-52b61cbf07a9 -4148ac36-1dcb-4e96-89cd-355e7e8f919b,56999896-e36b-4e63-a6b6-dbb85dfe1936 -4148ac36-1dcb-4e96-89cd-355e7e8f919b,51241857-a7a4-4517-96e3-21f797581f89 -4148ac36-1dcb-4e96-89cd-355e7e8f919b,4cd95073-6db3-4eb2-ac4b-0aacb182b352 -4148ac36-1dcb-4e96-89cd-355e7e8f919b,4148ac36-1dcb-4e96-89cd-355e7e8f919b -4148ac36-1dcb-4e96-89cd-355e7e8f919b,2e8eb256-84c9-499a-8bf6-bb39504373e1 -4148ac36-1dcb-4e96-89cd-355e7e8f919b,07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 -41d26b1c-57b9-408c-b955-bf0ee1db4809,fc771883-3bd6-46d9-9626-a130e1b902de -41d26b1c-57b9-408c-b955-bf0ee1db4809,e2387a81-5ff5-4daf-b2e8-881998d0d917 -41d26b1c-57b9-408c-b955-bf0ee1db4809,b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa -41d26b1c-57b9-408c-b955-bf0ee1db4809,b1d4dc41-07ae-44db-ae17-1df86c5a62cf -41d26b1c-57b9-408c-b955-bf0ee1db4809,67e5cf95-236b-4f75-abc5-034ca2966448 -41d26b1c-57b9-408c-b955-bf0ee1db4809,48cc2275-a478-4ade-b076-cf38e1d16017 -41d26b1c-57b9-408c-b955-bf0ee1db4809,41d26b1c-57b9-408c-b955-bf0ee1db4809 -41d26b1c-57b9-408c-b955-bf0ee1db4809,3ddf46fe-b5be-456d-810f-ea6a7402236d -41d26b1c-57b9-408c-b955-bf0ee1db4809,3397a796-894d-409c-97de-a9e6f3f88198 -41d26b1c-57b9-408c-b955-bf0ee1db4809,19502b5a-2031-487d-9d9c-2001f959408b -4215b5d0-bdd9-4222-a04a-81bb637d60af,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -4215b5d0-bdd9-4222-a04a-81bb637d60af,d90676d2-ce74-4867-a00f-6dbffa7c00be -4215b5d0-bdd9-4222-a04a-81bb637d60af,cc56af6d-25c6-480e-9767-da02a55551da -4215b5d0-bdd9-4222-a04a-81bb637d60af,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -4215b5d0-bdd9-4222-a04a-81bb637d60af,baf48725-f0f9-4357-a71d-b1104910deae -4215b5d0-bdd9-4222-a04a-81bb637d60af,9ed8703e-3b40-424c-9e98-b3b404051f99 -4215b5d0-bdd9-4222-a04a-81bb637d60af,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -4215b5d0-bdd9-4222-a04a-81bb637d60af,8be81657-8ba6-4ec5-8ff9-4809367096ea -4215b5d0-bdd9-4222-a04a-81bb637d60af,7f410064-638a-4477-85c4-97fc2bb14a49 -4215b5d0-bdd9-4222-a04a-81bb637d60af,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -4215b5d0-bdd9-4222-a04a-81bb637d60af,5cedf18a-fc44-4c39-a80f-2106a0d76934 -4215b5d0-bdd9-4222-a04a-81bb637d60af,4f342a71-dec3-403e-970b-e4c21b9eb98b -4215b5d0-bdd9-4222-a04a-81bb637d60af,4db144d3-a596-4718-a50a-8ac9175ad386 -4215b5d0-bdd9-4222-a04a-81bb637d60af,4215b5d0-bdd9-4222-a04a-81bb637d60af -4215b5d0-bdd9-4222-a04a-81bb637d60af,29d4e919-2bd2-44e6-bb8a-380a780f60ff -4215b5d0-bdd9-4222-a04a-81bb637d60af,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -4215b5d0-bdd9-4222-a04a-81bb637d60af,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -4215b5d0-bdd9-4222-a04a-81bb637d60af,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -4215b5d0-bdd9-4222-a04a-81bb637d60af,102e16c5-4920-4dad-b142-0b280b2aacad -4215b5d0-bdd9-4222-a04a-81bb637d60af,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -42a9a333-d09d-4b9e-af96-1f02af26bac3,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -42a9a333-d09d-4b9e-af96-1f02af26bac3,e8eba267-fecb-477e-9625-771fbcfc3cba -42a9a333-d09d-4b9e-af96-1f02af26bac3,e8c41168-a093-40eb-8baa-6802d24f554a -42a9a333-d09d-4b9e-af96-1f02af26bac3,e26ae29a-213a-4f79-98c2-cc81cf2f451d -42a9a333-d09d-4b9e-af96-1f02af26bac3,e0dfbc08-45ad-4a81-b648-7e65c066f673 -42a9a333-d09d-4b9e-af96-1f02af26bac3,d535b213-2abc-438f-aa6a-c06476d60b09 -42a9a333-d09d-4b9e-af96-1f02af26bac3,c892b1d7-7485-407d-8883-54fb7fecebc1 -42a9a333-d09d-4b9e-af96-1f02af26bac3,b6900c21-d310-4fb4-8b8f-176a09c91989 -42a9a333-d09d-4b9e-af96-1f02af26bac3,b00df815-7528-4967-bfe2-311130d91c21 -42a9a333-d09d-4b9e-af96-1f02af26bac3,add6d754-a075-4693-a33a-c061c9a368ff -42a9a333-d09d-4b9e-af96-1f02af26bac3,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -42a9a333-d09d-4b9e-af96-1f02af26bac3,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -42a9a333-d09d-4b9e-af96-1f02af26bac3,8a1908f7-47cc-4f82-859c-781a193e9901 -42a9a333-d09d-4b9e-af96-1f02af26bac3,8743e69b-17aa-44ff-b8fa-4f582665efc6 -42a9a333-d09d-4b9e-af96-1f02af26bac3,721e4027-a080-40d8-bc4a-43cd33477611 -42a9a333-d09d-4b9e-af96-1f02af26bac3,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -42a9a333-d09d-4b9e-af96-1f02af26bac3,54c750aa-570a-4e8e-9a02-418781923e39 -42a9a333-d09d-4b9e-af96-1f02af26bac3,50eac25a-3761-4de3-8a69-aae52e658300 -42a9a333-d09d-4b9e-af96-1f02af26bac3,4a464bb5-9373-4f1b-9c03-053c64797114 -42a9a333-d09d-4b9e-af96-1f02af26bac3,4506aa76-9d0d-40e4-85bc-5735f74522a0 -42a9a333-d09d-4b9e-af96-1f02af26bac3,42a9a333-d09d-4b9e-af96-1f02af26bac3 -42a9a333-d09d-4b9e-af96-1f02af26bac3,2fc75f11-2097-4e1e-8390-dbff3e844b7a -42a9a333-d09d-4b9e-af96-1f02af26bac3,274a2e49-9170-4945-bca2-ab6ef4bded75 -42a9a333-d09d-4b9e-af96-1f02af26bac3,0364073f-91d8-47a1-b8b0-107c6318a691 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -42aa48c7-68cc-4bee-9730-cff29f0e36c5,ee24bf89-81a3-4259-a382-86917f3829d4 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,e363eb67-64c7-440f-93fd-5c8787c69a85 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,db79f75d-af3c-4f82-b4e5-9b5d26598eef -42aa48c7-68cc-4bee-9730-cff29f0e36c5,d7a48a26-7731-4046-bf22-78f0b8084eb9 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,d080c116-681a-494a-a928-45924109b49d -42aa48c7-68cc-4bee-9730-cff29f0e36c5,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -42aa48c7-68cc-4bee-9730-cff29f0e36c5,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -42aa48c7-68cc-4bee-9730-cff29f0e36c5,990c21ab-433b-4920-873e-f9dfc7103d9d -42aa48c7-68cc-4bee-9730-cff29f0e36c5,922e2443-9cc5-421f-8310-9cd23f6e9f2d -42aa48c7-68cc-4bee-9730-cff29f0e36c5,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,8089a9ac-9e71-4487-a231-f720e4bc8d3e -42aa48c7-68cc-4bee-9730-cff29f0e36c5,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -42aa48c7-68cc-4bee-9730-cff29f0e36c5,6d47a2fe-3645-4c5c-bebe-1b822eff197f -42aa48c7-68cc-4bee-9730-cff29f0e36c5,67fc12fc-bb9f-40f1-9051-127a788b3081 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,634f0889-3a83-484a-bcc8-86f48e333c7b -42aa48c7-68cc-4bee-9730-cff29f0e36c5,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,4adcaf72-5241-4877-b61c-f34060576c50 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -42aa48c7-68cc-4bee-9730-cff29f0e36c5,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,374fd699-6b74-4500-9d60-2473c7e0364f -42aa48c7-68cc-4bee-9730-cff29f0e36c5,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,308eba53-29fa-489a-97dc-741b077841a7 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -42aa48c7-68cc-4bee-9730-cff29f0e36c5,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,21995497-0eb8-4c0b-8c23-e6a829f3d596 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,18ebf5ea-b0a2-4333-9e9c-40217de809ff -42aa48c7-68cc-4bee-9730-cff29f0e36c5,151e3048-66ad-4411-8753-677877e3bf0a -42aa48c7-68cc-4bee-9730-cff29f0e36c5,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -42aa48c7-68cc-4bee-9730-cff29f0e36c5,0af4a77e-892e-4b3f-9110-4c5224782250 -4506aa76-9d0d-40e4-85bc-5735f74522a0,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -4506aa76-9d0d-40e4-85bc-5735f74522a0,e8eba267-fecb-477e-9625-771fbcfc3cba -4506aa76-9d0d-40e4-85bc-5735f74522a0,e8c41168-a093-40eb-8baa-6802d24f554a -4506aa76-9d0d-40e4-85bc-5735f74522a0,e26ae29a-213a-4f79-98c2-cc81cf2f451d -4506aa76-9d0d-40e4-85bc-5735f74522a0,e0dfbc08-45ad-4a81-b648-7e65c066f673 -4506aa76-9d0d-40e4-85bc-5735f74522a0,d535b213-2abc-438f-aa6a-c06476d60b09 -4506aa76-9d0d-40e4-85bc-5735f74522a0,c892b1d7-7485-407d-8883-54fb7fecebc1 -4506aa76-9d0d-40e4-85bc-5735f74522a0,b6900c21-d310-4fb4-8b8f-176a09c91989 -4506aa76-9d0d-40e4-85bc-5735f74522a0,b00df815-7528-4967-bfe2-311130d91c21 -4506aa76-9d0d-40e4-85bc-5735f74522a0,add6d754-a075-4693-a33a-c061c9a368ff -4506aa76-9d0d-40e4-85bc-5735f74522a0,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -4506aa76-9d0d-40e4-85bc-5735f74522a0,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -4506aa76-9d0d-40e4-85bc-5735f74522a0,8a1908f7-47cc-4f82-859c-781a193e9901 -4506aa76-9d0d-40e4-85bc-5735f74522a0,8743e69b-17aa-44ff-b8fa-4f582665efc6 -4506aa76-9d0d-40e4-85bc-5735f74522a0,721e4027-a080-40d8-bc4a-43cd33477611 -4506aa76-9d0d-40e4-85bc-5735f74522a0,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -4506aa76-9d0d-40e4-85bc-5735f74522a0,54c750aa-570a-4e8e-9a02-418781923e39 -4506aa76-9d0d-40e4-85bc-5735f74522a0,50eac25a-3761-4de3-8a69-aae52e658300 -4506aa76-9d0d-40e4-85bc-5735f74522a0,4a464bb5-9373-4f1b-9c03-053c64797114 -4506aa76-9d0d-40e4-85bc-5735f74522a0,4506aa76-9d0d-40e4-85bc-5735f74522a0 -4506aa76-9d0d-40e4-85bc-5735f74522a0,42a9a333-d09d-4b9e-af96-1f02af26bac3 -4506aa76-9d0d-40e4-85bc-5735f74522a0,2fc75f11-2097-4e1e-8390-dbff3e844b7a -4506aa76-9d0d-40e4-85bc-5735f74522a0,274a2e49-9170-4945-bca2-ab6ef4bded75 -4506aa76-9d0d-40e4-85bc-5735f74522a0,0364073f-91d8-47a1-b8b0-107c6318a691 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,ee24bf89-81a3-4259-a382-86917f3829d4 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,e363eb67-64c7-440f-93fd-5c8787c69a85 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,db79f75d-af3c-4f82-b4e5-9b5d26598eef -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,d7a48a26-7731-4046-bf22-78f0b8084eb9 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,d080c116-681a-494a-a928-45924109b49d -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,990c21ab-433b-4920-873e-f9dfc7103d9d -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,922e2443-9cc5-421f-8310-9cd23f6e9f2d -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,8089a9ac-9e71-4487-a231-f720e4bc8d3e -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,6d47a2fe-3645-4c5c-bebe-1b822eff197f -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,67fc12fc-bb9f-40f1-9051-127a788b3081 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,634f0889-3a83-484a-bcc8-86f48e333c7b -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,4adcaf72-5241-4877-b61c-f34060576c50 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,374fd699-6b74-4500-9d60-2473c7e0364f -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,308eba53-29fa-489a-97dc-741b077841a7 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,21995497-0eb8-4c0b-8c23-e6a829f3d596 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,18ebf5ea-b0a2-4333-9e9c-40217de809ff -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,151e3048-66ad-4411-8753-677877e3bf0a -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,0af4a77e-892e-4b3f-9110-4c5224782250 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,ee24bf89-81a3-4259-a382-86917f3829d4 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,e363eb67-64c7-440f-93fd-5c8787c69a85 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,db79f75d-af3c-4f82-b4e5-9b5d26598eef -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,d7a48a26-7731-4046-bf22-78f0b8084eb9 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,d080c116-681a-494a-a928-45924109b49d -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,990c21ab-433b-4920-873e-f9dfc7103d9d -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,922e2443-9cc5-421f-8310-9cd23f6e9f2d -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,8089a9ac-9e71-4487-a231-f720e4bc8d3e -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,6d47a2fe-3645-4c5c-bebe-1b822eff197f -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,67fc12fc-bb9f-40f1-9051-127a788b3081 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,634f0889-3a83-484a-bcc8-86f48e333c7b -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,4adcaf72-5241-4877-b61c-f34060576c50 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,374fd699-6b74-4500-9d60-2473c7e0364f -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,308eba53-29fa-489a-97dc-741b077841a7 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,21995497-0eb8-4c0b-8c23-e6a829f3d596 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,18ebf5ea-b0a2-4333-9e9c-40217de809ff -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,151e3048-66ad-4411-8753-677877e3bf0a -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,0af4a77e-892e-4b3f-9110-4c5224782250 -48cc2275-a478-4ade-b076-cf38e1d16017,fc771883-3bd6-46d9-9626-a130e1b902de -48cc2275-a478-4ade-b076-cf38e1d16017,e2387a81-5ff5-4daf-b2e8-881998d0d917 -48cc2275-a478-4ade-b076-cf38e1d16017,b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa -48cc2275-a478-4ade-b076-cf38e1d16017,b1d4dc41-07ae-44db-ae17-1df86c5a62cf -48cc2275-a478-4ade-b076-cf38e1d16017,67e5cf95-236b-4f75-abc5-034ca2966448 -48cc2275-a478-4ade-b076-cf38e1d16017,48cc2275-a478-4ade-b076-cf38e1d16017 -48cc2275-a478-4ade-b076-cf38e1d16017,41d26b1c-57b9-408c-b955-bf0ee1db4809 -48cc2275-a478-4ade-b076-cf38e1d16017,3ddf46fe-b5be-456d-810f-ea6a7402236d -48cc2275-a478-4ade-b076-cf38e1d16017,3397a796-894d-409c-97de-a9e6f3f88198 -48cc2275-a478-4ade-b076-cf38e1d16017,19502b5a-2031-487d-9d9c-2001f959408b -498f4b18-bd4c-470d-9cde-2fca70ec8964,fce20464-ff98-4051-8714-6b9584e52740 -498f4b18-bd4c-470d-9cde-2fca70ec8964,f3eb0c38-2571-44e7-be39-732eb52cf1df -498f4b18-bd4c-470d-9cde-2fca70ec8964,f3c2f842-6aa3-42c9-a0f3-895c40456868 -498f4b18-bd4c-470d-9cde-2fca70ec8964,ede7745a-8f3e-4e20-9f16-33756be7ab6c -498f4b18-bd4c-470d-9cde-2fca70ec8964,ca3e0486-fd6b-4a13-a741-3a47760b412d -498f4b18-bd4c-470d-9cde-2fca70ec8964,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -498f4b18-bd4c-470d-9cde-2fca70ec8964,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -498f4b18-bd4c-470d-9cde-2fca70ec8964,a221198d-000e-497b-8b70-bb4d37f7bbe1 -498f4b18-bd4c-470d-9cde-2fca70ec8964,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -498f4b18-bd4c-470d-9cde-2fca70ec8964,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -498f4b18-bd4c-470d-9cde-2fca70ec8964,88b16960-bfff-41d0-81e4-9a4630834a00 -498f4b18-bd4c-470d-9cde-2fca70ec8964,522e2abe-ad96-4d1a-b5a5-748faa997531 -498f4b18-bd4c-470d-9cde-2fca70ec8964,4cfe72fe-21a0-4201-87b6-ef93695d2495 -498f4b18-bd4c-470d-9cde-2fca70ec8964,498f4b18-bd4c-470d-9cde-2fca70ec8964 -498f4b18-bd4c-470d-9cde-2fca70ec8964,202131ae-78bb-4104-89b0-fb90645760f6 -498f4b18-bd4c-470d-9cde-2fca70ec8964,11dafd5c-85e7-4275-a147-89a63641e35e -498f4b18-bd4c-470d-9cde-2fca70ec8964,02b993c0-b358-481c-b0d0-0767387feb9f -4a464bb5-9373-4f1b-9c03-053c64797114,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -4a464bb5-9373-4f1b-9c03-053c64797114,e8eba267-fecb-477e-9625-771fbcfc3cba -4a464bb5-9373-4f1b-9c03-053c64797114,e8c41168-a093-40eb-8baa-6802d24f554a -4a464bb5-9373-4f1b-9c03-053c64797114,e26ae29a-213a-4f79-98c2-cc81cf2f451d -4a464bb5-9373-4f1b-9c03-053c64797114,e0dfbc08-45ad-4a81-b648-7e65c066f673 -4a464bb5-9373-4f1b-9c03-053c64797114,d535b213-2abc-438f-aa6a-c06476d60b09 -4a464bb5-9373-4f1b-9c03-053c64797114,c892b1d7-7485-407d-8883-54fb7fecebc1 -4a464bb5-9373-4f1b-9c03-053c64797114,b6900c21-d310-4fb4-8b8f-176a09c91989 -4a464bb5-9373-4f1b-9c03-053c64797114,b00df815-7528-4967-bfe2-311130d91c21 -4a464bb5-9373-4f1b-9c03-053c64797114,add6d754-a075-4693-a33a-c061c9a368ff -4a464bb5-9373-4f1b-9c03-053c64797114,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -4a464bb5-9373-4f1b-9c03-053c64797114,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -4a464bb5-9373-4f1b-9c03-053c64797114,8a1908f7-47cc-4f82-859c-781a193e9901 -4a464bb5-9373-4f1b-9c03-053c64797114,8743e69b-17aa-44ff-b8fa-4f582665efc6 -4a464bb5-9373-4f1b-9c03-053c64797114,721e4027-a080-40d8-bc4a-43cd33477611 -4a464bb5-9373-4f1b-9c03-053c64797114,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -4a464bb5-9373-4f1b-9c03-053c64797114,54c750aa-570a-4e8e-9a02-418781923e39 -4a464bb5-9373-4f1b-9c03-053c64797114,50eac25a-3761-4de3-8a69-aae52e658300 -4a464bb5-9373-4f1b-9c03-053c64797114,4a464bb5-9373-4f1b-9c03-053c64797114 -4a464bb5-9373-4f1b-9c03-053c64797114,4506aa76-9d0d-40e4-85bc-5735f74522a0 -4a464bb5-9373-4f1b-9c03-053c64797114,42a9a333-d09d-4b9e-af96-1f02af26bac3 -4a464bb5-9373-4f1b-9c03-053c64797114,2fc75f11-2097-4e1e-8390-dbff3e844b7a -4a464bb5-9373-4f1b-9c03-053c64797114,274a2e49-9170-4945-bca2-ab6ef4bded75 -4a464bb5-9373-4f1b-9c03-053c64797114,0364073f-91d8-47a1-b8b0-107c6318a691 -4adcaf72-5241-4877-b61c-f34060576c50,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -4adcaf72-5241-4877-b61c-f34060576c50,ee24bf89-81a3-4259-a382-86917f3829d4 -4adcaf72-5241-4877-b61c-f34060576c50,e363eb67-64c7-440f-93fd-5c8787c69a85 -4adcaf72-5241-4877-b61c-f34060576c50,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -4adcaf72-5241-4877-b61c-f34060576c50,db79f75d-af3c-4f82-b4e5-9b5d26598eef -4adcaf72-5241-4877-b61c-f34060576c50,d7a48a26-7731-4046-bf22-78f0b8084eb9 -4adcaf72-5241-4877-b61c-f34060576c50,d080c116-681a-494a-a928-45924109b49d -4adcaf72-5241-4877-b61c-f34060576c50,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -4adcaf72-5241-4877-b61c-f34060576c50,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -4adcaf72-5241-4877-b61c-f34060576c50,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -4adcaf72-5241-4877-b61c-f34060576c50,990c21ab-433b-4920-873e-f9dfc7103d9d -4adcaf72-5241-4877-b61c-f34060576c50,922e2443-9cc5-421f-8310-9cd23f6e9f2d -4adcaf72-5241-4877-b61c-f34060576c50,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -4adcaf72-5241-4877-b61c-f34060576c50,8089a9ac-9e71-4487-a231-f720e4bc8d3e -4adcaf72-5241-4877-b61c-f34060576c50,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -4adcaf72-5241-4877-b61c-f34060576c50,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -4adcaf72-5241-4877-b61c-f34060576c50,6d47a2fe-3645-4c5c-bebe-1b822eff197f -4adcaf72-5241-4877-b61c-f34060576c50,67fc12fc-bb9f-40f1-9051-127a788b3081 -4adcaf72-5241-4877-b61c-f34060576c50,634f0889-3a83-484a-bcc8-86f48e333c7b -4adcaf72-5241-4877-b61c-f34060576c50,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -4adcaf72-5241-4877-b61c-f34060576c50,4adcaf72-5241-4877-b61c-f34060576c50 -4adcaf72-5241-4877-b61c-f34060576c50,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -4adcaf72-5241-4877-b61c-f34060576c50,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -4adcaf72-5241-4877-b61c-f34060576c50,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -4adcaf72-5241-4877-b61c-f34060576c50,374fd699-6b74-4500-9d60-2473c7e0364f -4adcaf72-5241-4877-b61c-f34060576c50,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -4adcaf72-5241-4877-b61c-f34060576c50,308eba53-29fa-489a-97dc-741b077841a7 -4adcaf72-5241-4877-b61c-f34060576c50,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -4adcaf72-5241-4877-b61c-f34060576c50,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -4adcaf72-5241-4877-b61c-f34060576c50,21995497-0eb8-4c0b-8c23-e6a829f3d596 -4adcaf72-5241-4877-b61c-f34060576c50,18ebf5ea-b0a2-4333-9e9c-40217de809ff -4adcaf72-5241-4877-b61c-f34060576c50,151e3048-66ad-4411-8753-677877e3bf0a -4adcaf72-5241-4877-b61c-f34060576c50,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -4adcaf72-5241-4877-b61c-f34060576c50,0af4a77e-892e-4b3f-9110-4c5224782250 -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,f3aa491a-4dbd-4436-b674-18b2177dcf18 -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,e1040d58-53bc-4467-8101-ca53b772cede -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,d05461d6-13bf-402c-890d-db6404933f7c -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,b7772635-1801-48e4-a442-f4aaa6860544 -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,b07fb98d-2da4-423a-83b4-7a673de93096 -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,97b42d67-8691-433d-8a31-dada076162ae -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,720f7ed6-7b2e-41dc-a0e0-724a3332aa24 -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,2ffd6142-b75b-406f-bc06-cdb1c02eaefc -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,224e538d-3622-4f5e-b772-211ed358d8de -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,16a3408d-c927-4d02-a1ae-cad32419caab -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,04d88eb4-fb1b-4fa1-802a-5624f4c61b32 -4cd95073-6db3-4eb2-ac4b-0aacb182b352,e1c2ad9f-6f61-4f16-805a-2f405b63659a -4cd95073-6db3-4eb2-ac4b-0aacb182b352,d6988116-3c63-44f8-9f94-9e9d51cc5a37 -4cd95073-6db3-4eb2-ac4b-0aacb182b352,cfcbe34a-edc5-4442-bc05-5927fa00336b -4cd95073-6db3-4eb2-ac4b-0aacb182b352,c90aae7e-5704-4e13-8794-41ab377193bd -4cd95073-6db3-4eb2-ac4b-0aacb182b352,bf895c48-1d52-4780-8e9a-532d69356d28 -4cd95073-6db3-4eb2-ac4b-0aacb182b352,ae9e663e-8c15-43c9-8a88-52b61cbf07a9 -4cd95073-6db3-4eb2-ac4b-0aacb182b352,56999896-e36b-4e63-a6b6-dbb85dfe1936 -4cd95073-6db3-4eb2-ac4b-0aacb182b352,51241857-a7a4-4517-96e3-21f797581f89 -4cd95073-6db3-4eb2-ac4b-0aacb182b352,4cd95073-6db3-4eb2-ac4b-0aacb182b352 -4cd95073-6db3-4eb2-ac4b-0aacb182b352,4148ac36-1dcb-4e96-89cd-355e7e8f919b -4cd95073-6db3-4eb2-ac4b-0aacb182b352,2e8eb256-84c9-499a-8bf6-bb39504373e1 -4cd95073-6db3-4eb2-ac4b-0aacb182b352,07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 -4cfe72fe-21a0-4201-87b6-ef93695d2495,fce20464-ff98-4051-8714-6b9584e52740 -4cfe72fe-21a0-4201-87b6-ef93695d2495,f3eb0c38-2571-44e7-be39-732eb52cf1df -4cfe72fe-21a0-4201-87b6-ef93695d2495,f3c2f842-6aa3-42c9-a0f3-895c40456868 -4cfe72fe-21a0-4201-87b6-ef93695d2495,ede7745a-8f3e-4e20-9f16-33756be7ab6c -4cfe72fe-21a0-4201-87b6-ef93695d2495,ca3e0486-fd6b-4a13-a741-3a47760b412d -4cfe72fe-21a0-4201-87b6-ef93695d2495,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -4cfe72fe-21a0-4201-87b6-ef93695d2495,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -4cfe72fe-21a0-4201-87b6-ef93695d2495,a221198d-000e-497b-8b70-bb4d37f7bbe1 -4cfe72fe-21a0-4201-87b6-ef93695d2495,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -4cfe72fe-21a0-4201-87b6-ef93695d2495,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -4cfe72fe-21a0-4201-87b6-ef93695d2495,88b16960-bfff-41d0-81e4-9a4630834a00 -4cfe72fe-21a0-4201-87b6-ef93695d2495,522e2abe-ad96-4d1a-b5a5-748faa997531 -4cfe72fe-21a0-4201-87b6-ef93695d2495,4cfe72fe-21a0-4201-87b6-ef93695d2495 -4cfe72fe-21a0-4201-87b6-ef93695d2495,498f4b18-bd4c-470d-9cde-2fca70ec8964 -4cfe72fe-21a0-4201-87b6-ef93695d2495,202131ae-78bb-4104-89b0-fb90645760f6 -4cfe72fe-21a0-4201-87b6-ef93695d2495,11dafd5c-85e7-4275-a147-89a63641e35e -4cfe72fe-21a0-4201-87b6-ef93695d2495,02b993c0-b358-481c-b0d0-0767387feb9f -4db144d3-a596-4718-a50a-8ac9175ad386,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -4db144d3-a596-4718-a50a-8ac9175ad386,d90676d2-ce74-4867-a00f-6dbffa7c00be -4db144d3-a596-4718-a50a-8ac9175ad386,cc56af6d-25c6-480e-9767-da02a55551da -4db144d3-a596-4718-a50a-8ac9175ad386,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -4db144d3-a596-4718-a50a-8ac9175ad386,baf48725-f0f9-4357-a71d-b1104910deae -4db144d3-a596-4718-a50a-8ac9175ad386,9ed8703e-3b40-424c-9e98-b3b404051f99 -4db144d3-a596-4718-a50a-8ac9175ad386,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -4db144d3-a596-4718-a50a-8ac9175ad386,8be81657-8ba6-4ec5-8ff9-4809367096ea -4db144d3-a596-4718-a50a-8ac9175ad386,7f410064-638a-4477-85c4-97fc2bb14a49 -4db144d3-a596-4718-a50a-8ac9175ad386,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -4db144d3-a596-4718-a50a-8ac9175ad386,5cedf18a-fc44-4c39-a80f-2106a0d76934 -4db144d3-a596-4718-a50a-8ac9175ad386,4f342a71-dec3-403e-970b-e4c21b9eb98b -4db144d3-a596-4718-a50a-8ac9175ad386,4db144d3-a596-4718-a50a-8ac9175ad386 -4db144d3-a596-4718-a50a-8ac9175ad386,4215b5d0-bdd9-4222-a04a-81bb637d60af -4db144d3-a596-4718-a50a-8ac9175ad386,29d4e919-2bd2-44e6-bb8a-380a780f60ff -4db144d3-a596-4718-a50a-8ac9175ad386,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -4db144d3-a596-4718-a50a-8ac9175ad386,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -4db144d3-a596-4718-a50a-8ac9175ad386,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -4db144d3-a596-4718-a50a-8ac9175ad386,102e16c5-4920-4dad-b142-0b280b2aacad -4db144d3-a596-4718-a50a-8ac9175ad386,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,ee24bf89-81a3-4259-a382-86917f3829d4 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,e363eb67-64c7-440f-93fd-5c8787c69a85 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,db79f75d-af3c-4f82-b4e5-9b5d26598eef -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,d7a48a26-7731-4046-bf22-78f0b8084eb9 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,d080c116-681a-494a-a928-45924109b49d -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,990c21ab-433b-4920-873e-f9dfc7103d9d -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,922e2443-9cc5-421f-8310-9cd23f6e9f2d -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,8089a9ac-9e71-4487-a231-f720e4bc8d3e -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,6d47a2fe-3645-4c5c-bebe-1b822eff197f -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,67fc12fc-bb9f-40f1-9051-127a788b3081 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,634f0889-3a83-484a-bcc8-86f48e333c7b -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,4adcaf72-5241-4877-b61c-f34060576c50 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,374fd699-6b74-4500-9d60-2473c7e0364f -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,308eba53-29fa-489a-97dc-741b077841a7 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,21995497-0eb8-4c0b-8c23-e6a829f3d596 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,18ebf5ea-b0a2-4333-9e9c-40217de809ff -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,151e3048-66ad-4411-8753-677877e3bf0a -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,0af4a77e-892e-4b3f-9110-4c5224782250 -4f14ca7f-5332-4263-a7b2-f0a838380309,f883f2ca-d523-4c9f-86b2-0add137901de -4f14ca7f-5332-4263-a7b2-f0a838380309,f28931e5-1f35-4f9f-a02e-78398735ea67 -4f14ca7f-5332-4263-a7b2-f0a838380309,e7f68d35-ae55-4fa4-b0be-0672a5a7186a -4f14ca7f-5332-4263-a7b2-f0a838380309,85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b -4f14ca7f-5332-4263-a7b2-f0a838380309,7ac56d5a-32a7-4b40-a956-356e3006faed -4f14ca7f-5332-4263-a7b2-f0a838380309,72788e7f-1bf1-40b5-a1f1-27c669dc7278 -4f14ca7f-5332-4263-a7b2-f0a838380309,6c84348c-5065-4e89-9412-bfda023683f2 -4f14ca7f-5332-4263-a7b2-f0a838380309,5abe533b-ae5f-47ad-8746-f60caf7483c0 -4f14ca7f-5332-4263-a7b2-f0a838380309,4f14ca7f-5332-4263-a7b2-f0a838380309 -4f14ca7f-5332-4263-a7b2-f0a838380309,359cb842-c25b-429f-ba9b-d52f171e5631 -4f342a71-dec3-403e-970b-e4c21b9eb98b,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -4f342a71-dec3-403e-970b-e4c21b9eb98b,d90676d2-ce74-4867-a00f-6dbffa7c00be -4f342a71-dec3-403e-970b-e4c21b9eb98b,cc56af6d-25c6-480e-9767-da02a55551da -4f342a71-dec3-403e-970b-e4c21b9eb98b,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -4f342a71-dec3-403e-970b-e4c21b9eb98b,baf48725-f0f9-4357-a71d-b1104910deae -4f342a71-dec3-403e-970b-e4c21b9eb98b,9ed8703e-3b40-424c-9e98-b3b404051f99 -4f342a71-dec3-403e-970b-e4c21b9eb98b,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -4f342a71-dec3-403e-970b-e4c21b9eb98b,8be81657-8ba6-4ec5-8ff9-4809367096ea -4f342a71-dec3-403e-970b-e4c21b9eb98b,7f410064-638a-4477-85c4-97fc2bb14a49 -4f342a71-dec3-403e-970b-e4c21b9eb98b,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -4f342a71-dec3-403e-970b-e4c21b9eb98b,5cedf18a-fc44-4c39-a80f-2106a0d76934 -4f342a71-dec3-403e-970b-e4c21b9eb98b,4f342a71-dec3-403e-970b-e4c21b9eb98b -4f342a71-dec3-403e-970b-e4c21b9eb98b,4db144d3-a596-4718-a50a-8ac9175ad386 -4f342a71-dec3-403e-970b-e4c21b9eb98b,4215b5d0-bdd9-4222-a04a-81bb637d60af -4f342a71-dec3-403e-970b-e4c21b9eb98b,29d4e919-2bd2-44e6-bb8a-380a780f60ff -4f342a71-dec3-403e-970b-e4c21b9eb98b,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -4f342a71-dec3-403e-970b-e4c21b9eb98b,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -4f342a71-dec3-403e-970b-e4c21b9eb98b,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -4f342a71-dec3-403e-970b-e4c21b9eb98b,102e16c5-4920-4dad-b142-0b280b2aacad -4f342a71-dec3-403e-970b-e4c21b9eb98b,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -4f3d1e93-a28f-446e-91af-2baf0168b82b,fcc916dd-5e4f-4bf7-9aef-c906a36d7301 -4f3d1e93-a28f-446e-91af-2baf0168b82b,c57d51a7-45de-41b9-92fc-efd0634effaf -4f3d1e93-a28f-446e-91af-2baf0168b82b,ae7ddaef-4911-418c-8401-d978617e145b -4f3d1e93-a28f-446e-91af-2baf0168b82b,acb2329d-b1c3-45c3-ad28-91a09aa521e1 -4f3d1e93-a28f-446e-91af-2baf0168b82b,a89c0c42-c19f-4d7f-a15e-d04e043c92f6 -4f3d1e93-a28f-446e-91af-2baf0168b82b,836b2e59-fb97-4014-ab0c-d5a5dc750969 -4f3d1e93-a28f-446e-91af-2baf0168b82b,683714ad-5194-49e7-9b8f-4e306cf68ae1 -4f3d1e93-a28f-446e-91af-2baf0168b82b,5f64131b-d410-449a-9de6-35415919fec5 -4f3d1e93-a28f-446e-91af-2baf0168b82b,4f3d1e93-a28f-446e-91af-2baf0168b82b -4f3d1e93-a28f-446e-91af-2baf0168b82b,1db1c71b-9eeb-4a98-abc1-eb699b38510c -50eac25a-3761-4de3-8a69-aae52e658300,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -50eac25a-3761-4de3-8a69-aae52e658300,e8eba267-fecb-477e-9625-771fbcfc3cba -50eac25a-3761-4de3-8a69-aae52e658300,e8c41168-a093-40eb-8baa-6802d24f554a -50eac25a-3761-4de3-8a69-aae52e658300,e26ae29a-213a-4f79-98c2-cc81cf2f451d -50eac25a-3761-4de3-8a69-aae52e658300,e0dfbc08-45ad-4a81-b648-7e65c066f673 -50eac25a-3761-4de3-8a69-aae52e658300,d535b213-2abc-438f-aa6a-c06476d60b09 -50eac25a-3761-4de3-8a69-aae52e658300,c892b1d7-7485-407d-8883-54fb7fecebc1 -50eac25a-3761-4de3-8a69-aae52e658300,b6900c21-d310-4fb4-8b8f-176a09c91989 -50eac25a-3761-4de3-8a69-aae52e658300,b00df815-7528-4967-bfe2-311130d91c21 -50eac25a-3761-4de3-8a69-aae52e658300,add6d754-a075-4693-a33a-c061c9a368ff -50eac25a-3761-4de3-8a69-aae52e658300,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -50eac25a-3761-4de3-8a69-aae52e658300,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -50eac25a-3761-4de3-8a69-aae52e658300,8a1908f7-47cc-4f82-859c-781a193e9901 -50eac25a-3761-4de3-8a69-aae52e658300,8743e69b-17aa-44ff-b8fa-4f582665efc6 -50eac25a-3761-4de3-8a69-aae52e658300,721e4027-a080-40d8-bc4a-43cd33477611 -50eac25a-3761-4de3-8a69-aae52e658300,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -50eac25a-3761-4de3-8a69-aae52e658300,54c750aa-570a-4e8e-9a02-418781923e39 -50eac25a-3761-4de3-8a69-aae52e658300,50eac25a-3761-4de3-8a69-aae52e658300 -50eac25a-3761-4de3-8a69-aae52e658300,4a464bb5-9373-4f1b-9c03-053c64797114 -50eac25a-3761-4de3-8a69-aae52e658300,4506aa76-9d0d-40e4-85bc-5735f74522a0 -50eac25a-3761-4de3-8a69-aae52e658300,42a9a333-d09d-4b9e-af96-1f02af26bac3 -50eac25a-3761-4de3-8a69-aae52e658300,2fc75f11-2097-4e1e-8390-dbff3e844b7a -50eac25a-3761-4de3-8a69-aae52e658300,274a2e49-9170-4945-bca2-ab6ef4bded75 -50eac25a-3761-4de3-8a69-aae52e658300,0364073f-91d8-47a1-b8b0-107c6318a691 -51241857-a7a4-4517-96e3-21f797581f89,e1c2ad9f-6f61-4f16-805a-2f405b63659a -51241857-a7a4-4517-96e3-21f797581f89,d6988116-3c63-44f8-9f94-9e9d51cc5a37 -51241857-a7a4-4517-96e3-21f797581f89,cfcbe34a-edc5-4442-bc05-5927fa00336b -51241857-a7a4-4517-96e3-21f797581f89,c90aae7e-5704-4e13-8794-41ab377193bd -51241857-a7a4-4517-96e3-21f797581f89,bf895c48-1d52-4780-8e9a-532d69356d28 -51241857-a7a4-4517-96e3-21f797581f89,ae9e663e-8c15-43c9-8a88-52b61cbf07a9 -51241857-a7a4-4517-96e3-21f797581f89,56999896-e36b-4e63-a6b6-dbb85dfe1936 -51241857-a7a4-4517-96e3-21f797581f89,51241857-a7a4-4517-96e3-21f797581f89 -51241857-a7a4-4517-96e3-21f797581f89,4cd95073-6db3-4eb2-ac4b-0aacb182b352 -51241857-a7a4-4517-96e3-21f797581f89,4148ac36-1dcb-4e96-89cd-355e7e8f919b -51241857-a7a4-4517-96e3-21f797581f89,2e8eb256-84c9-499a-8bf6-bb39504373e1 -51241857-a7a4-4517-96e3-21f797581f89,07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 -522e2abe-ad96-4d1a-b5a5-748faa997531,fce20464-ff98-4051-8714-6b9584e52740 -522e2abe-ad96-4d1a-b5a5-748faa997531,f3eb0c38-2571-44e7-be39-732eb52cf1df -522e2abe-ad96-4d1a-b5a5-748faa997531,f3c2f842-6aa3-42c9-a0f3-895c40456868 -522e2abe-ad96-4d1a-b5a5-748faa997531,ede7745a-8f3e-4e20-9f16-33756be7ab6c -522e2abe-ad96-4d1a-b5a5-748faa997531,ca3e0486-fd6b-4a13-a741-3a47760b412d -522e2abe-ad96-4d1a-b5a5-748faa997531,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -522e2abe-ad96-4d1a-b5a5-748faa997531,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -522e2abe-ad96-4d1a-b5a5-748faa997531,a221198d-000e-497b-8b70-bb4d37f7bbe1 -522e2abe-ad96-4d1a-b5a5-748faa997531,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -522e2abe-ad96-4d1a-b5a5-748faa997531,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -522e2abe-ad96-4d1a-b5a5-748faa997531,88b16960-bfff-41d0-81e4-9a4630834a00 -522e2abe-ad96-4d1a-b5a5-748faa997531,522e2abe-ad96-4d1a-b5a5-748faa997531 -522e2abe-ad96-4d1a-b5a5-748faa997531,4cfe72fe-21a0-4201-87b6-ef93695d2495 -522e2abe-ad96-4d1a-b5a5-748faa997531,498f4b18-bd4c-470d-9cde-2fca70ec8964 -522e2abe-ad96-4d1a-b5a5-748faa997531,202131ae-78bb-4104-89b0-fb90645760f6 -522e2abe-ad96-4d1a-b5a5-748faa997531,11dafd5c-85e7-4275-a147-89a63641e35e -522e2abe-ad96-4d1a-b5a5-748faa997531,02b993c0-b358-481c-b0d0-0767387feb9f -54c750aa-570a-4e8e-9a02-418781923e39,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -54c750aa-570a-4e8e-9a02-418781923e39,e8eba267-fecb-477e-9625-771fbcfc3cba -54c750aa-570a-4e8e-9a02-418781923e39,e8c41168-a093-40eb-8baa-6802d24f554a -54c750aa-570a-4e8e-9a02-418781923e39,e26ae29a-213a-4f79-98c2-cc81cf2f451d -54c750aa-570a-4e8e-9a02-418781923e39,e0dfbc08-45ad-4a81-b648-7e65c066f673 -54c750aa-570a-4e8e-9a02-418781923e39,d535b213-2abc-438f-aa6a-c06476d60b09 -54c750aa-570a-4e8e-9a02-418781923e39,c892b1d7-7485-407d-8883-54fb7fecebc1 -54c750aa-570a-4e8e-9a02-418781923e39,b6900c21-d310-4fb4-8b8f-176a09c91989 -54c750aa-570a-4e8e-9a02-418781923e39,b00df815-7528-4967-bfe2-311130d91c21 -54c750aa-570a-4e8e-9a02-418781923e39,add6d754-a075-4693-a33a-c061c9a368ff -54c750aa-570a-4e8e-9a02-418781923e39,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -54c750aa-570a-4e8e-9a02-418781923e39,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -54c750aa-570a-4e8e-9a02-418781923e39,8a1908f7-47cc-4f82-859c-781a193e9901 -54c750aa-570a-4e8e-9a02-418781923e39,8743e69b-17aa-44ff-b8fa-4f582665efc6 -54c750aa-570a-4e8e-9a02-418781923e39,721e4027-a080-40d8-bc4a-43cd33477611 -54c750aa-570a-4e8e-9a02-418781923e39,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -54c750aa-570a-4e8e-9a02-418781923e39,54c750aa-570a-4e8e-9a02-418781923e39 -54c750aa-570a-4e8e-9a02-418781923e39,50eac25a-3761-4de3-8a69-aae52e658300 -54c750aa-570a-4e8e-9a02-418781923e39,4a464bb5-9373-4f1b-9c03-053c64797114 -54c750aa-570a-4e8e-9a02-418781923e39,4506aa76-9d0d-40e4-85bc-5735f74522a0 -54c750aa-570a-4e8e-9a02-418781923e39,42a9a333-d09d-4b9e-af96-1f02af26bac3 -54c750aa-570a-4e8e-9a02-418781923e39,2fc75f11-2097-4e1e-8390-dbff3e844b7a -54c750aa-570a-4e8e-9a02-418781923e39,274a2e49-9170-4945-bca2-ab6ef4bded75 -54c750aa-570a-4e8e-9a02-418781923e39,0364073f-91d8-47a1-b8b0-107c6318a691 -55f17f44-287f-41aa-a1bc-d1c0104af36e,984067fc-3dfc-47b7-8ee0-4d9b27d43860 -55f17f44-287f-41aa-a1bc-d1c0104af36e,55f17f44-287f-41aa-a1bc-d1c0104af36e -55f17f44-287f-41aa-a1bc-d1c0104af36e,2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5 -56999896-e36b-4e63-a6b6-dbb85dfe1936,e1c2ad9f-6f61-4f16-805a-2f405b63659a -56999896-e36b-4e63-a6b6-dbb85dfe1936,d6988116-3c63-44f8-9f94-9e9d51cc5a37 -56999896-e36b-4e63-a6b6-dbb85dfe1936,cfcbe34a-edc5-4442-bc05-5927fa00336b -56999896-e36b-4e63-a6b6-dbb85dfe1936,c90aae7e-5704-4e13-8794-41ab377193bd -56999896-e36b-4e63-a6b6-dbb85dfe1936,bf895c48-1d52-4780-8e9a-532d69356d28 -56999896-e36b-4e63-a6b6-dbb85dfe1936,ae9e663e-8c15-43c9-8a88-52b61cbf07a9 -56999896-e36b-4e63-a6b6-dbb85dfe1936,56999896-e36b-4e63-a6b6-dbb85dfe1936 -56999896-e36b-4e63-a6b6-dbb85dfe1936,51241857-a7a4-4517-96e3-21f797581f89 -56999896-e36b-4e63-a6b6-dbb85dfe1936,4cd95073-6db3-4eb2-ac4b-0aacb182b352 -56999896-e36b-4e63-a6b6-dbb85dfe1936,4148ac36-1dcb-4e96-89cd-355e7e8f919b -56999896-e36b-4e63-a6b6-dbb85dfe1936,2e8eb256-84c9-499a-8bf6-bb39504373e1 -56999896-e36b-4e63-a6b6-dbb85dfe1936,07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 -578cc35c-0982-4d72-a729-b304c026f075,a2c3ee1d-ec35-4b26-9bab-12de3f47d604 -578cc35c-0982-4d72-a729-b304c026f075,86415df5-7e47-4637-9e09-aaad9e7628d1 -578cc35c-0982-4d72-a729-b304c026f075,749babeb-6883-4bd4-92a5-bec52769071c -578cc35c-0982-4d72-a729-b304c026f075,578cc35c-0982-4d72-a729-b304c026f075 -578cc35c-0982-4d72-a729-b304c026f075,0250a217-a663-403b-a708-5d14eadf0c40 -5abe533b-ae5f-47ad-8746-f60caf7483c0,f883f2ca-d523-4c9f-86b2-0add137901de -5abe533b-ae5f-47ad-8746-f60caf7483c0,f28931e5-1f35-4f9f-a02e-78398735ea67 -5abe533b-ae5f-47ad-8746-f60caf7483c0,e7f68d35-ae55-4fa4-b0be-0672a5a7186a -5abe533b-ae5f-47ad-8746-f60caf7483c0,85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b -5abe533b-ae5f-47ad-8746-f60caf7483c0,7ac56d5a-32a7-4b40-a956-356e3006faed -5abe533b-ae5f-47ad-8746-f60caf7483c0,72788e7f-1bf1-40b5-a1f1-27c669dc7278 -5abe533b-ae5f-47ad-8746-f60caf7483c0,6c84348c-5065-4e89-9412-bfda023683f2 -5abe533b-ae5f-47ad-8746-f60caf7483c0,5abe533b-ae5f-47ad-8746-f60caf7483c0 -5abe533b-ae5f-47ad-8746-f60caf7483c0,4f14ca7f-5332-4263-a7b2-f0a838380309 -5abe533b-ae5f-47ad-8746-f60caf7483c0,359cb842-c25b-429f-ba9b-d52f171e5631 -5cedf18a-fc44-4c39-a80f-2106a0d76934,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -5cedf18a-fc44-4c39-a80f-2106a0d76934,d90676d2-ce74-4867-a00f-6dbffa7c00be -5cedf18a-fc44-4c39-a80f-2106a0d76934,cc56af6d-25c6-480e-9767-da02a55551da -5cedf18a-fc44-4c39-a80f-2106a0d76934,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -5cedf18a-fc44-4c39-a80f-2106a0d76934,baf48725-f0f9-4357-a71d-b1104910deae -5cedf18a-fc44-4c39-a80f-2106a0d76934,9ed8703e-3b40-424c-9e98-b3b404051f99 -5cedf18a-fc44-4c39-a80f-2106a0d76934,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -5cedf18a-fc44-4c39-a80f-2106a0d76934,8be81657-8ba6-4ec5-8ff9-4809367096ea -5cedf18a-fc44-4c39-a80f-2106a0d76934,7f410064-638a-4477-85c4-97fc2bb14a49 -5cedf18a-fc44-4c39-a80f-2106a0d76934,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -5cedf18a-fc44-4c39-a80f-2106a0d76934,5cedf18a-fc44-4c39-a80f-2106a0d76934 -5cedf18a-fc44-4c39-a80f-2106a0d76934,4f342a71-dec3-403e-970b-e4c21b9eb98b -5cedf18a-fc44-4c39-a80f-2106a0d76934,4db144d3-a596-4718-a50a-8ac9175ad386 -5cedf18a-fc44-4c39-a80f-2106a0d76934,4215b5d0-bdd9-4222-a04a-81bb637d60af -5cedf18a-fc44-4c39-a80f-2106a0d76934,29d4e919-2bd2-44e6-bb8a-380a780f60ff -5cedf18a-fc44-4c39-a80f-2106a0d76934,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -5cedf18a-fc44-4c39-a80f-2106a0d76934,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -5cedf18a-fc44-4c39-a80f-2106a0d76934,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -5cedf18a-fc44-4c39-a80f-2106a0d76934,102e16c5-4920-4dad-b142-0b280b2aacad -5cedf18a-fc44-4c39-a80f-2106a0d76934,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -5e1fe0f2-4517-462b-8287-7824f9a60f4f,5e1fe0f2-4517-462b-8287-7824f9a60f4f -5f64131b-d410-449a-9de6-35415919fec5,fcc916dd-5e4f-4bf7-9aef-c906a36d7301 -5f64131b-d410-449a-9de6-35415919fec5,c57d51a7-45de-41b9-92fc-efd0634effaf -5f64131b-d410-449a-9de6-35415919fec5,ae7ddaef-4911-418c-8401-d978617e145b -5f64131b-d410-449a-9de6-35415919fec5,acb2329d-b1c3-45c3-ad28-91a09aa521e1 -5f64131b-d410-449a-9de6-35415919fec5,a89c0c42-c19f-4d7f-a15e-d04e043c92f6 -5f64131b-d410-449a-9de6-35415919fec5,836b2e59-fb97-4014-ab0c-d5a5dc750969 -5f64131b-d410-449a-9de6-35415919fec5,683714ad-5194-49e7-9b8f-4e306cf68ae1 -5f64131b-d410-449a-9de6-35415919fec5,5f64131b-d410-449a-9de6-35415919fec5 -5f64131b-d410-449a-9de6-35415919fec5,4f3d1e93-a28f-446e-91af-2baf0168b82b -5f64131b-d410-449a-9de6-35415919fec5,1db1c71b-9eeb-4a98-abc1-eb699b38510c -62059efc-7607-41c9-a3ba-70948f6a8a1d,f77a8561-5adc-452a-ac9a-76273b6a6678 -62059efc-7607-41c9-a3ba-70948f6a8a1d,d79728e1-8834-4f4a-8edc-ce18aca18be6 -62059efc-7607-41c9-a3ba-70948f6a8a1d,cf67a8d4-48e2-4dc9-a521-6aabcff0330b -62059efc-7607-41c9-a3ba-70948f6a8a1d,bb1a7816-4798-4f58-9ce4-c5bd3af682a8 -62059efc-7607-41c9-a3ba-70948f6a8a1d,b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 -62059efc-7607-41c9-a3ba-70948f6a8a1d,a6cb423a-7571-46df-83e2-ccc6820adb36 -62059efc-7607-41c9-a3ba-70948f6a8a1d,7e7322a6-7912-4101-b3be-d134245a0626 -62059efc-7607-41c9-a3ba-70948f6a8a1d,777aa5f2-2a09-4aed-92ea-912694e28b48 -62059efc-7607-41c9-a3ba-70948f6a8a1d,6e187f47-91a1-4217-a185-31b6f01db225 -62059efc-7607-41c9-a3ba-70948f6a8a1d,62059efc-7607-41c9-a3ba-70948f6a8a1d -62059efc-7607-41c9-a3ba-70948f6a8a1d,2d2e07ec-e697-4384-a679-867e93740921 -62059efc-7607-41c9-a3ba-70948f6a8a1d,20fbcb6e-d793-4b05-8e29-8ff82572b0db -62059efc-7607-41c9-a3ba-70948f6a8a1d,1fd714a6-48b4-425a-99b3-ba5c1a995cce -634f0889-3a83-484a-bcc8-86f48e333c7b,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -634f0889-3a83-484a-bcc8-86f48e333c7b,ee24bf89-81a3-4259-a382-86917f3829d4 -634f0889-3a83-484a-bcc8-86f48e333c7b,e363eb67-64c7-440f-93fd-5c8787c69a85 -634f0889-3a83-484a-bcc8-86f48e333c7b,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -634f0889-3a83-484a-bcc8-86f48e333c7b,db79f75d-af3c-4f82-b4e5-9b5d26598eef -634f0889-3a83-484a-bcc8-86f48e333c7b,d7a48a26-7731-4046-bf22-78f0b8084eb9 -634f0889-3a83-484a-bcc8-86f48e333c7b,d080c116-681a-494a-a928-45924109b49d -634f0889-3a83-484a-bcc8-86f48e333c7b,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -634f0889-3a83-484a-bcc8-86f48e333c7b,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -634f0889-3a83-484a-bcc8-86f48e333c7b,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -634f0889-3a83-484a-bcc8-86f48e333c7b,990c21ab-433b-4920-873e-f9dfc7103d9d -634f0889-3a83-484a-bcc8-86f48e333c7b,922e2443-9cc5-421f-8310-9cd23f6e9f2d -634f0889-3a83-484a-bcc8-86f48e333c7b,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -634f0889-3a83-484a-bcc8-86f48e333c7b,8089a9ac-9e71-4487-a231-f720e4bc8d3e -634f0889-3a83-484a-bcc8-86f48e333c7b,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -634f0889-3a83-484a-bcc8-86f48e333c7b,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -634f0889-3a83-484a-bcc8-86f48e333c7b,6d47a2fe-3645-4c5c-bebe-1b822eff197f -634f0889-3a83-484a-bcc8-86f48e333c7b,67fc12fc-bb9f-40f1-9051-127a788b3081 -634f0889-3a83-484a-bcc8-86f48e333c7b,634f0889-3a83-484a-bcc8-86f48e333c7b -634f0889-3a83-484a-bcc8-86f48e333c7b,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -634f0889-3a83-484a-bcc8-86f48e333c7b,4adcaf72-5241-4877-b61c-f34060576c50 -634f0889-3a83-484a-bcc8-86f48e333c7b,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -634f0889-3a83-484a-bcc8-86f48e333c7b,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -634f0889-3a83-484a-bcc8-86f48e333c7b,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -634f0889-3a83-484a-bcc8-86f48e333c7b,374fd699-6b74-4500-9d60-2473c7e0364f -634f0889-3a83-484a-bcc8-86f48e333c7b,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -634f0889-3a83-484a-bcc8-86f48e333c7b,308eba53-29fa-489a-97dc-741b077841a7 -634f0889-3a83-484a-bcc8-86f48e333c7b,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -634f0889-3a83-484a-bcc8-86f48e333c7b,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -634f0889-3a83-484a-bcc8-86f48e333c7b,21995497-0eb8-4c0b-8c23-e6a829f3d596 -634f0889-3a83-484a-bcc8-86f48e333c7b,18ebf5ea-b0a2-4333-9e9c-40217de809ff -634f0889-3a83-484a-bcc8-86f48e333c7b,151e3048-66ad-4411-8753-677877e3bf0a -634f0889-3a83-484a-bcc8-86f48e333c7b,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -634f0889-3a83-484a-bcc8-86f48e333c7b,0af4a77e-892e-4b3f-9110-4c5224782250 -67e5cf95-236b-4f75-abc5-034ca2966448,fc771883-3bd6-46d9-9626-a130e1b902de -67e5cf95-236b-4f75-abc5-034ca2966448,e2387a81-5ff5-4daf-b2e8-881998d0d917 -67e5cf95-236b-4f75-abc5-034ca2966448,b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa -67e5cf95-236b-4f75-abc5-034ca2966448,b1d4dc41-07ae-44db-ae17-1df86c5a62cf -67e5cf95-236b-4f75-abc5-034ca2966448,67e5cf95-236b-4f75-abc5-034ca2966448 -67e5cf95-236b-4f75-abc5-034ca2966448,48cc2275-a478-4ade-b076-cf38e1d16017 -67e5cf95-236b-4f75-abc5-034ca2966448,41d26b1c-57b9-408c-b955-bf0ee1db4809 -67e5cf95-236b-4f75-abc5-034ca2966448,3ddf46fe-b5be-456d-810f-ea6a7402236d -67e5cf95-236b-4f75-abc5-034ca2966448,3397a796-894d-409c-97de-a9e6f3f88198 -67e5cf95-236b-4f75-abc5-034ca2966448,19502b5a-2031-487d-9d9c-2001f959408b -67fc12fc-bb9f-40f1-9051-127a788b3081,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -67fc12fc-bb9f-40f1-9051-127a788b3081,ee24bf89-81a3-4259-a382-86917f3829d4 -67fc12fc-bb9f-40f1-9051-127a788b3081,e363eb67-64c7-440f-93fd-5c8787c69a85 -67fc12fc-bb9f-40f1-9051-127a788b3081,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -67fc12fc-bb9f-40f1-9051-127a788b3081,db79f75d-af3c-4f82-b4e5-9b5d26598eef -67fc12fc-bb9f-40f1-9051-127a788b3081,d7a48a26-7731-4046-bf22-78f0b8084eb9 -67fc12fc-bb9f-40f1-9051-127a788b3081,d080c116-681a-494a-a928-45924109b49d -67fc12fc-bb9f-40f1-9051-127a788b3081,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -67fc12fc-bb9f-40f1-9051-127a788b3081,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -67fc12fc-bb9f-40f1-9051-127a788b3081,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -67fc12fc-bb9f-40f1-9051-127a788b3081,990c21ab-433b-4920-873e-f9dfc7103d9d -67fc12fc-bb9f-40f1-9051-127a788b3081,922e2443-9cc5-421f-8310-9cd23f6e9f2d -67fc12fc-bb9f-40f1-9051-127a788b3081,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -67fc12fc-bb9f-40f1-9051-127a788b3081,8089a9ac-9e71-4487-a231-f720e4bc8d3e -67fc12fc-bb9f-40f1-9051-127a788b3081,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -67fc12fc-bb9f-40f1-9051-127a788b3081,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -67fc12fc-bb9f-40f1-9051-127a788b3081,6d47a2fe-3645-4c5c-bebe-1b822eff197f -67fc12fc-bb9f-40f1-9051-127a788b3081,67fc12fc-bb9f-40f1-9051-127a788b3081 -67fc12fc-bb9f-40f1-9051-127a788b3081,634f0889-3a83-484a-bcc8-86f48e333c7b -67fc12fc-bb9f-40f1-9051-127a788b3081,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -67fc12fc-bb9f-40f1-9051-127a788b3081,4adcaf72-5241-4877-b61c-f34060576c50 -67fc12fc-bb9f-40f1-9051-127a788b3081,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -67fc12fc-bb9f-40f1-9051-127a788b3081,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -67fc12fc-bb9f-40f1-9051-127a788b3081,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -67fc12fc-bb9f-40f1-9051-127a788b3081,374fd699-6b74-4500-9d60-2473c7e0364f -67fc12fc-bb9f-40f1-9051-127a788b3081,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -67fc12fc-bb9f-40f1-9051-127a788b3081,308eba53-29fa-489a-97dc-741b077841a7 -67fc12fc-bb9f-40f1-9051-127a788b3081,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -67fc12fc-bb9f-40f1-9051-127a788b3081,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -67fc12fc-bb9f-40f1-9051-127a788b3081,21995497-0eb8-4c0b-8c23-e6a829f3d596 -67fc12fc-bb9f-40f1-9051-127a788b3081,18ebf5ea-b0a2-4333-9e9c-40217de809ff -67fc12fc-bb9f-40f1-9051-127a788b3081,151e3048-66ad-4411-8753-677877e3bf0a -67fc12fc-bb9f-40f1-9051-127a788b3081,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -67fc12fc-bb9f-40f1-9051-127a788b3081,0af4a77e-892e-4b3f-9110-4c5224782250 -683714ad-5194-49e7-9b8f-4e306cf68ae1,fcc916dd-5e4f-4bf7-9aef-c906a36d7301 -683714ad-5194-49e7-9b8f-4e306cf68ae1,c57d51a7-45de-41b9-92fc-efd0634effaf -683714ad-5194-49e7-9b8f-4e306cf68ae1,ae7ddaef-4911-418c-8401-d978617e145b -683714ad-5194-49e7-9b8f-4e306cf68ae1,acb2329d-b1c3-45c3-ad28-91a09aa521e1 -683714ad-5194-49e7-9b8f-4e306cf68ae1,a89c0c42-c19f-4d7f-a15e-d04e043c92f6 -683714ad-5194-49e7-9b8f-4e306cf68ae1,836b2e59-fb97-4014-ab0c-d5a5dc750969 -683714ad-5194-49e7-9b8f-4e306cf68ae1,683714ad-5194-49e7-9b8f-4e306cf68ae1 -683714ad-5194-49e7-9b8f-4e306cf68ae1,5f64131b-d410-449a-9de6-35415919fec5 -683714ad-5194-49e7-9b8f-4e306cf68ae1,4f3d1e93-a28f-446e-91af-2baf0168b82b -683714ad-5194-49e7-9b8f-4e306cf68ae1,1db1c71b-9eeb-4a98-abc1-eb699b38510c -6954c5c2-dd77-490a-9152-f1d78eff913d,6954c5c2-dd77-490a-9152-f1d78eff913d -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,e8eba267-fecb-477e-9625-771fbcfc3cba -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,e8c41168-a093-40eb-8baa-6802d24f554a -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,e26ae29a-213a-4f79-98c2-cc81cf2f451d -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,e0dfbc08-45ad-4a81-b648-7e65c066f673 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,d535b213-2abc-438f-aa6a-c06476d60b09 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,c892b1d7-7485-407d-8883-54fb7fecebc1 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,b6900c21-d310-4fb4-8b8f-176a09c91989 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,b00df815-7528-4967-bfe2-311130d91c21 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,add6d754-a075-4693-a33a-c061c9a368ff -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,8a1908f7-47cc-4f82-859c-781a193e9901 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,8743e69b-17aa-44ff-b8fa-4f582665efc6 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,721e4027-a080-40d8-bc4a-43cd33477611 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,54c750aa-570a-4e8e-9a02-418781923e39 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,50eac25a-3761-4de3-8a69-aae52e658300 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,4a464bb5-9373-4f1b-9c03-053c64797114 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,4506aa76-9d0d-40e4-85bc-5735f74522a0 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,42a9a333-d09d-4b9e-af96-1f02af26bac3 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,2fc75f11-2097-4e1e-8390-dbff3e844b7a -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,274a2e49-9170-4945-bca2-ab6ef4bded75 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,0364073f-91d8-47a1-b8b0-107c6318a691 -6af73cae-2e4e-485f-a74d-6f4307eb3af3,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -6af73cae-2e4e-485f-a74d-6f4307eb3af3,d90676d2-ce74-4867-a00f-6dbffa7c00be -6af73cae-2e4e-485f-a74d-6f4307eb3af3,cc56af6d-25c6-480e-9767-da02a55551da -6af73cae-2e4e-485f-a74d-6f4307eb3af3,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -6af73cae-2e4e-485f-a74d-6f4307eb3af3,baf48725-f0f9-4357-a71d-b1104910deae -6af73cae-2e4e-485f-a74d-6f4307eb3af3,9ed8703e-3b40-424c-9e98-b3b404051f99 -6af73cae-2e4e-485f-a74d-6f4307eb3af3,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -6af73cae-2e4e-485f-a74d-6f4307eb3af3,8be81657-8ba6-4ec5-8ff9-4809367096ea -6af73cae-2e4e-485f-a74d-6f4307eb3af3,7f410064-638a-4477-85c4-97fc2bb14a49 -6af73cae-2e4e-485f-a74d-6f4307eb3af3,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -6af73cae-2e4e-485f-a74d-6f4307eb3af3,5cedf18a-fc44-4c39-a80f-2106a0d76934 -6af73cae-2e4e-485f-a74d-6f4307eb3af3,4f342a71-dec3-403e-970b-e4c21b9eb98b -6af73cae-2e4e-485f-a74d-6f4307eb3af3,4db144d3-a596-4718-a50a-8ac9175ad386 -6af73cae-2e4e-485f-a74d-6f4307eb3af3,4215b5d0-bdd9-4222-a04a-81bb637d60af -6af73cae-2e4e-485f-a74d-6f4307eb3af3,29d4e919-2bd2-44e6-bb8a-380a780f60ff -6af73cae-2e4e-485f-a74d-6f4307eb3af3,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -6af73cae-2e4e-485f-a74d-6f4307eb3af3,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -6af73cae-2e4e-485f-a74d-6f4307eb3af3,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -6af73cae-2e4e-485f-a74d-6f4307eb3af3,102e16c5-4920-4dad-b142-0b280b2aacad -6af73cae-2e4e-485f-a74d-6f4307eb3af3,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -6c84348c-5065-4e89-9412-bfda023683f2,f883f2ca-d523-4c9f-86b2-0add137901de -6c84348c-5065-4e89-9412-bfda023683f2,f28931e5-1f35-4f9f-a02e-78398735ea67 -6c84348c-5065-4e89-9412-bfda023683f2,e7f68d35-ae55-4fa4-b0be-0672a5a7186a -6c84348c-5065-4e89-9412-bfda023683f2,85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b -6c84348c-5065-4e89-9412-bfda023683f2,7ac56d5a-32a7-4b40-a956-356e3006faed -6c84348c-5065-4e89-9412-bfda023683f2,72788e7f-1bf1-40b5-a1f1-27c669dc7278 -6c84348c-5065-4e89-9412-bfda023683f2,6c84348c-5065-4e89-9412-bfda023683f2 -6c84348c-5065-4e89-9412-bfda023683f2,5abe533b-ae5f-47ad-8746-f60caf7483c0 -6c84348c-5065-4e89-9412-bfda023683f2,4f14ca7f-5332-4263-a7b2-f0a838380309 -6c84348c-5065-4e89-9412-bfda023683f2,359cb842-c25b-429f-ba9b-d52f171e5631 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -6d47a2fe-3645-4c5c-bebe-1b822eff197f,ee24bf89-81a3-4259-a382-86917f3829d4 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,e363eb67-64c7-440f-93fd-5c8787c69a85 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,db79f75d-af3c-4f82-b4e5-9b5d26598eef -6d47a2fe-3645-4c5c-bebe-1b822eff197f,d7a48a26-7731-4046-bf22-78f0b8084eb9 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,d080c116-681a-494a-a928-45924109b49d -6d47a2fe-3645-4c5c-bebe-1b822eff197f,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -6d47a2fe-3645-4c5c-bebe-1b822eff197f,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -6d47a2fe-3645-4c5c-bebe-1b822eff197f,990c21ab-433b-4920-873e-f9dfc7103d9d -6d47a2fe-3645-4c5c-bebe-1b822eff197f,922e2443-9cc5-421f-8310-9cd23f6e9f2d -6d47a2fe-3645-4c5c-bebe-1b822eff197f,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,8089a9ac-9e71-4487-a231-f720e4bc8d3e -6d47a2fe-3645-4c5c-bebe-1b822eff197f,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -6d47a2fe-3645-4c5c-bebe-1b822eff197f,6d47a2fe-3645-4c5c-bebe-1b822eff197f -6d47a2fe-3645-4c5c-bebe-1b822eff197f,67fc12fc-bb9f-40f1-9051-127a788b3081 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,634f0889-3a83-484a-bcc8-86f48e333c7b -6d47a2fe-3645-4c5c-bebe-1b822eff197f,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,4adcaf72-5241-4877-b61c-f34060576c50 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -6d47a2fe-3645-4c5c-bebe-1b822eff197f,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,374fd699-6b74-4500-9d60-2473c7e0364f -6d47a2fe-3645-4c5c-bebe-1b822eff197f,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,308eba53-29fa-489a-97dc-741b077841a7 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -6d47a2fe-3645-4c5c-bebe-1b822eff197f,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,21995497-0eb8-4c0b-8c23-e6a829f3d596 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,18ebf5ea-b0a2-4333-9e9c-40217de809ff -6d47a2fe-3645-4c5c-bebe-1b822eff197f,151e3048-66ad-4411-8753-677877e3bf0a -6d47a2fe-3645-4c5c-bebe-1b822eff197f,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -6d47a2fe-3645-4c5c-bebe-1b822eff197f,0af4a77e-892e-4b3f-9110-4c5224782250 -6e187f47-91a1-4217-a185-31b6f01db225,f77a8561-5adc-452a-ac9a-76273b6a6678 -6e187f47-91a1-4217-a185-31b6f01db225,d79728e1-8834-4f4a-8edc-ce18aca18be6 -6e187f47-91a1-4217-a185-31b6f01db225,cf67a8d4-48e2-4dc9-a521-6aabcff0330b -6e187f47-91a1-4217-a185-31b6f01db225,bb1a7816-4798-4f58-9ce4-c5bd3af682a8 -6e187f47-91a1-4217-a185-31b6f01db225,b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 -6e187f47-91a1-4217-a185-31b6f01db225,a6cb423a-7571-46df-83e2-ccc6820adb36 -6e187f47-91a1-4217-a185-31b6f01db225,7e7322a6-7912-4101-b3be-d134245a0626 -6e187f47-91a1-4217-a185-31b6f01db225,777aa5f2-2a09-4aed-92ea-912694e28b48 -6e187f47-91a1-4217-a185-31b6f01db225,6e187f47-91a1-4217-a185-31b6f01db225 -6e187f47-91a1-4217-a185-31b6f01db225,62059efc-7607-41c9-a3ba-70948f6a8a1d -6e187f47-91a1-4217-a185-31b6f01db225,2d2e07ec-e697-4384-a679-867e93740921 -6e187f47-91a1-4217-a185-31b6f01db225,20fbcb6e-d793-4b05-8e29-8ff82572b0db -6e187f47-91a1-4217-a185-31b6f01db225,1fd714a6-48b4-425a-99b3-ba5c1a995cce -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,f3aa491a-4dbd-4436-b674-18b2177dcf18 -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,e1040d58-53bc-4467-8101-ca53b772cede -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,d05461d6-13bf-402c-890d-db6404933f7c -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,b7772635-1801-48e4-a442-f4aaa6860544 -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,b07fb98d-2da4-423a-83b4-7a673de93096 -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,97b42d67-8691-433d-8a31-dada076162ae -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,720f7ed6-7b2e-41dc-a0e0-724a3332aa24 -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,2ffd6142-b75b-406f-bc06-cdb1c02eaefc -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,224e538d-3622-4f5e-b772-211ed358d8de -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,16a3408d-c927-4d02-a1ae-cad32419caab -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,04d88eb4-fb1b-4fa1-802a-5624f4c61b32 -721e4027-a080-40d8-bc4a-43cd33477611,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -721e4027-a080-40d8-bc4a-43cd33477611,e8eba267-fecb-477e-9625-771fbcfc3cba -721e4027-a080-40d8-bc4a-43cd33477611,e8c41168-a093-40eb-8baa-6802d24f554a -721e4027-a080-40d8-bc4a-43cd33477611,e26ae29a-213a-4f79-98c2-cc81cf2f451d -721e4027-a080-40d8-bc4a-43cd33477611,e0dfbc08-45ad-4a81-b648-7e65c066f673 -721e4027-a080-40d8-bc4a-43cd33477611,d535b213-2abc-438f-aa6a-c06476d60b09 -721e4027-a080-40d8-bc4a-43cd33477611,c892b1d7-7485-407d-8883-54fb7fecebc1 -721e4027-a080-40d8-bc4a-43cd33477611,b6900c21-d310-4fb4-8b8f-176a09c91989 -721e4027-a080-40d8-bc4a-43cd33477611,b00df815-7528-4967-bfe2-311130d91c21 -721e4027-a080-40d8-bc4a-43cd33477611,add6d754-a075-4693-a33a-c061c9a368ff -721e4027-a080-40d8-bc4a-43cd33477611,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -721e4027-a080-40d8-bc4a-43cd33477611,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -721e4027-a080-40d8-bc4a-43cd33477611,8a1908f7-47cc-4f82-859c-781a193e9901 -721e4027-a080-40d8-bc4a-43cd33477611,8743e69b-17aa-44ff-b8fa-4f582665efc6 -721e4027-a080-40d8-bc4a-43cd33477611,721e4027-a080-40d8-bc4a-43cd33477611 -721e4027-a080-40d8-bc4a-43cd33477611,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -721e4027-a080-40d8-bc4a-43cd33477611,54c750aa-570a-4e8e-9a02-418781923e39 -721e4027-a080-40d8-bc4a-43cd33477611,50eac25a-3761-4de3-8a69-aae52e658300 -721e4027-a080-40d8-bc4a-43cd33477611,4a464bb5-9373-4f1b-9c03-053c64797114 -721e4027-a080-40d8-bc4a-43cd33477611,4506aa76-9d0d-40e4-85bc-5735f74522a0 -721e4027-a080-40d8-bc4a-43cd33477611,42a9a333-d09d-4b9e-af96-1f02af26bac3 -721e4027-a080-40d8-bc4a-43cd33477611,2fc75f11-2097-4e1e-8390-dbff3e844b7a -721e4027-a080-40d8-bc4a-43cd33477611,274a2e49-9170-4945-bca2-ab6ef4bded75 -721e4027-a080-40d8-bc4a-43cd33477611,0364073f-91d8-47a1-b8b0-107c6318a691 -72788e7f-1bf1-40b5-a1f1-27c669dc7278,f883f2ca-d523-4c9f-86b2-0add137901de -72788e7f-1bf1-40b5-a1f1-27c669dc7278,f28931e5-1f35-4f9f-a02e-78398735ea67 -72788e7f-1bf1-40b5-a1f1-27c669dc7278,e7f68d35-ae55-4fa4-b0be-0672a5a7186a -72788e7f-1bf1-40b5-a1f1-27c669dc7278,85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b -72788e7f-1bf1-40b5-a1f1-27c669dc7278,7ac56d5a-32a7-4b40-a956-356e3006faed -72788e7f-1bf1-40b5-a1f1-27c669dc7278,72788e7f-1bf1-40b5-a1f1-27c669dc7278 -72788e7f-1bf1-40b5-a1f1-27c669dc7278,6c84348c-5065-4e89-9412-bfda023683f2 -72788e7f-1bf1-40b5-a1f1-27c669dc7278,5abe533b-ae5f-47ad-8746-f60caf7483c0 -72788e7f-1bf1-40b5-a1f1-27c669dc7278,4f14ca7f-5332-4263-a7b2-f0a838380309 -72788e7f-1bf1-40b5-a1f1-27c669dc7278,359cb842-c25b-429f-ba9b-d52f171e5631 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,ee24bf89-81a3-4259-a382-86917f3829d4 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,e363eb67-64c7-440f-93fd-5c8787c69a85 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,db79f75d-af3c-4f82-b4e5-9b5d26598eef -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,d7a48a26-7731-4046-bf22-78f0b8084eb9 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,d080c116-681a-494a-a928-45924109b49d -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,990c21ab-433b-4920-873e-f9dfc7103d9d -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,922e2443-9cc5-421f-8310-9cd23f6e9f2d -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,8089a9ac-9e71-4487-a231-f720e4bc8d3e -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,6d47a2fe-3645-4c5c-bebe-1b822eff197f -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,67fc12fc-bb9f-40f1-9051-127a788b3081 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,634f0889-3a83-484a-bcc8-86f48e333c7b -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,4adcaf72-5241-4877-b61c-f34060576c50 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,374fd699-6b74-4500-9d60-2473c7e0364f -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,308eba53-29fa-489a-97dc-741b077841a7 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,21995497-0eb8-4c0b-8c23-e6a829f3d596 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,18ebf5ea-b0a2-4333-9e9c-40217de809ff -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,151e3048-66ad-4411-8753-677877e3bf0a -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,0af4a77e-892e-4b3f-9110-4c5224782250 -749babeb-6883-4bd4-92a5-bec52769071c,a2c3ee1d-ec35-4b26-9bab-12de3f47d604 -749babeb-6883-4bd4-92a5-bec52769071c,86415df5-7e47-4637-9e09-aaad9e7628d1 -749babeb-6883-4bd4-92a5-bec52769071c,749babeb-6883-4bd4-92a5-bec52769071c -749babeb-6883-4bd4-92a5-bec52769071c,578cc35c-0982-4d72-a729-b304c026f075 -749babeb-6883-4bd4-92a5-bec52769071c,0250a217-a663-403b-a708-5d14eadf0c40 -75a09d4f-eef2-4404-a386-dfcb408ec9ed,bfc93cb7-e53a-49f7-b86e-81d6a13cfdea -75a09d4f-eef2-4404-a386-dfcb408ec9ed,a6790cbd-8349-432e-a976-5dfc07451203 -75a09d4f-eef2-4404-a386-dfcb408ec9ed,9b2eb632-6f1a-4e97-912b-3c8378a1b11c -75a09d4f-eef2-4404-a386-dfcb408ec9ed,85c997bf-63d9-4ebb-8e0b-320de3dddc6c -75a09d4f-eef2-4404-a386-dfcb408ec9ed,85481b3f-c75e-40cd-bacd-b0afb79e893c -75a09d4f-eef2-4404-a386-dfcb408ec9ed,75a09d4f-eef2-4404-a386-dfcb408ec9ed -75a09d4f-eef2-4404-a386-dfcb408ec9ed,30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 -75a09d4f-eef2-4404-a386-dfcb408ec9ed,2ee2ab26-09fb-4e12-bf21-9fed3b4c38de -777aa5f2-2a09-4aed-92ea-912694e28b48,f77a8561-5adc-452a-ac9a-76273b6a6678 -777aa5f2-2a09-4aed-92ea-912694e28b48,d79728e1-8834-4f4a-8edc-ce18aca18be6 -777aa5f2-2a09-4aed-92ea-912694e28b48,cf67a8d4-48e2-4dc9-a521-6aabcff0330b -777aa5f2-2a09-4aed-92ea-912694e28b48,bb1a7816-4798-4f58-9ce4-c5bd3af682a8 -777aa5f2-2a09-4aed-92ea-912694e28b48,b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 -777aa5f2-2a09-4aed-92ea-912694e28b48,a6cb423a-7571-46df-83e2-ccc6820adb36 -777aa5f2-2a09-4aed-92ea-912694e28b48,7e7322a6-7912-4101-b3be-d134245a0626 -777aa5f2-2a09-4aed-92ea-912694e28b48,777aa5f2-2a09-4aed-92ea-912694e28b48 -777aa5f2-2a09-4aed-92ea-912694e28b48,6e187f47-91a1-4217-a185-31b6f01db225 -777aa5f2-2a09-4aed-92ea-912694e28b48,62059efc-7607-41c9-a3ba-70948f6a8a1d -777aa5f2-2a09-4aed-92ea-912694e28b48,2d2e07ec-e697-4384-a679-867e93740921 -777aa5f2-2a09-4aed-92ea-912694e28b48,20fbcb6e-d793-4b05-8e29-8ff82572b0db -777aa5f2-2a09-4aed-92ea-912694e28b48,1fd714a6-48b4-425a-99b3-ba5c1a995cce -79be3b69-23d8-4408-8f01-68d993e63b6c,b0d337c5-3555-4817-ba59-4ceea5ad8a91 -79be3b69-23d8-4408-8f01-68d993e63b6c,79be3b69-23d8-4408-8f01-68d993e63b6c -79be3b69-23d8-4408-8f01-68d993e63b6c,04945715-8ad5-4d8f-bfda-ae26662a3610 -7ac56d5a-32a7-4b40-a956-356e3006faed,f883f2ca-d523-4c9f-86b2-0add137901de -7ac56d5a-32a7-4b40-a956-356e3006faed,f28931e5-1f35-4f9f-a02e-78398735ea67 -7ac56d5a-32a7-4b40-a956-356e3006faed,e7f68d35-ae55-4fa4-b0be-0672a5a7186a -7ac56d5a-32a7-4b40-a956-356e3006faed,85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b -7ac56d5a-32a7-4b40-a956-356e3006faed,7ac56d5a-32a7-4b40-a956-356e3006faed -7ac56d5a-32a7-4b40-a956-356e3006faed,72788e7f-1bf1-40b5-a1f1-27c669dc7278 -7ac56d5a-32a7-4b40-a956-356e3006faed,6c84348c-5065-4e89-9412-bfda023683f2 -7ac56d5a-32a7-4b40-a956-356e3006faed,5abe533b-ae5f-47ad-8746-f60caf7483c0 -7ac56d5a-32a7-4b40-a956-356e3006faed,4f14ca7f-5332-4263-a7b2-f0a838380309 -7ac56d5a-32a7-4b40-a956-356e3006faed,359cb842-c25b-429f-ba9b-d52f171e5631 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,ee24bf89-81a3-4259-a382-86917f3829d4 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,e363eb67-64c7-440f-93fd-5c8787c69a85 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,db79f75d-af3c-4f82-b4e5-9b5d26598eef -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,d7a48a26-7731-4046-bf22-78f0b8084eb9 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,d080c116-681a-494a-a928-45924109b49d -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,990c21ab-433b-4920-873e-f9dfc7103d9d -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,922e2443-9cc5-421f-8310-9cd23f6e9f2d -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,8089a9ac-9e71-4487-a231-f720e4bc8d3e -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,6d47a2fe-3645-4c5c-bebe-1b822eff197f -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,67fc12fc-bb9f-40f1-9051-127a788b3081 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,634f0889-3a83-484a-bcc8-86f48e333c7b -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,4adcaf72-5241-4877-b61c-f34060576c50 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,374fd699-6b74-4500-9d60-2473c7e0364f -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,308eba53-29fa-489a-97dc-741b077841a7 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,21995497-0eb8-4c0b-8c23-e6a829f3d596 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,18ebf5ea-b0a2-4333-9e9c-40217de809ff -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,151e3048-66ad-4411-8753-677877e3bf0a -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,0af4a77e-892e-4b3f-9110-4c5224782250 -7e7322a6-7912-4101-b3be-d134245a0626,f77a8561-5adc-452a-ac9a-76273b6a6678 -7e7322a6-7912-4101-b3be-d134245a0626,d79728e1-8834-4f4a-8edc-ce18aca18be6 -7e7322a6-7912-4101-b3be-d134245a0626,cf67a8d4-48e2-4dc9-a521-6aabcff0330b -7e7322a6-7912-4101-b3be-d134245a0626,bb1a7816-4798-4f58-9ce4-c5bd3af682a8 -7e7322a6-7912-4101-b3be-d134245a0626,b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 -7e7322a6-7912-4101-b3be-d134245a0626,a6cb423a-7571-46df-83e2-ccc6820adb36 -7e7322a6-7912-4101-b3be-d134245a0626,7e7322a6-7912-4101-b3be-d134245a0626 -7e7322a6-7912-4101-b3be-d134245a0626,777aa5f2-2a09-4aed-92ea-912694e28b48 -7e7322a6-7912-4101-b3be-d134245a0626,6e187f47-91a1-4217-a185-31b6f01db225 -7e7322a6-7912-4101-b3be-d134245a0626,62059efc-7607-41c9-a3ba-70948f6a8a1d -7e7322a6-7912-4101-b3be-d134245a0626,2d2e07ec-e697-4384-a679-867e93740921 -7e7322a6-7912-4101-b3be-d134245a0626,20fbcb6e-d793-4b05-8e29-8ff82572b0db -7e7322a6-7912-4101-b3be-d134245a0626,1fd714a6-48b4-425a-99b3-ba5c1a995cce -7ea43fc6-b1f7-46be-a308-5ecb275a1081,bac374c0-50e8-4a5c-947b-82a8e9a747a9 -7ea43fc6-b1f7-46be-a308-5ecb275a1081,8bc7af32-e5ad-4849-ba4d-b446db833ab4 -7ea43fc6-b1f7-46be-a308-5ecb275a1081,7ea43fc6-b1f7-46be-a308-5ecb275a1081 -7ea43fc6-b1f7-46be-a308-5ecb275a1081,2de1f829-c9d2-4f22-bf39-536dcb82fc3e -7ea43fc6-b1f7-46be-a308-5ecb275a1081,2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 -7ea43fc6-b1f7-46be-a308-5ecb275a1081,23780032-f3bb-4b40-af2e-3fa6b1677376 -7ea43fc6-b1f7-46be-a308-5ecb275a1081,21c6e0fc-bf47-4f80-b1ff-85b940445bdf -7ea43fc6-b1f7-46be-a308-5ecb275a1081,12b212d8-bc6e-415a-93b0-594381726668 -7f410064-638a-4477-85c4-97fc2bb14a49,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -7f410064-638a-4477-85c4-97fc2bb14a49,d90676d2-ce74-4867-a00f-6dbffa7c00be -7f410064-638a-4477-85c4-97fc2bb14a49,cc56af6d-25c6-480e-9767-da02a55551da -7f410064-638a-4477-85c4-97fc2bb14a49,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -7f410064-638a-4477-85c4-97fc2bb14a49,baf48725-f0f9-4357-a71d-b1104910deae -7f410064-638a-4477-85c4-97fc2bb14a49,9ed8703e-3b40-424c-9e98-b3b404051f99 -7f410064-638a-4477-85c4-97fc2bb14a49,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -7f410064-638a-4477-85c4-97fc2bb14a49,8be81657-8ba6-4ec5-8ff9-4809367096ea -7f410064-638a-4477-85c4-97fc2bb14a49,7f410064-638a-4477-85c4-97fc2bb14a49 -7f410064-638a-4477-85c4-97fc2bb14a49,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -7f410064-638a-4477-85c4-97fc2bb14a49,5cedf18a-fc44-4c39-a80f-2106a0d76934 -7f410064-638a-4477-85c4-97fc2bb14a49,4f342a71-dec3-403e-970b-e4c21b9eb98b -7f410064-638a-4477-85c4-97fc2bb14a49,4db144d3-a596-4718-a50a-8ac9175ad386 -7f410064-638a-4477-85c4-97fc2bb14a49,4215b5d0-bdd9-4222-a04a-81bb637d60af -7f410064-638a-4477-85c4-97fc2bb14a49,29d4e919-2bd2-44e6-bb8a-380a780f60ff -7f410064-638a-4477-85c4-97fc2bb14a49,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -7f410064-638a-4477-85c4-97fc2bb14a49,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -7f410064-638a-4477-85c4-97fc2bb14a49,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -7f410064-638a-4477-85c4-97fc2bb14a49,102e16c5-4920-4dad-b142-0b280b2aacad -7f410064-638a-4477-85c4-97fc2bb14a49,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -8089a9ac-9e71-4487-a231-f720e4bc8d3e,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -8089a9ac-9e71-4487-a231-f720e4bc8d3e,ee24bf89-81a3-4259-a382-86917f3829d4 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,e363eb67-64c7-440f-93fd-5c8787c69a85 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,db79f75d-af3c-4f82-b4e5-9b5d26598eef -8089a9ac-9e71-4487-a231-f720e4bc8d3e,d7a48a26-7731-4046-bf22-78f0b8084eb9 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,d080c116-681a-494a-a928-45924109b49d -8089a9ac-9e71-4487-a231-f720e4bc8d3e,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -8089a9ac-9e71-4487-a231-f720e4bc8d3e,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -8089a9ac-9e71-4487-a231-f720e4bc8d3e,990c21ab-433b-4920-873e-f9dfc7103d9d -8089a9ac-9e71-4487-a231-f720e4bc8d3e,922e2443-9cc5-421f-8310-9cd23f6e9f2d -8089a9ac-9e71-4487-a231-f720e4bc8d3e,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,8089a9ac-9e71-4487-a231-f720e4bc8d3e -8089a9ac-9e71-4487-a231-f720e4bc8d3e,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -8089a9ac-9e71-4487-a231-f720e4bc8d3e,6d47a2fe-3645-4c5c-bebe-1b822eff197f -8089a9ac-9e71-4487-a231-f720e4bc8d3e,67fc12fc-bb9f-40f1-9051-127a788b3081 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,634f0889-3a83-484a-bcc8-86f48e333c7b -8089a9ac-9e71-4487-a231-f720e4bc8d3e,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,4adcaf72-5241-4877-b61c-f34060576c50 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -8089a9ac-9e71-4487-a231-f720e4bc8d3e,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,374fd699-6b74-4500-9d60-2473c7e0364f -8089a9ac-9e71-4487-a231-f720e4bc8d3e,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,308eba53-29fa-489a-97dc-741b077841a7 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -8089a9ac-9e71-4487-a231-f720e4bc8d3e,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,21995497-0eb8-4c0b-8c23-e6a829f3d596 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,18ebf5ea-b0a2-4333-9e9c-40217de809ff -8089a9ac-9e71-4487-a231-f720e4bc8d3e,151e3048-66ad-4411-8753-677877e3bf0a -8089a9ac-9e71-4487-a231-f720e4bc8d3e,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -8089a9ac-9e71-4487-a231-f720e4bc8d3e,0af4a77e-892e-4b3f-9110-4c5224782250 -836b2e59-fb97-4014-ab0c-d5a5dc750969,fcc916dd-5e4f-4bf7-9aef-c906a36d7301 -836b2e59-fb97-4014-ab0c-d5a5dc750969,c57d51a7-45de-41b9-92fc-efd0634effaf -836b2e59-fb97-4014-ab0c-d5a5dc750969,ae7ddaef-4911-418c-8401-d978617e145b -836b2e59-fb97-4014-ab0c-d5a5dc750969,acb2329d-b1c3-45c3-ad28-91a09aa521e1 -836b2e59-fb97-4014-ab0c-d5a5dc750969,a89c0c42-c19f-4d7f-a15e-d04e043c92f6 -836b2e59-fb97-4014-ab0c-d5a5dc750969,836b2e59-fb97-4014-ab0c-d5a5dc750969 -836b2e59-fb97-4014-ab0c-d5a5dc750969,683714ad-5194-49e7-9b8f-4e306cf68ae1 -836b2e59-fb97-4014-ab0c-d5a5dc750969,5f64131b-d410-449a-9de6-35415919fec5 -836b2e59-fb97-4014-ab0c-d5a5dc750969,4f3d1e93-a28f-446e-91af-2baf0168b82b -836b2e59-fb97-4014-ab0c-d5a5dc750969,1db1c71b-9eeb-4a98-abc1-eb699b38510c -85481b3f-c75e-40cd-bacd-b0afb79e893c,bfc93cb7-e53a-49f7-b86e-81d6a13cfdea -85481b3f-c75e-40cd-bacd-b0afb79e893c,a6790cbd-8349-432e-a976-5dfc07451203 -85481b3f-c75e-40cd-bacd-b0afb79e893c,9b2eb632-6f1a-4e97-912b-3c8378a1b11c -85481b3f-c75e-40cd-bacd-b0afb79e893c,85c997bf-63d9-4ebb-8e0b-320de3dddc6c -85481b3f-c75e-40cd-bacd-b0afb79e893c,85481b3f-c75e-40cd-bacd-b0afb79e893c -85481b3f-c75e-40cd-bacd-b0afb79e893c,75a09d4f-eef2-4404-a386-dfcb408ec9ed -85481b3f-c75e-40cd-bacd-b0afb79e893c,30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 -85481b3f-c75e-40cd-bacd-b0afb79e893c,2ee2ab26-09fb-4e12-bf21-9fed3b4c38de -85c997bf-63d9-4ebb-8e0b-320de3dddc6c,bfc93cb7-e53a-49f7-b86e-81d6a13cfdea -85c997bf-63d9-4ebb-8e0b-320de3dddc6c,a6790cbd-8349-432e-a976-5dfc07451203 -85c997bf-63d9-4ebb-8e0b-320de3dddc6c,9b2eb632-6f1a-4e97-912b-3c8378a1b11c -85c997bf-63d9-4ebb-8e0b-320de3dddc6c,85c997bf-63d9-4ebb-8e0b-320de3dddc6c -85c997bf-63d9-4ebb-8e0b-320de3dddc6c,85481b3f-c75e-40cd-bacd-b0afb79e893c -85c997bf-63d9-4ebb-8e0b-320de3dddc6c,75a09d4f-eef2-4404-a386-dfcb408ec9ed -85c997bf-63d9-4ebb-8e0b-320de3dddc6c,30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 -85c997bf-63d9-4ebb-8e0b-320de3dddc6c,2ee2ab26-09fb-4e12-bf21-9fed3b4c38de -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,f883f2ca-d523-4c9f-86b2-0add137901de -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,f28931e5-1f35-4f9f-a02e-78398735ea67 -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,e7f68d35-ae55-4fa4-b0be-0672a5a7186a -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,7ac56d5a-32a7-4b40-a956-356e3006faed -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,72788e7f-1bf1-40b5-a1f1-27c669dc7278 -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,6c84348c-5065-4e89-9412-bfda023683f2 -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,5abe533b-ae5f-47ad-8746-f60caf7483c0 -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,4f14ca7f-5332-4263-a7b2-f0a838380309 -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,359cb842-c25b-429f-ba9b-d52f171e5631 -86415df5-7e47-4637-9e09-aaad9e7628d1,a2c3ee1d-ec35-4b26-9bab-12de3f47d604 -86415df5-7e47-4637-9e09-aaad9e7628d1,86415df5-7e47-4637-9e09-aaad9e7628d1 -86415df5-7e47-4637-9e09-aaad9e7628d1,749babeb-6883-4bd4-92a5-bec52769071c -86415df5-7e47-4637-9e09-aaad9e7628d1,578cc35c-0982-4d72-a729-b304c026f075 -86415df5-7e47-4637-9e09-aaad9e7628d1,0250a217-a663-403b-a708-5d14eadf0c40 -8743e69b-17aa-44ff-b8fa-4f582665efc6,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -8743e69b-17aa-44ff-b8fa-4f582665efc6,e8eba267-fecb-477e-9625-771fbcfc3cba -8743e69b-17aa-44ff-b8fa-4f582665efc6,e8c41168-a093-40eb-8baa-6802d24f554a -8743e69b-17aa-44ff-b8fa-4f582665efc6,e26ae29a-213a-4f79-98c2-cc81cf2f451d -8743e69b-17aa-44ff-b8fa-4f582665efc6,e0dfbc08-45ad-4a81-b648-7e65c066f673 -8743e69b-17aa-44ff-b8fa-4f582665efc6,d535b213-2abc-438f-aa6a-c06476d60b09 -8743e69b-17aa-44ff-b8fa-4f582665efc6,c892b1d7-7485-407d-8883-54fb7fecebc1 -8743e69b-17aa-44ff-b8fa-4f582665efc6,b6900c21-d310-4fb4-8b8f-176a09c91989 -8743e69b-17aa-44ff-b8fa-4f582665efc6,b00df815-7528-4967-bfe2-311130d91c21 -8743e69b-17aa-44ff-b8fa-4f582665efc6,add6d754-a075-4693-a33a-c061c9a368ff -8743e69b-17aa-44ff-b8fa-4f582665efc6,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -8743e69b-17aa-44ff-b8fa-4f582665efc6,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -8743e69b-17aa-44ff-b8fa-4f582665efc6,8a1908f7-47cc-4f82-859c-781a193e9901 -8743e69b-17aa-44ff-b8fa-4f582665efc6,8743e69b-17aa-44ff-b8fa-4f582665efc6 -8743e69b-17aa-44ff-b8fa-4f582665efc6,721e4027-a080-40d8-bc4a-43cd33477611 -8743e69b-17aa-44ff-b8fa-4f582665efc6,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -8743e69b-17aa-44ff-b8fa-4f582665efc6,54c750aa-570a-4e8e-9a02-418781923e39 -8743e69b-17aa-44ff-b8fa-4f582665efc6,50eac25a-3761-4de3-8a69-aae52e658300 -8743e69b-17aa-44ff-b8fa-4f582665efc6,4a464bb5-9373-4f1b-9c03-053c64797114 -8743e69b-17aa-44ff-b8fa-4f582665efc6,4506aa76-9d0d-40e4-85bc-5735f74522a0 -8743e69b-17aa-44ff-b8fa-4f582665efc6,42a9a333-d09d-4b9e-af96-1f02af26bac3 -8743e69b-17aa-44ff-b8fa-4f582665efc6,2fc75f11-2097-4e1e-8390-dbff3e844b7a -8743e69b-17aa-44ff-b8fa-4f582665efc6,274a2e49-9170-4945-bca2-ab6ef4bded75 -8743e69b-17aa-44ff-b8fa-4f582665efc6,0364073f-91d8-47a1-b8b0-107c6318a691 -87b04a97-1d33-4548-aec9-3d2856070702,c3ec1f2e-1411-41e4-8370-acd5a8f68cf4 -87b04a97-1d33-4548-aec9-3d2856070702,87b04a97-1d33-4548-aec9-3d2856070702 -87b04a97-1d33-4548-aec9-3d2856070702,2bddd98c-86e3-4ae0-9dc6-87da16ab6267 -88b16960-bfff-41d0-81e4-9a4630834a00,fce20464-ff98-4051-8714-6b9584e52740 -88b16960-bfff-41d0-81e4-9a4630834a00,f3eb0c38-2571-44e7-be39-732eb52cf1df -88b16960-bfff-41d0-81e4-9a4630834a00,f3c2f842-6aa3-42c9-a0f3-895c40456868 -88b16960-bfff-41d0-81e4-9a4630834a00,ede7745a-8f3e-4e20-9f16-33756be7ab6c -88b16960-bfff-41d0-81e4-9a4630834a00,ca3e0486-fd6b-4a13-a741-3a47760b412d -88b16960-bfff-41d0-81e4-9a4630834a00,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -88b16960-bfff-41d0-81e4-9a4630834a00,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -88b16960-bfff-41d0-81e4-9a4630834a00,a221198d-000e-497b-8b70-bb4d37f7bbe1 -88b16960-bfff-41d0-81e4-9a4630834a00,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -88b16960-bfff-41d0-81e4-9a4630834a00,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -88b16960-bfff-41d0-81e4-9a4630834a00,88b16960-bfff-41d0-81e4-9a4630834a00 -88b16960-bfff-41d0-81e4-9a4630834a00,522e2abe-ad96-4d1a-b5a5-748faa997531 -88b16960-bfff-41d0-81e4-9a4630834a00,4cfe72fe-21a0-4201-87b6-ef93695d2495 -88b16960-bfff-41d0-81e4-9a4630834a00,498f4b18-bd4c-470d-9cde-2fca70ec8964 -88b16960-bfff-41d0-81e4-9a4630834a00,202131ae-78bb-4104-89b0-fb90645760f6 -88b16960-bfff-41d0-81e4-9a4630834a00,11dafd5c-85e7-4275-a147-89a63641e35e -88b16960-bfff-41d0-81e4-9a4630834a00,02b993c0-b358-481c-b0d0-0767387feb9f -89f260bb-68b4-4dec-91f4-d52e673e0ff7,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -89f260bb-68b4-4dec-91f4-d52e673e0ff7,ee24bf89-81a3-4259-a382-86917f3829d4 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,e363eb67-64c7-440f-93fd-5c8787c69a85 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,db79f75d-af3c-4f82-b4e5-9b5d26598eef -89f260bb-68b4-4dec-91f4-d52e673e0ff7,d7a48a26-7731-4046-bf22-78f0b8084eb9 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,d080c116-681a-494a-a928-45924109b49d -89f260bb-68b4-4dec-91f4-d52e673e0ff7,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -89f260bb-68b4-4dec-91f4-d52e673e0ff7,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -89f260bb-68b4-4dec-91f4-d52e673e0ff7,990c21ab-433b-4920-873e-f9dfc7103d9d -89f260bb-68b4-4dec-91f4-d52e673e0ff7,922e2443-9cc5-421f-8310-9cd23f6e9f2d -89f260bb-68b4-4dec-91f4-d52e673e0ff7,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,8089a9ac-9e71-4487-a231-f720e4bc8d3e -89f260bb-68b4-4dec-91f4-d52e673e0ff7,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -89f260bb-68b4-4dec-91f4-d52e673e0ff7,6d47a2fe-3645-4c5c-bebe-1b822eff197f -89f260bb-68b4-4dec-91f4-d52e673e0ff7,67fc12fc-bb9f-40f1-9051-127a788b3081 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,634f0889-3a83-484a-bcc8-86f48e333c7b -89f260bb-68b4-4dec-91f4-d52e673e0ff7,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,4adcaf72-5241-4877-b61c-f34060576c50 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -89f260bb-68b4-4dec-91f4-d52e673e0ff7,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,374fd699-6b74-4500-9d60-2473c7e0364f -89f260bb-68b4-4dec-91f4-d52e673e0ff7,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,308eba53-29fa-489a-97dc-741b077841a7 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -89f260bb-68b4-4dec-91f4-d52e673e0ff7,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,21995497-0eb8-4c0b-8c23-e6a829f3d596 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,18ebf5ea-b0a2-4333-9e9c-40217de809ff -89f260bb-68b4-4dec-91f4-d52e673e0ff7,151e3048-66ad-4411-8753-677877e3bf0a -89f260bb-68b4-4dec-91f4-d52e673e0ff7,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -89f260bb-68b4-4dec-91f4-d52e673e0ff7,0af4a77e-892e-4b3f-9110-4c5224782250 -8a1908f7-47cc-4f82-859c-781a193e9901,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -8a1908f7-47cc-4f82-859c-781a193e9901,e8eba267-fecb-477e-9625-771fbcfc3cba -8a1908f7-47cc-4f82-859c-781a193e9901,e8c41168-a093-40eb-8baa-6802d24f554a -8a1908f7-47cc-4f82-859c-781a193e9901,e26ae29a-213a-4f79-98c2-cc81cf2f451d -8a1908f7-47cc-4f82-859c-781a193e9901,e0dfbc08-45ad-4a81-b648-7e65c066f673 -8a1908f7-47cc-4f82-859c-781a193e9901,d535b213-2abc-438f-aa6a-c06476d60b09 -8a1908f7-47cc-4f82-859c-781a193e9901,c892b1d7-7485-407d-8883-54fb7fecebc1 -8a1908f7-47cc-4f82-859c-781a193e9901,b6900c21-d310-4fb4-8b8f-176a09c91989 -8a1908f7-47cc-4f82-859c-781a193e9901,b00df815-7528-4967-bfe2-311130d91c21 -8a1908f7-47cc-4f82-859c-781a193e9901,add6d754-a075-4693-a33a-c061c9a368ff -8a1908f7-47cc-4f82-859c-781a193e9901,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -8a1908f7-47cc-4f82-859c-781a193e9901,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -8a1908f7-47cc-4f82-859c-781a193e9901,8a1908f7-47cc-4f82-859c-781a193e9901 -8a1908f7-47cc-4f82-859c-781a193e9901,8743e69b-17aa-44ff-b8fa-4f582665efc6 -8a1908f7-47cc-4f82-859c-781a193e9901,721e4027-a080-40d8-bc4a-43cd33477611 -8a1908f7-47cc-4f82-859c-781a193e9901,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -8a1908f7-47cc-4f82-859c-781a193e9901,54c750aa-570a-4e8e-9a02-418781923e39 -8a1908f7-47cc-4f82-859c-781a193e9901,50eac25a-3761-4de3-8a69-aae52e658300 -8a1908f7-47cc-4f82-859c-781a193e9901,4a464bb5-9373-4f1b-9c03-053c64797114 -8a1908f7-47cc-4f82-859c-781a193e9901,4506aa76-9d0d-40e4-85bc-5735f74522a0 -8a1908f7-47cc-4f82-859c-781a193e9901,42a9a333-d09d-4b9e-af96-1f02af26bac3 -8a1908f7-47cc-4f82-859c-781a193e9901,2fc75f11-2097-4e1e-8390-dbff3e844b7a -8a1908f7-47cc-4f82-859c-781a193e9901,274a2e49-9170-4945-bca2-ab6ef4bded75 -8a1908f7-47cc-4f82-859c-781a193e9901,0364073f-91d8-47a1-b8b0-107c6318a691 -8bc7af32-e5ad-4849-ba4d-b446db833ab4,bac374c0-50e8-4a5c-947b-82a8e9a747a9 -8bc7af32-e5ad-4849-ba4d-b446db833ab4,8bc7af32-e5ad-4849-ba4d-b446db833ab4 -8bc7af32-e5ad-4849-ba4d-b446db833ab4,7ea43fc6-b1f7-46be-a308-5ecb275a1081 -8bc7af32-e5ad-4849-ba4d-b446db833ab4,2de1f829-c9d2-4f22-bf39-536dcb82fc3e -8bc7af32-e5ad-4849-ba4d-b446db833ab4,2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 -8bc7af32-e5ad-4849-ba4d-b446db833ab4,23780032-f3bb-4b40-af2e-3fa6b1677376 -8bc7af32-e5ad-4849-ba4d-b446db833ab4,21c6e0fc-bf47-4f80-b1ff-85b940445bdf -8bc7af32-e5ad-4849-ba4d-b446db833ab4,12b212d8-bc6e-415a-93b0-594381726668 -8be81657-8ba6-4ec5-8ff9-4809367096ea,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -8be81657-8ba6-4ec5-8ff9-4809367096ea,d90676d2-ce74-4867-a00f-6dbffa7c00be -8be81657-8ba6-4ec5-8ff9-4809367096ea,cc56af6d-25c6-480e-9767-da02a55551da -8be81657-8ba6-4ec5-8ff9-4809367096ea,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -8be81657-8ba6-4ec5-8ff9-4809367096ea,baf48725-f0f9-4357-a71d-b1104910deae -8be81657-8ba6-4ec5-8ff9-4809367096ea,9ed8703e-3b40-424c-9e98-b3b404051f99 -8be81657-8ba6-4ec5-8ff9-4809367096ea,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -8be81657-8ba6-4ec5-8ff9-4809367096ea,8be81657-8ba6-4ec5-8ff9-4809367096ea -8be81657-8ba6-4ec5-8ff9-4809367096ea,7f410064-638a-4477-85c4-97fc2bb14a49 -8be81657-8ba6-4ec5-8ff9-4809367096ea,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -8be81657-8ba6-4ec5-8ff9-4809367096ea,5cedf18a-fc44-4c39-a80f-2106a0d76934 -8be81657-8ba6-4ec5-8ff9-4809367096ea,4f342a71-dec3-403e-970b-e4c21b9eb98b -8be81657-8ba6-4ec5-8ff9-4809367096ea,4db144d3-a596-4718-a50a-8ac9175ad386 -8be81657-8ba6-4ec5-8ff9-4809367096ea,4215b5d0-bdd9-4222-a04a-81bb637d60af -8be81657-8ba6-4ec5-8ff9-4809367096ea,29d4e919-2bd2-44e6-bb8a-380a780f60ff -8be81657-8ba6-4ec5-8ff9-4809367096ea,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -8be81657-8ba6-4ec5-8ff9-4809367096ea,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -8be81657-8ba6-4ec5-8ff9-4809367096ea,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -8be81657-8ba6-4ec5-8ff9-4809367096ea,102e16c5-4920-4dad-b142-0b280b2aacad -8be81657-8ba6-4ec5-8ff9-4809367096ea,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -8dc2ce26-aec7-43c0-9771-350af4257ad8,f19eefca-c1ad-4860-bdda-4674a631d464 -8dc2ce26-aec7-43c0-9771-350af4257ad8,f18b08bd-40da-43c1-87ec-4ba23542635f -8dc2ce26-aec7-43c0-9771-350af4257ad8,b1ccd55a-6b88-4240-b20c-ca893f36e23b -8dc2ce26-aec7-43c0-9771-350af4257ad8,900ce9fe-02d3-476b-902a-9467767ecdcf -8dc2ce26-aec7-43c0-9771-350af4257ad8,8dc2ce26-aec7-43c0-9771-350af4257ad8 -8dc2ce26-aec7-43c0-9771-350af4257ad8,1f3443ee-d15e-4894-b18e-185cfadfcdea -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,fce20464-ff98-4051-8714-6b9584e52740 -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,f3eb0c38-2571-44e7-be39-732eb52cf1df -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,f3c2f842-6aa3-42c9-a0f3-895c40456868 -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,ede7745a-8f3e-4e20-9f16-33756be7ab6c -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,ca3e0486-fd6b-4a13-a741-3a47760b412d -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,a221198d-000e-497b-8b70-bb4d37f7bbe1 -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,88b16960-bfff-41d0-81e4-9a4630834a00 -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,522e2abe-ad96-4d1a-b5a5-748faa997531 -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,4cfe72fe-21a0-4201-87b6-ef93695d2495 -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,498f4b18-bd4c-470d-9cde-2fca70ec8964 -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,202131ae-78bb-4104-89b0-fb90645760f6 -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,11dafd5c-85e7-4275-a147-89a63641e35e -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,02b993c0-b358-481c-b0d0-0767387feb9f -8f76d729-9f74-4cfd-be2a-8b033b568e18,fcab9efe-fb05-42d6-b366-ce6db1cc4093 -8f76d729-9f74-4cfd-be2a-8b033b568e18,f1293c5e-c997-4ba9-8bfd-438e7a840dc8 -8f76d729-9f74-4cfd-be2a-8b033b568e18,e4358f28-62a8-4e06-b2bd-cb00d3310349 -8f76d729-9f74-4cfd-be2a-8b033b568e18,8f76d729-9f74-4cfd-be2a-8b033b568e18 -8f76d729-9f74-4cfd-be2a-8b033b568e18,39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 -8f76d729-9f74-4cfd-be2a-8b033b568e18,31d4c712-75b1-4ecb-9d1a-ccc4b8453826 -8f76d729-9f74-4cfd-be2a-8b033b568e18,24c4dd5b-748b-4165-a0cc-2867b76c2dc2 -8f76d729-9f74-4cfd-be2a-8b033b568e18,16d280a2-c61f-487f-9034-22e884158969 -8f76d729-9f74-4cfd-be2a-8b033b568e18,0134901a-4a69-4b39-92ea-d6f48ec83c6e -900ce9fe-02d3-476b-902a-9467767ecdcf,f19eefca-c1ad-4860-bdda-4674a631d464 -900ce9fe-02d3-476b-902a-9467767ecdcf,f18b08bd-40da-43c1-87ec-4ba23542635f -900ce9fe-02d3-476b-902a-9467767ecdcf,b1ccd55a-6b88-4240-b20c-ca893f36e23b -900ce9fe-02d3-476b-902a-9467767ecdcf,900ce9fe-02d3-476b-902a-9467767ecdcf -900ce9fe-02d3-476b-902a-9467767ecdcf,8dc2ce26-aec7-43c0-9771-350af4257ad8 -900ce9fe-02d3-476b-902a-9467767ecdcf,1f3443ee-d15e-4894-b18e-185cfadfcdea -922e2443-9cc5-421f-8310-9cd23f6e9f2d,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -922e2443-9cc5-421f-8310-9cd23f6e9f2d,ee24bf89-81a3-4259-a382-86917f3829d4 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,e363eb67-64c7-440f-93fd-5c8787c69a85 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,db79f75d-af3c-4f82-b4e5-9b5d26598eef -922e2443-9cc5-421f-8310-9cd23f6e9f2d,d7a48a26-7731-4046-bf22-78f0b8084eb9 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,d080c116-681a-494a-a928-45924109b49d -922e2443-9cc5-421f-8310-9cd23f6e9f2d,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -922e2443-9cc5-421f-8310-9cd23f6e9f2d,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -922e2443-9cc5-421f-8310-9cd23f6e9f2d,990c21ab-433b-4920-873e-f9dfc7103d9d -922e2443-9cc5-421f-8310-9cd23f6e9f2d,922e2443-9cc5-421f-8310-9cd23f6e9f2d -922e2443-9cc5-421f-8310-9cd23f6e9f2d,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,8089a9ac-9e71-4487-a231-f720e4bc8d3e -922e2443-9cc5-421f-8310-9cd23f6e9f2d,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -922e2443-9cc5-421f-8310-9cd23f6e9f2d,6d47a2fe-3645-4c5c-bebe-1b822eff197f -922e2443-9cc5-421f-8310-9cd23f6e9f2d,67fc12fc-bb9f-40f1-9051-127a788b3081 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,634f0889-3a83-484a-bcc8-86f48e333c7b -922e2443-9cc5-421f-8310-9cd23f6e9f2d,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,4adcaf72-5241-4877-b61c-f34060576c50 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -922e2443-9cc5-421f-8310-9cd23f6e9f2d,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,374fd699-6b74-4500-9d60-2473c7e0364f -922e2443-9cc5-421f-8310-9cd23f6e9f2d,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,308eba53-29fa-489a-97dc-741b077841a7 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -922e2443-9cc5-421f-8310-9cd23f6e9f2d,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,21995497-0eb8-4c0b-8c23-e6a829f3d596 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,18ebf5ea-b0a2-4333-9e9c-40217de809ff -922e2443-9cc5-421f-8310-9cd23f6e9f2d,151e3048-66ad-4411-8753-677877e3bf0a -922e2443-9cc5-421f-8310-9cd23f6e9f2d,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -922e2443-9cc5-421f-8310-9cd23f6e9f2d,0af4a77e-892e-4b3f-9110-4c5224782250 -96b2282d-1384-4cfb-9958-f009fb501626,def5fd23-2b74-4c64-85e9-fac27e626bb7 -96b2282d-1384-4cfb-9958-f009fb501626,96b2282d-1384-4cfb-9958-f009fb501626 -96b2282d-1384-4cfb-9958-f009fb501626,17bcf2ea-d921-4715-91c5-6b15226b33d3 -97b42d67-8691-433d-8a31-dada076162ae,f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 -97b42d67-8691-433d-8a31-dada076162ae,f3aa491a-4dbd-4436-b674-18b2177dcf18 -97b42d67-8691-433d-8a31-dada076162ae,e1040d58-53bc-4467-8101-ca53b772cede -97b42d67-8691-433d-8a31-dada076162ae,d05461d6-13bf-402c-890d-db6404933f7c -97b42d67-8691-433d-8a31-dada076162ae,b7772635-1801-48e4-a442-f4aaa6860544 -97b42d67-8691-433d-8a31-dada076162ae,b07fb98d-2da4-423a-83b4-7a673de93096 -97b42d67-8691-433d-8a31-dada076162ae,97b42d67-8691-433d-8a31-dada076162ae -97b42d67-8691-433d-8a31-dada076162ae,720f7ed6-7b2e-41dc-a0e0-724a3332aa24 -97b42d67-8691-433d-8a31-dada076162ae,4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 -97b42d67-8691-433d-8a31-dada076162ae,2ffd6142-b75b-406f-bc06-cdb1c02eaefc -97b42d67-8691-433d-8a31-dada076162ae,224e538d-3622-4f5e-b772-211ed358d8de -97b42d67-8691-433d-8a31-dada076162ae,16a3408d-c927-4d02-a1ae-cad32419caab -97b42d67-8691-433d-8a31-dada076162ae,04d88eb4-fb1b-4fa1-802a-5624f4c61b32 -984067fc-3dfc-47b7-8ee0-4d9b27d43860,984067fc-3dfc-47b7-8ee0-4d9b27d43860 -984067fc-3dfc-47b7-8ee0-4d9b27d43860,55f17f44-287f-41aa-a1bc-d1c0104af36e -984067fc-3dfc-47b7-8ee0-4d9b27d43860,2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5 -990c21ab-433b-4920-873e-f9dfc7103d9d,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -990c21ab-433b-4920-873e-f9dfc7103d9d,ee24bf89-81a3-4259-a382-86917f3829d4 -990c21ab-433b-4920-873e-f9dfc7103d9d,e363eb67-64c7-440f-93fd-5c8787c69a85 -990c21ab-433b-4920-873e-f9dfc7103d9d,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -990c21ab-433b-4920-873e-f9dfc7103d9d,db79f75d-af3c-4f82-b4e5-9b5d26598eef -990c21ab-433b-4920-873e-f9dfc7103d9d,d7a48a26-7731-4046-bf22-78f0b8084eb9 -990c21ab-433b-4920-873e-f9dfc7103d9d,d080c116-681a-494a-a928-45924109b49d -990c21ab-433b-4920-873e-f9dfc7103d9d,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -990c21ab-433b-4920-873e-f9dfc7103d9d,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -990c21ab-433b-4920-873e-f9dfc7103d9d,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -990c21ab-433b-4920-873e-f9dfc7103d9d,990c21ab-433b-4920-873e-f9dfc7103d9d -990c21ab-433b-4920-873e-f9dfc7103d9d,922e2443-9cc5-421f-8310-9cd23f6e9f2d -990c21ab-433b-4920-873e-f9dfc7103d9d,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -990c21ab-433b-4920-873e-f9dfc7103d9d,8089a9ac-9e71-4487-a231-f720e4bc8d3e -990c21ab-433b-4920-873e-f9dfc7103d9d,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -990c21ab-433b-4920-873e-f9dfc7103d9d,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -990c21ab-433b-4920-873e-f9dfc7103d9d,6d47a2fe-3645-4c5c-bebe-1b822eff197f -990c21ab-433b-4920-873e-f9dfc7103d9d,67fc12fc-bb9f-40f1-9051-127a788b3081 -990c21ab-433b-4920-873e-f9dfc7103d9d,634f0889-3a83-484a-bcc8-86f48e333c7b -990c21ab-433b-4920-873e-f9dfc7103d9d,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -990c21ab-433b-4920-873e-f9dfc7103d9d,4adcaf72-5241-4877-b61c-f34060576c50 -990c21ab-433b-4920-873e-f9dfc7103d9d,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -990c21ab-433b-4920-873e-f9dfc7103d9d,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -990c21ab-433b-4920-873e-f9dfc7103d9d,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -990c21ab-433b-4920-873e-f9dfc7103d9d,374fd699-6b74-4500-9d60-2473c7e0364f -990c21ab-433b-4920-873e-f9dfc7103d9d,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -990c21ab-433b-4920-873e-f9dfc7103d9d,308eba53-29fa-489a-97dc-741b077841a7 -990c21ab-433b-4920-873e-f9dfc7103d9d,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -990c21ab-433b-4920-873e-f9dfc7103d9d,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -990c21ab-433b-4920-873e-f9dfc7103d9d,21995497-0eb8-4c0b-8c23-e6a829f3d596 -990c21ab-433b-4920-873e-f9dfc7103d9d,18ebf5ea-b0a2-4333-9e9c-40217de809ff -990c21ab-433b-4920-873e-f9dfc7103d9d,151e3048-66ad-4411-8753-677877e3bf0a -990c21ab-433b-4920-873e-f9dfc7103d9d,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -990c21ab-433b-4920-873e-f9dfc7103d9d,0af4a77e-892e-4b3f-9110-4c5224782250 -99fc3558-79f3-4d14-9aa1-ff63f621ecd9,99fc3558-79f3-4d14-9aa1-ff63f621ecd9 -99fc3558-79f3-4d14-9aa1-ff63f621ecd9,2aff9edd-def2-487a-b435-a162e11a303c -9b2eb632-6f1a-4e97-912b-3c8378a1b11c,bfc93cb7-e53a-49f7-b86e-81d6a13cfdea -9b2eb632-6f1a-4e97-912b-3c8378a1b11c,a6790cbd-8349-432e-a976-5dfc07451203 -9b2eb632-6f1a-4e97-912b-3c8378a1b11c,9b2eb632-6f1a-4e97-912b-3c8378a1b11c -9b2eb632-6f1a-4e97-912b-3c8378a1b11c,85c997bf-63d9-4ebb-8e0b-320de3dddc6c -9b2eb632-6f1a-4e97-912b-3c8378a1b11c,85481b3f-c75e-40cd-bacd-b0afb79e893c -9b2eb632-6f1a-4e97-912b-3c8378a1b11c,75a09d4f-eef2-4404-a386-dfcb408ec9ed -9b2eb632-6f1a-4e97-912b-3c8378a1b11c,30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 -9b2eb632-6f1a-4e97-912b-3c8378a1b11c,2ee2ab26-09fb-4e12-bf21-9fed3b4c38de -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,d90676d2-ce74-4867-a00f-6dbffa7c00be -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,cc56af6d-25c6-480e-9767-da02a55551da -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,baf48725-f0f9-4357-a71d-b1104910deae -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,9ed8703e-3b40-424c-9e98-b3b404051f99 -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,8be81657-8ba6-4ec5-8ff9-4809367096ea -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,7f410064-638a-4477-85c4-97fc2bb14a49 -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,5cedf18a-fc44-4c39-a80f-2106a0d76934 -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,4f342a71-dec3-403e-970b-e4c21b9eb98b -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,4db144d3-a596-4718-a50a-8ac9175ad386 -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,4215b5d0-bdd9-4222-a04a-81bb637d60af -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,29d4e919-2bd2-44e6-bb8a-380a780f60ff -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,102e16c5-4920-4dad-b142-0b280b2aacad -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -9bc516d4-7c82-4340-a90c-bb493f96fbe4,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -9bc516d4-7c82-4340-a90c-bb493f96fbe4,e8eba267-fecb-477e-9625-771fbcfc3cba -9bc516d4-7c82-4340-a90c-bb493f96fbe4,e8c41168-a093-40eb-8baa-6802d24f554a -9bc516d4-7c82-4340-a90c-bb493f96fbe4,e26ae29a-213a-4f79-98c2-cc81cf2f451d -9bc516d4-7c82-4340-a90c-bb493f96fbe4,e0dfbc08-45ad-4a81-b648-7e65c066f673 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,d535b213-2abc-438f-aa6a-c06476d60b09 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,c892b1d7-7485-407d-8883-54fb7fecebc1 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,b6900c21-d310-4fb4-8b8f-176a09c91989 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,b00df815-7528-4967-bfe2-311130d91c21 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,add6d754-a075-4693-a33a-c061c9a368ff -9bc516d4-7c82-4340-a90c-bb493f96fbe4,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,8a1908f7-47cc-4f82-859c-781a193e9901 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,8743e69b-17aa-44ff-b8fa-4f582665efc6 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,721e4027-a080-40d8-bc4a-43cd33477611 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,54c750aa-570a-4e8e-9a02-418781923e39 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,50eac25a-3761-4de3-8a69-aae52e658300 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,4a464bb5-9373-4f1b-9c03-053c64797114 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,4506aa76-9d0d-40e4-85bc-5735f74522a0 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,42a9a333-d09d-4b9e-af96-1f02af26bac3 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,2fc75f11-2097-4e1e-8390-dbff3e844b7a -9bc516d4-7c82-4340-a90c-bb493f96fbe4,274a2e49-9170-4945-bca2-ab6ef4bded75 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,0364073f-91d8-47a1-b8b0-107c6318a691 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,e8eba267-fecb-477e-9625-771fbcfc3cba -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,e8c41168-a093-40eb-8baa-6802d24f554a -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,e26ae29a-213a-4f79-98c2-cc81cf2f451d -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,e0dfbc08-45ad-4a81-b648-7e65c066f673 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,d535b213-2abc-438f-aa6a-c06476d60b09 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,c892b1d7-7485-407d-8883-54fb7fecebc1 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,b6900c21-d310-4fb4-8b8f-176a09c91989 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,b00df815-7528-4967-bfe2-311130d91c21 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,add6d754-a075-4693-a33a-c061c9a368ff -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,8a1908f7-47cc-4f82-859c-781a193e9901 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,8743e69b-17aa-44ff-b8fa-4f582665efc6 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,721e4027-a080-40d8-bc4a-43cd33477611 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,54c750aa-570a-4e8e-9a02-418781923e39 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,50eac25a-3761-4de3-8a69-aae52e658300 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,4a464bb5-9373-4f1b-9c03-053c64797114 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,4506aa76-9d0d-40e4-85bc-5735f74522a0 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,42a9a333-d09d-4b9e-af96-1f02af26bac3 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,2fc75f11-2097-4e1e-8390-dbff3e844b7a -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,274a2e49-9170-4945-bca2-ab6ef4bded75 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,0364073f-91d8-47a1-b8b0-107c6318a691 -9ed8703e-3b40-424c-9e98-b3b404051f99,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -9ed8703e-3b40-424c-9e98-b3b404051f99,d90676d2-ce74-4867-a00f-6dbffa7c00be -9ed8703e-3b40-424c-9e98-b3b404051f99,cc56af6d-25c6-480e-9767-da02a55551da -9ed8703e-3b40-424c-9e98-b3b404051f99,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -9ed8703e-3b40-424c-9e98-b3b404051f99,baf48725-f0f9-4357-a71d-b1104910deae -9ed8703e-3b40-424c-9e98-b3b404051f99,9ed8703e-3b40-424c-9e98-b3b404051f99 -9ed8703e-3b40-424c-9e98-b3b404051f99,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -9ed8703e-3b40-424c-9e98-b3b404051f99,8be81657-8ba6-4ec5-8ff9-4809367096ea -9ed8703e-3b40-424c-9e98-b3b404051f99,7f410064-638a-4477-85c4-97fc2bb14a49 -9ed8703e-3b40-424c-9e98-b3b404051f99,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -9ed8703e-3b40-424c-9e98-b3b404051f99,5cedf18a-fc44-4c39-a80f-2106a0d76934 -9ed8703e-3b40-424c-9e98-b3b404051f99,4f342a71-dec3-403e-970b-e4c21b9eb98b -9ed8703e-3b40-424c-9e98-b3b404051f99,4db144d3-a596-4718-a50a-8ac9175ad386 -9ed8703e-3b40-424c-9e98-b3b404051f99,4215b5d0-bdd9-4222-a04a-81bb637d60af -9ed8703e-3b40-424c-9e98-b3b404051f99,29d4e919-2bd2-44e6-bb8a-380a780f60ff -9ed8703e-3b40-424c-9e98-b3b404051f99,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -9ed8703e-3b40-424c-9e98-b3b404051f99,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -9ed8703e-3b40-424c-9e98-b3b404051f99,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -9ed8703e-3b40-424c-9e98-b3b404051f99,102e16c5-4920-4dad-b142-0b280b2aacad -9ed8703e-3b40-424c-9e98-b3b404051f99,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -a1228f20-7c50-4d3a-b88c-2d277ca79d79,fce20464-ff98-4051-8714-6b9584e52740 -a1228f20-7c50-4d3a-b88c-2d277ca79d79,f3eb0c38-2571-44e7-be39-732eb52cf1df -a1228f20-7c50-4d3a-b88c-2d277ca79d79,f3c2f842-6aa3-42c9-a0f3-895c40456868 -a1228f20-7c50-4d3a-b88c-2d277ca79d79,ede7745a-8f3e-4e20-9f16-33756be7ab6c -a1228f20-7c50-4d3a-b88c-2d277ca79d79,ca3e0486-fd6b-4a13-a741-3a47760b412d -a1228f20-7c50-4d3a-b88c-2d277ca79d79,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -a1228f20-7c50-4d3a-b88c-2d277ca79d79,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -a1228f20-7c50-4d3a-b88c-2d277ca79d79,a221198d-000e-497b-8b70-bb4d37f7bbe1 -a1228f20-7c50-4d3a-b88c-2d277ca79d79,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -a1228f20-7c50-4d3a-b88c-2d277ca79d79,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -a1228f20-7c50-4d3a-b88c-2d277ca79d79,88b16960-bfff-41d0-81e4-9a4630834a00 -a1228f20-7c50-4d3a-b88c-2d277ca79d79,522e2abe-ad96-4d1a-b5a5-748faa997531 -a1228f20-7c50-4d3a-b88c-2d277ca79d79,4cfe72fe-21a0-4201-87b6-ef93695d2495 -a1228f20-7c50-4d3a-b88c-2d277ca79d79,498f4b18-bd4c-470d-9cde-2fca70ec8964 -a1228f20-7c50-4d3a-b88c-2d277ca79d79,202131ae-78bb-4104-89b0-fb90645760f6 -a1228f20-7c50-4d3a-b88c-2d277ca79d79,11dafd5c-85e7-4275-a147-89a63641e35e -a1228f20-7c50-4d3a-b88c-2d277ca79d79,02b993c0-b358-481c-b0d0-0767387feb9f -a221198d-000e-497b-8b70-bb4d37f7bbe1,fce20464-ff98-4051-8714-6b9584e52740 -a221198d-000e-497b-8b70-bb4d37f7bbe1,f3eb0c38-2571-44e7-be39-732eb52cf1df -a221198d-000e-497b-8b70-bb4d37f7bbe1,f3c2f842-6aa3-42c9-a0f3-895c40456868 -a221198d-000e-497b-8b70-bb4d37f7bbe1,ede7745a-8f3e-4e20-9f16-33756be7ab6c -a221198d-000e-497b-8b70-bb4d37f7bbe1,ca3e0486-fd6b-4a13-a741-3a47760b412d -a221198d-000e-497b-8b70-bb4d37f7bbe1,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -a221198d-000e-497b-8b70-bb4d37f7bbe1,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -a221198d-000e-497b-8b70-bb4d37f7bbe1,a221198d-000e-497b-8b70-bb4d37f7bbe1 -a221198d-000e-497b-8b70-bb4d37f7bbe1,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -a221198d-000e-497b-8b70-bb4d37f7bbe1,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -a221198d-000e-497b-8b70-bb4d37f7bbe1,88b16960-bfff-41d0-81e4-9a4630834a00 -a221198d-000e-497b-8b70-bb4d37f7bbe1,522e2abe-ad96-4d1a-b5a5-748faa997531 -a221198d-000e-497b-8b70-bb4d37f7bbe1,4cfe72fe-21a0-4201-87b6-ef93695d2495 -a221198d-000e-497b-8b70-bb4d37f7bbe1,498f4b18-bd4c-470d-9cde-2fca70ec8964 -a221198d-000e-497b-8b70-bb4d37f7bbe1,202131ae-78bb-4104-89b0-fb90645760f6 -a221198d-000e-497b-8b70-bb4d37f7bbe1,11dafd5c-85e7-4275-a147-89a63641e35e -a221198d-000e-497b-8b70-bb4d37f7bbe1,02b993c0-b358-481c-b0d0-0767387feb9f -a2c3ee1d-ec35-4b26-9bab-12de3f47d604,a2c3ee1d-ec35-4b26-9bab-12de3f47d604 -a2c3ee1d-ec35-4b26-9bab-12de3f47d604,86415df5-7e47-4637-9e09-aaad9e7628d1 -a2c3ee1d-ec35-4b26-9bab-12de3f47d604,749babeb-6883-4bd4-92a5-bec52769071c -a2c3ee1d-ec35-4b26-9bab-12de3f47d604,578cc35c-0982-4d72-a729-b304c026f075 -a2c3ee1d-ec35-4b26-9bab-12de3f47d604,0250a217-a663-403b-a708-5d14eadf0c40 -a6790cbd-8349-432e-a976-5dfc07451203,bfc93cb7-e53a-49f7-b86e-81d6a13cfdea -a6790cbd-8349-432e-a976-5dfc07451203,a6790cbd-8349-432e-a976-5dfc07451203 -a6790cbd-8349-432e-a976-5dfc07451203,9b2eb632-6f1a-4e97-912b-3c8378a1b11c -a6790cbd-8349-432e-a976-5dfc07451203,85c997bf-63d9-4ebb-8e0b-320de3dddc6c -a6790cbd-8349-432e-a976-5dfc07451203,85481b3f-c75e-40cd-bacd-b0afb79e893c -a6790cbd-8349-432e-a976-5dfc07451203,75a09d4f-eef2-4404-a386-dfcb408ec9ed -a6790cbd-8349-432e-a976-5dfc07451203,30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 -a6790cbd-8349-432e-a976-5dfc07451203,2ee2ab26-09fb-4e12-bf21-9fed3b4c38de -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,ee24bf89-81a3-4259-a382-86917f3829d4 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,e363eb67-64c7-440f-93fd-5c8787c69a85 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,db79f75d-af3c-4f82-b4e5-9b5d26598eef -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,d7a48a26-7731-4046-bf22-78f0b8084eb9 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,d080c116-681a-494a-a928-45924109b49d -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,990c21ab-433b-4920-873e-f9dfc7103d9d -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,922e2443-9cc5-421f-8310-9cd23f6e9f2d -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,8089a9ac-9e71-4487-a231-f720e4bc8d3e -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,6d47a2fe-3645-4c5c-bebe-1b822eff197f -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,67fc12fc-bb9f-40f1-9051-127a788b3081 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,634f0889-3a83-484a-bcc8-86f48e333c7b -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,4adcaf72-5241-4877-b61c-f34060576c50 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,374fd699-6b74-4500-9d60-2473c7e0364f -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,308eba53-29fa-489a-97dc-741b077841a7 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,21995497-0eb8-4c0b-8c23-e6a829f3d596 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,18ebf5ea-b0a2-4333-9e9c-40217de809ff -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,151e3048-66ad-4411-8753-677877e3bf0a -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,0af4a77e-892e-4b3f-9110-4c5224782250 -a6cb423a-7571-46df-83e2-ccc6820adb36,f77a8561-5adc-452a-ac9a-76273b6a6678 -a6cb423a-7571-46df-83e2-ccc6820adb36,d79728e1-8834-4f4a-8edc-ce18aca18be6 -a6cb423a-7571-46df-83e2-ccc6820adb36,cf67a8d4-48e2-4dc9-a521-6aabcff0330b -a6cb423a-7571-46df-83e2-ccc6820adb36,bb1a7816-4798-4f58-9ce4-c5bd3af682a8 -a6cb423a-7571-46df-83e2-ccc6820adb36,b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 -a6cb423a-7571-46df-83e2-ccc6820adb36,a6cb423a-7571-46df-83e2-ccc6820adb36 -a6cb423a-7571-46df-83e2-ccc6820adb36,7e7322a6-7912-4101-b3be-d134245a0626 -a6cb423a-7571-46df-83e2-ccc6820adb36,777aa5f2-2a09-4aed-92ea-912694e28b48 -a6cb423a-7571-46df-83e2-ccc6820adb36,6e187f47-91a1-4217-a185-31b6f01db225 -a6cb423a-7571-46df-83e2-ccc6820adb36,62059efc-7607-41c9-a3ba-70948f6a8a1d -a6cb423a-7571-46df-83e2-ccc6820adb36,2d2e07ec-e697-4384-a679-867e93740921 -a6cb423a-7571-46df-83e2-ccc6820adb36,20fbcb6e-d793-4b05-8e29-8ff82572b0db -a6cb423a-7571-46df-83e2-ccc6820adb36,1fd714a6-48b4-425a-99b3-ba5c1a995cce -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,fcc916dd-5e4f-4bf7-9aef-c906a36d7301 -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,c57d51a7-45de-41b9-92fc-efd0634effaf -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,ae7ddaef-4911-418c-8401-d978617e145b -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,acb2329d-b1c3-45c3-ad28-91a09aa521e1 -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,a89c0c42-c19f-4d7f-a15e-d04e043c92f6 -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,836b2e59-fb97-4014-ab0c-d5a5dc750969 -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,683714ad-5194-49e7-9b8f-4e306cf68ae1 -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,5f64131b-d410-449a-9de6-35415919fec5 -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,4f3d1e93-a28f-446e-91af-2baf0168b82b -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,1db1c71b-9eeb-4a98-abc1-eb699b38510c -acb2329d-b1c3-45c3-ad28-91a09aa521e1,fcc916dd-5e4f-4bf7-9aef-c906a36d7301 -acb2329d-b1c3-45c3-ad28-91a09aa521e1,c57d51a7-45de-41b9-92fc-efd0634effaf -acb2329d-b1c3-45c3-ad28-91a09aa521e1,ae7ddaef-4911-418c-8401-d978617e145b -acb2329d-b1c3-45c3-ad28-91a09aa521e1,acb2329d-b1c3-45c3-ad28-91a09aa521e1 -acb2329d-b1c3-45c3-ad28-91a09aa521e1,a89c0c42-c19f-4d7f-a15e-d04e043c92f6 -acb2329d-b1c3-45c3-ad28-91a09aa521e1,836b2e59-fb97-4014-ab0c-d5a5dc750969 -acb2329d-b1c3-45c3-ad28-91a09aa521e1,683714ad-5194-49e7-9b8f-4e306cf68ae1 -acb2329d-b1c3-45c3-ad28-91a09aa521e1,5f64131b-d410-449a-9de6-35415919fec5 -acb2329d-b1c3-45c3-ad28-91a09aa521e1,4f3d1e93-a28f-446e-91af-2baf0168b82b -acb2329d-b1c3-45c3-ad28-91a09aa521e1,1db1c71b-9eeb-4a98-abc1-eb699b38510c -add6d754-a075-4693-a33a-c061c9a368ff,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -add6d754-a075-4693-a33a-c061c9a368ff,e8eba267-fecb-477e-9625-771fbcfc3cba -add6d754-a075-4693-a33a-c061c9a368ff,e8c41168-a093-40eb-8baa-6802d24f554a -add6d754-a075-4693-a33a-c061c9a368ff,e26ae29a-213a-4f79-98c2-cc81cf2f451d -add6d754-a075-4693-a33a-c061c9a368ff,e0dfbc08-45ad-4a81-b648-7e65c066f673 -add6d754-a075-4693-a33a-c061c9a368ff,d535b213-2abc-438f-aa6a-c06476d60b09 -add6d754-a075-4693-a33a-c061c9a368ff,c892b1d7-7485-407d-8883-54fb7fecebc1 -add6d754-a075-4693-a33a-c061c9a368ff,b6900c21-d310-4fb4-8b8f-176a09c91989 -add6d754-a075-4693-a33a-c061c9a368ff,b00df815-7528-4967-bfe2-311130d91c21 -add6d754-a075-4693-a33a-c061c9a368ff,add6d754-a075-4693-a33a-c061c9a368ff -add6d754-a075-4693-a33a-c061c9a368ff,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -add6d754-a075-4693-a33a-c061c9a368ff,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -add6d754-a075-4693-a33a-c061c9a368ff,8a1908f7-47cc-4f82-859c-781a193e9901 -add6d754-a075-4693-a33a-c061c9a368ff,8743e69b-17aa-44ff-b8fa-4f582665efc6 -add6d754-a075-4693-a33a-c061c9a368ff,721e4027-a080-40d8-bc4a-43cd33477611 -add6d754-a075-4693-a33a-c061c9a368ff,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -add6d754-a075-4693-a33a-c061c9a368ff,54c750aa-570a-4e8e-9a02-418781923e39 -add6d754-a075-4693-a33a-c061c9a368ff,50eac25a-3761-4de3-8a69-aae52e658300 -add6d754-a075-4693-a33a-c061c9a368ff,4a464bb5-9373-4f1b-9c03-053c64797114 -add6d754-a075-4693-a33a-c061c9a368ff,4506aa76-9d0d-40e4-85bc-5735f74522a0 -add6d754-a075-4693-a33a-c061c9a368ff,42a9a333-d09d-4b9e-af96-1f02af26bac3 -add6d754-a075-4693-a33a-c061c9a368ff,2fc75f11-2097-4e1e-8390-dbff3e844b7a -add6d754-a075-4693-a33a-c061c9a368ff,274a2e49-9170-4945-bca2-ab6ef4bded75 -add6d754-a075-4693-a33a-c061c9a368ff,0364073f-91d8-47a1-b8b0-107c6318a691 -ae7ddaef-4911-418c-8401-d978617e145b,fcc916dd-5e4f-4bf7-9aef-c906a36d7301 -ae7ddaef-4911-418c-8401-d978617e145b,c57d51a7-45de-41b9-92fc-efd0634effaf -ae7ddaef-4911-418c-8401-d978617e145b,ae7ddaef-4911-418c-8401-d978617e145b -ae7ddaef-4911-418c-8401-d978617e145b,acb2329d-b1c3-45c3-ad28-91a09aa521e1 -ae7ddaef-4911-418c-8401-d978617e145b,a89c0c42-c19f-4d7f-a15e-d04e043c92f6 -ae7ddaef-4911-418c-8401-d978617e145b,836b2e59-fb97-4014-ab0c-d5a5dc750969 -ae7ddaef-4911-418c-8401-d978617e145b,683714ad-5194-49e7-9b8f-4e306cf68ae1 -ae7ddaef-4911-418c-8401-d978617e145b,5f64131b-d410-449a-9de6-35415919fec5 -ae7ddaef-4911-418c-8401-d978617e145b,4f3d1e93-a28f-446e-91af-2baf0168b82b -ae7ddaef-4911-418c-8401-d978617e145b,1db1c71b-9eeb-4a98-abc1-eb699b38510c -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,e1c2ad9f-6f61-4f16-805a-2f405b63659a -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,d6988116-3c63-44f8-9f94-9e9d51cc5a37 -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,cfcbe34a-edc5-4442-bc05-5927fa00336b -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,c90aae7e-5704-4e13-8794-41ab377193bd -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,bf895c48-1d52-4780-8e9a-532d69356d28 -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,ae9e663e-8c15-43c9-8a88-52b61cbf07a9 -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,56999896-e36b-4e63-a6b6-dbb85dfe1936 -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,51241857-a7a4-4517-96e3-21f797581f89 -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,4cd95073-6db3-4eb2-ac4b-0aacb182b352 -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,4148ac36-1dcb-4e96-89cd-355e7e8f919b -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,2e8eb256-84c9-499a-8bf6-bb39504373e1 -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 -b00df815-7528-4967-bfe2-311130d91c21,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -b00df815-7528-4967-bfe2-311130d91c21,e8eba267-fecb-477e-9625-771fbcfc3cba -b00df815-7528-4967-bfe2-311130d91c21,e8c41168-a093-40eb-8baa-6802d24f554a -b00df815-7528-4967-bfe2-311130d91c21,e26ae29a-213a-4f79-98c2-cc81cf2f451d -b00df815-7528-4967-bfe2-311130d91c21,e0dfbc08-45ad-4a81-b648-7e65c066f673 -b00df815-7528-4967-bfe2-311130d91c21,d535b213-2abc-438f-aa6a-c06476d60b09 -b00df815-7528-4967-bfe2-311130d91c21,c892b1d7-7485-407d-8883-54fb7fecebc1 -b00df815-7528-4967-bfe2-311130d91c21,b6900c21-d310-4fb4-8b8f-176a09c91989 -b00df815-7528-4967-bfe2-311130d91c21,b00df815-7528-4967-bfe2-311130d91c21 -b00df815-7528-4967-bfe2-311130d91c21,add6d754-a075-4693-a33a-c061c9a368ff -b00df815-7528-4967-bfe2-311130d91c21,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -b00df815-7528-4967-bfe2-311130d91c21,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -b00df815-7528-4967-bfe2-311130d91c21,8a1908f7-47cc-4f82-859c-781a193e9901 -b00df815-7528-4967-bfe2-311130d91c21,8743e69b-17aa-44ff-b8fa-4f582665efc6 -b00df815-7528-4967-bfe2-311130d91c21,721e4027-a080-40d8-bc4a-43cd33477611 -b00df815-7528-4967-bfe2-311130d91c21,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -b00df815-7528-4967-bfe2-311130d91c21,54c750aa-570a-4e8e-9a02-418781923e39 -b00df815-7528-4967-bfe2-311130d91c21,50eac25a-3761-4de3-8a69-aae52e658300 -b00df815-7528-4967-bfe2-311130d91c21,4a464bb5-9373-4f1b-9c03-053c64797114 -b00df815-7528-4967-bfe2-311130d91c21,4506aa76-9d0d-40e4-85bc-5735f74522a0 -b00df815-7528-4967-bfe2-311130d91c21,42a9a333-d09d-4b9e-af96-1f02af26bac3 -b00df815-7528-4967-bfe2-311130d91c21,2fc75f11-2097-4e1e-8390-dbff3e844b7a -b00df815-7528-4967-bfe2-311130d91c21,274a2e49-9170-4945-bca2-ab6ef4bded75 -b00df815-7528-4967-bfe2-311130d91c21,0364073f-91d8-47a1-b8b0-107c6318a691 -b07fb98d-2da4-423a-83b4-7a673de93096,f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 -b07fb98d-2da4-423a-83b4-7a673de93096,f3aa491a-4dbd-4436-b674-18b2177dcf18 -b07fb98d-2da4-423a-83b4-7a673de93096,e1040d58-53bc-4467-8101-ca53b772cede -b07fb98d-2da4-423a-83b4-7a673de93096,d05461d6-13bf-402c-890d-db6404933f7c -b07fb98d-2da4-423a-83b4-7a673de93096,b7772635-1801-48e4-a442-f4aaa6860544 -b07fb98d-2da4-423a-83b4-7a673de93096,b07fb98d-2da4-423a-83b4-7a673de93096 -b07fb98d-2da4-423a-83b4-7a673de93096,97b42d67-8691-433d-8a31-dada076162ae -b07fb98d-2da4-423a-83b4-7a673de93096,720f7ed6-7b2e-41dc-a0e0-724a3332aa24 -b07fb98d-2da4-423a-83b4-7a673de93096,4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 -b07fb98d-2da4-423a-83b4-7a673de93096,2ffd6142-b75b-406f-bc06-cdb1c02eaefc -b07fb98d-2da4-423a-83b4-7a673de93096,224e538d-3622-4f5e-b772-211ed358d8de -b07fb98d-2da4-423a-83b4-7a673de93096,16a3408d-c927-4d02-a1ae-cad32419caab -b07fb98d-2da4-423a-83b4-7a673de93096,04d88eb4-fb1b-4fa1-802a-5624f4c61b32 -b0d337c5-3555-4817-ba59-4ceea5ad8a91,b0d337c5-3555-4817-ba59-4ceea5ad8a91 -b0d337c5-3555-4817-ba59-4ceea5ad8a91,79be3b69-23d8-4408-8f01-68d993e63b6c -b0d337c5-3555-4817-ba59-4ceea5ad8a91,04945715-8ad5-4d8f-bfda-ae26662a3610 -b1ccd55a-6b88-4240-b20c-ca893f36e23b,f19eefca-c1ad-4860-bdda-4674a631d464 -b1ccd55a-6b88-4240-b20c-ca893f36e23b,f18b08bd-40da-43c1-87ec-4ba23542635f -b1ccd55a-6b88-4240-b20c-ca893f36e23b,b1ccd55a-6b88-4240-b20c-ca893f36e23b -b1ccd55a-6b88-4240-b20c-ca893f36e23b,900ce9fe-02d3-476b-902a-9467767ecdcf -b1ccd55a-6b88-4240-b20c-ca893f36e23b,8dc2ce26-aec7-43c0-9771-350af4257ad8 -b1ccd55a-6b88-4240-b20c-ca893f36e23b,1f3443ee-d15e-4894-b18e-185cfadfcdea -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,fc771883-3bd6-46d9-9626-a130e1b902de -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,e2387a81-5ff5-4daf-b2e8-881998d0d917 -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,b1d4dc41-07ae-44db-ae17-1df86c5a62cf -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,67e5cf95-236b-4f75-abc5-034ca2966448 -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,48cc2275-a478-4ade-b076-cf38e1d16017 -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,41d26b1c-57b9-408c-b955-bf0ee1db4809 -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,3ddf46fe-b5be-456d-810f-ea6a7402236d -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,3397a796-894d-409c-97de-a9e6f3f88198 -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,19502b5a-2031-487d-9d9c-2001f959408b -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,fc771883-3bd6-46d9-9626-a130e1b902de -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,e2387a81-5ff5-4daf-b2e8-881998d0d917 -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,b1d4dc41-07ae-44db-ae17-1df86c5a62cf -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,67e5cf95-236b-4f75-abc5-034ca2966448 -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,48cc2275-a478-4ade-b076-cf38e1d16017 -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,41d26b1c-57b9-408c-b955-bf0ee1db4809 -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,3ddf46fe-b5be-456d-810f-ea6a7402236d -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,3397a796-894d-409c-97de-a9e6f3f88198 -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,19502b5a-2031-487d-9d9c-2001f959408b -b6900c21-d310-4fb4-8b8f-176a09c91989,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -b6900c21-d310-4fb4-8b8f-176a09c91989,e8eba267-fecb-477e-9625-771fbcfc3cba -b6900c21-d310-4fb4-8b8f-176a09c91989,e8c41168-a093-40eb-8baa-6802d24f554a -b6900c21-d310-4fb4-8b8f-176a09c91989,e26ae29a-213a-4f79-98c2-cc81cf2f451d -b6900c21-d310-4fb4-8b8f-176a09c91989,e0dfbc08-45ad-4a81-b648-7e65c066f673 -b6900c21-d310-4fb4-8b8f-176a09c91989,d535b213-2abc-438f-aa6a-c06476d60b09 -b6900c21-d310-4fb4-8b8f-176a09c91989,c892b1d7-7485-407d-8883-54fb7fecebc1 -b6900c21-d310-4fb4-8b8f-176a09c91989,b6900c21-d310-4fb4-8b8f-176a09c91989 -b6900c21-d310-4fb4-8b8f-176a09c91989,b00df815-7528-4967-bfe2-311130d91c21 -b6900c21-d310-4fb4-8b8f-176a09c91989,add6d754-a075-4693-a33a-c061c9a368ff -b6900c21-d310-4fb4-8b8f-176a09c91989,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -b6900c21-d310-4fb4-8b8f-176a09c91989,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -b6900c21-d310-4fb4-8b8f-176a09c91989,8a1908f7-47cc-4f82-859c-781a193e9901 -b6900c21-d310-4fb4-8b8f-176a09c91989,8743e69b-17aa-44ff-b8fa-4f582665efc6 -b6900c21-d310-4fb4-8b8f-176a09c91989,721e4027-a080-40d8-bc4a-43cd33477611 -b6900c21-d310-4fb4-8b8f-176a09c91989,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -b6900c21-d310-4fb4-8b8f-176a09c91989,54c750aa-570a-4e8e-9a02-418781923e39 -b6900c21-d310-4fb4-8b8f-176a09c91989,50eac25a-3761-4de3-8a69-aae52e658300 -b6900c21-d310-4fb4-8b8f-176a09c91989,4a464bb5-9373-4f1b-9c03-053c64797114 -b6900c21-d310-4fb4-8b8f-176a09c91989,4506aa76-9d0d-40e4-85bc-5735f74522a0 -b6900c21-d310-4fb4-8b8f-176a09c91989,42a9a333-d09d-4b9e-af96-1f02af26bac3 -b6900c21-d310-4fb4-8b8f-176a09c91989,2fc75f11-2097-4e1e-8390-dbff3e844b7a -b6900c21-d310-4fb4-8b8f-176a09c91989,274a2e49-9170-4945-bca2-ab6ef4bded75 -b6900c21-d310-4fb4-8b8f-176a09c91989,0364073f-91d8-47a1-b8b0-107c6318a691 -b7772635-1801-48e4-a442-f4aaa6860544,f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 -b7772635-1801-48e4-a442-f4aaa6860544,f3aa491a-4dbd-4436-b674-18b2177dcf18 -b7772635-1801-48e4-a442-f4aaa6860544,e1040d58-53bc-4467-8101-ca53b772cede -b7772635-1801-48e4-a442-f4aaa6860544,d05461d6-13bf-402c-890d-db6404933f7c -b7772635-1801-48e4-a442-f4aaa6860544,b7772635-1801-48e4-a442-f4aaa6860544 -b7772635-1801-48e4-a442-f4aaa6860544,b07fb98d-2da4-423a-83b4-7a673de93096 -b7772635-1801-48e4-a442-f4aaa6860544,97b42d67-8691-433d-8a31-dada076162ae -b7772635-1801-48e4-a442-f4aaa6860544,720f7ed6-7b2e-41dc-a0e0-724a3332aa24 -b7772635-1801-48e4-a442-f4aaa6860544,4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 -b7772635-1801-48e4-a442-f4aaa6860544,2ffd6142-b75b-406f-bc06-cdb1c02eaefc -b7772635-1801-48e4-a442-f4aaa6860544,224e538d-3622-4f5e-b772-211ed358d8de -b7772635-1801-48e4-a442-f4aaa6860544,16a3408d-c927-4d02-a1ae-cad32419caab -b7772635-1801-48e4-a442-f4aaa6860544,04d88eb4-fb1b-4fa1-802a-5624f4c61b32 -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,f77a8561-5adc-452a-ac9a-76273b6a6678 -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,d79728e1-8834-4f4a-8edc-ce18aca18be6 -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,cf67a8d4-48e2-4dc9-a521-6aabcff0330b -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,bb1a7816-4798-4f58-9ce4-c5bd3af682a8 -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,a6cb423a-7571-46df-83e2-ccc6820adb36 -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,7e7322a6-7912-4101-b3be-d134245a0626 -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,777aa5f2-2a09-4aed-92ea-912694e28b48 -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,6e187f47-91a1-4217-a185-31b6f01db225 -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,62059efc-7607-41c9-a3ba-70948f6a8a1d -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,2d2e07ec-e697-4384-a679-867e93740921 -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,20fbcb6e-d793-4b05-8e29-8ff82572b0db -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,1fd714a6-48b4-425a-99b3-ba5c1a995cce -bac374c0-50e8-4a5c-947b-82a8e9a747a9,bac374c0-50e8-4a5c-947b-82a8e9a747a9 -bac374c0-50e8-4a5c-947b-82a8e9a747a9,8bc7af32-e5ad-4849-ba4d-b446db833ab4 -bac374c0-50e8-4a5c-947b-82a8e9a747a9,7ea43fc6-b1f7-46be-a308-5ecb275a1081 -bac374c0-50e8-4a5c-947b-82a8e9a747a9,2de1f829-c9d2-4f22-bf39-536dcb82fc3e -bac374c0-50e8-4a5c-947b-82a8e9a747a9,2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 -bac374c0-50e8-4a5c-947b-82a8e9a747a9,23780032-f3bb-4b40-af2e-3fa6b1677376 -bac374c0-50e8-4a5c-947b-82a8e9a747a9,21c6e0fc-bf47-4f80-b1ff-85b940445bdf -bac374c0-50e8-4a5c-947b-82a8e9a747a9,12b212d8-bc6e-415a-93b0-594381726668 -baf48725-f0f9-4357-a71d-b1104910deae,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -baf48725-f0f9-4357-a71d-b1104910deae,d90676d2-ce74-4867-a00f-6dbffa7c00be -baf48725-f0f9-4357-a71d-b1104910deae,cc56af6d-25c6-480e-9767-da02a55551da -baf48725-f0f9-4357-a71d-b1104910deae,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -baf48725-f0f9-4357-a71d-b1104910deae,baf48725-f0f9-4357-a71d-b1104910deae -baf48725-f0f9-4357-a71d-b1104910deae,9ed8703e-3b40-424c-9e98-b3b404051f99 -baf48725-f0f9-4357-a71d-b1104910deae,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -baf48725-f0f9-4357-a71d-b1104910deae,8be81657-8ba6-4ec5-8ff9-4809367096ea -baf48725-f0f9-4357-a71d-b1104910deae,7f410064-638a-4477-85c4-97fc2bb14a49 -baf48725-f0f9-4357-a71d-b1104910deae,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -baf48725-f0f9-4357-a71d-b1104910deae,5cedf18a-fc44-4c39-a80f-2106a0d76934 -baf48725-f0f9-4357-a71d-b1104910deae,4f342a71-dec3-403e-970b-e4c21b9eb98b -baf48725-f0f9-4357-a71d-b1104910deae,4db144d3-a596-4718-a50a-8ac9175ad386 -baf48725-f0f9-4357-a71d-b1104910deae,4215b5d0-bdd9-4222-a04a-81bb637d60af -baf48725-f0f9-4357-a71d-b1104910deae,29d4e919-2bd2-44e6-bb8a-380a780f60ff -baf48725-f0f9-4357-a71d-b1104910deae,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -baf48725-f0f9-4357-a71d-b1104910deae,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -baf48725-f0f9-4357-a71d-b1104910deae,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -baf48725-f0f9-4357-a71d-b1104910deae,102e16c5-4920-4dad-b142-0b280b2aacad -baf48725-f0f9-4357-a71d-b1104910deae,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,f77a8561-5adc-452a-ac9a-76273b6a6678 -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,d79728e1-8834-4f4a-8edc-ce18aca18be6 -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,cf67a8d4-48e2-4dc9-a521-6aabcff0330b -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,bb1a7816-4798-4f58-9ce4-c5bd3af682a8 -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,a6cb423a-7571-46df-83e2-ccc6820adb36 -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,7e7322a6-7912-4101-b3be-d134245a0626 -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,777aa5f2-2a09-4aed-92ea-912694e28b48 -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,6e187f47-91a1-4217-a185-31b6f01db225 -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,62059efc-7607-41c9-a3ba-70948f6a8a1d -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,2d2e07ec-e697-4384-a679-867e93740921 -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,20fbcb6e-d793-4b05-8e29-8ff82572b0db -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,1fd714a6-48b4-425a-99b3-ba5c1a995cce -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,fce20464-ff98-4051-8714-6b9584e52740 -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,f3eb0c38-2571-44e7-be39-732eb52cf1df -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,f3c2f842-6aa3-42c9-a0f3-895c40456868 -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,ede7745a-8f3e-4e20-9f16-33756be7ab6c -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,ca3e0486-fd6b-4a13-a741-3a47760b412d -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,a221198d-000e-497b-8b70-bb4d37f7bbe1 -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,88b16960-bfff-41d0-81e4-9a4630834a00 -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,522e2abe-ad96-4d1a-b5a5-748faa997531 -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,4cfe72fe-21a0-4201-87b6-ef93695d2495 -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,498f4b18-bd4c-470d-9cde-2fca70ec8964 -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,202131ae-78bb-4104-89b0-fb90645760f6 -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,11dafd5c-85e7-4275-a147-89a63641e35e -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,02b993c0-b358-481c-b0d0-0767387feb9f -bf895c48-1d52-4780-8e9a-532d69356d28,e1c2ad9f-6f61-4f16-805a-2f405b63659a -bf895c48-1d52-4780-8e9a-532d69356d28,d6988116-3c63-44f8-9f94-9e9d51cc5a37 -bf895c48-1d52-4780-8e9a-532d69356d28,cfcbe34a-edc5-4442-bc05-5927fa00336b -bf895c48-1d52-4780-8e9a-532d69356d28,c90aae7e-5704-4e13-8794-41ab377193bd -bf895c48-1d52-4780-8e9a-532d69356d28,bf895c48-1d52-4780-8e9a-532d69356d28 -bf895c48-1d52-4780-8e9a-532d69356d28,ae9e663e-8c15-43c9-8a88-52b61cbf07a9 -bf895c48-1d52-4780-8e9a-532d69356d28,56999896-e36b-4e63-a6b6-dbb85dfe1936 -bf895c48-1d52-4780-8e9a-532d69356d28,51241857-a7a4-4517-96e3-21f797581f89 -bf895c48-1d52-4780-8e9a-532d69356d28,4cd95073-6db3-4eb2-ac4b-0aacb182b352 -bf895c48-1d52-4780-8e9a-532d69356d28,4148ac36-1dcb-4e96-89cd-355e7e8f919b -bf895c48-1d52-4780-8e9a-532d69356d28,2e8eb256-84c9-499a-8bf6-bb39504373e1 -bf895c48-1d52-4780-8e9a-532d69356d28,07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 -bfc93cb7-e53a-49f7-b86e-81d6a13cfdea,bfc93cb7-e53a-49f7-b86e-81d6a13cfdea -bfc93cb7-e53a-49f7-b86e-81d6a13cfdea,a6790cbd-8349-432e-a976-5dfc07451203 -bfc93cb7-e53a-49f7-b86e-81d6a13cfdea,9b2eb632-6f1a-4e97-912b-3c8378a1b11c -bfc93cb7-e53a-49f7-b86e-81d6a13cfdea,85c997bf-63d9-4ebb-8e0b-320de3dddc6c -bfc93cb7-e53a-49f7-b86e-81d6a13cfdea,85481b3f-c75e-40cd-bacd-b0afb79e893c -bfc93cb7-e53a-49f7-b86e-81d6a13cfdea,75a09d4f-eef2-4404-a386-dfcb408ec9ed -bfc93cb7-e53a-49f7-b86e-81d6a13cfdea,30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 -bfc93cb7-e53a-49f7-b86e-81d6a13cfdea,2ee2ab26-09fb-4e12-bf21-9fed3b4c38de -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,ee24bf89-81a3-4259-a382-86917f3829d4 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,e363eb67-64c7-440f-93fd-5c8787c69a85 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,db79f75d-af3c-4f82-b4e5-9b5d26598eef -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,d7a48a26-7731-4046-bf22-78f0b8084eb9 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,d080c116-681a-494a-a928-45924109b49d -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,990c21ab-433b-4920-873e-f9dfc7103d9d -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,922e2443-9cc5-421f-8310-9cd23f6e9f2d -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,8089a9ac-9e71-4487-a231-f720e4bc8d3e -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,6d47a2fe-3645-4c5c-bebe-1b822eff197f -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,67fc12fc-bb9f-40f1-9051-127a788b3081 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,634f0889-3a83-484a-bcc8-86f48e333c7b -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,4adcaf72-5241-4877-b61c-f34060576c50 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,374fd699-6b74-4500-9d60-2473c7e0364f -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,308eba53-29fa-489a-97dc-741b077841a7 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,21995497-0eb8-4c0b-8c23-e6a829f3d596 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,18ebf5ea-b0a2-4333-9e9c-40217de809ff -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,151e3048-66ad-4411-8753-677877e3bf0a -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,0af4a77e-892e-4b3f-9110-4c5224782250 -c3ec1f2e-1411-41e4-8370-acd5a8f68cf4,c3ec1f2e-1411-41e4-8370-acd5a8f68cf4 -c3ec1f2e-1411-41e4-8370-acd5a8f68cf4,87b04a97-1d33-4548-aec9-3d2856070702 -c3ec1f2e-1411-41e4-8370-acd5a8f68cf4,2bddd98c-86e3-4ae0-9dc6-87da16ab6267 -c57d51a7-45de-41b9-92fc-efd0634effaf,fcc916dd-5e4f-4bf7-9aef-c906a36d7301 -c57d51a7-45de-41b9-92fc-efd0634effaf,c57d51a7-45de-41b9-92fc-efd0634effaf -c57d51a7-45de-41b9-92fc-efd0634effaf,ae7ddaef-4911-418c-8401-d978617e145b -c57d51a7-45de-41b9-92fc-efd0634effaf,acb2329d-b1c3-45c3-ad28-91a09aa521e1 -c57d51a7-45de-41b9-92fc-efd0634effaf,a89c0c42-c19f-4d7f-a15e-d04e043c92f6 -c57d51a7-45de-41b9-92fc-efd0634effaf,836b2e59-fb97-4014-ab0c-d5a5dc750969 -c57d51a7-45de-41b9-92fc-efd0634effaf,683714ad-5194-49e7-9b8f-4e306cf68ae1 -c57d51a7-45de-41b9-92fc-efd0634effaf,5f64131b-d410-449a-9de6-35415919fec5 -c57d51a7-45de-41b9-92fc-efd0634effaf,4f3d1e93-a28f-446e-91af-2baf0168b82b -c57d51a7-45de-41b9-92fc-efd0634effaf,1db1c71b-9eeb-4a98-abc1-eb699b38510c -c5c6eed5-aec6-4b8a-b689-adbb219c2267,fce20464-ff98-4051-8714-6b9584e52740 -c5c6eed5-aec6-4b8a-b689-adbb219c2267,f3eb0c38-2571-44e7-be39-732eb52cf1df -c5c6eed5-aec6-4b8a-b689-adbb219c2267,f3c2f842-6aa3-42c9-a0f3-895c40456868 -c5c6eed5-aec6-4b8a-b689-adbb219c2267,ede7745a-8f3e-4e20-9f16-33756be7ab6c -c5c6eed5-aec6-4b8a-b689-adbb219c2267,ca3e0486-fd6b-4a13-a741-3a47760b412d -c5c6eed5-aec6-4b8a-b689-adbb219c2267,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -c5c6eed5-aec6-4b8a-b689-adbb219c2267,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -c5c6eed5-aec6-4b8a-b689-adbb219c2267,a221198d-000e-497b-8b70-bb4d37f7bbe1 -c5c6eed5-aec6-4b8a-b689-adbb219c2267,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -c5c6eed5-aec6-4b8a-b689-adbb219c2267,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -c5c6eed5-aec6-4b8a-b689-adbb219c2267,88b16960-bfff-41d0-81e4-9a4630834a00 -c5c6eed5-aec6-4b8a-b689-adbb219c2267,522e2abe-ad96-4d1a-b5a5-748faa997531 -c5c6eed5-aec6-4b8a-b689-adbb219c2267,4cfe72fe-21a0-4201-87b6-ef93695d2495 -c5c6eed5-aec6-4b8a-b689-adbb219c2267,498f4b18-bd4c-470d-9cde-2fca70ec8964 -c5c6eed5-aec6-4b8a-b689-adbb219c2267,202131ae-78bb-4104-89b0-fb90645760f6 -c5c6eed5-aec6-4b8a-b689-adbb219c2267,11dafd5c-85e7-4275-a147-89a63641e35e -c5c6eed5-aec6-4b8a-b689-adbb219c2267,02b993c0-b358-481c-b0d0-0767387feb9f -c892b1d7-7485-407d-8883-54fb7fecebc1,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -c892b1d7-7485-407d-8883-54fb7fecebc1,e8eba267-fecb-477e-9625-771fbcfc3cba -c892b1d7-7485-407d-8883-54fb7fecebc1,e8c41168-a093-40eb-8baa-6802d24f554a -c892b1d7-7485-407d-8883-54fb7fecebc1,e26ae29a-213a-4f79-98c2-cc81cf2f451d -c892b1d7-7485-407d-8883-54fb7fecebc1,e0dfbc08-45ad-4a81-b648-7e65c066f673 -c892b1d7-7485-407d-8883-54fb7fecebc1,d535b213-2abc-438f-aa6a-c06476d60b09 -c892b1d7-7485-407d-8883-54fb7fecebc1,c892b1d7-7485-407d-8883-54fb7fecebc1 -c892b1d7-7485-407d-8883-54fb7fecebc1,b6900c21-d310-4fb4-8b8f-176a09c91989 -c892b1d7-7485-407d-8883-54fb7fecebc1,b00df815-7528-4967-bfe2-311130d91c21 -c892b1d7-7485-407d-8883-54fb7fecebc1,add6d754-a075-4693-a33a-c061c9a368ff -c892b1d7-7485-407d-8883-54fb7fecebc1,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -c892b1d7-7485-407d-8883-54fb7fecebc1,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -c892b1d7-7485-407d-8883-54fb7fecebc1,8a1908f7-47cc-4f82-859c-781a193e9901 -c892b1d7-7485-407d-8883-54fb7fecebc1,8743e69b-17aa-44ff-b8fa-4f582665efc6 -c892b1d7-7485-407d-8883-54fb7fecebc1,721e4027-a080-40d8-bc4a-43cd33477611 -c892b1d7-7485-407d-8883-54fb7fecebc1,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -c892b1d7-7485-407d-8883-54fb7fecebc1,54c750aa-570a-4e8e-9a02-418781923e39 -c892b1d7-7485-407d-8883-54fb7fecebc1,50eac25a-3761-4de3-8a69-aae52e658300 -c892b1d7-7485-407d-8883-54fb7fecebc1,4a464bb5-9373-4f1b-9c03-053c64797114 -c892b1d7-7485-407d-8883-54fb7fecebc1,4506aa76-9d0d-40e4-85bc-5735f74522a0 -c892b1d7-7485-407d-8883-54fb7fecebc1,42a9a333-d09d-4b9e-af96-1f02af26bac3 -c892b1d7-7485-407d-8883-54fb7fecebc1,2fc75f11-2097-4e1e-8390-dbff3e844b7a -c892b1d7-7485-407d-8883-54fb7fecebc1,274a2e49-9170-4945-bca2-ab6ef4bded75 -c892b1d7-7485-407d-8883-54fb7fecebc1,0364073f-91d8-47a1-b8b0-107c6318a691 -c90aae7e-5704-4e13-8794-41ab377193bd,e1c2ad9f-6f61-4f16-805a-2f405b63659a -c90aae7e-5704-4e13-8794-41ab377193bd,d6988116-3c63-44f8-9f94-9e9d51cc5a37 -c90aae7e-5704-4e13-8794-41ab377193bd,cfcbe34a-edc5-4442-bc05-5927fa00336b -c90aae7e-5704-4e13-8794-41ab377193bd,c90aae7e-5704-4e13-8794-41ab377193bd -c90aae7e-5704-4e13-8794-41ab377193bd,bf895c48-1d52-4780-8e9a-532d69356d28 -c90aae7e-5704-4e13-8794-41ab377193bd,ae9e663e-8c15-43c9-8a88-52b61cbf07a9 -c90aae7e-5704-4e13-8794-41ab377193bd,56999896-e36b-4e63-a6b6-dbb85dfe1936 -c90aae7e-5704-4e13-8794-41ab377193bd,51241857-a7a4-4517-96e3-21f797581f89 -c90aae7e-5704-4e13-8794-41ab377193bd,4cd95073-6db3-4eb2-ac4b-0aacb182b352 -c90aae7e-5704-4e13-8794-41ab377193bd,4148ac36-1dcb-4e96-89cd-355e7e8f919b -c90aae7e-5704-4e13-8794-41ab377193bd,2e8eb256-84c9-499a-8bf6-bb39504373e1 -c90aae7e-5704-4e13-8794-41ab377193bd,07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 -ca3e0486-fd6b-4a13-a741-3a47760b412d,fce20464-ff98-4051-8714-6b9584e52740 -ca3e0486-fd6b-4a13-a741-3a47760b412d,f3eb0c38-2571-44e7-be39-732eb52cf1df -ca3e0486-fd6b-4a13-a741-3a47760b412d,f3c2f842-6aa3-42c9-a0f3-895c40456868 -ca3e0486-fd6b-4a13-a741-3a47760b412d,ede7745a-8f3e-4e20-9f16-33756be7ab6c -ca3e0486-fd6b-4a13-a741-3a47760b412d,ca3e0486-fd6b-4a13-a741-3a47760b412d -ca3e0486-fd6b-4a13-a741-3a47760b412d,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -ca3e0486-fd6b-4a13-a741-3a47760b412d,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -ca3e0486-fd6b-4a13-a741-3a47760b412d,a221198d-000e-497b-8b70-bb4d37f7bbe1 -ca3e0486-fd6b-4a13-a741-3a47760b412d,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -ca3e0486-fd6b-4a13-a741-3a47760b412d,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -ca3e0486-fd6b-4a13-a741-3a47760b412d,88b16960-bfff-41d0-81e4-9a4630834a00 -ca3e0486-fd6b-4a13-a741-3a47760b412d,522e2abe-ad96-4d1a-b5a5-748faa997531 -ca3e0486-fd6b-4a13-a741-3a47760b412d,4cfe72fe-21a0-4201-87b6-ef93695d2495 -ca3e0486-fd6b-4a13-a741-3a47760b412d,498f4b18-bd4c-470d-9cde-2fca70ec8964 -ca3e0486-fd6b-4a13-a741-3a47760b412d,202131ae-78bb-4104-89b0-fb90645760f6 -ca3e0486-fd6b-4a13-a741-3a47760b412d,11dafd5c-85e7-4275-a147-89a63641e35e -ca3e0486-fd6b-4a13-a741-3a47760b412d,02b993c0-b358-481c-b0d0-0767387feb9f -ca7f269d-3794-4994-8c46-d8e9f2aa6378,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -ca7f269d-3794-4994-8c46-d8e9f2aa6378,d90676d2-ce74-4867-a00f-6dbffa7c00be -ca7f269d-3794-4994-8c46-d8e9f2aa6378,cc56af6d-25c6-480e-9767-da02a55551da -ca7f269d-3794-4994-8c46-d8e9f2aa6378,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -ca7f269d-3794-4994-8c46-d8e9f2aa6378,baf48725-f0f9-4357-a71d-b1104910deae -ca7f269d-3794-4994-8c46-d8e9f2aa6378,9ed8703e-3b40-424c-9e98-b3b404051f99 -ca7f269d-3794-4994-8c46-d8e9f2aa6378,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -ca7f269d-3794-4994-8c46-d8e9f2aa6378,8be81657-8ba6-4ec5-8ff9-4809367096ea -ca7f269d-3794-4994-8c46-d8e9f2aa6378,7f410064-638a-4477-85c4-97fc2bb14a49 -ca7f269d-3794-4994-8c46-d8e9f2aa6378,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -ca7f269d-3794-4994-8c46-d8e9f2aa6378,5cedf18a-fc44-4c39-a80f-2106a0d76934 -ca7f269d-3794-4994-8c46-d8e9f2aa6378,4f342a71-dec3-403e-970b-e4c21b9eb98b -ca7f269d-3794-4994-8c46-d8e9f2aa6378,4db144d3-a596-4718-a50a-8ac9175ad386 -ca7f269d-3794-4994-8c46-d8e9f2aa6378,4215b5d0-bdd9-4222-a04a-81bb637d60af -ca7f269d-3794-4994-8c46-d8e9f2aa6378,29d4e919-2bd2-44e6-bb8a-380a780f60ff -ca7f269d-3794-4994-8c46-d8e9f2aa6378,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -ca7f269d-3794-4994-8c46-d8e9f2aa6378,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -ca7f269d-3794-4994-8c46-d8e9f2aa6378,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -ca7f269d-3794-4994-8c46-d8e9f2aa6378,102e16c5-4920-4dad-b142-0b280b2aacad -ca7f269d-3794-4994-8c46-d8e9f2aa6378,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -cc56af6d-25c6-480e-9767-da02a55551da,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -cc56af6d-25c6-480e-9767-da02a55551da,d90676d2-ce74-4867-a00f-6dbffa7c00be -cc56af6d-25c6-480e-9767-da02a55551da,cc56af6d-25c6-480e-9767-da02a55551da -cc56af6d-25c6-480e-9767-da02a55551da,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -cc56af6d-25c6-480e-9767-da02a55551da,baf48725-f0f9-4357-a71d-b1104910deae -cc56af6d-25c6-480e-9767-da02a55551da,9ed8703e-3b40-424c-9e98-b3b404051f99 -cc56af6d-25c6-480e-9767-da02a55551da,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -cc56af6d-25c6-480e-9767-da02a55551da,8be81657-8ba6-4ec5-8ff9-4809367096ea -cc56af6d-25c6-480e-9767-da02a55551da,7f410064-638a-4477-85c4-97fc2bb14a49 -cc56af6d-25c6-480e-9767-da02a55551da,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -cc56af6d-25c6-480e-9767-da02a55551da,5cedf18a-fc44-4c39-a80f-2106a0d76934 -cc56af6d-25c6-480e-9767-da02a55551da,4f342a71-dec3-403e-970b-e4c21b9eb98b -cc56af6d-25c6-480e-9767-da02a55551da,4db144d3-a596-4718-a50a-8ac9175ad386 -cc56af6d-25c6-480e-9767-da02a55551da,4215b5d0-bdd9-4222-a04a-81bb637d60af -cc56af6d-25c6-480e-9767-da02a55551da,29d4e919-2bd2-44e6-bb8a-380a780f60ff -cc56af6d-25c6-480e-9767-da02a55551da,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -cc56af6d-25c6-480e-9767-da02a55551da,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -cc56af6d-25c6-480e-9767-da02a55551da,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -cc56af6d-25c6-480e-9767-da02a55551da,102e16c5-4920-4dad-b142-0b280b2aacad -cc56af6d-25c6-480e-9767-da02a55551da,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -cdb8493c-e3b0-447f-b16b-e58dd64660f3,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -cdb8493c-e3b0-447f-b16b-e58dd64660f3,ee24bf89-81a3-4259-a382-86917f3829d4 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,e363eb67-64c7-440f-93fd-5c8787c69a85 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,db79f75d-af3c-4f82-b4e5-9b5d26598eef -cdb8493c-e3b0-447f-b16b-e58dd64660f3,d7a48a26-7731-4046-bf22-78f0b8084eb9 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,d080c116-681a-494a-a928-45924109b49d -cdb8493c-e3b0-447f-b16b-e58dd64660f3,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -cdb8493c-e3b0-447f-b16b-e58dd64660f3,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -cdb8493c-e3b0-447f-b16b-e58dd64660f3,990c21ab-433b-4920-873e-f9dfc7103d9d -cdb8493c-e3b0-447f-b16b-e58dd64660f3,922e2443-9cc5-421f-8310-9cd23f6e9f2d -cdb8493c-e3b0-447f-b16b-e58dd64660f3,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,8089a9ac-9e71-4487-a231-f720e4bc8d3e -cdb8493c-e3b0-447f-b16b-e58dd64660f3,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -cdb8493c-e3b0-447f-b16b-e58dd64660f3,6d47a2fe-3645-4c5c-bebe-1b822eff197f -cdb8493c-e3b0-447f-b16b-e58dd64660f3,67fc12fc-bb9f-40f1-9051-127a788b3081 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,634f0889-3a83-484a-bcc8-86f48e333c7b -cdb8493c-e3b0-447f-b16b-e58dd64660f3,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,4adcaf72-5241-4877-b61c-f34060576c50 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -cdb8493c-e3b0-447f-b16b-e58dd64660f3,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,374fd699-6b74-4500-9d60-2473c7e0364f -cdb8493c-e3b0-447f-b16b-e58dd64660f3,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,308eba53-29fa-489a-97dc-741b077841a7 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -cdb8493c-e3b0-447f-b16b-e58dd64660f3,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,21995497-0eb8-4c0b-8c23-e6a829f3d596 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,18ebf5ea-b0a2-4333-9e9c-40217de809ff -cdb8493c-e3b0-447f-b16b-e58dd64660f3,151e3048-66ad-4411-8753-677877e3bf0a -cdb8493c-e3b0-447f-b16b-e58dd64660f3,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -cdb8493c-e3b0-447f-b16b-e58dd64660f3,0af4a77e-892e-4b3f-9110-4c5224782250 -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,f77a8561-5adc-452a-ac9a-76273b6a6678 -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,d79728e1-8834-4f4a-8edc-ce18aca18be6 -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,cf67a8d4-48e2-4dc9-a521-6aabcff0330b -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,bb1a7816-4798-4f58-9ce4-c5bd3af682a8 -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,a6cb423a-7571-46df-83e2-ccc6820adb36 -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,7e7322a6-7912-4101-b3be-d134245a0626 -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,777aa5f2-2a09-4aed-92ea-912694e28b48 -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,6e187f47-91a1-4217-a185-31b6f01db225 -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,62059efc-7607-41c9-a3ba-70948f6a8a1d -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,2d2e07ec-e697-4384-a679-867e93740921 -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,20fbcb6e-d793-4b05-8e29-8ff82572b0db -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,1fd714a6-48b4-425a-99b3-ba5c1a995cce -cfcbe34a-edc5-4442-bc05-5927fa00336b,e1c2ad9f-6f61-4f16-805a-2f405b63659a -cfcbe34a-edc5-4442-bc05-5927fa00336b,d6988116-3c63-44f8-9f94-9e9d51cc5a37 -cfcbe34a-edc5-4442-bc05-5927fa00336b,cfcbe34a-edc5-4442-bc05-5927fa00336b -cfcbe34a-edc5-4442-bc05-5927fa00336b,c90aae7e-5704-4e13-8794-41ab377193bd -cfcbe34a-edc5-4442-bc05-5927fa00336b,bf895c48-1d52-4780-8e9a-532d69356d28 -cfcbe34a-edc5-4442-bc05-5927fa00336b,ae9e663e-8c15-43c9-8a88-52b61cbf07a9 -cfcbe34a-edc5-4442-bc05-5927fa00336b,56999896-e36b-4e63-a6b6-dbb85dfe1936 -cfcbe34a-edc5-4442-bc05-5927fa00336b,51241857-a7a4-4517-96e3-21f797581f89 -cfcbe34a-edc5-4442-bc05-5927fa00336b,4cd95073-6db3-4eb2-ac4b-0aacb182b352 -cfcbe34a-edc5-4442-bc05-5927fa00336b,4148ac36-1dcb-4e96-89cd-355e7e8f919b -cfcbe34a-edc5-4442-bc05-5927fa00336b,2e8eb256-84c9-499a-8bf6-bb39504373e1 -cfcbe34a-edc5-4442-bc05-5927fa00336b,07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 -d05461d6-13bf-402c-890d-db6404933f7c,f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 -d05461d6-13bf-402c-890d-db6404933f7c,f3aa491a-4dbd-4436-b674-18b2177dcf18 -d05461d6-13bf-402c-890d-db6404933f7c,e1040d58-53bc-4467-8101-ca53b772cede -d05461d6-13bf-402c-890d-db6404933f7c,d05461d6-13bf-402c-890d-db6404933f7c -d05461d6-13bf-402c-890d-db6404933f7c,b7772635-1801-48e4-a442-f4aaa6860544 -d05461d6-13bf-402c-890d-db6404933f7c,b07fb98d-2da4-423a-83b4-7a673de93096 -d05461d6-13bf-402c-890d-db6404933f7c,97b42d67-8691-433d-8a31-dada076162ae -d05461d6-13bf-402c-890d-db6404933f7c,720f7ed6-7b2e-41dc-a0e0-724a3332aa24 -d05461d6-13bf-402c-890d-db6404933f7c,4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 -d05461d6-13bf-402c-890d-db6404933f7c,2ffd6142-b75b-406f-bc06-cdb1c02eaefc -d05461d6-13bf-402c-890d-db6404933f7c,224e538d-3622-4f5e-b772-211ed358d8de -d05461d6-13bf-402c-890d-db6404933f7c,16a3408d-c927-4d02-a1ae-cad32419caab -d05461d6-13bf-402c-890d-db6404933f7c,04d88eb4-fb1b-4fa1-802a-5624f4c61b32 -d080c116-681a-494a-a928-45924109b49d,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -d080c116-681a-494a-a928-45924109b49d,ee24bf89-81a3-4259-a382-86917f3829d4 -d080c116-681a-494a-a928-45924109b49d,e363eb67-64c7-440f-93fd-5c8787c69a85 -d080c116-681a-494a-a928-45924109b49d,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -d080c116-681a-494a-a928-45924109b49d,db79f75d-af3c-4f82-b4e5-9b5d26598eef -d080c116-681a-494a-a928-45924109b49d,d7a48a26-7731-4046-bf22-78f0b8084eb9 -d080c116-681a-494a-a928-45924109b49d,d080c116-681a-494a-a928-45924109b49d -d080c116-681a-494a-a928-45924109b49d,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -d080c116-681a-494a-a928-45924109b49d,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -d080c116-681a-494a-a928-45924109b49d,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -d080c116-681a-494a-a928-45924109b49d,990c21ab-433b-4920-873e-f9dfc7103d9d -d080c116-681a-494a-a928-45924109b49d,922e2443-9cc5-421f-8310-9cd23f6e9f2d -d080c116-681a-494a-a928-45924109b49d,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -d080c116-681a-494a-a928-45924109b49d,8089a9ac-9e71-4487-a231-f720e4bc8d3e -d080c116-681a-494a-a928-45924109b49d,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -d080c116-681a-494a-a928-45924109b49d,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -d080c116-681a-494a-a928-45924109b49d,6d47a2fe-3645-4c5c-bebe-1b822eff197f -d080c116-681a-494a-a928-45924109b49d,67fc12fc-bb9f-40f1-9051-127a788b3081 -d080c116-681a-494a-a928-45924109b49d,634f0889-3a83-484a-bcc8-86f48e333c7b -d080c116-681a-494a-a928-45924109b49d,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -d080c116-681a-494a-a928-45924109b49d,4adcaf72-5241-4877-b61c-f34060576c50 -d080c116-681a-494a-a928-45924109b49d,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -d080c116-681a-494a-a928-45924109b49d,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -d080c116-681a-494a-a928-45924109b49d,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -d080c116-681a-494a-a928-45924109b49d,374fd699-6b74-4500-9d60-2473c7e0364f -d080c116-681a-494a-a928-45924109b49d,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -d080c116-681a-494a-a928-45924109b49d,308eba53-29fa-489a-97dc-741b077841a7 -d080c116-681a-494a-a928-45924109b49d,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -d080c116-681a-494a-a928-45924109b49d,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -d080c116-681a-494a-a928-45924109b49d,21995497-0eb8-4c0b-8c23-e6a829f3d596 -d080c116-681a-494a-a928-45924109b49d,18ebf5ea-b0a2-4333-9e9c-40217de809ff -d080c116-681a-494a-a928-45924109b49d,151e3048-66ad-4411-8753-677877e3bf0a -d080c116-681a-494a-a928-45924109b49d,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -d080c116-681a-494a-a928-45924109b49d,0af4a77e-892e-4b3f-9110-4c5224782250 -d535b213-2abc-438f-aa6a-c06476d60b09,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -d535b213-2abc-438f-aa6a-c06476d60b09,e8eba267-fecb-477e-9625-771fbcfc3cba -d535b213-2abc-438f-aa6a-c06476d60b09,e8c41168-a093-40eb-8baa-6802d24f554a -d535b213-2abc-438f-aa6a-c06476d60b09,e26ae29a-213a-4f79-98c2-cc81cf2f451d -d535b213-2abc-438f-aa6a-c06476d60b09,e0dfbc08-45ad-4a81-b648-7e65c066f673 -d535b213-2abc-438f-aa6a-c06476d60b09,d535b213-2abc-438f-aa6a-c06476d60b09 -d535b213-2abc-438f-aa6a-c06476d60b09,c892b1d7-7485-407d-8883-54fb7fecebc1 -d535b213-2abc-438f-aa6a-c06476d60b09,b6900c21-d310-4fb4-8b8f-176a09c91989 -d535b213-2abc-438f-aa6a-c06476d60b09,b00df815-7528-4967-bfe2-311130d91c21 -d535b213-2abc-438f-aa6a-c06476d60b09,add6d754-a075-4693-a33a-c061c9a368ff -d535b213-2abc-438f-aa6a-c06476d60b09,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -d535b213-2abc-438f-aa6a-c06476d60b09,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -d535b213-2abc-438f-aa6a-c06476d60b09,8a1908f7-47cc-4f82-859c-781a193e9901 -d535b213-2abc-438f-aa6a-c06476d60b09,8743e69b-17aa-44ff-b8fa-4f582665efc6 -d535b213-2abc-438f-aa6a-c06476d60b09,721e4027-a080-40d8-bc4a-43cd33477611 -d535b213-2abc-438f-aa6a-c06476d60b09,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -d535b213-2abc-438f-aa6a-c06476d60b09,54c750aa-570a-4e8e-9a02-418781923e39 -d535b213-2abc-438f-aa6a-c06476d60b09,50eac25a-3761-4de3-8a69-aae52e658300 -d535b213-2abc-438f-aa6a-c06476d60b09,4a464bb5-9373-4f1b-9c03-053c64797114 -d535b213-2abc-438f-aa6a-c06476d60b09,4506aa76-9d0d-40e4-85bc-5735f74522a0 -d535b213-2abc-438f-aa6a-c06476d60b09,42a9a333-d09d-4b9e-af96-1f02af26bac3 -d535b213-2abc-438f-aa6a-c06476d60b09,2fc75f11-2097-4e1e-8390-dbff3e844b7a -d535b213-2abc-438f-aa6a-c06476d60b09,274a2e49-9170-4945-bca2-ab6ef4bded75 -d535b213-2abc-438f-aa6a-c06476d60b09,0364073f-91d8-47a1-b8b0-107c6318a691 -d6988116-3c63-44f8-9f94-9e9d51cc5a37,e1c2ad9f-6f61-4f16-805a-2f405b63659a -d6988116-3c63-44f8-9f94-9e9d51cc5a37,d6988116-3c63-44f8-9f94-9e9d51cc5a37 -d6988116-3c63-44f8-9f94-9e9d51cc5a37,cfcbe34a-edc5-4442-bc05-5927fa00336b -d6988116-3c63-44f8-9f94-9e9d51cc5a37,c90aae7e-5704-4e13-8794-41ab377193bd -d6988116-3c63-44f8-9f94-9e9d51cc5a37,bf895c48-1d52-4780-8e9a-532d69356d28 -d6988116-3c63-44f8-9f94-9e9d51cc5a37,ae9e663e-8c15-43c9-8a88-52b61cbf07a9 -d6988116-3c63-44f8-9f94-9e9d51cc5a37,56999896-e36b-4e63-a6b6-dbb85dfe1936 -d6988116-3c63-44f8-9f94-9e9d51cc5a37,51241857-a7a4-4517-96e3-21f797581f89 -d6988116-3c63-44f8-9f94-9e9d51cc5a37,4cd95073-6db3-4eb2-ac4b-0aacb182b352 -d6988116-3c63-44f8-9f94-9e9d51cc5a37,4148ac36-1dcb-4e96-89cd-355e7e8f919b -d6988116-3c63-44f8-9f94-9e9d51cc5a37,2e8eb256-84c9-499a-8bf6-bb39504373e1 -d6988116-3c63-44f8-9f94-9e9d51cc5a37,07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 -d79728e1-8834-4f4a-8edc-ce18aca18be6,f77a8561-5adc-452a-ac9a-76273b6a6678 -d79728e1-8834-4f4a-8edc-ce18aca18be6,d79728e1-8834-4f4a-8edc-ce18aca18be6 -d79728e1-8834-4f4a-8edc-ce18aca18be6,cf67a8d4-48e2-4dc9-a521-6aabcff0330b -d79728e1-8834-4f4a-8edc-ce18aca18be6,bb1a7816-4798-4f58-9ce4-c5bd3af682a8 -d79728e1-8834-4f4a-8edc-ce18aca18be6,b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 -d79728e1-8834-4f4a-8edc-ce18aca18be6,a6cb423a-7571-46df-83e2-ccc6820adb36 -d79728e1-8834-4f4a-8edc-ce18aca18be6,7e7322a6-7912-4101-b3be-d134245a0626 -d79728e1-8834-4f4a-8edc-ce18aca18be6,777aa5f2-2a09-4aed-92ea-912694e28b48 -d79728e1-8834-4f4a-8edc-ce18aca18be6,6e187f47-91a1-4217-a185-31b6f01db225 -d79728e1-8834-4f4a-8edc-ce18aca18be6,62059efc-7607-41c9-a3ba-70948f6a8a1d -d79728e1-8834-4f4a-8edc-ce18aca18be6,2d2e07ec-e697-4384-a679-867e93740921 -d79728e1-8834-4f4a-8edc-ce18aca18be6,20fbcb6e-d793-4b05-8e29-8ff82572b0db -d79728e1-8834-4f4a-8edc-ce18aca18be6,1fd714a6-48b4-425a-99b3-ba5c1a995cce -d7a48a26-7731-4046-bf22-78f0b8084eb9,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -d7a48a26-7731-4046-bf22-78f0b8084eb9,ee24bf89-81a3-4259-a382-86917f3829d4 -d7a48a26-7731-4046-bf22-78f0b8084eb9,e363eb67-64c7-440f-93fd-5c8787c69a85 -d7a48a26-7731-4046-bf22-78f0b8084eb9,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -d7a48a26-7731-4046-bf22-78f0b8084eb9,db79f75d-af3c-4f82-b4e5-9b5d26598eef -d7a48a26-7731-4046-bf22-78f0b8084eb9,d7a48a26-7731-4046-bf22-78f0b8084eb9 -d7a48a26-7731-4046-bf22-78f0b8084eb9,d080c116-681a-494a-a928-45924109b49d -d7a48a26-7731-4046-bf22-78f0b8084eb9,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -d7a48a26-7731-4046-bf22-78f0b8084eb9,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -d7a48a26-7731-4046-bf22-78f0b8084eb9,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -d7a48a26-7731-4046-bf22-78f0b8084eb9,990c21ab-433b-4920-873e-f9dfc7103d9d -d7a48a26-7731-4046-bf22-78f0b8084eb9,922e2443-9cc5-421f-8310-9cd23f6e9f2d -d7a48a26-7731-4046-bf22-78f0b8084eb9,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -d7a48a26-7731-4046-bf22-78f0b8084eb9,8089a9ac-9e71-4487-a231-f720e4bc8d3e -d7a48a26-7731-4046-bf22-78f0b8084eb9,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -d7a48a26-7731-4046-bf22-78f0b8084eb9,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -d7a48a26-7731-4046-bf22-78f0b8084eb9,6d47a2fe-3645-4c5c-bebe-1b822eff197f -d7a48a26-7731-4046-bf22-78f0b8084eb9,67fc12fc-bb9f-40f1-9051-127a788b3081 -d7a48a26-7731-4046-bf22-78f0b8084eb9,634f0889-3a83-484a-bcc8-86f48e333c7b -d7a48a26-7731-4046-bf22-78f0b8084eb9,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -d7a48a26-7731-4046-bf22-78f0b8084eb9,4adcaf72-5241-4877-b61c-f34060576c50 -d7a48a26-7731-4046-bf22-78f0b8084eb9,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -d7a48a26-7731-4046-bf22-78f0b8084eb9,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -d7a48a26-7731-4046-bf22-78f0b8084eb9,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -d7a48a26-7731-4046-bf22-78f0b8084eb9,374fd699-6b74-4500-9d60-2473c7e0364f -d7a48a26-7731-4046-bf22-78f0b8084eb9,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -d7a48a26-7731-4046-bf22-78f0b8084eb9,308eba53-29fa-489a-97dc-741b077841a7 -d7a48a26-7731-4046-bf22-78f0b8084eb9,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -d7a48a26-7731-4046-bf22-78f0b8084eb9,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -d7a48a26-7731-4046-bf22-78f0b8084eb9,21995497-0eb8-4c0b-8c23-e6a829f3d596 -d7a48a26-7731-4046-bf22-78f0b8084eb9,18ebf5ea-b0a2-4333-9e9c-40217de809ff -d7a48a26-7731-4046-bf22-78f0b8084eb9,151e3048-66ad-4411-8753-677877e3bf0a -d7a48a26-7731-4046-bf22-78f0b8084eb9,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -d7a48a26-7731-4046-bf22-78f0b8084eb9,0af4a77e-892e-4b3f-9110-4c5224782250 -d90676d2-ce74-4867-a00f-6dbffa7c00be,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -d90676d2-ce74-4867-a00f-6dbffa7c00be,d90676d2-ce74-4867-a00f-6dbffa7c00be -d90676d2-ce74-4867-a00f-6dbffa7c00be,cc56af6d-25c6-480e-9767-da02a55551da -d90676d2-ce74-4867-a00f-6dbffa7c00be,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -d90676d2-ce74-4867-a00f-6dbffa7c00be,baf48725-f0f9-4357-a71d-b1104910deae -d90676d2-ce74-4867-a00f-6dbffa7c00be,9ed8703e-3b40-424c-9e98-b3b404051f99 -d90676d2-ce74-4867-a00f-6dbffa7c00be,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -d90676d2-ce74-4867-a00f-6dbffa7c00be,8be81657-8ba6-4ec5-8ff9-4809367096ea -d90676d2-ce74-4867-a00f-6dbffa7c00be,7f410064-638a-4477-85c4-97fc2bb14a49 -d90676d2-ce74-4867-a00f-6dbffa7c00be,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -d90676d2-ce74-4867-a00f-6dbffa7c00be,5cedf18a-fc44-4c39-a80f-2106a0d76934 -d90676d2-ce74-4867-a00f-6dbffa7c00be,4f342a71-dec3-403e-970b-e4c21b9eb98b -d90676d2-ce74-4867-a00f-6dbffa7c00be,4db144d3-a596-4718-a50a-8ac9175ad386 -d90676d2-ce74-4867-a00f-6dbffa7c00be,4215b5d0-bdd9-4222-a04a-81bb637d60af -d90676d2-ce74-4867-a00f-6dbffa7c00be,29d4e919-2bd2-44e6-bb8a-380a780f60ff -d90676d2-ce74-4867-a00f-6dbffa7c00be,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -d90676d2-ce74-4867-a00f-6dbffa7c00be,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -d90676d2-ce74-4867-a00f-6dbffa7c00be,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -d90676d2-ce74-4867-a00f-6dbffa7c00be,102e16c5-4920-4dad-b142-0b280b2aacad -d90676d2-ce74-4867-a00f-6dbffa7c00be,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2,d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2 -d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2,395019b6-84e8-4919-8b8b-ac64e4a6eade -db79f75d-af3c-4f82-b4e5-9b5d26598eef,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -db79f75d-af3c-4f82-b4e5-9b5d26598eef,ee24bf89-81a3-4259-a382-86917f3829d4 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,e363eb67-64c7-440f-93fd-5c8787c69a85 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,db79f75d-af3c-4f82-b4e5-9b5d26598eef -db79f75d-af3c-4f82-b4e5-9b5d26598eef,d7a48a26-7731-4046-bf22-78f0b8084eb9 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,d080c116-681a-494a-a928-45924109b49d -db79f75d-af3c-4f82-b4e5-9b5d26598eef,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -db79f75d-af3c-4f82-b4e5-9b5d26598eef,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -db79f75d-af3c-4f82-b4e5-9b5d26598eef,990c21ab-433b-4920-873e-f9dfc7103d9d -db79f75d-af3c-4f82-b4e5-9b5d26598eef,922e2443-9cc5-421f-8310-9cd23f6e9f2d -db79f75d-af3c-4f82-b4e5-9b5d26598eef,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,8089a9ac-9e71-4487-a231-f720e4bc8d3e -db79f75d-af3c-4f82-b4e5-9b5d26598eef,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -db79f75d-af3c-4f82-b4e5-9b5d26598eef,6d47a2fe-3645-4c5c-bebe-1b822eff197f -db79f75d-af3c-4f82-b4e5-9b5d26598eef,67fc12fc-bb9f-40f1-9051-127a788b3081 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,634f0889-3a83-484a-bcc8-86f48e333c7b -db79f75d-af3c-4f82-b4e5-9b5d26598eef,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,4adcaf72-5241-4877-b61c-f34060576c50 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -db79f75d-af3c-4f82-b4e5-9b5d26598eef,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,374fd699-6b74-4500-9d60-2473c7e0364f -db79f75d-af3c-4f82-b4e5-9b5d26598eef,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,308eba53-29fa-489a-97dc-741b077841a7 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -db79f75d-af3c-4f82-b4e5-9b5d26598eef,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,21995497-0eb8-4c0b-8c23-e6a829f3d596 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,18ebf5ea-b0a2-4333-9e9c-40217de809ff -db79f75d-af3c-4f82-b4e5-9b5d26598eef,151e3048-66ad-4411-8753-677877e3bf0a -db79f75d-af3c-4f82-b4e5-9b5d26598eef,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -db79f75d-af3c-4f82-b4e5-9b5d26598eef,0af4a77e-892e-4b3f-9110-4c5224782250 -def5fd23-2b74-4c64-85e9-fac27e626bb7,def5fd23-2b74-4c64-85e9-fac27e626bb7 -def5fd23-2b74-4c64-85e9-fac27e626bb7,96b2282d-1384-4cfb-9958-f009fb501626 -def5fd23-2b74-4c64-85e9-fac27e626bb7,17bcf2ea-d921-4715-91c5-6b15226b33d3 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,ee24bf89-81a3-4259-a382-86917f3829d4 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,e363eb67-64c7-440f-93fd-5c8787c69a85 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,db79f75d-af3c-4f82-b4e5-9b5d26598eef -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,d7a48a26-7731-4046-bf22-78f0b8084eb9 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,d080c116-681a-494a-a928-45924109b49d -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,990c21ab-433b-4920-873e-f9dfc7103d9d -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,922e2443-9cc5-421f-8310-9cd23f6e9f2d -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,8089a9ac-9e71-4487-a231-f720e4bc8d3e -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,6d47a2fe-3645-4c5c-bebe-1b822eff197f -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,67fc12fc-bb9f-40f1-9051-127a788b3081 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,634f0889-3a83-484a-bcc8-86f48e333c7b -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,4adcaf72-5241-4877-b61c-f34060576c50 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,374fd699-6b74-4500-9d60-2473c7e0364f -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,308eba53-29fa-489a-97dc-741b077841a7 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,21995497-0eb8-4c0b-8c23-e6a829f3d596 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,18ebf5ea-b0a2-4333-9e9c-40217de809ff -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,151e3048-66ad-4411-8753-677877e3bf0a -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,0af4a77e-892e-4b3f-9110-4c5224782250 -e0dfbc08-45ad-4a81-b648-7e65c066f673,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -e0dfbc08-45ad-4a81-b648-7e65c066f673,e8eba267-fecb-477e-9625-771fbcfc3cba -e0dfbc08-45ad-4a81-b648-7e65c066f673,e8c41168-a093-40eb-8baa-6802d24f554a -e0dfbc08-45ad-4a81-b648-7e65c066f673,e26ae29a-213a-4f79-98c2-cc81cf2f451d -e0dfbc08-45ad-4a81-b648-7e65c066f673,e0dfbc08-45ad-4a81-b648-7e65c066f673 -e0dfbc08-45ad-4a81-b648-7e65c066f673,d535b213-2abc-438f-aa6a-c06476d60b09 -e0dfbc08-45ad-4a81-b648-7e65c066f673,c892b1d7-7485-407d-8883-54fb7fecebc1 -e0dfbc08-45ad-4a81-b648-7e65c066f673,b6900c21-d310-4fb4-8b8f-176a09c91989 -e0dfbc08-45ad-4a81-b648-7e65c066f673,b00df815-7528-4967-bfe2-311130d91c21 -e0dfbc08-45ad-4a81-b648-7e65c066f673,add6d754-a075-4693-a33a-c061c9a368ff -e0dfbc08-45ad-4a81-b648-7e65c066f673,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -e0dfbc08-45ad-4a81-b648-7e65c066f673,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -e0dfbc08-45ad-4a81-b648-7e65c066f673,8a1908f7-47cc-4f82-859c-781a193e9901 -e0dfbc08-45ad-4a81-b648-7e65c066f673,8743e69b-17aa-44ff-b8fa-4f582665efc6 -e0dfbc08-45ad-4a81-b648-7e65c066f673,721e4027-a080-40d8-bc4a-43cd33477611 -e0dfbc08-45ad-4a81-b648-7e65c066f673,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -e0dfbc08-45ad-4a81-b648-7e65c066f673,54c750aa-570a-4e8e-9a02-418781923e39 -e0dfbc08-45ad-4a81-b648-7e65c066f673,50eac25a-3761-4de3-8a69-aae52e658300 -e0dfbc08-45ad-4a81-b648-7e65c066f673,4a464bb5-9373-4f1b-9c03-053c64797114 -e0dfbc08-45ad-4a81-b648-7e65c066f673,4506aa76-9d0d-40e4-85bc-5735f74522a0 -e0dfbc08-45ad-4a81-b648-7e65c066f673,42a9a333-d09d-4b9e-af96-1f02af26bac3 -e0dfbc08-45ad-4a81-b648-7e65c066f673,2fc75f11-2097-4e1e-8390-dbff3e844b7a -e0dfbc08-45ad-4a81-b648-7e65c066f673,274a2e49-9170-4945-bca2-ab6ef4bded75 -e0dfbc08-45ad-4a81-b648-7e65c066f673,0364073f-91d8-47a1-b8b0-107c6318a691 -e1040d58-53bc-4467-8101-ca53b772cede,f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 -e1040d58-53bc-4467-8101-ca53b772cede,f3aa491a-4dbd-4436-b674-18b2177dcf18 -e1040d58-53bc-4467-8101-ca53b772cede,e1040d58-53bc-4467-8101-ca53b772cede -e1040d58-53bc-4467-8101-ca53b772cede,d05461d6-13bf-402c-890d-db6404933f7c -e1040d58-53bc-4467-8101-ca53b772cede,b7772635-1801-48e4-a442-f4aaa6860544 -e1040d58-53bc-4467-8101-ca53b772cede,b07fb98d-2da4-423a-83b4-7a673de93096 -e1040d58-53bc-4467-8101-ca53b772cede,97b42d67-8691-433d-8a31-dada076162ae -e1040d58-53bc-4467-8101-ca53b772cede,720f7ed6-7b2e-41dc-a0e0-724a3332aa24 -e1040d58-53bc-4467-8101-ca53b772cede,4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 -e1040d58-53bc-4467-8101-ca53b772cede,2ffd6142-b75b-406f-bc06-cdb1c02eaefc -e1040d58-53bc-4467-8101-ca53b772cede,224e538d-3622-4f5e-b772-211ed358d8de -e1040d58-53bc-4467-8101-ca53b772cede,16a3408d-c927-4d02-a1ae-cad32419caab -e1040d58-53bc-4467-8101-ca53b772cede,04d88eb4-fb1b-4fa1-802a-5624f4c61b32 -e1c2ad9f-6f61-4f16-805a-2f405b63659a,e1c2ad9f-6f61-4f16-805a-2f405b63659a -e1c2ad9f-6f61-4f16-805a-2f405b63659a,d6988116-3c63-44f8-9f94-9e9d51cc5a37 -e1c2ad9f-6f61-4f16-805a-2f405b63659a,cfcbe34a-edc5-4442-bc05-5927fa00336b -e1c2ad9f-6f61-4f16-805a-2f405b63659a,c90aae7e-5704-4e13-8794-41ab377193bd -e1c2ad9f-6f61-4f16-805a-2f405b63659a,bf895c48-1d52-4780-8e9a-532d69356d28 -e1c2ad9f-6f61-4f16-805a-2f405b63659a,ae9e663e-8c15-43c9-8a88-52b61cbf07a9 -e1c2ad9f-6f61-4f16-805a-2f405b63659a,56999896-e36b-4e63-a6b6-dbb85dfe1936 -e1c2ad9f-6f61-4f16-805a-2f405b63659a,51241857-a7a4-4517-96e3-21f797581f89 -e1c2ad9f-6f61-4f16-805a-2f405b63659a,4cd95073-6db3-4eb2-ac4b-0aacb182b352 -e1c2ad9f-6f61-4f16-805a-2f405b63659a,4148ac36-1dcb-4e96-89cd-355e7e8f919b -e1c2ad9f-6f61-4f16-805a-2f405b63659a,2e8eb256-84c9-499a-8bf6-bb39504373e1 -e1c2ad9f-6f61-4f16-805a-2f405b63659a,07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 -e2387a81-5ff5-4daf-b2e8-881998d0d917,fc771883-3bd6-46d9-9626-a130e1b902de -e2387a81-5ff5-4daf-b2e8-881998d0d917,e2387a81-5ff5-4daf-b2e8-881998d0d917 -e2387a81-5ff5-4daf-b2e8-881998d0d917,b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa -e2387a81-5ff5-4daf-b2e8-881998d0d917,b1d4dc41-07ae-44db-ae17-1df86c5a62cf -e2387a81-5ff5-4daf-b2e8-881998d0d917,67e5cf95-236b-4f75-abc5-034ca2966448 -e2387a81-5ff5-4daf-b2e8-881998d0d917,48cc2275-a478-4ade-b076-cf38e1d16017 -e2387a81-5ff5-4daf-b2e8-881998d0d917,41d26b1c-57b9-408c-b955-bf0ee1db4809 -e2387a81-5ff5-4daf-b2e8-881998d0d917,3ddf46fe-b5be-456d-810f-ea6a7402236d -e2387a81-5ff5-4daf-b2e8-881998d0d917,3397a796-894d-409c-97de-a9e6f3f88198 -e2387a81-5ff5-4daf-b2e8-881998d0d917,19502b5a-2031-487d-9d9c-2001f959408b -e26ae29a-213a-4f79-98c2-cc81cf2f451d,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -e26ae29a-213a-4f79-98c2-cc81cf2f451d,e8eba267-fecb-477e-9625-771fbcfc3cba -e26ae29a-213a-4f79-98c2-cc81cf2f451d,e8c41168-a093-40eb-8baa-6802d24f554a -e26ae29a-213a-4f79-98c2-cc81cf2f451d,e26ae29a-213a-4f79-98c2-cc81cf2f451d -e26ae29a-213a-4f79-98c2-cc81cf2f451d,e0dfbc08-45ad-4a81-b648-7e65c066f673 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,d535b213-2abc-438f-aa6a-c06476d60b09 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,c892b1d7-7485-407d-8883-54fb7fecebc1 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,b6900c21-d310-4fb4-8b8f-176a09c91989 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,b00df815-7528-4967-bfe2-311130d91c21 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,add6d754-a075-4693-a33a-c061c9a368ff -e26ae29a-213a-4f79-98c2-cc81cf2f451d,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,8a1908f7-47cc-4f82-859c-781a193e9901 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,8743e69b-17aa-44ff-b8fa-4f582665efc6 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,721e4027-a080-40d8-bc4a-43cd33477611 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,54c750aa-570a-4e8e-9a02-418781923e39 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,50eac25a-3761-4de3-8a69-aae52e658300 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,4a464bb5-9373-4f1b-9c03-053c64797114 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,4506aa76-9d0d-40e4-85bc-5735f74522a0 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,42a9a333-d09d-4b9e-af96-1f02af26bac3 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,2fc75f11-2097-4e1e-8390-dbff3e844b7a -e26ae29a-213a-4f79-98c2-cc81cf2f451d,274a2e49-9170-4945-bca2-ab6ef4bded75 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,0364073f-91d8-47a1-b8b0-107c6318a691 -e363eb67-64c7-440f-93fd-5c8787c69a85,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -e363eb67-64c7-440f-93fd-5c8787c69a85,ee24bf89-81a3-4259-a382-86917f3829d4 -e363eb67-64c7-440f-93fd-5c8787c69a85,e363eb67-64c7-440f-93fd-5c8787c69a85 -e363eb67-64c7-440f-93fd-5c8787c69a85,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -e363eb67-64c7-440f-93fd-5c8787c69a85,db79f75d-af3c-4f82-b4e5-9b5d26598eef -e363eb67-64c7-440f-93fd-5c8787c69a85,d7a48a26-7731-4046-bf22-78f0b8084eb9 -e363eb67-64c7-440f-93fd-5c8787c69a85,d080c116-681a-494a-a928-45924109b49d -e363eb67-64c7-440f-93fd-5c8787c69a85,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -e363eb67-64c7-440f-93fd-5c8787c69a85,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -e363eb67-64c7-440f-93fd-5c8787c69a85,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -e363eb67-64c7-440f-93fd-5c8787c69a85,990c21ab-433b-4920-873e-f9dfc7103d9d -e363eb67-64c7-440f-93fd-5c8787c69a85,922e2443-9cc5-421f-8310-9cd23f6e9f2d -e363eb67-64c7-440f-93fd-5c8787c69a85,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -e363eb67-64c7-440f-93fd-5c8787c69a85,8089a9ac-9e71-4487-a231-f720e4bc8d3e -e363eb67-64c7-440f-93fd-5c8787c69a85,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -e363eb67-64c7-440f-93fd-5c8787c69a85,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -e363eb67-64c7-440f-93fd-5c8787c69a85,6d47a2fe-3645-4c5c-bebe-1b822eff197f -e363eb67-64c7-440f-93fd-5c8787c69a85,67fc12fc-bb9f-40f1-9051-127a788b3081 -e363eb67-64c7-440f-93fd-5c8787c69a85,634f0889-3a83-484a-bcc8-86f48e333c7b -e363eb67-64c7-440f-93fd-5c8787c69a85,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -e363eb67-64c7-440f-93fd-5c8787c69a85,4adcaf72-5241-4877-b61c-f34060576c50 -e363eb67-64c7-440f-93fd-5c8787c69a85,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -e363eb67-64c7-440f-93fd-5c8787c69a85,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -e363eb67-64c7-440f-93fd-5c8787c69a85,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -e363eb67-64c7-440f-93fd-5c8787c69a85,374fd699-6b74-4500-9d60-2473c7e0364f -e363eb67-64c7-440f-93fd-5c8787c69a85,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -e363eb67-64c7-440f-93fd-5c8787c69a85,308eba53-29fa-489a-97dc-741b077841a7 -e363eb67-64c7-440f-93fd-5c8787c69a85,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -e363eb67-64c7-440f-93fd-5c8787c69a85,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -e363eb67-64c7-440f-93fd-5c8787c69a85,21995497-0eb8-4c0b-8c23-e6a829f3d596 -e363eb67-64c7-440f-93fd-5c8787c69a85,18ebf5ea-b0a2-4333-9e9c-40217de809ff -e363eb67-64c7-440f-93fd-5c8787c69a85,151e3048-66ad-4411-8753-677877e3bf0a -e363eb67-64c7-440f-93fd-5c8787c69a85,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -e363eb67-64c7-440f-93fd-5c8787c69a85,0af4a77e-892e-4b3f-9110-4c5224782250 -e4358f28-62a8-4e06-b2bd-cb00d3310349,fcab9efe-fb05-42d6-b366-ce6db1cc4093 -e4358f28-62a8-4e06-b2bd-cb00d3310349,f1293c5e-c997-4ba9-8bfd-438e7a840dc8 -e4358f28-62a8-4e06-b2bd-cb00d3310349,e4358f28-62a8-4e06-b2bd-cb00d3310349 -e4358f28-62a8-4e06-b2bd-cb00d3310349,8f76d729-9f74-4cfd-be2a-8b033b568e18 -e4358f28-62a8-4e06-b2bd-cb00d3310349,39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 -e4358f28-62a8-4e06-b2bd-cb00d3310349,31d4c712-75b1-4ecb-9d1a-ccc4b8453826 -e4358f28-62a8-4e06-b2bd-cb00d3310349,24c4dd5b-748b-4165-a0cc-2867b76c2dc2 -e4358f28-62a8-4e06-b2bd-cb00d3310349,16d280a2-c61f-487f-9034-22e884158969 -e4358f28-62a8-4e06-b2bd-cb00d3310349,0134901a-4a69-4b39-92ea-d6f48ec83c6e -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,f883f2ca-d523-4c9f-86b2-0add137901de -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,f28931e5-1f35-4f9f-a02e-78398735ea67 -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,e7f68d35-ae55-4fa4-b0be-0672a5a7186a -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,7ac56d5a-32a7-4b40-a956-356e3006faed -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,72788e7f-1bf1-40b5-a1f1-27c669dc7278 -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,6c84348c-5065-4e89-9412-bfda023683f2 -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,5abe533b-ae5f-47ad-8746-f60caf7483c0 -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,4f14ca7f-5332-4263-a7b2-f0a838380309 -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,359cb842-c25b-429f-ba9b-d52f171e5631 -e8c41168-a093-40eb-8baa-6802d24f554a,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -e8c41168-a093-40eb-8baa-6802d24f554a,e8eba267-fecb-477e-9625-771fbcfc3cba -e8c41168-a093-40eb-8baa-6802d24f554a,e8c41168-a093-40eb-8baa-6802d24f554a -e8c41168-a093-40eb-8baa-6802d24f554a,e26ae29a-213a-4f79-98c2-cc81cf2f451d -e8c41168-a093-40eb-8baa-6802d24f554a,e0dfbc08-45ad-4a81-b648-7e65c066f673 -e8c41168-a093-40eb-8baa-6802d24f554a,d535b213-2abc-438f-aa6a-c06476d60b09 -e8c41168-a093-40eb-8baa-6802d24f554a,c892b1d7-7485-407d-8883-54fb7fecebc1 -e8c41168-a093-40eb-8baa-6802d24f554a,b6900c21-d310-4fb4-8b8f-176a09c91989 -e8c41168-a093-40eb-8baa-6802d24f554a,b00df815-7528-4967-bfe2-311130d91c21 -e8c41168-a093-40eb-8baa-6802d24f554a,add6d754-a075-4693-a33a-c061c9a368ff -e8c41168-a093-40eb-8baa-6802d24f554a,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -e8c41168-a093-40eb-8baa-6802d24f554a,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -e8c41168-a093-40eb-8baa-6802d24f554a,8a1908f7-47cc-4f82-859c-781a193e9901 -e8c41168-a093-40eb-8baa-6802d24f554a,8743e69b-17aa-44ff-b8fa-4f582665efc6 -e8c41168-a093-40eb-8baa-6802d24f554a,721e4027-a080-40d8-bc4a-43cd33477611 -e8c41168-a093-40eb-8baa-6802d24f554a,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -e8c41168-a093-40eb-8baa-6802d24f554a,54c750aa-570a-4e8e-9a02-418781923e39 -e8c41168-a093-40eb-8baa-6802d24f554a,50eac25a-3761-4de3-8a69-aae52e658300 -e8c41168-a093-40eb-8baa-6802d24f554a,4a464bb5-9373-4f1b-9c03-053c64797114 -e8c41168-a093-40eb-8baa-6802d24f554a,4506aa76-9d0d-40e4-85bc-5735f74522a0 -e8c41168-a093-40eb-8baa-6802d24f554a,42a9a333-d09d-4b9e-af96-1f02af26bac3 -e8c41168-a093-40eb-8baa-6802d24f554a,2fc75f11-2097-4e1e-8390-dbff3e844b7a -e8c41168-a093-40eb-8baa-6802d24f554a,274a2e49-9170-4945-bca2-ab6ef4bded75 -e8c41168-a093-40eb-8baa-6802d24f554a,0364073f-91d8-47a1-b8b0-107c6318a691 -e8eba267-fecb-477e-9625-771fbcfc3cba,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -e8eba267-fecb-477e-9625-771fbcfc3cba,e8eba267-fecb-477e-9625-771fbcfc3cba -e8eba267-fecb-477e-9625-771fbcfc3cba,e8c41168-a093-40eb-8baa-6802d24f554a -e8eba267-fecb-477e-9625-771fbcfc3cba,e26ae29a-213a-4f79-98c2-cc81cf2f451d -e8eba267-fecb-477e-9625-771fbcfc3cba,e0dfbc08-45ad-4a81-b648-7e65c066f673 -e8eba267-fecb-477e-9625-771fbcfc3cba,d535b213-2abc-438f-aa6a-c06476d60b09 -e8eba267-fecb-477e-9625-771fbcfc3cba,c892b1d7-7485-407d-8883-54fb7fecebc1 -e8eba267-fecb-477e-9625-771fbcfc3cba,b6900c21-d310-4fb4-8b8f-176a09c91989 -e8eba267-fecb-477e-9625-771fbcfc3cba,b00df815-7528-4967-bfe2-311130d91c21 -e8eba267-fecb-477e-9625-771fbcfc3cba,add6d754-a075-4693-a33a-c061c9a368ff -e8eba267-fecb-477e-9625-771fbcfc3cba,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -e8eba267-fecb-477e-9625-771fbcfc3cba,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -e8eba267-fecb-477e-9625-771fbcfc3cba,8a1908f7-47cc-4f82-859c-781a193e9901 -e8eba267-fecb-477e-9625-771fbcfc3cba,8743e69b-17aa-44ff-b8fa-4f582665efc6 -e8eba267-fecb-477e-9625-771fbcfc3cba,721e4027-a080-40d8-bc4a-43cd33477611 -e8eba267-fecb-477e-9625-771fbcfc3cba,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -e8eba267-fecb-477e-9625-771fbcfc3cba,54c750aa-570a-4e8e-9a02-418781923e39 -e8eba267-fecb-477e-9625-771fbcfc3cba,50eac25a-3761-4de3-8a69-aae52e658300 -e8eba267-fecb-477e-9625-771fbcfc3cba,4a464bb5-9373-4f1b-9c03-053c64797114 -e8eba267-fecb-477e-9625-771fbcfc3cba,4506aa76-9d0d-40e4-85bc-5735f74522a0 -e8eba267-fecb-477e-9625-771fbcfc3cba,42a9a333-d09d-4b9e-af96-1f02af26bac3 -e8eba267-fecb-477e-9625-771fbcfc3cba,2fc75f11-2097-4e1e-8390-dbff3e844b7a -e8eba267-fecb-477e-9625-771fbcfc3cba,274a2e49-9170-4945-bca2-ab6ef4bded75 -e8eba267-fecb-477e-9625-771fbcfc3cba,0364073f-91d8-47a1-b8b0-107c6318a691 -ede7745a-8f3e-4e20-9f16-33756be7ab6c,fce20464-ff98-4051-8714-6b9584e52740 -ede7745a-8f3e-4e20-9f16-33756be7ab6c,f3eb0c38-2571-44e7-be39-732eb52cf1df -ede7745a-8f3e-4e20-9f16-33756be7ab6c,f3c2f842-6aa3-42c9-a0f3-895c40456868 -ede7745a-8f3e-4e20-9f16-33756be7ab6c,ede7745a-8f3e-4e20-9f16-33756be7ab6c -ede7745a-8f3e-4e20-9f16-33756be7ab6c,ca3e0486-fd6b-4a13-a741-3a47760b412d -ede7745a-8f3e-4e20-9f16-33756be7ab6c,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -ede7745a-8f3e-4e20-9f16-33756be7ab6c,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -ede7745a-8f3e-4e20-9f16-33756be7ab6c,a221198d-000e-497b-8b70-bb4d37f7bbe1 -ede7745a-8f3e-4e20-9f16-33756be7ab6c,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -ede7745a-8f3e-4e20-9f16-33756be7ab6c,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -ede7745a-8f3e-4e20-9f16-33756be7ab6c,88b16960-bfff-41d0-81e4-9a4630834a00 -ede7745a-8f3e-4e20-9f16-33756be7ab6c,522e2abe-ad96-4d1a-b5a5-748faa997531 -ede7745a-8f3e-4e20-9f16-33756be7ab6c,4cfe72fe-21a0-4201-87b6-ef93695d2495 -ede7745a-8f3e-4e20-9f16-33756be7ab6c,498f4b18-bd4c-470d-9cde-2fca70ec8964 -ede7745a-8f3e-4e20-9f16-33756be7ab6c,202131ae-78bb-4104-89b0-fb90645760f6 -ede7745a-8f3e-4e20-9f16-33756be7ab6c,11dafd5c-85e7-4275-a147-89a63641e35e -ede7745a-8f3e-4e20-9f16-33756be7ab6c,02b993c0-b358-481c-b0d0-0767387feb9f -ee24bf89-81a3-4259-a382-86917f3829d4,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -ee24bf89-81a3-4259-a382-86917f3829d4,ee24bf89-81a3-4259-a382-86917f3829d4 -ee24bf89-81a3-4259-a382-86917f3829d4,e363eb67-64c7-440f-93fd-5c8787c69a85 -ee24bf89-81a3-4259-a382-86917f3829d4,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -ee24bf89-81a3-4259-a382-86917f3829d4,db79f75d-af3c-4f82-b4e5-9b5d26598eef -ee24bf89-81a3-4259-a382-86917f3829d4,d7a48a26-7731-4046-bf22-78f0b8084eb9 -ee24bf89-81a3-4259-a382-86917f3829d4,d080c116-681a-494a-a928-45924109b49d -ee24bf89-81a3-4259-a382-86917f3829d4,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -ee24bf89-81a3-4259-a382-86917f3829d4,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -ee24bf89-81a3-4259-a382-86917f3829d4,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -ee24bf89-81a3-4259-a382-86917f3829d4,990c21ab-433b-4920-873e-f9dfc7103d9d -ee24bf89-81a3-4259-a382-86917f3829d4,922e2443-9cc5-421f-8310-9cd23f6e9f2d -ee24bf89-81a3-4259-a382-86917f3829d4,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -ee24bf89-81a3-4259-a382-86917f3829d4,8089a9ac-9e71-4487-a231-f720e4bc8d3e -ee24bf89-81a3-4259-a382-86917f3829d4,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -ee24bf89-81a3-4259-a382-86917f3829d4,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -ee24bf89-81a3-4259-a382-86917f3829d4,6d47a2fe-3645-4c5c-bebe-1b822eff197f -ee24bf89-81a3-4259-a382-86917f3829d4,67fc12fc-bb9f-40f1-9051-127a788b3081 -ee24bf89-81a3-4259-a382-86917f3829d4,634f0889-3a83-484a-bcc8-86f48e333c7b -ee24bf89-81a3-4259-a382-86917f3829d4,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -ee24bf89-81a3-4259-a382-86917f3829d4,4adcaf72-5241-4877-b61c-f34060576c50 -ee24bf89-81a3-4259-a382-86917f3829d4,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -ee24bf89-81a3-4259-a382-86917f3829d4,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -ee24bf89-81a3-4259-a382-86917f3829d4,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -ee24bf89-81a3-4259-a382-86917f3829d4,374fd699-6b74-4500-9d60-2473c7e0364f -ee24bf89-81a3-4259-a382-86917f3829d4,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -ee24bf89-81a3-4259-a382-86917f3829d4,308eba53-29fa-489a-97dc-741b077841a7 -ee24bf89-81a3-4259-a382-86917f3829d4,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -ee24bf89-81a3-4259-a382-86917f3829d4,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -ee24bf89-81a3-4259-a382-86917f3829d4,21995497-0eb8-4c0b-8c23-e6a829f3d596 -ee24bf89-81a3-4259-a382-86917f3829d4,18ebf5ea-b0a2-4333-9e9c-40217de809ff -ee24bf89-81a3-4259-a382-86917f3829d4,151e3048-66ad-4411-8753-677877e3bf0a -ee24bf89-81a3-4259-a382-86917f3829d4,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -ee24bf89-81a3-4259-a382-86917f3829d4,0af4a77e-892e-4b3f-9110-4c5224782250 -f1293c5e-c997-4ba9-8bfd-438e7a840dc8,fcab9efe-fb05-42d6-b366-ce6db1cc4093 -f1293c5e-c997-4ba9-8bfd-438e7a840dc8,f1293c5e-c997-4ba9-8bfd-438e7a840dc8 -f1293c5e-c997-4ba9-8bfd-438e7a840dc8,e4358f28-62a8-4e06-b2bd-cb00d3310349 -f1293c5e-c997-4ba9-8bfd-438e7a840dc8,8f76d729-9f74-4cfd-be2a-8b033b568e18 -f1293c5e-c997-4ba9-8bfd-438e7a840dc8,39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 -f1293c5e-c997-4ba9-8bfd-438e7a840dc8,31d4c712-75b1-4ecb-9d1a-ccc4b8453826 -f1293c5e-c997-4ba9-8bfd-438e7a840dc8,24c4dd5b-748b-4165-a0cc-2867b76c2dc2 -f1293c5e-c997-4ba9-8bfd-438e7a840dc8,16d280a2-c61f-487f-9034-22e884158969 -f1293c5e-c997-4ba9-8bfd-438e7a840dc8,0134901a-4a69-4b39-92ea-d6f48ec83c6e -f18b08bd-40da-43c1-87ec-4ba23542635f,f19eefca-c1ad-4860-bdda-4674a631d464 -f18b08bd-40da-43c1-87ec-4ba23542635f,f18b08bd-40da-43c1-87ec-4ba23542635f -f18b08bd-40da-43c1-87ec-4ba23542635f,b1ccd55a-6b88-4240-b20c-ca893f36e23b -f18b08bd-40da-43c1-87ec-4ba23542635f,900ce9fe-02d3-476b-902a-9467767ecdcf -f18b08bd-40da-43c1-87ec-4ba23542635f,8dc2ce26-aec7-43c0-9771-350af4257ad8 -f18b08bd-40da-43c1-87ec-4ba23542635f,1f3443ee-d15e-4894-b18e-185cfadfcdea -f19eefca-c1ad-4860-bdda-4674a631d464,f19eefca-c1ad-4860-bdda-4674a631d464 -f19eefca-c1ad-4860-bdda-4674a631d464,f18b08bd-40da-43c1-87ec-4ba23542635f -f19eefca-c1ad-4860-bdda-4674a631d464,b1ccd55a-6b88-4240-b20c-ca893f36e23b -f19eefca-c1ad-4860-bdda-4674a631d464,900ce9fe-02d3-476b-902a-9467767ecdcf -f19eefca-c1ad-4860-bdda-4674a631d464,8dc2ce26-aec7-43c0-9771-350af4257ad8 -f19eefca-c1ad-4860-bdda-4674a631d464,1f3443ee-d15e-4894-b18e-185cfadfcdea -f28931e5-1f35-4f9f-a02e-78398735ea67,f883f2ca-d523-4c9f-86b2-0add137901de -f28931e5-1f35-4f9f-a02e-78398735ea67,f28931e5-1f35-4f9f-a02e-78398735ea67 -f28931e5-1f35-4f9f-a02e-78398735ea67,e7f68d35-ae55-4fa4-b0be-0672a5a7186a -f28931e5-1f35-4f9f-a02e-78398735ea67,85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b -f28931e5-1f35-4f9f-a02e-78398735ea67,7ac56d5a-32a7-4b40-a956-356e3006faed -f28931e5-1f35-4f9f-a02e-78398735ea67,72788e7f-1bf1-40b5-a1f1-27c669dc7278 -f28931e5-1f35-4f9f-a02e-78398735ea67,6c84348c-5065-4e89-9412-bfda023683f2 -f28931e5-1f35-4f9f-a02e-78398735ea67,5abe533b-ae5f-47ad-8746-f60caf7483c0 -f28931e5-1f35-4f9f-a02e-78398735ea67,4f14ca7f-5332-4263-a7b2-f0a838380309 -f28931e5-1f35-4f9f-a02e-78398735ea67,359cb842-c25b-429f-ba9b-d52f171e5631 -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,f29330d0-5b32-4f75-b558-a1c7f05c0b4a -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,d90676d2-ce74-4867-a00f-6dbffa7c00be -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,cc56af6d-25c6-480e-9767-da02a55551da -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,ca7f269d-3794-4994-8c46-d8e9f2aa6378 -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,baf48725-f0f9-4357-a71d-b1104910deae -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,9ed8703e-3b40-424c-9e98-b3b404051f99 -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,8be81657-8ba6-4ec5-8ff9-4809367096ea -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,7f410064-638a-4477-85c4-97fc2bb14a49 -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,6af73cae-2e4e-485f-a74d-6f4307eb3af3 -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,5cedf18a-fc44-4c39-a80f-2106a0d76934 -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,4f342a71-dec3-403e-970b-e4c21b9eb98b -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,4db144d3-a596-4718-a50a-8ac9175ad386 -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,4215b5d0-bdd9-4222-a04a-81bb637d60af -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,29d4e919-2bd2-44e6-bb8a-380a780f60ff -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,2830e99a-e6b0-411b-a051-7ea1e9f815d1 -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,1cb88e7a-3db6-4a88-9b68-b0e1492114bc -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,12df1bed-5472-4c48-8e88-2cbb3e5eab33 -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,102e16c5-4920-4dad-b142-0b280b2aacad -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,01ac1476-0c9c-4cd7-82a6-4ff526018e9a -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,f2c9ca81-8b1e-4873-bd8a-1a6e1411193c -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,e8eba267-fecb-477e-9625-771fbcfc3cba -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,e8c41168-a093-40eb-8baa-6802d24f554a -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,e26ae29a-213a-4f79-98c2-cc81cf2f451d -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,e0dfbc08-45ad-4a81-b648-7e65c066f673 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,d535b213-2abc-438f-aa6a-c06476d60b09 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,c892b1d7-7485-407d-8883-54fb7fecebc1 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,b6900c21-d310-4fb4-8b8f-176a09c91989 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,b00df815-7528-4967-bfe2-311130d91c21 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,add6d754-a075-4693-a33a-c061c9a368ff -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,9e0b8dc3-65c4-490d-97c0-d5e3db944de5 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,9bc516d4-7c82-4340-a90c-bb493f96fbe4 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,8a1908f7-47cc-4f82-859c-781a193e9901 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,8743e69b-17aa-44ff-b8fa-4f582665efc6 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,721e4027-a080-40d8-bc4a-43cd33477611 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,54c750aa-570a-4e8e-9a02-418781923e39 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,50eac25a-3761-4de3-8a69-aae52e658300 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,4a464bb5-9373-4f1b-9c03-053c64797114 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,4506aa76-9d0d-40e4-85bc-5735f74522a0 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,42a9a333-d09d-4b9e-af96-1f02af26bac3 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,2fc75f11-2097-4e1e-8390-dbff3e844b7a -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,274a2e49-9170-4945-bca2-ab6ef4bded75 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,0364073f-91d8-47a1-b8b0-107c6318a691 -f3aa491a-4dbd-4436-b674-18b2177dcf18,f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 -f3aa491a-4dbd-4436-b674-18b2177dcf18,f3aa491a-4dbd-4436-b674-18b2177dcf18 -f3aa491a-4dbd-4436-b674-18b2177dcf18,e1040d58-53bc-4467-8101-ca53b772cede -f3aa491a-4dbd-4436-b674-18b2177dcf18,d05461d6-13bf-402c-890d-db6404933f7c -f3aa491a-4dbd-4436-b674-18b2177dcf18,b7772635-1801-48e4-a442-f4aaa6860544 -f3aa491a-4dbd-4436-b674-18b2177dcf18,b07fb98d-2da4-423a-83b4-7a673de93096 -f3aa491a-4dbd-4436-b674-18b2177dcf18,97b42d67-8691-433d-8a31-dada076162ae -f3aa491a-4dbd-4436-b674-18b2177dcf18,720f7ed6-7b2e-41dc-a0e0-724a3332aa24 -f3aa491a-4dbd-4436-b674-18b2177dcf18,4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 -f3aa491a-4dbd-4436-b674-18b2177dcf18,2ffd6142-b75b-406f-bc06-cdb1c02eaefc -f3aa491a-4dbd-4436-b674-18b2177dcf18,224e538d-3622-4f5e-b772-211ed358d8de -f3aa491a-4dbd-4436-b674-18b2177dcf18,16a3408d-c927-4d02-a1ae-cad32419caab -f3aa491a-4dbd-4436-b674-18b2177dcf18,04d88eb4-fb1b-4fa1-802a-5624f4c61b32 -f3c2f842-6aa3-42c9-a0f3-895c40456868,fce20464-ff98-4051-8714-6b9584e52740 -f3c2f842-6aa3-42c9-a0f3-895c40456868,f3eb0c38-2571-44e7-be39-732eb52cf1df -f3c2f842-6aa3-42c9-a0f3-895c40456868,f3c2f842-6aa3-42c9-a0f3-895c40456868 -f3c2f842-6aa3-42c9-a0f3-895c40456868,ede7745a-8f3e-4e20-9f16-33756be7ab6c -f3c2f842-6aa3-42c9-a0f3-895c40456868,ca3e0486-fd6b-4a13-a741-3a47760b412d -f3c2f842-6aa3-42c9-a0f3-895c40456868,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -f3c2f842-6aa3-42c9-a0f3-895c40456868,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -f3c2f842-6aa3-42c9-a0f3-895c40456868,a221198d-000e-497b-8b70-bb4d37f7bbe1 -f3c2f842-6aa3-42c9-a0f3-895c40456868,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -f3c2f842-6aa3-42c9-a0f3-895c40456868,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -f3c2f842-6aa3-42c9-a0f3-895c40456868,88b16960-bfff-41d0-81e4-9a4630834a00 -f3c2f842-6aa3-42c9-a0f3-895c40456868,522e2abe-ad96-4d1a-b5a5-748faa997531 -f3c2f842-6aa3-42c9-a0f3-895c40456868,4cfe72fe-21a0-4201-87b6-ef93695d2495 -f3c2f842-6aa3-42c9-a0f3-895c40456868,498f4b18-bd4c-470d-9cde-2fca70ec8964 -f3c2f842-6aa3-42c9-a0f3-895c40456868,202131ae-78bb-4104-89b0-fb90645760f6 -f3c2f842-6aa3-42c9-a0f3-895c40456868,11dafd5c-85e7-4275-a147-89a63641e35e -f3c2f842-6aa3-42c9-a0f3-895c40456868,02b993c0-b358-481c-b0d0-0767387feb9f -f3eb0c38-2571-44e7-be39-732eb52cf1df,fce20464-ff98-4051-8714-6b9584e52740 -f3eb0c38-2571-44e7-be39-732eb52cf1df,f3eb0c38-2571-44e7-be39-732eb52cf1df -f3eb0c38-2571-44e7-be39-732eb52cf1df,f3c2f842-6aa3-42c9-a0f3-895c40456868 -f3eb0c38-2571-44e7-be39-732eb52cf1df,ede7745a-8f3e-4e20-9f16-33756be7ab6c -f3eb0c38-2571-44e7-be39-732eb52cf1df,ca3e0486-fd6b-4a13-a741-3a47760b412d -f3eb0c38-2571-44e7-be39-732eb52cf1df,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -f3eb0c38-2571-44e7-be39-732eb52cf1df,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -f3eb0c38-2571-44e7-be39-732eb52cf1df,a221198d-000e-497b-8b70-bb4d37f7bbe1 -f3eb0c38-2571-44e7-be39-732eb52cf1df,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -f3eb0c38-2571-44e7-be39-732eb52cf1df,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -f3eb0c38-2571-44e7-be39-732eb52cf1df,88b16960-bfff-41d0-81e4-9a4630834a00 -f3eb0c38-2571-44e7-be39-732eb52cf1df,522e2abe-ad96-4d1a-b5a5-748faa997531 -f3eb0c38-2571-44e7-be39-732eb52cf1df,4cfe72fe-21a0-4201-87b6-ef93695d2495 -f3eb0c38-2571-44e7-be39-732eb52cf1df,498f4b18-bd4c-470d-9cde-2fca70ec8964 -f3eb0c38-2571-44e7-be39-732eb52cf1df,202131ae-78bb-4104-89b0-fb90645760f6 -f3eb0c38-2571-44e7-be39-732eb52cf1df,11dafd5c-85e7-4275-a147-89a63641e35e -f3eb0c38-2571-44e7-be39-732eb52cf1df,02b993c0-b358-481c-b0d0-0767387feb9f -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,f3aa491a-4dbd-4436-b674-18b2177dcf18 -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,e1040d58-53bc-4467-8101-ca53b772cede -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,d05461d6-13bf-402c-890d-db6404933f7c -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,b7772635-1801-48e4-a442-f4aaa6860544 -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,b07fb98d-2da4-423a-83b4-7a673de93096 -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,97b42d67-8691-433d-8a31-dada076162ae -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,720f7ed6-7b2e-41dc-a0e0-724a3332aa24 -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,2ffd6142-b75b-406f-bc06-cdb1c02eaefc -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,224e538d-3622-4f5e-b772-211ed358d8de -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,16a3408d-c927-4d02-a1ae-cad32419caab -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,04d88eb4-fb1b-4fa1-802a-5624f4c61b32 -f77a8561-5adc-452a-ac9a-76273b6a6678,f77a8561-5adc-452a-ac9a-76273b6a6678 -f77a8561-5adc-452a-ac9a-76273b6a6678,d79728e1-8834-4f4a-8edc-ce18aca18be6 -f77a8561-5adc-452a-ac9a-76273b6a6678,cf67a8d4-48e2-4dc9-a521-6aabcff0330b -f77a8561-5adc-452a-ac9a-76273b6a6678,bb1a7816-4798-4f58-9ce4-c5bd3af682a8 -f77a8561-5adc-452a-ac9a-76273b6a6678,b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 -f77a8561-5adc-452a-ac9a-76273b6a6678,a6cb423a-7571-46df-83e2-ccc6820adb36 -f77a8561-5adc-452a-ac9a-76273b6a6678,7e7322a6-7912-4101-b3be-d134245a0626 -f77a8561-5adc-452a-ac9a-76273b6a6678,777aa5f2-2a09-4aed-92ea-912694e28b48 -f77a8561-5adc-452a-ac9a-76273b6a6678,6e187f47-91a1-4217-a185-31b6f01db225 -f77a8561-5adc-452a-ac9a-76273b6a6678,62059efc-7607-41c9-a3ba-70948f6a8a1d -f77a8561-5adc-452a-ac9a-76273b6a6678,2d2e07ec-e697-4384-a679-867e93740921 -f77a8561-5adc-452a-ac9a-76273b6a6678,20fbcb6e-d793-4b05-8e29-8ff82572b0db -f77a8561-5adc-452a-ac9a-76273b6a6678,1fd714a6-48b4-425a-99b3-ba5c1a995cce -f883f2ca-d523-4c9f-86b2-0add137901de,f883f2ca-d523-4c9f-86b2-0add137901de -f883f2ca-d523-4c9f-86b2-0add137901de,f28931e5-1f35-4f9f-a02e-78398735ea67 -f883f2ca-d523-4c9f-86b2-0add137901de,e7f68d35-ae55-4fa4-b0be-0672a5a7186a -f883f2ca-d523-4c9f-86b2-0add137901de,85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b -f883f2ca-d523-4c9f-86b2-0add137901de,7ac56d5a-32a7-4b40-a956-356e3006faed -f883f2ca-d523-4c9f-86b2-0add137901de,72788e7f-1bf1-40b5-a1f1-27c669dc7278 -f883f2ca-d523-4c9f-86b2-0add137901de,6c84348c-5065-4e89-9412-bfda023683f2 -f883f2ca-d523-4c9f-86b2-0add137901de,5abe533b-ae5f-47ad-8746-f60caf7483c0 -f883f2ca-d523-4c9f-86b2-0add137901de,4f14ca7f-5332-4263-a7b2-f0a838380309 -f883f2ca-d523-4c9f-86b2-0add137901de,359cb842-c25b-429f-ba9b-d52f171e5631 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,ee24bf89-81a3-4259-a382-86917f3829d4 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,e363eb67-64c7-440f-93fd-5c8787c69a85 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,db79f75d-af3c-4f82-b4e5-9b5d26598eef -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,d7a48a26-7731-4046-bf22-78f0b8084eb9 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,d080c116-681a-494a-a928-45924109b49d -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,cdb8493c-e3b0-447f-b16b-e58dd64660f3 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,c06a9f9b-90fa-4bd6-9581-0c6950f03e4a -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,a6b084fb-ada7-480a-9adc-f180d4eb1e2a -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,990c21ab-433b-4920-873e-f9dfc7103d9d -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,922e2443-9cc5-421f-8310-9cd23f6e9f2d -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,89f260bb-68b4-4dec-91f4-d52e673e0ff7 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,8089a9ac-9e71-4487-a231-f720e4bc8d3e -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,72c611bb-471e-40f2-a6a8-0ae7b7b6b63e -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,6d47a2fe-3645-4c5c-bebe-1b822eff197f -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,67fc12fc-bb9f-40f1-9051-127a788b3081 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,634f0889-3a83-484a-bcc8-86f48e333c7b -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,4adcaf72-5241-4877-b61c-f34060576c50 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,45b25ced-6eed-4fca-8ec1-b7d32ea13efe -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,42aa48c7-68cc-4bee-9730-cff29f0e36c5 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,374fd699-6b74-4500-9d60-2473c7e0364f -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,308eba53-29fa-489a-97dc-741b077841a7 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,2bd766e5-60e4-4469-bb97-54f0503a1eb0 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,21995497-0eb8-4c0b-8c23-e6a829f3d596 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,18ebf5ea-b0a2-4333-9e9c-40217de809ff -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,151e3048-66ad-4411-8753-677877e3bf0a -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,0c315d37-c758-4cf5-a7cb-cbf712fa7dfd -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,0af4a77e-892e-4b3f-9110-4c5224782250 -fc771883-3bd6-46d9-9626-a130e1b902de,fc771883-3bd6-46d9-9626-a130e1b902de -fc771883-3bd6-46d9-9626-a130e1b902de,e2387a81-5ff5-4daf-b2e8-881998d0d917 -fc771883-3bd6-46d9-9626-a130e1b902de,b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa -fc771883-3bd6-46d9-9626-a130e1b902de,b1d4dc41-07ae-44db-ae17-1df86c5a62cf -fc771883-3bd6-46d9-9626-a130e1b902de,67e5cf95-236b-4f75-abc5-034ca2966448 -fc771883-3bd6-46d9-9626-a130e1b902de,48cc2275-a478-4ade-b076-cf38e1d16017 -fc771883-3bd6-46d9-9626-a130e1b902de,41d26b1c-57b9-408c-b955-bf0ee1db4809 -fc771883-3bd6-46d9-9626-a130e1b902de,3ddf46fe-b5be-456d-810f-ea6a7402236d -fc771883-3bd6-46d9-9626-a130e1b902de,3397a796-894d-409c-97de-a9e6f3f88198 -fc771883-3bd6-46d9-9626-a130e1b902de,19502b5a-2031-487d-9d9c-2001f959408b -fcab9efe-fb05-42d6-b366-ce6db1cc4093,fcab9efe-fb05-42d6-b366-ce6db1cc4093 -fcab9efe-fb05-42d6-b366-ce6db1cc4093,f1293c5e-c997-4ba9-8bfd-438e7a840dc8 -fcab9efe-fb05-42d6-b366-ce6db1cc4093,e4358f28-62a8-4e06-b2bd-cb00d3310349 -fcab9efe-fb05-42d6-b366-ce6db1cc4093,8f76d729-9f74-4cfd-be2a-8b033b568e18 -fcab9efe-fb05-42d6-b366-ce6db1cc4093,39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 -fcab9efe-fb05-42d6-b366-ce6db1cc4093,31d4c712-75b1-4ecb-9d1a-ccc4b8453826 -fcab9efe-fb05-42d6-b366-ce6db1cc4093,24c4dd5b-748b-4165-a0cc-2867b76c2dc2 -fcab9efe-fb05-42d6-b366-ce6db1cc4093,16d280a2-c61f-487f-9034-22e884158969 -fcab9efe-fb05-42d6-b366-ce6db1cc4093,0134901a-4a69-4b39-92ea-d6f48ec83c6e -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,fcc916dd-5e4f-4bf7-9aef-c906a36d7301 -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,c57d51a7-45de-41b9-92fc-efd0634effaf -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,ae7ddaef-4911-418c-8401-d978617e145b -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,acb2329d-b1c3-45c3-ad28-91a09aa521e1 -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,a89c0c42-c19f-4d7f-a15e-d04e043c92f6 -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,836b2e59-fb97-4014-ab0c-d5a5dc750969 -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,683714ad-5194-49e7-9b8f-4e306cf68ae1 -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,5f64131b-d410-449a-9de6-35415919fec5 -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,4f3d1e93-a28f-446e-91af-2baf0168b82b -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,1db1c71b-9eeb-4a98-abc1-eb699b38510c -fce20464-ff98-4051-8714-6b9584e52740,fce20464-ff98-4051-8714-6b9584e52740 -fce20464-ff98-4051-8714-6b9584e52740,f3eb0c38-2571-44e7-be39-732eb52cf1df -fce20464-ff98-4051-8714-6b9584e52740,f3c2f842-6aa3-42c9-a0f3-895c40456868 -fce20464-ff98-4051-8714-6b9584e52740,ede7745a-8f3e-4e20-9f16-33756be7ab6c -fce20464-ff98-4051-8714-6b9584e52740,ca3e0486-fd6b-4a13-a741-3a47760b412d -fce20464-ff98-4051-8714-6b9584e52740,c5c6eed5-aec6-4b8a-b689-adbb219c2267 -fce20464-ff98-4051-8714-6b9584e52740,bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb -fce20464-ff98-4051-8714-6b9584e52740,a221198d-000e-497b-8b70-bb4d37f7bbe1 -fce20464-ff98-4051-8714-6b9584e52740,a1228f20-7c50-4d3a-b88c-2d277ca79d79 -fce20464-ff98-4051-8714-6b9584e52740,8e9e848c-0547-4f2c-8b5f-e33d79bbed67 -fce20464-ff98-4051-8714-6b9584e52740,88b16960-bfff-41d0-81e4-9a4630834a00 -fce20464-ff98-4051-8714-6b9584e52740,522e2abe-ad96-4d1a-b5a5-748faa997531 -fce20464-ff98-4051-8714-6b9584e52740,4cfe72fe-21a0-4201-87b6-ef93695d2495 -fce20464-ff98-4051-8714-6b9584e52740,498f4b18-bd4c-470d-9cde-2fca70ec8964 -fce20464-ff98-4051-8714-6b9584e52740,202131ae-78bb-4104-89b0-fb90645760f6 -fce20464-ff98-4051-8714-6b9584e52740,11dafd5c-85e7-4275-a147-89a63641e35e -fce20464-ff98-4051-8714-6b9584e52740,02b993c0-b358-481c-b0d0-0767387feb9f diff --git a/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingMonomorphicResolve.tsv b/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingMonomorphicResolve.tsv new file mode 100644 index 0000000000..fd2e9d9eb5 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingMonomorphicResolve.tsv @@ -0,0 +1,3519 @@ +0134901a-4a69-4b39-92ea-d6f48ec83c6e fcab9efe-fb05-42d6-b366-ce6db1cc4093 +0134901a-4a69-4b39-92ea-d6f48ec83c6e f1293c5e-c997-4ba9-8bfd-438e7a840dc8 +0134901a-4a69-4b39-92ea-d6f48ec83c6e e4358f28-62a8-4e06-b2bd-cb00d3310349 +0134901a-4a69-4b39-92ea-d6f48ec83c6e 8f76d729-9f74-4cfd-be2a-8b033b568e18 +0134901a-4a69-4b39-92ea-d6f48ec83c6e 39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 +0134901a-4a69-4b39-92ea-d6f48ec83c6e 31d4c712-75b1-4ecb-9d1a-ccc4b8453826 +0134901a-4a69-4b39-92ea-d6f48ec83c6e 24c4dd5b-748b-4165-a0cc-2867b76c2dc2 +0134901a-4a69-4b39-92ea-d6f48ec83c6e 16d280a2-c61f-487f-9034-22e884158969 +0134901a-4a69-4b39-92ea-d6f48ec83c6e 0134901a-4a69-4b39-92ea-d6f48ec83c6e +01ac1476-0c9c-4cd7-82a6-4ff526018e9a f29330d0-5b32-4f75-b558-a1c7f05c0b4a +01ac1476-0c9c-4cd7-82a6-4ff526018e9a d90676d2-ce74-4867-a00f-6dbffa7c00be +01ac1476-0c9c-4cd7-82a6-4ff526018e9a cc56af6d-25c6-480e-9767-da02a55551da +01ac1476-0c9c-4cd7-82a6-4ff526018e9a ca7f269d-3794-4994-8c46-d8e9f2aa6378 +01ac1476-0c9c-4cd7-82a6-4ff526018e9a baf48725-f0f9-4357-a71d-b1104910deae +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 9ed8703e-3b40-424c-9e98-b3b404051f99 +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 8be81657-8ba6-4ec5-8ff9-4809367096ea +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 7f410064-638a-4477-85c4-97fc2bb14a49 +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 5cedf18a-fc44-4c39-a80f-2106a0d76934 +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 4f342a71-dec3-403e-970b-e4c21b9eb98b +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 4db144d3-a596-4718-a50a-8ac9175ad386 +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 4215b5d0-bdd9-4222-a04a-81bb637d60af +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 29d4e919-2bd2-44e6-bb8a-380a780f60ff +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 102e16c5-4920-4dad-b142-0b280b2aacad +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +0250a217-a663-403b-a708-5d14eadf0c40 a2c3ee1d-ec35-4b26-9bab-12de3f47d604 +0250a217-a663-403b-a708-5d14eadf0c40 86415df5-7e47-4637-9e09-aaad9e7628d1 +0250a217-a663-403b-a708-5d14eadf0c40 749babeb-6883-4bd4-92a5-bec52769071c +0250a217-a663-403b-a708-5d14eadf0c40 578cc35c-0982-4d72-a729-b304c026f075 +0250a217-a663-403b-a708-5d14eadf0c40 0250a217-a663-403b-a708-5d14eadf0c40 +02b993c0-b358-481c-b0d0-0767387feb9f fce20464-ff98-4051-8714-6b9584e52740 +02b993c0-b358-481c-b0d0-0767387feb9f f3eb0c38-2571-44e7-be39-732eb52cf1df +02b993c0-b358-481c-b0d0-0767387feb9f f3c2f842-6aa3-42c9-a0f3-895c40456868 +02b993c0-b358-481c-b0d0-0767387feb9f ede7745a-8f3e-4e20-9f16-33756be7ab6c +02b993c0-b358-481c-b0d0-0767387feb9f ca3e0486-fd6b-4a13-a741-3a47760b412d +02b993c0-b358-481c-b0d0-0767387feb9f c5c6eed5-aec6-4b8a-b689-adbb219c2267 +02b993c0-b358-481c-b0d0-0767387feb9f bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +02b993c0-b358-481c-b0d0-0767387feb9f a221198d-000e-497b-8b70-bb4d37f7bbe1 +02b993c0-b358-481c-b0d0-0767387feb9f a1228f20-7c50-4d3a-b88c-2d277ca79d79 +02b993c0-b358-481c-b0d0-0767387feb9f 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +02b993c0-b358-481c-b0d0-0767387feb9f 88b16960-bfff-41d0-81e4-9a4630834a00 +02b993c0-b358-481c-b0d0-0767387feb9f 522e2abe-ad96-4d1a-b5a5-748faa997531 +02b993c0-b358-481c-b0d0-0767387feb9f 4cfe72fe-21a0-4201-87b6-ef93695d2495 +02b993c0-b358-481c-b0d0-0767387feb9f 498f4b18-bd4c-470d-9cde-2fca70ec8964 +02b993c0-b358-481c-b0d0-0767387feb9f 202131ae-78bb-4104-89b0-fb90645760f6 +02b993c0-b358-481c-b0d0-0767387feb9f 11dafd5c-85e7-4275-a147-89a63641e35e +02b993c0-b358-481c-b0d0-0767387feb9f 02b993c0-b358-481c-b0d0-0767387feb9f +0364073f-91d8-47a1-b8b0-107c6318a691 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +0364073f-91d8-47a1-b8b0-107c6318a691 e8eba267-fecb-477e-9625-771fbcfc3cba +0364073f-91d8-47a1-b8b0-107c6318a691 e8c41168-a093-40eb-8baa-6802d24f554a +0364073f-91d8-47a1-b8b0-107c6318a691 e26ae29a-213a-4f79-98c2-cc81cf2f451d +0364073f-91d8-47a1-b8b0-107c6318a691 e0dfbc08-45ad-4a81-b648-7e65c066f673 +0364073f-91d8-47a1-b8b0-107c6318a691 d535b213-2abc-438f-aa6a-c06476d60b09 +0364073f-91d8-47a1-b8b0-107c6318a691 c892b1d7-7485-407d-8883-54fb7fecebc1 +0364073f-91d8-47a1-b8b0-107c6318a691 b6900c21-d310-4fb4-8b8f-176a09c91989 +0364073f-91d8-47a1-b8b0-107c6318a691 b00df815-7528-4967-bfe2-311130d91c21 +0364073f-91d8-47a1-b8b0-107c6318a691 add6d754-a075-4693-a33a-c061c9a368ff +0364073f-91d8-47a1-b8b0-107c6318a691 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +0364073f-91d8-47a1-b8b0-107c6318a691 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +0364073f-91d8-47a1-b8b0-107c6318a691 8a1908f7-47cc-4f82-859c-781a193e9901 +0364073f-91d8-47a1-b8b0-107c6318a691 8743e69b-17aa-44ff-b8fa-4f582665efc6 +0364073f-91d8-47a1-b8b0-107c6318a691 721e4027-a080-40d8-bc4a-43cd33477611 +0364073f-91d8-47a1-b8b0-107c6318a691 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +0364073f-91d8-47a1-b8b0-107c6318a691 54c750aa-570a-4e8e-9a02-418781923e39 +0364073f-91d8-47a1-b8b0-107c6318a691 50eac25a-3761-4de3-8a69-aae52e658300 +0364073f-91d8-47a1-b8b0-107c6318a691 4a464bb5-9373-4f1b-9c03-053c64797114 +0364073f-91d8-47a1-b8b0-107c6318a691 4506aa76-9d0d-40e4-85bc-5735f74522a0 +0364073f-91d8-47a1-b8b0-107c6318a691 42a9a333-d09d-4b9e-af96-1f02af26bac3 +0364073f-91d8-47a1-b8b0-107c6318a691 2fc75f11-2097-4e1e-8390-dbff3e844b7a +0364073f-91d8-47a1-b8b0-107c6318a691 274a2e49-9170-4945-bca2-ab6ef4bded75 +0364073f-91d8-47a1-b8b0-107c6318a691 0364073f-91d8-47a1-b8b0-107c6318a691 +04945715-8ad5-4d8f-bfda-ae26662a3610 b0d337c5-3555-4817-ba59-4ceea5ad8a91 +04945715-8ad5-4d8f-bfda-ae26662a3610 79be3b69-23d8-4408-8f01-68d993e63b6c +04945715-8ad5-4d8f-bfda-ae26662a3610 04945715-8ad5-4d8f-bfda-ae26662a3610 +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 f3aa491a-4dbd-4436-b674-18b2177dcf18 +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 e1040d58-53bc-4467-8101-ca53b772cede +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 d05461d6-13bf-402c-890d-db6404933f7c +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 b7772635-1801-48e4-a442-f4aaa6860544 +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 b07fb98d-2da4-423a-83b4-7a673de93096 +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 97b42d67-8691-433d-8a31-dada076162ae +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 720f7ed6-7b2e-41dc-a0e0-724a3332aa24 +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 2ffd6142-b75b-406f-bc06-cdb1c02eaefc +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 224e538d-3622-4f5e-b772-211ed358d8de +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 16a3408d-c927-4d02-a1ae-cad32419caab +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 04d88eb4-fb1b-4fa1-802a-5624f4c61b32 +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 e1c2ad9f-6f61-4f16-805a-2f405b63659a +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 d6988116-3c63-44f8-9f94-9e9d51cc5a37 +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 cfcbe34a-edc5-4442-bc05-5927fa00336b +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 c90aae7e-5704-4e13-8794-41ab377193bd +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 bf895c48-1d52-4780-8e9a-532d69356d28 +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 ae9e663e-8c15-43c9-8a88-52b61cbf07a9 +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 56999896-e36b-4e63-a6b6-dbb85dfe1936 +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 51241857-a7a4-4517-96e3-21f797581f89 +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 4cd95073-6db3-4eb2-ac4b-0aacb182b352 +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 4148ac36-1dcb-4e96-89cd-355e7e8f919b +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 2e8eb256-84c9-499a-8bf6-bb39504373e1 +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 +0af4a77e-892e-4b3f-9110-4c5224782250 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +0af4a77e-892e-4b3f-9110-4c5224782250 ee24bf89-81a3-4259-a382-86917f3829d4 +0af4a77e-892e-4b3f-9110-4c5224782250 e363eb67-64c7-440f-93fd-5c8787c69a85 +0af4a77e-892e-4b3f-9110-4c5224782250 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +0af4a77e-892e-4b3f-9110-4c5224782250 db79f75d-af3c-4f82-b4e5-9b5d26598eef +0af4a77e-892e-4b3f-9110-4c5224782250 d7a48a26-7731-4046-bf22-78f0b8084eb9 +0af4a77e-892e-4b3f-9110-4c5224782250 d080c116-681a-494a-a928-45924109b49d +0af4a77e-892e-4b3f-9110-4c5224782250 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +0af4a77e-892e-4b3f-9110-4c5224782250 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +0af4a77e-892e-4b3f-9110-4c5224782250 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +0af4a77e-892e-4b3f-9110-4c5224782250 990c21ab-433b-4920-873e-f9dfc7103d9d +0af4a77e-892e-4b3f-9110-4c5224782250 922e2443-9cc5-421f-8310-9cd23f6e9f2d +0af4a77e-892e-4b3f-9110-4c5224782250 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +0af4a77e-892e-4b3f-9110-4c5224782250 8089a9ac-9e71-4487-a231-f720e4bc8d3e +0af4a77e-892e-4b3f-9110-4c5224782250 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +0af4a77e-892e-4b3f-9110-4c5224782250 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +0af4a77e-892e-4b3f-9110-4c5224782250 6d47a2fe-3645-4c5c-bebe-1b822eff197f +0af4a77e-892e-4b3f-9110-4c5224782250 67fc12fc-bb9f-40f1-9051-127a788b3081 +0af4a77e-892e-4b3f-9110-4c5224782250 634f0889-3a83-484a-bcc8-86f48e333c7b +0af4a77e-892e-4b3f-9110-4c5224782250 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +0af4a77e-892e-4b3f-9110-4c5224782250 4adcaf72-5241-4877-b61c-f34060576c50 +0af4a77e-892e-4b3f-9110-4c5224782250 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +0af4a77e-892e-4b3f-9110-4c5224782250 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +0af4a77e-892e-4b3f-9110-4c5224782250 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +0af4a77e-892e-4b3f-9110-4c5224782250 374fd699-6b74-4500-9d60-2473c7e0364f +0af4a77e-892e-4b3f-9110-4c5224782250 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +0af4a77e-892e-4b3f-9110-4c5224782250 308eba53-29fa-489a-97dc-741b077841a7 +0af4a77e-892e-4b3f-9110-4c5224782250 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +0af4a77e-892e-4b3f-9110-4c5224782250 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +0af4a77e-892e-4b3f-9110-4c5224782250 21995497-0eb8-4c0b-8c23-e6a829f3d596 +0af4a77e-892e-4b3f-9110-4c5224782250 18ebf5ea-b0a2-4333-9e9c-40217de809ff +0af4a77e-892e-4b3f-9110-4c5224782250 151e3048-66ad-4411-8753-677877e3bf0a +0af4a77e-892e-4b3f-9110-4c5224782250 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +0af4a77e-892e-4b3f-9110-4c5224782250 0af4a77e-892e-4b3f-9110-4c5224782250 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd ee24bf89-81a3-4259-a382-86917f3829d4 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd e363eb67-64c7-440f-93fd-5c8787c69a85 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd db79f75d-af3c-4f82-b4e5-9b5d26598eef +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd d7a48a26-7731-4046-bf22-78f0b8084eb9 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd d080c116-681a-494a-a928-45924109b49d +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd cdb8493c-e3b0-447f-b16b-e58dd64660f3 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd a6b084fb-ada7-480a-9adc-f180d4eb1e2a +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 990c21ab-433b-4920-873e-f9dfc7103d9d +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 922e2443-9cc5-421f-8310-9cd23f6e9f2d +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 8089a9ac-9e71-4487-a231-f720e4bc8d3e +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 6d47a2fe-3645-4c5c-bebe-1b822eff197f +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 67fc12fc-bb9f-40f1-9051-127a788b3081 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 634f0889-3a83-484a-bcc8-86f48e333c7b +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 4adcaf72-5241-4877-b61c-f34060576c50 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 374fd699-6b74-4500-9d60-2473c7e0364f +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 308eba53-29fa-489a-97dc-741b077841a7 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 21995497-0eb8-4c0b-8c23-e6a829f3d596 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 18ebf5ea-b0a2-4333-9e9c-40217de809ff +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 151e3048-66ad-4411-8753-677877e3bf0a +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 0af4a77e-892e-4b3f-9110-4c5224782250 +102e16c5-4920-4dad-b142-0b280b2aacad f29330d0-5b32-4f75-b558-a1c7f05c0b4a +102e16c5-4920-4dad-b142-0b280b2aacad d90676d2-ce74-4867-a00f-6dbffa7c00be +102e16c5-4920-4dad-b142-0b280b2aacad cc56af6d-25c6-480e-9767-da02a55551da +102e16c5-4920-4dad-b142-0b280b2aacad ca7f269d-3794-4994-8c46-d8e9f2aa6378 +102e16c5-4920-4dad-b142-0b280b2aacad baf48725-f0f9-4357-a71d-b1104910deae +102e16c5-4920-4dad-b142-0b280b2aacad 9ed8703e-3b40-424c-9e98-b3b404051f99 +102e16c5-4920-4dad-b142-0b280b2aacad 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +102e16c5-4920-4dad-b142-0b280b2aacad 8be81657-8ba6-4ec5-8ff9-4809367096ea +102e16c5-4920-4dad-b142-0b280b2aacad 7f410064-638a-4477-85c4-97fc2bb14a49 +102e16c5-4920-4dad-b142-0b280b2aacad 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +102e16c5-4920-4dad-b142-0b280b2aacad 5cedf18a-fc44-4c39-a80f-2106a0d76934 +102e16c5-4920-4dad-b142-0b280b2aacad 4f342a71-dec3-403e-970b-e4c21b9eb98b +102e16c5-4920-4dad-b142-0b280b2aacad 4db144d3-a596-4718-a50a-8ac9175ad386 +102e16c5-4920-4dad-b142-0b280b2aacad 4215b5d0-bdd9-4222-a04a-81bb637d60af +102e16c5-4920-4dad-b142-0b280b2aacad 29d4e919-2bd2-44e6-bb8a-380a780f60ff +102e16c5-4920-4dad-b142-0b280b2aacad 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +102e16c5-4920-4dad-b142-0b280b2aacad 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +102e16c5-4920-4dad-b142-0b280b2aacad 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +102e16c5-4920-4dad-b142-0b280b2aacad 102e16c5-4920-4dad-b142-0b280b2aacad +102e16c5-4920-4dad-b142-0b280b2aacad 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +11dafd5c-85e7-4275-a147-89a63641e35e fce20464-ff98-4051-8714-6b9584e52740 +11dafd5c-85e7-4275-a147-89a63641e35e f3eb0c38-2571-44e7-be39-732eb52cf1df +11dafd5c-85e7-4275-a147-89a63641e35e f3c2f842-6aa3-42c9-a0f3-895c40456868 +11dafd5c-85e7-4275-a147-89a63641e35e ede7745a-8f3e-4e20-9f16-33756be7ab6c +11dafd5c-85e7-4275-a147-89a63641e35e ca3e0486-fd6b-4a13-a741-3a47760b412d +11dafd5c-85e7-4275-a147-89a63641e35e c5c6eed5-aec6-4b8a-b689-adbb219c2267 +11dafd5c-85e7-4275-a147-89a63641e35e bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +11dafd5c-85e7-4275-a147-89a63641e35e a221198d-000e-497b-8b70-bb4d37f7bbe1 +11dafd5c-85e7-4275-a147-89a63641e35e a1228f20-7c50-4d3a-b88c-2d277ca79d79 +11dafd5c-85e7-4275-a147-89a63641e35e 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +11dafd5c-85e7-4275-a147-89a63641e35e 88b16960-bfff-41d0-81e4-9a4630834a00 +11dafd5c-85e7-4275-a147-89a63641e35e 522e2abe-ad96-4d1a-b5a5-748faa997531 +11dafd5c-85e7-4275-a147-89a63641e35e 4cfe72fe-21a0-4201-87b6-ef93695d2495 +11dafd5c-85e7-4275-a147-89a63641e35e 498f4b18-bd4c-470d-9cde-2fca70ec8964 +11dafd5c-85e7-4275-a147-89a63641e35e 202131ae-78bb-4104-89b0-fb90645760f6 +11dafd5c-85e7-4275-a147-89a63641e35e 11dafd5c-85e7-4275-a147-89a63641e35e +11dafd5c-85e7-4275-a147-89a63641e35e 02b993c0-b358-481c-b0d0-0767387feb9f +12b212d8-bc6e-415a-93b0-594381726668 bac374c0-50e8-4a5c-947b-82a8e9a747a9 +12b212d8-bc6e-415a-93b0-594381726668 8bc7af32-e5ad-4849-ba4d-b446db833ab4 +12b212d8-bc6e-415a-93b0-594381726668 7ea43fc6-b1f7-46be-a308-5ecb275a1081 +12b212d8-bc6e-415a-93b0-594381726668 2de1f829-c9d2-4f22-bf39-536dcb82fc3e +12b212d8-bc6e-415a-93b0-594381726668 2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 +12b212d8-bc6e-415a-93b0-594381726668 23780032-f3bb-4b40-af2e-3fa6b1677376 +12b212d8-bc6e-415a-93b0-594381726668 21c6e0fc-bf47-4f80-b1ff-85b940445bdf +12b212d8-bc6e-415a-93b0-594381726668 12b212d8-bc6e-415a-93b0-594381726668 +12df1bed-5472-4c48-8e88-2cbb3e5eab33 f29330d0-5b32-4f75-b558-a1c7f05c0b4a +12df1bed-5472-4c48-8e88-2cbb3e5eab33 d90676d2-ce74-4867-a00f-6dbffa7c00be +12df1bed-5472-4c48-8e88-2cbb3e5eab33 cc56af6d-25c6-480e-9767-da02a55551da +12df1bed-5472-4c48-8e88-2cbb3e5eab33 ca7f269d-3794-4994-8c46-d8e9f2aa6378 +12df1bed-5472-4c48-8e88-2cbb3e5eab33 baf48725-f0f9-4357-a71d-b1104910deae +12df1bed-5472-4c48-8e88-2cbb3e5eab33 9ed8703e-3b40-424c-9e98-b3b404051f99 +12df1bed-5472-4c48-8e88-2cbb3e5eab33 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +12df1bed-5472-4c48-8e88-2cbb3e5eab33 8be81657-8ba6-4ec5-8ff9-4809367096ea +12df1bed-5472-4c48-8e88-2cbb3e5eab33 7f410064-638a-4477-85c4-97fc2bb14a49 +12df1bed-5472-4c48-8e88-2cbb3e5eab33 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +12df1bed-5472-4c48-8e88-2cbb3e5eab33 5cedf18a-fc44-4c39-a80f-2106a0d76934 +12df1bed-5472-4c48-8e88-2cbb3e5eab33 4f342a71-dec3-403e-970b-e4c21b9eb98b +12df1bed-5472-4c48-8e88-2cbb3e5eab33 4db144d3-a596-4718-a50a-8ac9175ad386 +12df1bed-5472-4c48-8e88-2cbb3e5eab33 4215b5d0-bdd9-4222-a04a-81bb637d60af +12df1bed-5472-4c48-8e88-2cbb3e5eab33 29d4e919-2bd2-44e6-bb8a-380a780f60ff +12df1bed-5472-4c48-8e88-2cbb3e5eab33 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +12df1bed-5472-4c48-8e88-2cbb3e5eab33 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +12df1bed-5472-4c48-8e88-2cbb3e5eab33 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +12df1bed-5472-4c48-8e88-2cbb3e5eab33 102e16c5-4920-4dad-b142-0b280b2aacad +12df1bed-5472-4c48-8e88-2cbb3e5eab33 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +151e3048-66ad-4411-8753-677877e3bf0a fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +151e3048-66ad-4411-8753-677877e3bf0a ee24bf89-81a3-4259-a382-86917f3829d4 +151e3048-66ad-4411-8753-677877e3bf0a e363eb67-64c7-440f-93fd-5c8787c69a85 +151e3048-66ad-4411-8753-677877e3bf0a dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +151e3048-66ad-4411-8753-677877e3bf0a db79f75d-af3c-4f82-b4e5-9b5d26598eef +151e3048-66ad-4411-8753-677877e3bf0a d7a48a26-7731-4046-bf22-78f0b8084eb9 +151e3048-66ad-4411-8753-677877e3bf0a d080c116-681a-494a-a928-45924109b49d +151e3048-66ad-4411-8753-677877e3bf0a cdb8493c-e3b0-447f-b16b-e58dd64660f3 +151e3048-66ad-4411-8753-677877e3bf0a c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +151e3048-66ad-4411-8753-677877e3bf0a a6b084fb-ada7-480a-9adc-f180d4eb1e2a +151e3048-66ad-4411-8753-677877e3bf0a 990c21ab-433b-4920-873e-f9dfc7103d9d +151e3048-66ad-4411-8753-677877e3bf0a 922e2443-9cc5-421f-8310-9cd23f6e9f2d +151e3048-66ad-4411-8753-677877e3bf0a 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +151e3048-66ad-4411-8753-677877e3bf0a 8089a9ac-9e71-4487-a231-f720e4bc8d3e +151e3048-66ad-4411-8753-677877e3bf0a 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +151e3048-66ad-4411-8753-677877e3bf0a 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +151e3048-66ad-4411-8753-677877e3bf0a 6d47a2fe-3645-4c5c-bebe-1b822eff197f +151e3048-66ad-4411-8753-677877e3bf0a 67fc12fc-bb9f-40f1-9051-127a788b3081 +151e3048-66ad-4411-8753-677877e3bf0a 634f0889-3a83-484a-bcc8-86f48e333c7b +151e3048-66ad-4411-8753-677877e3bf0a 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +151e3048-66ad-4411-8753-677877e3bf0a 4adcaf72-5241-4877-b61c-f34060576c50 +151e3048-66ad-4411-8753-677877e3bf0a 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +151e3048-66ad-4411-8753-677877e3bf0a 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +151e3048-66ad-4411-8753-677877e3bf0a 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +151e3048-66ad-4411-8753-677877e3bf0a 374fd699-6b74-4500-9d60-2473c7e0364f +151e3048-66ad-4411-8753-677877e3bf0a 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +151e3048-66ad-4411-8753-677877e3bf0a 308eba53-29fa-489a-97dc-741b077841a7 +151e3048-66ad-4411-8753-677877e3bf0a 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +151e3048-66ad-4411-8753-677877e3bf0a 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +151e3048-66ad-4411-8753-677877e3bf0a 21995497-0eb8-4c0b-8c23-e6a829f3d596 +151e3048-66ad-4411-8753-677877e3bf0a 18ebf5ea-b0a2-4333-9e9c-40217de809ff +151e3048-66ad-4411-8753-677877e3bf0a 151e3048-66ad-4411-8753-677877e3bf0a +151e3048-66ad-4411-8753-677877e3bf0a 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +151e3048-66ad-4411-8753-677877e3bf0a 0af4a77e-892e-4b3f-9110-4c5224782250 +16a3408d-c927-4d02-a1ae-cad32419caab f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 +16a3408d-c927-4d02-a1ae-cad32419caab f3aa491a-4dbd-4436-b674-18b2177dcf18 +16a3408d-c927-4d02-a1ae-cad32419caab e1040d58-53bc-4467-8101-ca53b772cede +16a3408d-c927-4d02-a1ae-cad32419caab d05461d6-13bf-402c-890d-db6404933f7c +16a3408d-c927-4d02-a1ae-cad32419caab b7772635-1801-48e4-a442-f4aaa6860544 +16a3408d-c927-4d02-a1ae-cad32419caab b07fb98d-2da4-423a-83b4-7a673de93096 +16a3408d-c927-4d02-a1ae-cad32419caab 97b42d67-8691-433d-8a31-dada076162ae +16a3408d-c927-4d02-a1ae-cad32419caab 720f7ed6-7b2e-41dc-a0e0-724a3332aa24 +16a3408d-c927-4d02-a1ae-cad32419caab 4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 +16a3408d-c927-4d02-a1ae-cad32419caab 2ffd6142-b75b-406f-bc06-cdb1c02eaefc +16a3408d-c927-4d02-a1ae-cad32419caab 224e538d-3622-4f5e-b772-211ed358d8de +16a3408d-c927-4d02-a1ae-cad32419caab 16a3408d-c927-4d02-a1ae-cad32419caab +16a3408d-c927-4d02-a1ae-cad32419caab 04d88eb4-fb1b-4fa1-802a-5624f4c61b32 +16d280a2-c61f-487f-9034-22e884158969 fcab9efe-fb05-42d6-b366-ce6db1cc4093 +16d280a2-c61f-487f-9034-22e884158969 f1293c5e-c997-4ba9-8bfd-438e7a840dc8 +16d280a2-c61f-487f-9034-22e884158969 e4358f28-62a8-4e06-b2bd-cb00d3310349 +16d280a2-c61f-487f-9034-22e884158969 8f76d729-9f74-4cfd-be2a-8b033b568e18 +16d280a2-c61f-487f-9034-22e884158969 39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 +16d280a2-c61f-487f-9034-22e884158969 31d4c712-75b1-4ecb-9d1a-ccc4b8453826 +16d280a2-c61f-487f-9034-22e884158969 24c4dd5b-748b-4165-a0cc-2867b76c2dc2 +16d280a2-c61f-487f-9034-22e884158969 16d280a2-c61f-487f-9034-22e884158969 +16d280a2-c61f-487f-9034-22e884158969 0134901a-4a69-4b39-92ea-d6f48ec83c6e +17bcf2ea-d921-4715-91c5-6b15226b33d3 def5fd23-2b74-4c64-85e9-fac27e626bb7 +17bcf2ea-d921-4715-91c5-6b15226b33d3 96b2282d-1384-4cfb-9958-f009fb501626 +17bcf2ea-d921-4715-91c5-6b15226b33d3 17bcf2ea-d921-4715-91c5-6b15226b33d3 +18ebf5ea-b0a2-4333-9e9c-40217de809ff fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +18ebf5ea-b0a2-4333-9e9c-40217de809ff ee24bf89-81a3-4259-a382-86917f3829d4 +18ebf5ea-b0a2-4333-9e9c-40217de809ff e363eb67-64c7-440f-93fd-5c8787c69a85 +18ebf5ea-b0a2-4333-9e9c-40217de809ff dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +18ebf5ea-b0a2-4333-9e9c-40217de809ff db79f75d-af3c-4f82-b4e5-9b5d26598eef +18ebf5ea-b0a2-4333-9e9c-40217de809ff d7a48a26-7731-4046-bf22-78f0b8084eb9 +18ebf5ea-b0a2-4333-9e9c-40217de809ff d080c116-681a-494a-a928-45924109b49d +18ebf5ea-b0a2-4333-9e9c-40217de809ff cdb8493c-e3b0-447f-b16b-e58dd64660f3 +18ebf5ea-b0a2-4333-9e9c-40217de809ff c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +18ebf5ea-b0a2-4333-9e9c-40217de809ff a6b084fb-ada7-480a-9adc-f180d4eb1e2a +18ebf5ea-b0a2-4333-9e9c-40217de809ff 990c21ab-433b-4920-873e-f9dfc7103d9d +18ebf5ea-b0a2-4333-9e9c-40217de809ff 922e2443-9cc5-421f-8310-9cd23f6e9f2d +18ebf5ea-b0a2-4333-9e9c-40217de809ff 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +18ebf5ea-b0a2-4333-9e9c-40217de809ff 8089a9ac-9e71-4487-a231-f720e4bc8d3e +18ebf5ea-b0a2-4333-9e9c-40217de809ff 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +18ebf5ea-b0a2-4333-9e9c-40217de809ff 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +18ebf5ea-b0a2-4333-9e9c-40217de809ff 6d47a2fe-3645-4c5c-bebe-1b822eff197f +18ebf5ea-b0a2-4333-9e9c-40217de809ff 67fc12fc-bb9f-40f1-9051-127a788b3081 +18ebf5ea-b0a2-4333-9e9c-40217de809ff 634f0889-3a83-484a-bcc8-86f48e333c7b +18ebf5ea-b0a2-4333-9e9c-40217de809ff 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +18ebf5ea-b0a2-4333-9e9c-40217de809ff 4adcaf72-5241-4877-b61c-f34060576c50 +18ebf5ea-b0a2-4333-9e9c-40217de809ff 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +18ebf5ea-b0a2-4333-9e9c-40217de809ff 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +18ebf5ea-b0a2-4333-9e9c-40217de809ff 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +18ebf5ea-b0a2-4333-9e9c-40217de809ff 374fd699-6b74-4500-9d60-2473c7e0364f +18ebf5ea-b0a2-4333-9e9c-40217de809ff 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +18ebf5ea-b0a2-4333-9e9c-40217de809ff 308eba53-29fa-489a-97dc-741b077841a7 +18ebf5ea-b0a2-4333-9e9c-40217de809ff 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +18ebf5ea-b0a2-4333-9e9c-40217de809ff 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +18ebf5ea-b0a2-4333-9e9c-40217de809ff 21995497-0eb8-4c0b-8c23-e6a829f3d596 +18ebf5ea-b0a2-4333-9e9c-40217de809ff 18ebf5ea-b0a2-4333-9e9c-40217de809ff +18ebf5ea-b0a2-4333-9e9c-40217de809ff 151e3048-66ad-4411-8753-677877e3bf0a +18ebf5ea-b0a2-4333-9e9c-40217de809ff 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +18ebf5ea-b0a2-4333-9e9c-40217de809ff 0af4a77e-892e-4b3f-9110-4c5224782250 +19502b5a-2031-487d-9d9c-2001f959408b fc771883-3bd6-46d9-9626-a130e1b902de +19502b5a-2031-487d-9d9c-2001f959408b e2387a81-5ff5-4daf-b2e8-881998d0d917 +19502b5a-2031-487d-9d9c-2001f959408b b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa +19502b5a-2031-487d-9d9c-2001f959408b b1d4dc41-07ae-44db-ae17-1df86c5a62cf +19502b5a-2031-487d-9d9c-2001f959408b 67e5cf95-236b-4f75-abc5-034ca2966448 +19502b5a-2031-487d-9d9c-2001f959408b 48cc2275-a478-4ade-b076-cf38e1d16017 +19502b5a-2031-487d-9d9c-2001f959408b 41d26b1c-57b9-408c-b955-bf0ee1db4809 +19502b5a-2031-487d-9d9c-2001f959408b 3ddf46fe-b5be-456d-810f-ea6a7402236d +19502b5a-2031-487d-9d9c-2001f959408b 3397a796-894d-409c-97de-a9e6f3f88198 +19502b5a-2031-487d-9d9c-2001f959408b 19502b5a-2031-487d-9d9c-2001f959408b +1cb88e7a-3db6-4a88-9b68-b0e1492114bc f29330d0-5b32-4f75-b558-a1c7f05c0b4a +1cb88e7a-3db6-4a88-9b68-b0e1492114bc d90676d2-ce74-4867-a00f-6dbffa7c00be +1cb88e7a-3db6-4a88-9b68-b0e1492114bc cc56af6d-25c6-480e-9767-da02a55551da +1cb88e7a-3db6-4a88-9b68-b0e1492114bc ca7f269d-3794-4994-8c46-d8e9f2aa6378 +1cb88e7a-3db6-4a88-9b68-b0e1492114bc baf48725-f0f9-4357-a71d-b1104910deae +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 9ed8703e-3b40-424c-9e98-b3b404051f99 +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 8be81657-8ba6-4ec5-8ff9-4809367096ea +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 7f410064-638a-4477-85c4-97fc2bb14a49 +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 5cedf18a-fc44-4c39-a80f-2106a0d76934 +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 4f342a71-dec3-403e-970b-e4c21b9eb98b +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 4db144d3-a596-4718-a50a-8ac9175ad386 +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 4215b5d0-bdd9-4222-a04a-81bb637d60af +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 29d4e919-2bd2-44e6-bb8a-380a780f60ff +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 102e16c5-4920-4dad-b142-0b280b2aacad +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +1db1c71b-9eeb-4a98-abc1-eb699b38510c fcc916dd-5e4f-4bf7-9aef-c906a36d7301 +1db1c71b-9eeb-4a98-abc1-eb699b38510c c57d51a7-45de-41b9-92fc-efd0634effaf +1db1c71b-9eeb-4a98-abc1-eb699b38510c ae7ddaef-4911-418c-8401-d978617e145b +1db1c71b-9eeb-4a98-abc1-eb699b38510c acb2329d-b1c3-45c3-ad28-91a09aa521e1 +1db1c71b-9eeb-4a98-abc1-eb699b38510c a89c0c42-c19f-4d7f-a15e-d04e043c92f6 +1db1c71b-9eeb-4a98-abc1-eb699b38510c 836b2e59-fb97-4014-ab0c-d5a5dc750969 +1db1c71b-9eeb-4a98-abc1-eb699b38510c 683714ad-5194-49e7-9b8f-4e306cf68ae1 +1db1c71b-9eeb-4a98-abc1-eb699b38510c 5f64131b-d410-449a-9de6-35415919fec5 +1db1c71b-9eeb-4a98-abc1-eb699b38510c 4f3d1e93-a28f-446e-91af-2baf0168b82b +1db1c71b-9eeb-4a98-abc1-eb699b38510c 1db1c71b-9eeb-4a98-abc1-eb699b38510c +1f3443ee-d15e-4894-b18e-185cfadfcdea f19eefca-c1ad-4860-bdda-4674a631d464 +1f3443ee-d15e-4894-b18e-185cfadfcdea f18b08bd-40da-43c1-87ec-4ba23542635f +1f3443ee-d15e-4894-b18e-185cfadfcdea b1ccd55a-6b88-4240-b20c-ca893f36e23b +1f3443ee-d15e-4894-b18e-185cfadfcdea 900ce9fe-02d3-476b-902a-9467767ecdcf +1f3443ee-d15e-4894-b18e-185cfadfcdea 8dc2ce26-aec7-43c0-9771-350af4257ad8 +1f3443ee-d15e-4894-b18e-185cfadfcdea 1f3443ee-d15e-4894-b18e-185cfadfcdea +1fd714a6-48b4-425a-99b3-ba5c1a995cce f77a8561-5adc-452a-ac9a-76273b6a6678 +1fd714a6-48b4-425a-99b3-ba5c1a995cce d79728e1-8834-4f4a-8edc-ce18aca18be6 +1fd714a6-48b4-425a-99b3-ba5c1a995cce cf67a8d4-48e2-4dc9-a521-6aabcff0330b +1fd714a6-48b4-425a-99b3-ba5c1a995cce bb1a7816-4798-4f58-9ce4-c5bd3af682a8 +1fd714a6-48b4-425a-99b3-ba5c1a995cce b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 +1fd714a6-48b4-425a-99b3-ba5c1a995cce a6cb423a-7571-46df-83e2-ccc6820adb36 +1fd714a6-48b4-425a-99b3-ba5c1a995cce 7e7322a6-7912-4101-b3be-d134245a0626 +1fd714a6-48b4-425a-99b3-ba5c1a995cce 777aa5f2-2a09-4aed-92ea-912694e28b48 +1fd714a6-48b4-425a-99b3-ba5c1a995cce 6e187f47-91a1-4217-a185-31b6f01db225 +1fd714a6-48b4-425a-99b3-ba5c1a995cce 62059efc-7607-41c9-a3ba-70948f6a8a1d +1fd714a6-48b4-425a-99b3-ba5c1a995cce 2d2e07ec-e697-4384-a679-867e93740921 +1fd714a6-48b4-425a-99b3-ba5c1a995cce 20fbcb6e-d793-4b05-8e29-8ff82572b0db +1fd714a6-48b4-425a-99b3-ba5c1a995cce 1fd714a6-48b4-425a-99b3-ba5c1a995cce +202131ae-78bb-4104-89b0-fb90645760f6 fce20464-ff98-4051-8714-6b9584e52740 +202131ae-78bb-4104-89b0-fb90645760f6 f3eb0c38-2571-44e7-be39-732eb52cf1df +202131ae-78bb-4104-89b0-fb90645760f6 f3c2f842-6aa3-42c9-a0f3-895c40456868 +202131ae-78bb-4104-89b0-fb90645760f6 ede7745a-8f3e-4e20-9f16-33756be7ab6c +202131ae-78bb-4104-89b0-fb90645760f6 ca3e0486-fd6b-4a13-a741-3a47760b412d +202131ae-78bb-4104-89b0-fb90645760f6 c5c6eed5-aec6-4b8a-b689-adbb219c2267 +202131ae-78bb-4104-89b0-fb90645760f6 bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +202131ae-78bb-4104-89b0-fb90645760f6 a221198d-000e-497b-8b70-bb4d37f7bbe1 +202131ae-78bb-4104-89b0-fb90645760f6 a1228f20-7c50-4d3a-b88c-2d277ca79d79 +202131ae-78bb-4104-89b0-fb90645760f6 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +202131ae-78bb-4104-89b0-fb90645760f6 88b16960-bfff-41d0-81e4-9a4630834a00 +202131ae-78bb-4104-89b0-fb90645760f6 522e2abe-ad96-4d1a-b5a5-748faa997531 +202131ae-78bb-4104-89b0-fb90645760f6 4cfe72fe-21a0-4201-87b6-ef93695d2495 +202131ae-78bb-4104-89b0-fb90645760f6 498f4b18-bd4c-470d-9cde-2fca70ec8964 +202131ae-78bb-4104-89b0-fb90645760f6 202131ae-78bb-4104-89b0-fb90645760f6 +202131ae-78bb-4104-89b0-fb90645760f6 11dafd5c-85e7-4275-a147-89a63641e35e +202131ae-78bb-4104-89b0-fb90645760f6 02b993c0-b358-481c-b0d0-0767387feb9f +20fbcb6e-d793-4b05-8e29-8ff82572b0db f77a8561-5adc-452a-ac9a-76273b6a6678 +20fbcb6e-d793-4b05-8e29-8ff82572b0db d79728e1-8834-4f4a-8edc-ce18aca18be6 +20fbcb6e-d793-4b05-8e29-8ff82572b0db cf67a8d4-48e2-4dc9-a521-6aabcff0330b +20fbcb6e-d793-4b05-8e29-8ff82572b0db bb1a7816-4798-4f58-9ce4-c5bd3af682a8 +20fbcb6e-d793-4b05-8e29-8ff82572b0db b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 +20fbcb6e-d793-4b05-8e29-8ff82572b0db a6cb423a-7571-46df-83e2-ccc6820adb36 +20fbcb6e-d793-4b05-8e29-8ff82572b0db 7e7322a6-7912-4101-b3be-d134245a0626 +20fbcb6e-d793-4b05-8e29-8ff82572b0db 777aa5f2-2a09-4aed-92ea-912694e28b48 +20fbcb6e-d793-4b05-8e29-8ff82572b0db 6e187f47-91a1-4217-a185-31b6f01db225 +20fbcb6e-d793-4b05-8e29-8ff82572b0db 62059efc-7607-41c9-a3ba-70948f6a8a1d +20fbcb6e-d793-4b05-8e29-8ff82572b0db 2d2e07ec-e697-4384-a679-867e93740921 +20fbcb6e-d793-4b05-8e29-8ff82572b0db 20fbcb6e-d793-4b05-8e29-8ff82572b0db +20fbcb6e-d793-4b05-8e29-8ff82572b0db 1fd714a6-48b4-425a-99b3-ba5c1a995cce +21995497-0eb8-4c0b-8c23-e6a829f3d596 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +21995497-0eb8-4c0b-8c23-e6a829f3d596 ee24bf89-81a3-4259-a382-86917f3829d4 +21995497-0eb8-4c0b-8c23-e6a829f3d596 e363eb67-64c7-440f-93fd-5c8787c69a85 +21995497-0eb8-4c0b-8c23-e6a829f3d596 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +21995497-0eb8-4c0b-8c23-e6a829f3d596 db79f75d-af3c-4f82-b4e5-9b5d26598eef +21995497-0eb8-4c0b-8c23-e6a829f3d596 d7a48a26-7731-4046-bf22-78f0b8084eb9 +21995497-0eb8-4c0b-8c23-e6a829f3d596 d080c116-681a-494a-a928-45924109b49d +21995497-0eb8-4c0b-8c23-e6a829f3d596 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +21995497-0eb8-4c0b-8c23-e6a829f3d596 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +21995497-0eb8-4c0b-8c23-e6a829f3d596 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +21995497-0eb8-4c0b-8c23-e6a829f3d596 990c21ab-433b-4920-873e-f9dfc7103d9d +21995497-0eb8-4c0b-8c23-e6a829f3d596 922e2443-9cc5-421f-8310-9cd23f6e9f2d +21995497-0eb8-4c0b-8c23-e6a829f3d596 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +21995497-0eb8-4c0b-8c23-e6a829f3d596 8089a9ac-9e71-4487-a231-f720e4bc8d3e +21995497-0eb8-4c0b-8c23-e6a829f3d596 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +21995497-0eb8-4c0b-8c23-e6a829f3d596 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +21995497-0eb8-4c0b-8c23-e6a829f3d596 6d47a2fe-3645-4c5c-bebe-1b822eff197f +21995497-0eb8-4c0b-8c23-e6a829f3d596 67fc12fc-bb9f-40f1-9051-127a788b3081 +21995497-0eb8-4c0b-8c23-e6a829f3d596 634f0889-3a83-484a-bcc8-86f48e333c7b +21995497-0eb8-4c0b-8c23-e6a829f3d596 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +21995497-0eb8-4c0b-8c23-e6a829f3d596 4adcaf72-5241-4877-b61c-f34060576c50 +21995497-0eb8-4c0b-8c23-e6a829f3d596 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +21995497-0eb8-4c0b-8c23-e6a829f3d596 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +21995497-0eb8-4c0b-8c23-e6a829f3d596 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +21995497-0eb8-4c0b-8c23-e6a829f3d596 374fd699-6b74-4500-9d60-2473c7e0364f +21995497-0eb8-4c0b-8c23-e6a829f3d596 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +21995497-0eb8-4c0b-8c23-e6a829f3d596 308eba53-29fa-489a-97dc-741b077841a7 +21995497-0eb8-4c0b-8c23-e6a829f3d596 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +21995497-0eb8-4c0b-8c23-e6a829f3d596 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +21995497-0eb8-4c0b-8c23-e6a829f3d596 21995497-0eb8-4c0b-8c23-e6a829f3d596 +21995497-0eb8-4c0b-8c23-e6a829f3d596 18ebf5ea-b0a2-4333-9e9c-40217de809ff +21995497-0eb8-4c0b-8c23-e6a829f3d596 151e3048-66ad-4411-8753-677877e3bf0a +21995497-0eb8-4c0b-8c23-e6a829f3d596 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +21995497-0eb8-4c0b-8c23-e6a829f3d596 0af4a77e-892e-4b3f-9110-4c5224782250 +21c6e0fc-bf47-4f80-b1ff-85b940445bdf bac374c0-50e8-4a5c-947b-82a8e9a747a9 +21c6e0fc-bf47-4f80-b1ff-85b940445bdf 8bc7af32-e5ad-4849-ba4d-b446db833ab4 +21c6e0fc-bf47-4f80-b1ff-85b940445bdf 7ea43fc6-b1f7-46be-a308-5ecb275a1081 +21c6e0fc-bf47-4f80-b1ff-85b940445bdf 2de1f829-c9d2-4f22-bf39-536dcb82fc3e +21c6e0fc-bf47-4f80-b1ff-85b940445bdf 2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 +21c6e0fc-bf47-4f80-b1ff-85b940445bdf 23780032-f3bb-4b40-af2e-3fa6b1677376 +21c6e0fc-bf47-4f80-b1ff-85b940445bdf 21c6e0fc-bf47-4f80-b1ff-85b940445bdf +21c6e0fc-bf47-4f80-b1ff-85b940445bdf 12b212d8-bc6e-415a-93b0-594381726668 +224e538d-3622-4f5e-b772-211ed358d8de f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 +224e538d-3622-4f5e-b772-211ed358d8de f3aa491a-4dbd-4436-b674-18b2177dcf18 +224e538d-3622-4f5e-b772-211ed358d8de e1040d58-53bc-4467-8101-ca53b772cede +224e538d-3622-4f5e-b772-211ed358d8de d05461d6-13bf-402c-890d-db6404933f7c +224e538d-3622-4f5e-b772-211ed358d8de b7772635-1801-48e4-a442-f4aaa6860544 +224e538d-3622-4f5e-b772-211ed358d8de b07fb98d-2da4-423a-83b4-7a673de93096 +224e538d-3622-4f5e-b772-211ed358d8de 97b42d67-8691-433d-8a31-dada076162ae +224e538d-3622-4f5e-b772-211ed358d8de 720f7ed6-7b2e-41dc-a0e0-724a3332aa24 +224e538d-3622-4f5e-b772-211ed358d8de 4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 +224e538d-3622-4f5e-b772-211ed358d8de 2ffd6142-b75b-406f-bc06-cdb1c02eaefc +224e538d-3622-4f5e-b772-211ed358d8de 224e538d-3622-4f5e-b772-211ed358d8de +224e538d-3622-4f5e-b772-211ed358d8de 16a3408d-c927-4d02-a1ae-cad32419caab +224e538d-3622-4f5e-b772-211ed358d8de 04d88eb4-fb1b-4fa1-802a-5624f4c61b32 +23780032-f3bb-4b40-af2e-3fa6b1677376 bac374c0-50e8-4a5c-947b-82a8e9a747a9 +23780032-f3bb-4b40-af2e-3fa6b1677376 8bc7af32-e5ad-4849-ba4d-b446db833ab4 +23780032-f3bb-4b40-af2e-3fa6b1677376 7ea43fc6-b1f7-46be-a308-5ecb275a1081 +23780032-f3bb-4b40-af2e-3fa6b1677376 2de1f829-c9d2-4f22-bf39-536dcb82fc3e +23780032-f3bb-4b40-af2e-3fa6b1677376 2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 +23780032-f3bb-4b40-af2e-3fa6b1677376 23780032-f3bb-4b40-af2e-3fa6b1677376 +23780032-f3bb-4b40-af2e-3fa6b1677376 21c6e0fc-bf47-4f80-b1ff-85b940445bdf +23780032-f3bb-4b40-af2e-3fa6b1677376 12b212d8-bc6e-415a-93b0-594381726668 +24c4dd5b-748b-4165-a0cc-2867b76c2dc2 fcab9efe-fb05-42d6-b366-ce6db1cc4093 +24c4dd5b-748b-4165-a0cc-2867b76c2dc2 f1293c5e-c997-4ba9-8bfd-438e7a840dc8 +24c4dd5b-748b-4165-a0cc-2867b76c2dc2 e4358f28-62a8-4e06-b2bd-cb00d3310349 +24c4dd5b-748b-4165-a0cc-2867b76c2dc2 8f76d729-9f74-4cfd-be2a-8b033b568e18 +24c4dd5b-748b-4165-a0cc-2867b76c2dc2 39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 +24c4dd5b-748b-4165-a0cc-2867b76c2dc2 31d4c712-75b1-4ecb-9d1a-ccc4b8453826 +24c4dd5b-748b-4165-a0cc-2867b76c2dc2 24c4dd5b-748b-4165-a0cc-2867b76c2dc2 +24c4dd5b-748b-4165-a0cc-2867b76c2dc2 16d280a2-c61f-487f-9034-22e884158969 +24c4dd5b-748b-4165-a0cc-2867b76c2dc2 0134901a-4a69-4b39-92ea-d6f48ec83c6e +2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 bac374c0-50e8-4a5c-947b-82a8e9a747a9 +2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 8bc7af32-e5ad-4849-ba4d-b446db833ab4 +2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 7ea43fc6-b1f7-46be-a308-5ecb275a1081 +2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 2de1f829-c9d2-4f22-bf39-536dcb82fc3e +2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 +2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 23780032-f3bb-4b40-af2e-3fa6b1677376 +2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 21c6e0fc-bf47-4f80-b1ff-85b940445bdf +2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 12b212d8-bc6e-415a-93b0-594381726668 +274a2e49-9170-4945-bca2-ab6ef4bded75 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +274a2e49-9170-4945-bca2-ab6ef4bded75 e8eba267-fecb-477e-9625-771fbcfc3cba +274a2e49-9170-4945-bca2-ab6ef4bded75 e8c41168-a093-40eb-8baa-6802d24f554a +274a2e49-9170-4945-bca2-ab6ef4bded75 e26ae29a-213a-4f79-98c2-cc81cf2f451d +274a2e49-9170-4945-bca2-ab6ef4bded75 e0dfbc08-45ad-4a81-b648-7e65c066f673 +274a2e49-9170-4945-bca2-ab6ef4bded75 d535b213-2abc-438f-aa6a-c06476d60b09 +274a2e49-9170-4945-bca2-ab6ef4bded75 c892b1d7-7485-407d-8883-54fb7fecebc1 +274a2e49-9170-4945-bca2-ab6ef4bded75 b6900c21-d310-4fb4-8b8f-176a09c91989 +274a2e49-9170-4945-bca2-ab6ef4bded75 b00df815-7528-4967-bfe2-311130d91c21 +274a2e49-9170-4945-bca2-ab6ef4bded75 add6d754-a075-4693-a33a-c061c9a368ff +274a2e49-9170-4945-bca2-ab6ef4bded75 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +274a2e49-9170-4945-bca2-ab6ef4bded75 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +274a2e49-9170-4945-bca2-ab6ef4bded75 8a1908f7-47cc-4f82-859c-781a193e9901 +274a2e49-9170-4945-bca2-ab6ef4bded75 8743e69b-17aa-44ff-b8fa-4f582665efc6 +274a2e49-9170-4945-bca2-ab6ef4bded75 721e4027-a080-40d8-bc4a-43cd33477611 +274a2e49-9170-4945-bca2-ab6ef4bded75 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +274a2e49-9170-4945-bca2-ab6ef4bded75 54c750aa-570a-4e8e-9a02-418781923e39 +274a2e49-9170-4945-bca2-ab6ef4bded75 50eac25a-3761-4de3-8a69-aae52e658300 +274a2e49-9170-4945-bca2-ab6ef4bded75 4a464bb5-9373-4f1b-9c03-053c64797114 +274a2e49-9170-4945-bca2-ab6ef4bded75 4506aa76-9d0d-40e4-85bc-5735f74522a0 +274a2e49-9170-4945-bca2-ab6ef4bded75 42a9a333-d09d-4b9e-af96-1f02af26bac3 +274a2e49-9170-4945-bca2-ab6ef4bded75 2fc75f11-2097-4e1e-8390-dbff3e844b7a +274a2e49-9170-4945-bca2-ab6ef4bded75 274a2e49-9170-4945-bca2-ab6ef4bded75 +274a2e49-9170-4945-bca2-ab6ef4bded75 0364073f-91d8-47a1-b8b0-107c6318a691 +2830e99a-e6b0-411b-a051-7ea1e9f815d1 f29330d0-5b32-4f75-b558-a1c7f05c0b4a +2830e99a-e6b0-411b-a051-7ea1e9f815d1 d90676d2-ce74-4867-a00f-6dbffa7c00be +2830e99a-e6b0-411b-a051-7ea1e9f815d1 cc56af6d-25c6-480e-9767-da02a55551da +2830e99a-e6b0-411b-a051-7ea1e9f815d1 ca7f269d-3794-4994-8c46-d8e9f2aa6378 +2830e99a-e6b0-411b-a051-7ea1e9f815d1 baf48725-f0f9-4357-a71d-b1104910deae +2830e99a-e6b0-411b-a051-7ea1e9f815d1 9ed8703e-3b40-424c-9e98-b3b404051f99 +2830e99a-e6b0-411b-a051-7ea1e9f815d1 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +2830e99a-e6b0-411b-a051-7ea1e9f815d1 8be81657-8ba6-4ec5-8ff9-4809367096ea +2830e99a-e6b0-411b-a051-7ea1e9f815d1 7f410064-638a-4477-85c4-97fc2bb14a49 +2830e99a-e6b0-411b-a051-7ea1e9f815d1 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +2830e99a-e6b0-411b-a051-7ea1e9f815d1 5cedf18a-fc44-4c39-a80f-2106a0d76934 +2830e99a-e6b0-411b-a051-7ea1e9f815d1 4f342a71-dec3-403e-970b-e4c21b9eb98b +2830e99a-e6b0-411b-a051-7ea1e9f815d1 4db144d3-a596-4718-a50a-8ac9175ad386 +2830e99a-e6b0-411b-a051-7ea1e9f815d1 4215b5d0-bdd9-4222-a04a-81bb637d60af +2830e99a-e6b0-411b-a051-7ea1e9f815d1 29d4e919-2bd2-44e6-bb8a-380a780f60ff +2830e99a-e6b0-411b-a051-7ea1e9f815d1 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +2830e99a-e6b0-411b-a051-7ea1e9f815d1 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +2830e99a-e6b0-411b-a051-7ea1e9f815d1 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +2830e99a-e6b0-411b-a051-7ea1e9f815d1 102e16c5-4920-4dad-b142-0b280b2aacad +2830e99a-e6b0-411b-a051-7ea1e9f815d1 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +29d4e919-2bd2-44e6-bb8a-380a780f60ff f29330d0-5b32-4f75-b558-a1c7f05c0b4a +29d4e919-2bd2-44e6-bb8a-380a780f60ff d90676d2-ce74-4867-a00f-6dbffa7c00be +29d4e919-2bd2-44e6-bb8a-380a780f60ff cc56af6d-25c6-480e-9767-da02a55551da +29d4e919-2bd2-44e6-bb8a-380a780f60ff ca7f269d-3794-4994-8c46-d8e9f2aa6378 +29d4e919-2bd2-44e6-bb8a-380a780f60ff baf48725-f0f9-4357-a71d-b1104910deae +29d4e919-2bd2-44e6-bb8a-380a780f60ff 9ed8703e-3b40-424c-9e98-b3b404051f99 +29d4e919-2bd2-44e6-bb8a-380a780f60ff 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +29d4e919-2bd2-44e6-bb8a-380a780f60ff 8be81657-8ba6-4ec5-8ff9-4809367096ea +29d4e919-2bd2-44e6-bb8a-380a780f60ff 7f410064-638a-4477-85c4-97fc2bb14a49 +29d4e919-2bd2-44e6-bb8a-380a780f60ff 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +29d4e919-2bd2-44e6-bb8a-380a780f60ff 5cedf18a-fc44-4c39-a80f-2106a0d76934 +29d4e919-2bd2-44e6-bb8a-380a780f60ff 4f342a71-dec3-403e-970b-e4c21b9eb98b +29d4e919-2bd2-44e6-bb8a-380a780f60ff 4db144d3-a596-4718-a50a-8ac9175ad386 +29d4e919-2bd2-44e6-bb8a-380a780f60ff 4215b5d0-bdd9-4222-a04a-81bb637d60af +29d4e919-2bd2-44e6-bb8a-380a780f60ff 29d4e919-2bd2-44e6-bb8a-380a780f60ff +29d4e919-2bd2-44e6-bb8a-380a780f60ff 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +29d4e919-2bd2-44e6-bb8a-380a780f60ff 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +29d4e919-2bd2-44e6-bb8a-380a780f60ff 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +29d4e919-2bd2-44e6-bb8a-380a780f60ff 102e16c5-4920-4dad-b142-0b280b2aacad +29d4e919-2bd2-44e6-bb8a-380a780f60ff 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +2aff9edd-def2-487a-b435-a162e11a303c 99fc3558-79f3-4d14-9aa1-ff63f621ecd9 +2aff9edd-def2-487a-b435-a162e11a303c 2aff9edd-def2-487a-b435-a162e11a303c +2bd766e5-60e4-4469-bb97-54f0503a1eb0 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +2bd766e5-60e4-4469-bb97-54f0503a1eb0 ee24bf89-81a3-4259-a382-86917f3829d4 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 e363eb67-64c7-440f-93fd-5c8787c69a85 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 db79f75d-af3c-4f82-b4e5-9b5d26598eef +2bd766e5-60e4-4469-bb97-54f0503a1eb0 d7a48a26-7731-4046-bf22-78f0b8084eb9 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 d080c116-681a-494a-a928-45924109b49d +2bd766e5-60e4-4469-bb97-54f0503a1eb0 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +2bd766e5-60e4-4469-bb97-54f0503a1eb0 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +2bd766e5-60e4-4469-bb97-54f0503a1eb0 990c21ab-433b-4920-873e-f9dfc7103d9d +2bd766e5-60e4-4469-bb97-54f0503a1eb0 922e2443-9cc5-421f-8310-9cd23f6e9f2d +2bd766e5-60e4-4469-bb97-54f0503a1eb0 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 8089a9ac-9e71-4487-a231-f720e4bc8d3e +2bd766e5-60e4-4469-bb97-54f0503a1eb0 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +2bd766e5-60e4-4469-bb97-54f0503a1eb0 6d47a2fe-3645-4c5c-bebe-1b822eff197f +2bd766e5-60e4-4469-bb97-54f0503a1eb0 67fc12fc-bb9f-40f1-9051-127a788b3081 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 634f0889-3a83-484a-bcc8-86f48e333c7b +2bd766e5-60e4-4469-bb97-54f0503a1eb0 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 4adcaf72-5241-4877-b61c-f34060576c50 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +2bd766e5-60e4-4469-bb97-54f0503a1eb0 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 374fd699-6b74-4500-9d60-2473c7e0364f +2bd766e5-60e4-4469-bb97-54f0503a1eb0 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 308eba53-29fa-489a-97dc-741b077841a7 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +2bd766e5-60e4-4469-bb97-54f0503a1eb0 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 21995497-0eb8-4c0b-8c23-e6a829f3d596 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 18ebf5ea-b0a2-4333-9e9c-40217de809ff +2bd766e5-60e4-4469-bb97-54f0503a1eb0 151e3048-66ad-4411-8753-677877e3bf0a +2bd766e5-60e4-4469-bb97-54f0503a1eb0 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +2bd766e5-60e4-4469-bb97-54f0503a1eb0 0af4a77e-892e-4b3f-9110-4c5224782250 +2bddd98c-86e3-4ae0-9dc6-87da16ab6267 c3ec1f2e-1411-41e4-8370-acd5a8f68cf4 +2bddd98c-86e3-4ae0-9dc6-87da16ab6267 87b04a97-1d33-4548-aec9-3d2856070702 +2bddd98c-86e3-4ae0-9dc6-87da16ab6267 2bddd98c-86e3-4ae0-9dc6-87da16ab6267 +2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5 984067fc-3dfc-47b7-8ee0-4d9b27d43860 +2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5 55f17f44-287f-41aa-a1bc-d1c0104af36e +2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5 2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5 +2d2e07ec-e697-4384-a679-867e93740921 f77a8561-5adc-452a-ac9a-76273b6a6678 +2d2e07ec-e697-4384-a679-867e93740921 d79728e1-8834-4f4a-8edc-ce18aca18be6 +2d2e07ec-e697-4384-a679-867e93740921 cf67a8d4-48e2-4dc9-a521-6aabcff0330b +2d2e07ec-e697-4384-a679-867e93740921 bb1a7816-4798-4f58-9ce4-c5bd3af682a8 +2d2e07ec-e697-4384-a679-867e93740921 b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 +2d2e07ec-e697-4384-a679-867e93740921 a6cb423a-7571-46df-83e2-ccc6820adb36 +2d2e07ec-e697-4384-a679-867e93740921 7e7322a6-7912-4101-b3be-d134245a0626 +2d2e07ec-e697-4384-a679-867e93740921 777aa5f2-2a09-4aed-92ea-912694e28b48 +2d2e07ec-e697-4384-a679-867e93740921 6e187f47-91a1-4217-a185-31b6f01db225 +2d2e07ec-e697-4384-a679-867e93740921 62059efc-7607-41c9-a3ba-70948f6a8a1d +2d2e07ec-e697-4384-a679-867e93740921 2d2e07ec-e697-4384-a679-867e93740921 +2d2e07ec-e697-4384-a679-867e93740921 20fbcb6e-d793-4b05-8e29-8ff82572b0db +2d2e07ec-e697-4384-a679-867e93740921 1fd714a6-48b4-425a-99b3-ba5c1a995cce +2de1f829-c9d2-4f22-bf39-536dcb82fc3e bac374c0-50e8-4a5c-947b-82a8e9a747a9 +2de1f829-c9d2-4f22-bf39-536dcb82fc3e 8bc7af32-e5ad-4849-ba4d-b446db833ab4 +2de1f829-c9d2-4f22-bf39-536dcb82fc3e 7ea43fc6-b1f7-46be-a308-5ecb275a1081 +2de1f829-c9d2-4f22-bf39-536dcb82fc3e 2de1f829-c9d2-4f22-bf39-536dcb82fc3e +2de1f829-c9d2-4f22-bf39-536dcb82fc3e 2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 +2de1f829-c9d2-4f22-bf39-536dcb82fc3e 23780032-f3bb-4b40-af2e-3fa6b1677376 +2de1f829-c9d2-4f22-bf39-536dcb82fc3e 21c6e0fc-bf47-4f80-b1ff-85b940445bdf +2de1f829-c9d2-4f22-bf39-536dcb82fc3e 12b212d8-bc6e-415a-93b0-594381726668 +2e8eb256-84c9-499a-8bf6-bb39504373e1 e1c2ad9f-6f61-4f16-805a-2f405b63659a +2e8eb256-84c9-499a-8bf6-bb39504373e1 d6988116-3c63-44f8-9f94-9e9d51cc5a37 +2e8eb256-84c9-499a-8bf6-bb39504373e1 cfcbe34a-edc5-4442-bc05-5927fa00336b +2e8eb256-84c9-499a-8bf6-bb39504373e1 c90aae7e-5704-4e13-8794-41ab377193bd +2e8eb256-84c9-499a-8bf6-bb39504373e1 bf895c48-1d52-4780-8e9a-532d69356d28 +2e8eb256-84c9-499a-8bf6-bb39504373e1 ae9e663e-8c15-43c9-8a88-52b61cbf07a9 +2e8eb256-84c9-499a-8bf6-bb39504373e1 56999896-e36b-4e63-a6b6-dbb85dfe1936 +2e8eb256-84c9-499a-8bf6-bb39504373e1 51241857-a7a4-4517-96e3-21f797581f89 +2e8eb256-84c9-499a-8bf6-bb39504373e1 4cd95073-6db3-4eb2-ac4b-0aacb182b352 +2e8eb256-84c9-499a-8bf6-bb39504373e1 4148ac36-1dcb-4e96-89cd-355e7e8f919b +2e8eb256-84c9-499a-8bf6-bb39504373e1 2e8eb256-84c9-499a-8bf6-bb39504373e1 +2e8eb256-84c9-499a-8bf6-bb39504373e1 07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f ee24bf89-81a3-4259-a382-86917f3829d4 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f e363eb67-64c7-440f-93fd-5c8787c69a85 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f db79f75d-af3c-4f82-b4e5-9b5d26598eef +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f d7a48a26-7731-4046-bf22-78f0b8084eb9 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f d080c116-681a-494a-a928-45924109b49d +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f cdb8493c-e3b0-447f-b16b-e58dd64660f3 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f a6b084fb-ada7-480a-9adc-f180d4eb1e2a +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 990c21ab-433b-4920-873e-f9dfc7103d9d +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 922e2443-9cc5-421f-8310-9cd23f6e9f2d +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 8089a9ac-9e71-4487-a231-f720e4bc8d3e +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 6d47a2fe-3645-4c5c-bebe-1b822eff197f +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 67fc12fc-bb9f-40f1-9051-127a788b3081 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 634f0889-3a83-484a-bcc8-86f48e333c7b +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 4adcaf72-5241-4877-b61c-f34060576c50 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 374fd699-6b74-4500-9d60-2473c7e0364f +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 308eba53-29fa-489a-97dc-741b077841a7 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 21995497-0eb8-4c0b-8c23-e6a829f3d596 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 18ebf5ea-b0a2-4333-9e9c-40217de809ff +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 151e3048-66ad-4411-8753-677877e3bf0a +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 0af4a77e-892e-4b3f-9110-4c5224782250 +2ee2ab26-09fb-4e12-bf21-9fed3b4c38de bfc93cb7-e53a-49f7-b86e-81d6a13cfdea +2ee2ab26-09fb-4e12-bf21-9fed3b4c38de a6790cbd-8349-432e-a976-5dfc07451203 +2ee2ab26-09fb-4e12-bf21-9fed3b4c38de 9b2eb632-6f1a-4e97-912b-3c8378a1b11c +2ee2ab26-09fb-4e12-bf21-9fed3b4c38de 85c997bf-63d9-4ebb-8e0b-320de3dddc6c +2ee2ab26-09fb-4e12-bf21-9fed3b4c38de 85481b3f-c75e-40cd-bacd-b0afb79e893c +2ee2ab26-09fb-4e12-bf21-9fed3b4c38de 75a09d4f-eef2-4404-a386-dfcb408ec9ed +2ee2ab26-09fb-4e12-bf21-9fed3b4c38de 30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 +2ee2ab26-09fb-4e12-bf21-9fed3b4c38de 2ee2ab26-09fb-4e12-bf21-9fed3b4c38de +2fc75f11-2097-4e1e-8390-dbff3e844b7a f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +2fc75f11-2097-4e1e-8390-dbff3e844b7a e8eba267-fecb-477e-9625-771fbcfc3cba +2fc75f11-2097-4e1e-8390-dbff3e844b7a e8c41168-a093-40eb-8baa-6802d24f554a +2fc75f11-2097-4e1e-8390-dbff3e844b7a e26ae29a-213a-4f79-98c2-cc81cf2f451d +2fc75f11-2097-4e1e-8390-dbff3e844b7a e0dfbc08-45ad-4a81-b648-7e65c066f673 +2fc75f11-2097-4e1e-8390-dbff3e844b7a d535b213-2abc-438f-aa6a-c06476d60b09 +2fc75f11-2097-4e1e-8390-dbff3e844b7a c892b1d7-7485-407d-8883-54fb7fecebc1 +2fc75f11-2097-4e1e-8390-dbff3e844b7a b6900c21-d310-4fb4-8b8f-176a09c91989 +2fc75f11-2097-4e1e-8390-dbff3e844b7a b00df815-7528-4967-bfe2-311130d91c21 +2fc75f11-2097-4e1e-8390-dbff3e844b7a add6d754-a075-4693-a33a-c061c9a368ff +2fc75f11-2097-4e1e-8390-dbff3e844b7a 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +2fc75f11-2097-4e1e-8390-dbff3e844b7a 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +2fc75f11-2097-4e1e-8390-dbff3e844b7a 8a1908f7-47cc-4f82-859c-781a193e9901 +2fc75f11-2097-4e1e-8390-dbff3e844b7a 8743e69b-17aa-44ff-b8fa-4f582665efc6 +2fc75f11-2097-4e1e-8390-dbff3e844b7a 721e4027-a080-40d8-bc4a-43cd33477611 +2fc75f11-2097-4e1e-8390-dbff3e844b7a 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +2fc75f11-2097-4e1e-8390-dbff3e844b7a 54c750aa-570a-4e8e-9a02-418781923e39 +2fc75f11-2097-4e1e-8390-dbff3e844b7a 50eac25a-3761-4de3-8a69-aae52e658300 +2fc75f11-2097-4e1e-8390-dbff3e844b7a 4a464bb5-9373-4f1b-9c03-053c64797114 +2fc75f11-2097-4e1e-8390-dbff3e844b7a 4506aa76-9d0d-40e4-85bc-5735f74522a0 +2fc75f11-2097-4e1e-8390-dbff3e844b7a 42a9a333-d09d-4b9e-af96-1f02af26bac3 +2fc75f11-2097-4e1e-8390-dbff3e844b7a 2fc75f11-2097-4e1e-8390-dbff3e844b7a +2fc75f11-2097-4e1e-8390-dbff3e844b7a 274a2e49-9170-4945-bca2-ab6ef4bded75 +2fc75f11-2097-4e1e-8390-dbff3e844b7a 0364073f-91d8-47a1-b8b0-107c6318a691 +2ffd6142-b75b-406f-bc06-cdb1c02eaefc f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 +2ffd6142-b75b-406f-bc06-cdb1c02eaefc f3aa491a-4dbd-4436-b674-18b2177dcf18 +2ffd6142-b75b-406f-bc06-cdb1c02eaefc e1040d58-53bc-4467-8101-ca53b772cede +2ffd6142-b75b-406f-bc06-cdb1c02eaefc d05461d6-13bf-402c-890d-db6404933f7c +2ffd6142-b75b-406f-bc06-cdb1c02eaefc b7772635-1801-48e4-a442-f4aaa6860544 +2ffd6142-b75b-406f-bc06-cdb1c02eaefc b07fb98d-2da4-423a-83b4-7a673de93096 +2ffd6142-b75b-406f-bc06-cdb1c02eaefc 97b42d67-8691-433d-8a31-dada076162ae +2ffd6142-b75b-406f-bc06-cdb1c02eaefc 720f7ed6-7b2e-41dc-a0e0-724a3332aa24 +2ffd6142-b75b-406f-bc06-cdb1c02eaefc 4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 +2ffd6142-b75b-406f-bc06-cdb1c02eaefc 2ffd6142-b75b-406f-bc06-cdb1c02eaefc +2ffd6142-b75b-406f-bc06-cdb1c02eaefc 224e538d-3622-4f5e-b772-211ed358d8de +2ffd6142-b75b-406f-bc06-cdb1c02eaefc 16a3408d-c927-4d02-a1ae-cad32419caab +2ffd6142-b75b-406f-bc06-cdb1c02eaefc 04d88eb4-fb1b-4fa1-802a-5624f4c61b32 +308eba53-29fa-489a-97dc-741b077841a7 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +308eba53-29fa-489a-97dc-741b077841a7 ee24bf89-81a3-4259-a382-86917f3829d4 +308eba53-29fa-489a-97dc-741b077841a7 e363eb67-64c7-440f-93fd-5c8787c69a85 +308eba53-29fa-489a-97dc-741b077841a7 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +308eba53-29fa-489a-97dc-741b077841a7 db79f75d-af3c-4f82-b4e5-9b5d26598eef +308eba53-29fa-489a-97dc-741b077841a7 d7a48a26-7731-4046-bf22-78f0b8084eb9 +308eba53-29fa-489a-97dc-741b077841a7 d080c116-681a-494a-a928-45924109b49d +308eba53-29fa-489a-97dc-741b077841a7 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +308eba53-29fa-489a-97dc-741b077841a7 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +308eba53-29fa-489a-97dc-741b077841a7 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +308eba53-29fa-489a-97dc-741b077841a7 990c21ab-433b-4920-873e-f9dfc7103d9d +308eba53-29fa-489a-97dc-741b077841a7 922e2443-9cc5-421f-8310-9cd23f6e9f2d +308eba53-29fa-489a-97dc-741b077841a7 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +308eba53-29fa-489a-97dc-741b077841a7 8089a9ac-9e71-4487-a231-f720e4bc8d3e +308eba53-29fa-489a-97dc-741b077841a7 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +308eba53-29fa-489a-97dc-741b077841a7 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +308eba53-29fa-489a-97dc-741b077841a7 6d47a2fe-3645-4c5c-bebe-1b822eff197f +308eba53-29fa-489a-97dc-741b077841a7 67fc12fc-bb9f-40f1-9051-127a788b3081 +308eba53-29fa-489a-97dc-741b077841a7 634f0889-3a83-484a-bcc8-86f48e333c7b +308eba53-29fa-489a-97dc-741b077841a7 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +308eba53-29fa-489a-97dc-741b077841a7 4adcaf72-5241-4877-b61c-f34060576c50 +308eba53-29fa-489a-97dc-741b077841a7 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +308eba53-29fa-489a-97dc-741b077841a7 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +308eba53-29fa-489a-97dc-741b077841a7 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +308eba53-29fa-489a-97dc-741b077841a7 374fd699-6b74-4500-9d60-2473c7e0364f +308eba53-29fa-489a-97dc-741b077841a7 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +308eba53-29fa-489a-97dc-741b077841a7 308eba53-29fa-489a-97dc-741b077841a7 +308eba53-29fa-489a-97dc-741b077841a7 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +308eba53-29fa-489a-97dc-741b077841a7 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +308eba53-29fa-489a-97dc-741b077841a7 21995497-0eb8-4c0b-8c23-e6a829f3d596 +308eba53-29fa-489a-97dc-741b077841a7 18ebf5ea-b0a2-4333-9e9c-40217de809ff +308eba53-29fa-489a-97dc-741b077841a7 151e3048-66ad-4411-8753-677877e3bf0a +308eba53-29fa-489a-97dc-741b077841a7 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +308eba53-29fa-489a-97dc-741b077841a7 0af4a77e-892e-4b3f-9110-4c5224782250 +30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 bfc93cb7-e53a-49f7-b86e-81d6a13cfdea +30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 a6790cbd-8349-432e-a976-5dfc07451203 +30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 9b2eb632-6f1a-4e97-912b-3c8378a1b11c +30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 85c997bf-63d9-4ebb-8e0b-320de3dddc6c +30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 85481b3f-c75e-40cd-bacd-b0afb79e893c +30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 75a09d4f-eef2-4404-a386-dfcb408ec9ed +30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 +30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 2ee2ab26-09fb-4e12-bf21-9fed3b4c38de +31d4c712-75b1-4ecb-9d1a-ccc4b8453826 fcab9efe-fb05-42d6-b366-ce6db1cc4093 +31d4c712-75b1-4ecb-9d1a-ccc4b8453826 f1293c5e-c997-4ba9-8bfd-438e7a840dc8 +31d4c712-75b1-4ecb-9d1a-ccc4b8453826 e4358f28-62a8-4e06-b2bd-cb00d3310349 +31d4c712-75b1-4ecb-9d1a-ccc4b8453826 8f76d729-9f74-4cfd-be2a-8b033b568e18 +31d4c712-75b1-4ecb-9d1a-ccc4b8453826 39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 +31d4c712-75b1-4ecb-9d1a-ccc4b8453826 31d4c712-75b1-4ecb-9d1a-ccc4b8453826 +31d4c712-75b1-4ecb-9d1a-ccc4b8453826 24c4dd5b-748b-4165-a0cc-2867b76c2dc2 +31d4c712-75b1-4ecb-9d1a-ccc4b8453826 16d280a2-c61f-487f-9034-22e884158969 +31d4c712-75b1-4ecb-9d1a-ccc4b8453826 0134901a-4a69-4b39-92ea-d6f48ec83c6e +3397a796-894d-409c-97de-a9e6f3f88198 fc771883-3bd6-46d9-9626-a130e1b902de +3397a796-894d-409c-97de-a9e6f3f88198 e2387a81-5ff5-4daf-b2e8-881998d0d917 +3397a796-894d-409c-97de-a9e6f3f88198 b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa +3397a796-894d-409c-97de-a9e6f3f88198 b1d4dc41-07ae-44db-ae17-1df86c5a62cf +3397a796-894d-409c-97de-a9e6f3f88198 67e5cf95-236b-4f75-abc5-034ca2966448 +3397a796-894d-409c-97de-a9e6f3f88198 48cc2275-a478-4ade-b076-cf38e1d16017 +3397a796-894d-409c-97de-a9e6f3f88198 41d26b1c-57b9-408c-b955-bf0ee1db4809 +3397a796-894d-409c-97de-a9e6f3f88198 3ddf46fe-b5be-456d-810f-ea6a7402236d +3397a796-894d-409c-97de-a9e6f3f88198 3397a796-894d-409c-97de-a9e6f3f88198 +3397a796-894d-409c-97de-a9e6f3f88198 19502b5a-2031-487d-9d9c-2001f959408b +359cb842-c25b-429f-ba9b-d52f171e5631 f883f2ca-d523-4c9f-86b2-0add137901de +359cb842-c25b-429f-ba9b-d52f171e5631 f28931e5-1f35-4f9f-a02e-78398735ea67 +359cb842-c25b-429f-ba9b-d52f171e5631 e7f68d35-ae55-4fa4-b0be-0672a5a7186a +359cb842-c25b-429f-ba9b-d52f171e5631 85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b +359cb842-c25b-429f-ba9b-d52f171e5631 7ac56d5a-32a7-4b40-a956-356e3006faed +359cb842-c25b-429f-ba9b-d52f171e5631 72788e7f-1bf1-40b5-a1f1-27c669dc7278 +359cb842-c25b-429f-ba9b-d52f171e5631 6c84348c-5065-4e89-9412-bfda023683f2 +359cb842-c25b-429f-ba9b-d52f171e5631 5abe533b-ae5f-47ad-8746-f60caf7483c0 +359cb842-c25b-429f-ba9b-d52f171e5631 4f14ca7f-5332-4263-a7b2-f0a838380309 +359cb842-c25b-429f-ba9b-d52f171e5631 359cb842-c25b-429f-ba9b-d52f171e5631 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 ee24bf89-81a3-4259-a382-86917f3829d4 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 e363eb67-64c7-440f-93fd-5c8787c69a85 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 db79f75d-af3c-4f82-b4e5-9b5d26598eef +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 d7a48a26-7731-4046-bf22-78f0b8084eb9 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 d080c116-681a-494a-a928-45924109b49d +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 990c21ab-433b-4920-873e-f9dfc7103d9d +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 922e2443-9cc5-421f-8310-9cd23f6e9f2d +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 8089a9ac-9e71-4487-a231-f720e4bc8d3e +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 6d47a2fe-3645-4c5c-bebe-1b822eff197f +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 67fc12fc-bb9f-40f1-9051-127a788b3081 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 634f0889-3a83-484a-bcc8-86f48e333c7b +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 4adcaf72-5241-4877-b61c-f34060576c50 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 374fd699-6b74-4500-9d60-2473c7e0364f +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 308eba53-29fa-489a-97dc-741b077841a7 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 21995497-0eb8-4c0b-8c23-e6a829f3d596 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 18ebf5ea-b0a2-4333-9e9c-40217de809ff +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 151e3048-66ad-4411-8753-677877e3bf0a +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 0af4a77e-892e-4b3f-9110-4c5224782250 +374fd699-6b74-4500-9d60-2473c7e0364f fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +374fd699-6b74-4500-9d60-2473c7e0364f ee24bf89-81a3-4259-a382-86917f3829d4 +374fd699-6b74-4500-9d60-2473c7e0364f e363eb67-64c7-440f-93fd-5c8787c69a85 +374fd699-6b74-4500-9d60-2473c7e0364f dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +374fd699-6b74-4500-9d60-2473c7e0364f db79f75d-af3c-4f82-b4e5-9b5d26598eef +374fd699-6b74-4500-9d60-2473c7e0364f d7a48a26-7731-4046-bf22-78f0b8084eb9 +374fd699-6b74-4500-9d60-2473c7e0364f d080c116-681a-494a-a928-45924109b49d +374fd699-6b74-4500-9d60-2473c7e0364f cdb8493c-e3b0-447f-b16b-e58dd64660f3 +374fd699-6b74-4500-9d60-2473c7e0364f c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +374fd699-6b74-4500-9d60-2473c7e0364f a6b084fb-ada7-480a-9adc-f180d4eb1e2a +374fd699-6b74-4500-9d60-2473c7e0364f 990c21ab-433b-4920-873e-f9dfc7103d9d +374fd699-6b74-4500-9d60-2473c7e0364f 922e2443-9cc5-421f-8310-9cd23f6e9f2d +374fd699-6b74-4500-9d60-2473c7e0364f 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +374fd699-6b74-4500-9d60-2473c7e0364f 8089a9ac-9e71-4487-a231-f720e4bc8d3e +374fd699-6b74-4500-9d60-2473c7e0364f 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +374fd699-6b74-4500-9d60-2473c7e0364f 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +374fd699-6b74-4500-9d60-2473c7e0364f 6d47a2fe-3645-4c5c-bebe-1b822eff197f +374fd699-6b74-4500-9d60-2473c7e0364f 67fc12fc-bb9f-40f1-9051-127a788b3081 +374fd699-6b74-4500-9d60-2473c7e0364f 634f0889-3a83-484a-bcc8-86f48e333c7b +374fd699-6b74-4500-9d60-2473c7e0364f 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +374fd699-6b74-4500-9d60-2473c7e0364f 4adcaf72-5241-4877-b61c-f34060576c50 +374fd699-6b74-4500-9d60-2473c7e0364f 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +374fd699-6b74-4500-9d60-2473c7e0364f 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +374fd699-6b74-4500-9d60-2473c7e0364f 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +374fd699-6b74-4500-9d60-2473c7e0364f 374fd699-6b74-4500-9d60-2473c7e0364f +374fd699-6b74-4500-9d60-2473c7e0364f 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +374fd699-6b74-4500-9d60-2473c7e0364f 308eba53-29fa-489a-97dc-741b077841a7 +374fd699-6b74-4500-9d60-2473c7e0364f 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +374fd699-6b74-4500-9d60-2473c7e0364f 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +374fd699-6b74-4500-9d60-2473c7e0364f 21995497-0eb8-4c0b-8c23-e6a829f3d596 +374fd699-6b74-4500-9d60-2473c7e0364f 18ebf5ea-b0a2-4333-9e9c-40217de809ff +374fd699-6b74-4500-9d60-2473c7e0364f 151e3048-66ad-4411-8753-677877e3bf0a +374fd699-6b74-4500-9d60-2473c7e0364f 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +374fd699-6b74-4500-9d60-2473c7e0364f 0af4a77e-892e-4b3f-9110-4c5224782250 +395019b6-84e8-4919-8b8b-ac64e4a6eade d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2 +395019b6-84e8-4919-8b8b-ac64e4a6eade 395019b6-84e8-4919-8b8b-ac64e4a6eade +39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 fcab9efe-fb05-42d6-b366-ce6db1cc4093 +39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 f1293c5e-c997-4ba9-8bfd-438e7a840dc8 +39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 e4358f28-62a8-4e06-b2bd-cb00d3310349 +39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 8f76d729-9f74-4cfd-be2a-8b033b568e18 +39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 +39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 31d4c712-75b1-4ecb-9d1a-ccc4b8453826 +39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 24c4dd5b-748b-4165-a0cc-2867b76c2dc2 +39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 16d280a2-c61f-487f-9034-22e884158969 +39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 0134901a-4a69-4b39-92ea-d6f48ec83c6e +3ddf46fe-b5be-456d-810f-ea6a7402236d fc771883-3bd6-46d9-9626-a130e1b902de +3ddf46fe-b5be-456d-810f-ea6a7402236d e2387a81-5ff5-4daf-b2e8-881998d0d917 +3ddf46fe-b5be-456d-810f-ea6a7402236d b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa +3ddf46fe-b5be-456d-810f-ea6a7402236d b1d4dc41-07ae-44db-ae17-1df86c5a62cf +3ddf46fe-b5be-456d-810f-ea6a7402236d 67e5cf95-236b-4f75-abc5-034ca2966448 +3ddf46fe-b5be-456d-810f-ea6a7402236d 48cc2275-a478-4ade-b076-cf38e1d16017 +3ddf46fe-b5be-456d-810f-ea6a7402236d 41d26b1c-57b9-408c-b955-bf0ee1db4809 +3ddf46fe-b5be-456d-810f-ea6a7402236d 3ddf46fe-b5be-456d-810f-ea6a7402236d +3ddf46fe-b5be-456d-810f-ea6a7402236d 3397a796-894d-409c-97de-a9e6f3f88198 +3ddf46fe-b5be-456d-810f-ea6a7402236d 19502b5a-2031-487d-9d9c-2001f959408b +4148ac36-1dcb-4e96-89cd-355e7e8f919b e1c2ad9f-6f61-4f16-805a-2f405b63659a +4148ac36-1dcb-4e96-89cd-355e7e8f919b d6988116-3c63-44f8-9f94-9e9d51cc5a37 +4148ac36-1dcb-4e96-89cd-355e7e8f919b cfcbe34a-edc5-4442-bc05-5927fa00336b +4148ac36-1dcb-4e96-89cd-355e7e8f919b c90aae7e-5704-4e13-8794-41ab377193bd +4148ac36-1dcb-4e96-89cd-355e7e8f919b bf895c48-1d52-4780-8e9a-532d69356d28 +4148ac36-1dcb-4e96-89cd-355e7e8f919b ae9e663e-8c15-43c9-8a88-52b61cbf07a9 +4148ac36-1dcb-4e96-89cd-355e7e8f919b 56999896-e36b-4e63-a6b6-dbb85dfe1936 +4148ac36-1dcb-4e96-89cd-355e7e8f919b 51241857-a7a4-4517-96e3-21f797581f89 +4148ac36-1dcb-4e96-89cd-355e7e8f919b 4cd95073-6db3-4eb2-ac4b-0aacb182b352 +4148ac36-1dcb-4e96-89cd-355e7e8f919b 4148ac36-1dcb-4e96-89cd-355e7e8f919b +4148ac36-1dcb-4e96-89cd-355e7e8f919b 2e8eb256-84c9-499a-8bf6-bb39504373e1 +4148ac36-1dcb-4e96-89cd-355e7e8f919b 07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 +41d26b1c-57b9-408c-b955-bf0ee1db4809 fc771883-3bd6-46d9-9626-a130e1b902de +41d26b1c-57b9-408c-b955-bf0ee1db4809 e2387a81-5ff5-4daf-b2e8-881998d0d917 +41d26b1c-57b9-408c-b955-bf0ee1db4809 b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa +41d26b1c-57b9-408c-b955-bf0ee1db4809 b1d4dc41-07ae-44db-ae17-1df86c5a62cf +41d26b1c-57b9-408c-b955-bf0ee1db4809 67e5cf95-236b-4f75-abc5-034ca2966448 +41d26b1c-57b9-408c-b955-bf0ee1db4809 48cc2275-a478-4ade-b076-cf38e1d16017 +41d26b1c-57b9-408c-b955-bf0ee1db4809 41d26b1c-57b9-408c-b955-bf0ee1db4809 +41d26b1c-57b9-408c-b955-bf0ee1db4809 3ddf46fe-b5be-456d-810f-ea6a7402236d +41d26b1c-57b9-408c-b955-bf0ee1db4809 3397a796-894d-409c-97de-a9e6f3f88198 +41d26b1c-57b9-408c-b955-bf0ee1db4809 19502b5a-2031-487d-9d9c-2001f959408b +4215b5d0-bdd9-4222-a04a-81bb637d60af f29330d0-5b32-4f75-b558-a1c7f05c0b4a +4215b5d0-bdd9-4222-a04a-81bb637d60af d90676d2-ce74-4867-a00f-6dbffa7c00be +4215b5d0-bdd9-4222-a04a-81bb637d60af cc56af6d-25c6-480e-9767-da02a55551da +4215b5d0-bdd9-4222-a04a-81bb637d60af ca7f269d-3794-4994-8c46-d8e9f2aa6378 +4215b5d0-bdd9-4222-a04a-81bb637d60af baf48725-f0f9-4357-a71d-b1104910deae +4215b5d0-bdd9-4222-a04a-81bb637d60af 9ed8703e-3b40-424c-9e98-b3b404051f99 +4215b5d0-bdd9-4222-a04a-81bb637d60af 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +4215b5d0-bdd9-4222-a04a-81bb637d60af 8be81657-8ba6-4ec5-8ff9-4809367096ea +4215b5d0-bdd9-4222-a04a-81bb637d60af 7f410064-638a-4477-85c4-97fc2bb14a49 +4215b5d0-bdd9-4222-a04a-81bb637d60af 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +4215b5d0-bdd9-4222-a04a-81bb637d60af 5cedf18a-fc44-4c39-a80f-2106a0d76934 +4215b5d0-bdd9-4222-a04a-81bb637d60af 4f342a71-dec3-403e-970b-e4c21b9eb98b +4215b5d0-bdd9-4222-a04a-81bb637d60af 4db144d3-a596-4718-a50a-8ac9175ad386 +4215b5d0-bdd9-4222-a04a-81bb637d60af 4215b5d0-bdd9-4222-a04a-81bb637d60af +4215b5d0-bdd9-4222-a04a-81bb637d60af 29d4e919-2bd2-44e6-bb8a-380a780f60ff +4215b5d0-bdd9-4222-a04a-81bb637d60af 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +4215b5d0-bdd9-4222-a04a-81bb637d60af 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +4215b5d0-bdd9-4222-a04a-81bb637d60af 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +4215b5d0-bdd9-4222-a04a-81bb637d60af 102e16c5-4920-4dad-b142-0b280b2aacad +4215b5d0-bdd9-4222-a04a-81bb637d60af 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +42a9a333-d09d-4b9e-af96-1f02af26bac3 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +42a9a333-d09d-4b9e-af96-1f02af26bac3 e8eba267-fecb-477e-9625-771fbcfc3cba +42a9a333-d09d-4b9e-af96-1f02af26bac3 e8c41168-a093-40eb-8baa-6802d24f554a +42a9a333-d09d-4b9e-af96-1f02af26bac3 e26ae29a-213a-4f79-98c2-cc81cf2f451d +42a9a333-d09d-4b9e-af96-1f02af26bac3 e0dfbc08-45ad-4a81-b648-7e65c066f673 +42a9a333-d09d-4b9e-af96-1f02af26bac3 d535b213-2abc-438f-aa6a-c06476d60b09 +42a9a333-d09d-4b9e-af96-1f02af26bac3 c892b1d7-7485-407d-8883-54fb7fecebc1 +42a9a333-d09d-4b9e-af96-1f02af26bac3 b6900c21-d310-4fb4-8b8f-176a09c91989 +42a9a333-d09d-4b9e-af96-1f02af26bac3 b00df815-7528-4967-bfe2-311130d91c21 +42a9a333-d09d-4b9e-af96-1f02af26bac3 add6d754-a075-4693-a33a-c061c9a368ff +42a9a333-d09d-4b9e-af96-1f02af26bac3 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +42a9a333-d09d-4b9e-af96-1f02af26bac3 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +42a9a333-d09d-4b9e-af96-1f02af26bac3 8a1908f7-47cc-4f82-859c-781a193e9901 +42a9a333-d09d-4b9e-af96-1f02af26bac3 8743e69b-17aa-44ff-b8fa-4f582665efc6 +42a9a333-d09d-4b9e-af96-1f02af26bac3 721e4027-a080-40d8-bc4a-43cd33477611 +42a9a333-d09d-4b9e-af96-1f02af26bac3 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +42a9a333-d09d-4b9e-af96-1f02af26bac3 54c750aa-570a-4e8e-9a02-418781923e39 +42a9a333-d09d-4b9e-af96-1f02af26bac3 50eac25a-3761-4de3-8a69-aae52e658300 +42a9a333-d09d-4b9e-af96-1f02af26bac3 4a464bb5-9373-4f1b-9c03-053c64797114 +42a9a333-d09d-4b9e-af96-1f02af26bac3 4506aa76-9d0d-40e4-85bc-5735f74522a0 +42a9a333-d09d-4b9e-af96-1f02af26bac3 42a9a333-d09d-4b9e-af96-1f02af26bac3 +42a9a333-d09d-4b9e-af96-1f02af26bac3 2fc75f11-2097-4e1e-8390-dbff3e844b7a +42a9a333-d09d-4b9e-af96-1f02af26bac3 274a2e49-9170-4945-bca2-ab6ef4bded75 +42a9a333-d09d-4b9e-af96-1f02af26bac3 0364073f-91d8-47a1-b8b0-107c6318a691 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +42aa48c7-68cc-4bee-9730-cff29f0e36c5 ee24bf89-81a3-4259-a382-86917f3829d4 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 e363eb67-64c7-440f-93fd-5c8787c69a85 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 db79f75d-af3c-4f82-b4e5-9b5d26598eef +42aa48c7-68cc-4bee-9730-cff29f0e36c5 d7a48a26-7731-4046-bf22-78f0b8084eb9 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 d080c116-681a-494a-a928-45924109b49d +42aa48c7-68cc-4bee-9730-cff29f0e36c5 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +42aa48c7-68cc-4bee-9730-cff29f0e36c5 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +42aa48c7-68cc-4bee-9730-cff29f0e36c5 990c21ab-433b-4920-873e-f9dfc7103d9d +42aa48c7-68cc-4bee-9730-cff29f0e36c5 922e2443-9cc5-421f-8310-9cd23f6e9f2d +42aa48c7-68cc-4bee-9730-cff29f0e36c5 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 8089a9ac-9e71-4487-a231-f720e4bc8d3e +42aa48c7-68cc-4bee-9730-cff29f0e36c5 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +42aa48c7-68cc-4bee-9730-cff29f0e36c5 6d47a2fe-3645-4c5c-bebe-1b822eff197f +42aa48c7-68cc-4bee-9730-cff29f0e36c5 67fc12fc-bb9f-40f1-9051-127a788b3081 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 634f0889-3a83-484a-bcc8-86f48e333c7b +42aa48c7-68cc-4bee-9730-cff29f0e36c5 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 4adcaf72-5241-4877-b61c-f34060576c50 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +42aa48c7-68cc-4bee-9730-cff29f0e36c5 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 374fd699-6b74-4500-9d60-2473c7e0364f +42aa48c7-68cc-4bee-9730-cff29f0e36c5 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 308eba53-29fa-489a-97dc-741b077841a7 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +42aa48c7-68cc-4bee-9730-cff29f0e36c5 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 21995497-0eb8-4c0b-8c23-e6a829f3d596 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 18ebf5ea-b0a2-4333-9e9c-40217de809ff +42aa48c7-68cc-4bee-9730-cff29f0e36c5 151e3048-66ad-4411-8753-677877e3bf0a +42aa48c7-68cc-4bee-9730-cff29f0e36c5 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +42aa48c7-68cc-4bee-9730-cff29f0e36c5 0af4a77e-892e-4b3f-9110-4c5224782250 +4506aa76-9d0d-40e4-85bc-5735f74522a0 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +4506aa76-9d0d-40e4-85bc-5735f74522a0 e8eba267-fecb-477e-9625-771fbcfc3cba +4506aa76-9d0d-40e4-85bc-5735f74522a0 e8c41168-a093-40eb-8baa-6802d24f554a +4506aa76-9d0d-40e4-85bc-5735f74522a0 e26ae29a-213a-4f79-98c2-cc81cf2f451d +4506aa76-9d0d-40e4-85bc-5735f74522a0 e0dfbc08-45ad-4a81-b648-7e65c066f673 +4506aa76-9d0d-40e4-85bc-5735f74522a0 d535b213-2abc-438f-aa6a-c06476d60b09 +4506aa76-9d0d-40e4-85bc-5735f74522a0 c892b1d7-7485-407d-8883-54fb7fecebc1 +4506aa76-9d0d-40e4-85bc-5735f74522a0 b6900c21-d310-4fb4-8b8f-176a09c91989 +4506aa76-9d0d-40e4-85bc-5735f74522a0 b00df815-7528-4967-bfe2-311130d91c21 +4506aa76-9d0d-40e4-85bc-5735f74522a0 add6d754-a075-4693-a33a-c061c9a368ff +4506aa76-9d0d-40e4-85bc-5735f74522a0 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +4506aa76-9d0d-40e4-85bc-5735f74522a0 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +4506aa76-9d0d-40e4-85bc-5735f74522a0 8a1908f7-47cc-4f82-859c-781a193e9901 +4506aa76-9d0d-40e4-85bc-5735f74522a0 8743e69b-17aa-44ff-b8fa-4f582665efc6 +4506aa76-9d0d-40e4-85bc-5735f74522a0 721e4027-a080-40d8-bc4a-43cd33477611 +4506aa76-9d0d-40e4-85bc-5735f74522a0 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +4506aa76-9d0d-40e4-85bc-5735f74522a0 54c750aa-570a-4e8e-9a02-418781923e39 +4506aa76-9d0d-40e4-85bc-5735f74522a0 50eac25a-3761-4de3-8a69-aae52e658300 +4506aa76-9d0d-40e4-85bc-5735f74522a0 4a464bb5-9373-4f1b-9c03-053c64797114 +4506aa76-9d0d-40e4-85bc-5735f74522a0 4506aa76-9d0d-40e4-85bc-5735f74522a0 +4506aa76-9d0d-40e4-85bc-5735f74522a0 42a9a333-d09d-4b9e-af96-1f02af26bac3 +4506aa76-9d0d-40e4-85bc-5735f74522a0 2fc75f11-2097-4e1e-8390-dbff3e844b7a +4506aa76-9d0d-40e4-85bc-5735f74522a0 274a2e49-9170-4945-bca2-ab6ef4bded75 +4506aa76-9d0d-40e4-85bc-5735f74522a0 0364073f-91d8-47a1-b8b0-107c6318a691 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +45b25ced-6eed-4fca-8ec1-b7d32ea13efe ee24bf89-81a3-4259-a382-86917f3829d4 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe e363eb67-64c7-440f-93fd-5c8787c69a85 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe db79f75d-af3c-4f82-b4e5-9b5d26598eef +45b25ced-6eed-4fca-8ec1-b7d32ea13efe d7a48a26-7731-4046-bf22-78f0b8084eb9 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe d080c116-681a-494a-a928-45924109b49d +45b25ced-6eed-4fca-8ec1-b7d32ea13efe cdb8493c-e3b0-447f-b16b-e58dd64660f3 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +45b25ced-6eed-4fca-8ec1-b7d32ea13efe a6b084fb-ada7-480a-9adc-f180d4eb1e2a +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 990c21ab-433b-4920-873e-f9dfc7103d9d +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 922e2443-9cc5-421f-8310-9cd23f6e9f2d +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 8089a9ac-9e71-4487-a231-f720e4bc8d3e +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 6d47a2fe-3645-4c5c-bebe-1b822eff197f +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 67fc12fc-bb9f-40f1-9051-127a788b3081 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 634f0889-3a83-484a-bcc8-86f48e333c7b +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 4adcaf72-5241-4877-b61c-f34060576c50 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 374fd699-6b74-4500-9d60-2473c7e0364f +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 308eba53-29fa-489a-97dc-741b077841a7 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 21995497-0eb8-4c0b-8c23-e6a829f3d596 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 18ebf5ea-b0a2-4333-9e9c-40217de809ff +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 151e3048-66ad-4411-8753-677877e3bf0a +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 0af4a77e-892e-4b3f-9110-4c5224782250 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 ee24bf89-81a3-4259-a382-86917f3829d4 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 e363eb67-64c7-440f-93fd-5c8787c69a85 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 db79f75d-af3c-4f82-b4e5-9b5d26598eef +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 d7a48a26-7731-4046-bf22-78f0b8084eb9 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 d080c116-681a-494a-a928-45924109b49d +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 990c21ab-433b-4920-873e-f9dfc7103d9d +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 922e2443-9cc5-421f-8310-9cd23f6e9f2d +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 8089a9ac-9e71-4487-a231-f720e4bc8d3e +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 6d47a2fe-3645-4c5c-bebe-1b822eff197f +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 67fc12fc-bb9f-40f1-9051-127a788b3081 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 634f0889-3a83-484a-bcc8-86f48e333c7b +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 4adcaf72-5241-4877-b61c-f34060576c50 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 374fd699-6b74-4500-9d60-2473c7e0364f +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 308eba53-29fa-489a-97dc-741b077841a7 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 21995497-0eb8-4c0b-8c23-e6a829f3d596 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 18ebf5ea-b0a2-4333-9e9c-40217de809ff +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 151e3048-66ad-4411-8753-677877e3bf0a +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 0af4a77e-892e-4b3f-9110-4c5224782250 +48cc2275-a478-4ade-b076-cf38e1d16017 fc771883-3bd6-46d9-9626-a130e1b902de +48cc2275-a478-4ade-b076-cf38e1d16017 e2387a81-5ff5-4daf-b2e8-881998d0d917 +48cc2275-a478-4ade-b076-cf38e1d16017 b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa +48cc2275-a478-4ade-b076-cf38e1d16017 b1d4dc41-07ae-44db-ae17-1df86c5a62cf +48cc2275-a478-4ade-b076-cf38e1d16017 67e5cf95-236b-4f75-abc5-034ca2966448 +48cc2275-a478-4ade-b076-cf38e1d16017 48cc2275-a478-4ade-b076-cf38e1d16017 +48cc2275-a478-4ade-b076-cf38e1d16017 41d26b1c-57b9-408c-b955-bf0ee1db4809 +48cc2275-a478-4ade-b076-cf38e1d16017 3ddf46fe-b5be-456d-810f-ea6a7402236d +48cc2275-a478-4ade-b076-cf38e1d16017 3397a796-894d-409c-97de-a9e6f3f88198 +48cc2275-a478-4ade-b076-cf38e1d16017 19502b5a-2031-487d-9d9c-2001f959408b +498f4b18-bd4c-470d-9cde-2fca70ec8964 fce20464-ff98-4051-8714-6b9584e52740 +498f4b18-bd4c-470d-9cde-2fca70ec8964 f3eb0c38-2571-44e7-be39-732eb52cf1df +498f4b18-bd4c-470d-9cde-2fca70ec8964 f3c2f842-6aa3-42c9-a0f3-895c40456868 +498f4b18-bd4c-470d-9cde-2fca70ec8964 ede7745a-8f3e-4e20-9f16-33756be7ab6c +498f4b18-bd4c-470d-9cde-2fca70ec8964 ca3e0486-fd6b-4a13-a741-3a47760b412d +498f4b18-bd4c-470d-9cde-2fca70ec8964 c5c6eed5-aec6-4b8a-b689-adbb219c2267 +498f4b18-bd4c-470d-9cde-2fca70ec8964 bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +498f4b18-bd4c-470d-9cde-2fca70ec8964 a221198d-000e-497b-8b70-bb4d37f7bbe1 +498f4b18-bd4c-470d-9cde-2fca70ec8964 a1228f20-7c50-4d3a-b88c-2d277ca79d79 +498f4b18-bd4c-470d-9cde-2fca70ec8964 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +498f4b18-bd4c-470d-9cde-2fca70ec8964 88b16960-bfff-41d0-81e4-9a4630834a00 +498f4b18-bd4c-470d-9cde-2fca70ec8964 522e2abe-ad96-4d1a-b5a5-748faa997531 +498f4b18-bd4c-470d-9cde-2fca70ec8964 4cfe72fe-21a0-4201-87b6-ef93695d2495 +498f4b18-bd4c-470d-9cde-2fca70ec8964 498f4b18-bd4c-470d-9cde-2fca70ec8964 +498f4b18-bd4c-470d-9cde-2fca70ec8964 202131ae-78bb-4104-89b0-fb90645760f6 +498f4b18-bd4c-470d-9cde-2fca70ec8964 11dafd5c-85e7-4275-a147-89a63641e35e +498f4b18-bd4c-470d-9cde-2fca70ec8964 02b993c0-b358-481c-b0d0-0767387feb9f +4a464bb5-9373-4f1b-9c03-053c64797114 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +4a464bb5-9373-4f1b-9c03-053c64797114 e8eba267-fecb-477e-9625-771fbcfc3cba +4a464bb5-9373-4f1b-9c03-053c64797114 e8c41168-a093-40eb-8baa-6802d24f554a +4a464bb5-9373-4f1b-9c03-053c64797114 e26ae29a-213a-4f79-98c2-cc81cf2f451d +4a464bb5-9373-4f1b-9c03-053c64797114 e0dfbc08-45ad-4a81-b648-7e65c066f673 +4a464bb5-9373-4f1b-9c03-053c64797114 d535b213-2abc-438f-aa6a-c06476d60b09 +4a464bb5-9373-4f1b-9c03-053c64797114 c892b1d7-7485-407d-8883-54fb7fecebc1 +4a464bb5-9373-4f1b-9c03-053c64797114 b6900c21-d310-4fb4-8b8f-176a09c91989 +4a464bb5-9373-4f1b-9c03-053c64797114 b00df815-7528-4967-bfe2-311130d91c21 +4a464bb5-9373-4f1b-9c03-053c64797114 add6d754-a075-4693-a33a-c061c9a368ff +4a464bb5-9373-4f1b-9c03-053c64797114 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +4a464bb5-9373-4f1b-9c03-053c64797114 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +4a464bb5-9373-4f1b-9c03-053c64797114 8a1908f7-47cc-4f82-859c-781a193e9901 +4a464bb5-9373-4f1b-9c03-053c64797114 8743e69b-17aa-44ff-b8fa-4f582665efc6 +4a464bb5-9373-4f1b-9c03-053c64797114 721e4027-a080-40d8-bc4a-43cd33477611 +4a464bb5-9373-4f1b-9c03-053c64797114 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +4a464bb5-9373-4f1b-9c03-053c64797114 54c750aa-570a-4e8e-9a02-418781923e39 +4a464bb5-9373-4f1b-9c03-053c64797114 50eac25a-3761-4de3-8a69-aae52e658300 +4a464bb5-9373-4f1b-9c03-053c64797114 4a464bb5-9373-4f1b-9c03-053c64797114 +4a464bb5-9373-4f1b-9c03-053c64797114 4506aa76-9d0d-40e4-85bc-5735f74522a0 +4a464bb5-9373-4f1b-9c03-053c64797114 42a9a333-d09d-4b9e-af96-1f02af26bac3 +4a464bb5-9373-4f1b-9c03-053c64797114 2fc75f11-2097-4e1e-8390-dbff3e844b7a +4a464bb5-9373-4f1b-9c03-053c64797114 274a2e49-9170-4945-bca2-ab6ef4bded75 +4a464bb5-9373-4f1b-9c03-053c64797114 0364073f-91d8-47a1-b8b0-107c6318a691 +4adcaf72-5241-4877-b61c-f34060576c50 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +4adcaf72-5241-4877-b61c-f34060576c50 ee24bf89-81a3-4259-a382-86917f3829d4 +4adcaf72-5241-4877-b61c-f34060576c50 e363eb67-64c7-440f-93fd-5c8787c69a85 +4adcaf72-5241-4877-b61c-f34060576c50 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +4adcaf72-5241-4877-b61c-f34060576c50 db79f75d-af3c-4f82-b4e5-9b5d26598eef +4adcaf72-5241-4877-b61c-f34060576c50 d7a48a26-7731-4046-bf22-78f0b8084eb9 +4adcaf72-5241-4877-b61c-f34060576c50 d080c116-681a-494a-a928-45924109b49d +4adcaf72-5241-4877-b61c-f34060576c50 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +4adcaf72-5241-4877-b61c-f34060576c50 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +4adcaf72-5241-4877-b61c-f34060576c50 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +4adcaf72-5241-4877-b61c-f34060576c50 990c21ab-433b-4920-873e-f9dfc7103d9d +4adcaf72-5241-4877-b61c-f34060576c50 922e2443-9cc5-421f-8310-9cd23f6e9f2d +4adcaf72-5241-4877-b61c-f34060576c50 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +4adcaf72-5241-4877-b61c-f34060576c50 8089a9ac-9e71-4487-a231-f720e4bc8d3e +4adcaf72-5241-4877-b61c-f34060576c50 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +4adcaf72-5241-4877-b61c-f34060576c50 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +4adcaf72-5241-4877-b61c-f34060576c50 6d47a2fe-3645-4c5c-bebe-1b822eff197f +4adcaf72-5241-4877-b61c-f34060576c50 67fc12fc-bb9f-40f1-9051-127a788b3081 +4adcaf72-5241-4877-b61c-f34060576c50 634f0889-3a83-484a-bcc8-86f48e333c7b +4adcaf72-5241-4877-b61c-f34060576c50 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +4adcaf72-5241-4877-b61c-f34060576c50 4adcaf72-5241-4877-b61c-f34060576c50 +4adcaf72-5241-4877-b61c-f34060576c50 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +4adcaf72-5241-4877-b61c-f34060576c50 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +4adcaf72-5241-4877-b61c-f34060576c50 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +4adcaf72-5241-4877-b61c-f34060576c50 374fd699-6b74-4500-9d60-2473c7e0364f +4adcaf72-5241-4877-b61c-f34060576c50 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +4adcaf72-5241-4877-b61c-f34060576c50 308eba53-29fa-489a-97dc-741b077841a7 +4adcaf72-5241-4877-b61c-f34060576c50 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +4adcaf72-5241-4877-b61c-f34060576c50 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +4adcaf72-5241-4877-b61c-f34060576c50 21995497-0eb8-4c0b-8c23-e6a829f3d596 +4adcaf72-5241-4877-b61c-f34060576c50 18ebf5ea-b0a2-4333-9e9c-40217de809ff +4adcaf72-5241-4877-b61c-f34060576c50 151e3048-66ad-4411-8753-677877e3bf0a +4adcaf72-5241-4877-b61c-f34060576c50 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +4adcaf72-5241-4877-b61c-f34060576c50 0af4a77e-892e-4b3f-9110-4c5224782250 +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 f3aa491a-4dbd-4436-b674-18b2177dcf18 +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 e1040d58-53bc-4467-8101-ca53b772cede +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 d05461d6-13bf-402c-890d-db6404933f7c +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 b7772635-1801-48e4-a442-f4aaa6860544 +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 b07fb98d-2da4-423a-83b4-7a673de93096 +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 97b42d67-8691-433d-8a31-dada076162ae +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 720f7ed6-7b2e-41dc-a0e0-724a3332aa24 +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 2ffd6142-b75b-406f-bc06-cdb1c02eaefc +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 224e538d-3622-4f5e-b772-211ed358d8de +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 16a3408d-c927-4d02-a1ae-cad32419caab +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 04d88eb4-fb1b-4fa1-802a-5624f4c61b32 +4cd95073-6db3-4eb2-ac4b-0aacb182b352 e1c2ad9f-6f61-4f16-805a-2f405b63659a +4cd95073-6db3-4eb2-ac4b-0aacb182b352 d6988116-3c63-44f8-9f94-9e9d51cc5a37 +4cd95073-6db3-4eb2-ac4b-0aacb182b352 cfcbe34a-edc5-4442-bc05-5927fa00336b +4cd95073-6db3-4eb2-ac4b-0aacb182b352 c90aae7e-5704-4e13-8794-41ab377193bd +4cd95073-6db3-4eb2-ac4b-0aacb182b352 bf895c48-1d52-4780-8e9a-532d69356d28 +4cd95073-6db3-4eb2-ac4b-0aacb182b352 ae9e663e-8c15-43c9-8a88-52b61cbf07a9 +4cd95073-6db3-4eb2-ac4b-0aacb182b352 56999896-e36b-4e63-a6b6-dbb85dfe1936 +4cd95073-6db3-4eb2-ac4b-0aacb182b352 51241857-a7a4-4517-96e3-21f797581f89 +4cd95073-6db3-4eb2-ac4b-0aacb182b352 4cd95073-6db3-4eb2-ac4b-0aacb182b352 +4cd95073-6db3-4eb2-ac4b-0aacb182b352 4148ac36-1dcb-4e96-89cd-355e7e8f919b +4cd95073-6db3-4eb2-ac4b-0aacb182b352 2e8eb256-84c9-499a-8bf6-bb39504373e1 +4cd95073-6db3-4eb2-ac4b-0aacb182b352 07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 +4cfe72fe-21a0-4201-87b6-ef93695d2495 fce20464-ff98-4051-8714-6b9584e52740 +4cfe72fe-21a0-4201-87b6-ef93695d2495 f3eb0c38-2571-44e7-be39-732eb52cf1df +4cfe72fe-21a0-4201-87b6-ef93695d2495 f3c2f842-6aa3-42c9-a0f3-895c40456868 +4cfe72fe-21a0-4201-87b6-ef93695d2495 ede7745a-8f3e-4e20-9f16-33756be7ab6c +4cfe72fe-21a0-4201-87b6-ef93695d2495 ca3e0486-fd6b-4a13-a741-3a47760b412d +4cfe72fe-21a0-4201-87b6-ef93695d2495 c5c6eed5-aec6-4b8a-b689-adbb219c2267 +4cfe72fe-21a0-4201-87b6-ef93695d2495 bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +4cfe72fe-21a0-4201-87b6-ef93695d2495 a221198d-000e-497b-8b70-bb4d37f7bbe1 +4cfe72fe-21a0-4201-87b6-ef93695d2495 a1228f20-7c50-4d3a-b88c-2d277ca79d79 +4cfe72fe-21a0-4201-87b6-ef93695d2495 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +4cfe72fe-21a0-4201-87b6-ef93695d2495 88b16960-bfff-41d0-81e4-9a4630834a00 +4cfe72fe-21a0-4201-87b6-ef93695d2495 522e2abe-ad96-4d1a-b5a5-748faa997531 +4cfe72fe-21a0-4201-87b6-ef93695d2495 4cfe72fe-21a0-4201-87b6-ef93695d2495 +4cfe72fe-21a0-4201-87b6-ef93695d2495 498f4b18-bd4c-470d-9cde-2fca70ec8964 +4cfe72fe-21a0-4201-87b6-ef93695d2495 202131ae-78bb-4104-89b0-fb90645760f6 +4cfe72fe-21a0-4201-87b6-ef93695d2495 11dafd5c-85e7-4275-a147-89a63641e35e +4cfe72fe-21a0-4201-87b6-ef93695d2495 02b993c0-b358-481c-b0d0-0767387feb9f +4db144d3-a596-4718-a50a-8ac9175ad386 f29330d0-5b32-4f75-b558-a1c7f05c0b4a +4db144d3-a596-4718-a50a-8ac9175ad386 d90676d2-ce74-4867-a00f-6dbffa7c00be +4db144d3-a596-4718-a50a-8ac9175ad386 cc56af6d-25c6-480e-9767-da02a55551da +4db144d3-a596-4718-a50a-8ac9175ad386 ca7f269d-3794-4994-8c46-d8e9f2aa6378 +4db144d3-a596-4718-a50a-8ac9175ad386 baf48725-f0f9-4357-a71d-b1104910deae +4db144d3-a596-4718-a50a-8ac9175ad386 9ed8703e-3b40-424c-9e98-b3b404051f99 +4db144d3-a596-4718-a50a-8ac9175ad386 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +4db144d3-a596-4718-a50a-8ac9175ad386 8be81657-8ba6-4ec5-8ff9-4809367096ea +4db144d3-a596-4718-a50a-8ac9175ad386 7f410064-638a-4477-85c4-97fc2bb14a49 +4db144d3-a596-4718-a50a-8ac9175ad386 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +4db144d3-a596-4718-a50a-8ac9175ad386 5cedf18a-fc44-4c39-a80f-2106a0d76934 +4db144d3-a596-4718-a50a-8ac9175ad386 4f342a71-dec3-403e-970b-e4c21b9eb98b +4db144d3-a596-4718-a50a-8ac9175ad386 4db144d3-a596-4718-a50a-8ac9175ad386 +4db144d3-a596-4718-a50a-8ac9175ad386 4215b5d0-bdd9-4222-a04a-81bb637d60af +4db144d3-a596-4718-a50a-8ac9175ad386 29d4e919-2bd2-44e6-bb8a-380a780f60ff +4db144d3-a596-4718-a50a-8ac9175ad386 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +4db144d3-a596-4718-a50a-8ac9175ad386 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +4db144d3-a596-4718-a50a-8ac9175ad386 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +4db144d3-a596-4718-a50a-8ac9175ad386 102e16c5-4920-4dad-b142-0b280b2aacad +4db144d3-a596-4718-a50a-8ac9175ad386 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 ee24bf89-81a3-4259-a382-86917f3829d4 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 e363eb67-64c7-440f-93fd-5c8787c69a85 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 db79f75d-af3c-4f82-b4e5-9b5d26598eef +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 d7a48a26-7731-4046-bf22-78f0b8084eb9 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 d080c116-681a-494a-a928-45924109b49d +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 990c21ab-433b-4920-873e-f9dfc7103d9d +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 922e2443-9cc5-421f-8310-9cd23f6e9f2d +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 8089a9ac-9e71-4487-a231-f720e4bc8d3e +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 6d47a2fe-3645-4c5c-bebe-1b822eff197f +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 67fc12fc-bb9f-40f1-9051-127a788b3081 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 634f0889-3a83-484a-bcc8-86f48e333c7b +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 4adcaf72-5241-4877-b61c-f34060576c50 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 374fd699-6b74-4500-9d60-2473c7e0364f +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 308eba53-29fa-489a-97dc-741b077841a7 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 21995497-0eb8-4c0b-8c23-e6a829f3d596 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 18ebf5ea-b0a2-4333-9e9c-40217de809ff +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 151e3048-66ad-4411-8753-677877e3bf0a +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 0af4a77e-892e-4b3f-9110-4c5224782250 +4f14ca7f-5332-4263-a7b2-f0a838380309 f883f2ca-d523-4c9f-86b2-0add137901de +4f14ca7f-5332-4263-a7b2-f0a838380309 f28931e5-1f35-4f9f-a02e-78398735ea67 +4f14ca7f-5332-4263-a7b2-f0a838380309 e7f68d35-ae55-4fa4-b0be-0672a5a7186a +4f14ca7f-5332-4263-a7b2-f0a838380309 85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b +4f14ca7f-5332-4263-a7b2-f0a838380309 7ac56d5a-32a7-4b40-a956-356e3006faed +4f14ca7f-5332-4263-a7b2-f0a838380309 72788e7f-1bf1-40b5-a1f1-27c669dc7278 +4f14ca7f-5332-4263-a7b2-f0a838380309 6c84348c-5065-4e89-9412-bfda023683f2 +4f14ca7f-5332-4263-a7b2-f0a838380309 5abe533b-ae5f-47ad-8746-f60caf7483c0 +4f14ca7f-5332-4263-a7b2-f0a838380309 4f14ca7f-5332-4263-a7b2-f0a838380309 +4f14ca7f-5332-4263-a7b2-f0a838380309 359cb842-c25b-429f-ba9b-d52f171e5631 +4f342a71-dec3-403e-970b-e4c21b9eb98b f29330d0-5b32-4f75-b558-a1c7f05c0b4a +4f342a71-dec3-403e-970b-e4c21b9eb98b d90676d2-ce74-4867-a00f-6dbffa7c00be +4f342a71-dec3-403e-970b-e4c21b9eb98b cc56af6d-25c6-480e-9767-da02a55551da +4f342a71-dec3-403e-970b-e4c21b9eb98b ca7f269d-3794-4994-8c46-d8e9f2aa6378 +4f342a71-dec3-403e-970b-e4c21b9eb98b baf48725-f0f9-4357-a71d-b1104910deae +4f342a71-dec3-403e-970b-e4c21b9eb98b 9ed8703e-3b40-424c-9e98-b3b404051f99 +4f342a71-dec3-403e-970b-e4c21b9eb98b 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +4f342a71-dec3-403e-970b-e4c21b9eb98b 8be81657-8ba6-4ec5-8ff9-4809367096ea +4f342a71-dec3-403e-970b-e4c21b9eb98b 7f410064-638a-4477-85c4-97fc2bb14a49 +4f342a71-dec3-403e-970b-e4c21b9eb98b 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +4f342a71-dec3-403e-970b-e4c21b9eb98b 5cedf18a-fc44-4c39-a80f-2106a0d76934 +4f342a71-dec3-403e-970b-e4c21b9eb98b 4f342a71-dec3-403e-970b-e4c21b9eb98b +4f342a71-dec3-403e-970b-e4c21b9eb98b 4db144d3-a596-4718-a50a-8ac9175ad386 +4f342a71-dec3-403e-970b-e4c21b9eb98b 4215b5d0-bdd9-4222-a04a-81bb637d60af +4f342a71-dec3-403e-970b-e4c21b9eb98b 29d4e919-2bd2-44e6-bb8a-380a780f60ff +4f342a71-dec3-403e-970b-e4c21b9eb98b 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +4f342a71-dec3-403e-970b-e4c21b9eb98b 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +4f342a71-dec3-403e-970b-e4c21b9eb98b 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +4f342a71-dec3-403e-970b-e4c21b9eb98b 102e16c5-4920-4dad-b142-0b280b2aacad +4f342a71-dec3-403e-970b-e4c21b9eb98b 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +4f3d1e93-a28f-446e-91af-2baf0168b82b fcc916dd-5e4f-4bf7-9aef-c906a36d7301 +4f3d1e93-a28f-446e-91af-2baf0168b82b c57d51a7-45de-41b9-92fc-efd0634effaf +4f3d1e93-a28f-446e-91af-2baf0168b82b ae7ddaef-4911-418c-8401-d978617e145b +4f3d1e93-a28f-446e-91af-2baf0168b82b acb2329d-b1c3-45c3-ad28-91a09aa521e1 +4f3d1e93-a28f-446e-91af-2baf0168b82b a89c0c42-c19f-4d7f-a15e-d04e043c92f6 +4f3d1e93-a28f-446e-91af-2baf0168b82b 836b2e59-fb97-4014-ab0c-d5a5dc750969 +4f3d1e93-a28f-446e-91af-2baf0168b82b 683714ad-5194-49e7-9b8f-4e306cf68ae1 +4f3d1e93-a28f-446e-91af-2baf0168b82b 5f64131b-d410-449a-9de6-35415919fec5 +4f3d1e93-a28f-446e-91af-2baf0168b82b 4f3d1e93-a28f-446e-91af-2baf0168b82b +4f3d1e93-a28f-446e-91af-2baf0168b82b 1db1c71b-9eeb-4a98-abc1-eb699b38510c +50eac25a-3761-4de3-8a69-aae52e658300 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +50eac25a-3761-4de3-8a69-aae52e658300 e8eba267-fecb-477e-9625-771fbcfc3cba +50eac25a-3761-4de3-8a69-aae52e658300 e8c41168-a093-40eb-8baa-6802d24f554a +50eac25a-3761-4de3-8a69-aae52e658300 e26ae29a-213a-4f79-98c2-cc81cf2f451d +50eac25a-3761-4de3-8a69-aae52e658300 e0dfbc08-45ad-4a81-b648-7e65c066f673 +50eac25a-3761-4de3-8a69-aae52e658300 d535b213-2abc-438f-aa6a-c06476d60b09 +50eac25a-3761-4de3-8a69-aae52e658300 c892b1d7-7485-407d-8883-54fb7fecebc1 +50eac25a-3761-4de3-8a69-aae52e658300 b6900c21-d310-4fb4-8b8f-176a09c91989 +50eac25a-3761-4de3-8a69-aae52e658300 b00df815-7528-4967-bfe2-311130d91c21 +50eac25a-3761-4de3-8a69-aae52e658300 add6d754-a075-4693-a33a-c061c9a368ff +50eac25a-3761-4de3-8a69-aae52e658300 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +50eac25a-3761-4de3-8a69-aae52e658300 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +50eac25a-3761-4de3-8a69-aae52e658300 8a1908f7-47cc-4f82-859c-781a193e9901 +50eac25a-3761-4de3-8a69-aae52e658300 8743e69b-17aa-44ff-b8fa-4f582665efc6 +50eac25a-3761-4de3-8a69-aae52e658300 721e4027-a080-40d8-bc4a-43cd33477611 +50eac25a-3761-4de3-8a69-aae52e658300 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +50eac25a-3761-4de3-8a69-aae52e658300 54c750aa-570a-4e8e-9a02-418781923e39 +50eac25a-3761-4de3-8a69-aae52e658300 50eac25a-3761-4de3-8a69-aae52e658300 +50eac25a-3761-4de3-8a69-aae52e658300 4a464bb5-9373-4f1b-9c03-053c64797114 +50eac25a-3761-4de3-8a69-aae52e658300 4506aa76-9d0d-40e4-85bc-5735f74522a0 +50eac25a-3761-4de3-8a69-aae52e658300 42a9a333-d09d-4b9e-af96-1f02af26bac3 +50eac25a-3761-4de3-8a69-aae52e658300 2fc75f11-2097-4e1e-8390-dbff3e844b7a +50eac25a-3761-4de3-8a69-aae52e658300 274a2e49-9170-4945-bca2-ab6ef4bded75 +50eac25a-3761-4de3-8a69-aae52e658300 0364073f-91d8-47a1-b8b0-107c6318a691 +51241857-a7a4-4517-96e3-21f797581f89 e1c2ad9f-6f61-4f16-805a-2f405b63659a +51241857-a7a4-4517-96e3-21f797581f89 d6988116-3c63-44f8-9f94-9e9d51cc5a37 +51241857-a7a4-4517-96e3-21f797581f89 cfcbe34a-edc5-4442-bc05-5927fa00336b +51241857-a7a4-4517-96e3-21f797581f89 c90aae7e-5704-4e13-8794-41ab377193bd +51241857-a7a4-4517-96e3-21f797581f89 bf895c48-1d52-4780-8e9a-532d69356d28 +51241857-a7a4-4517-96e3-21f797581f89 ae9e663e-8c15-43c9-8a88-52b61cbf07a9 +51241857-a7a4-4517-96e3-21f797581f89 56999896-e36b-4e63-a6b6-dbb85dfe1936 +51241857-a7a4-4517-96e3-21f797581f89 51241857-a7a4-4517-96e3-21f797581f89 +51241857-a7a4-4517-96e3-21f797581f89 4cd95073-6db3-4eb2-ac4b-0aacb182b352 +51241857-a7a4-4517-96e3-21f797581f89 4148ac36-1dcb-4e96-89cd-355e7e8f919b +51241857-a7a4-4517-96e3-21f797581f89 2e8eb256-84c9-499a-8bf6-bb39504373e1 +51241857-a7a4-4517-96e3-21f797581f89 07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 +522e2abe-ad96-4d1a-b5a5-748faa997531 fce20464-ff98-4051-8714-6b9584e52740 +522e2abe-ad96-4d1a-b5a5-748faa997531 f3eb0c38-2571-44e7-be39-732eb52cf1df +522e2abe-ad96-4d1a-b5a5-748faa997531 f3c2f842-6aa3-42c9-a0f3-895c40456868 +522e2abe-ad96-4d1a-b5a5-748faa997531 ede7745a-8f3e-4e20-9f16-33756be7ab6c +522e2abe-ad96-4d1a-b5a5-748faa997531 ca3e0486-fd6b-4a13-a741-3a47760b412d +522e2abe-ad96-4d1a-b5a5-748faa997531 c5c6eed5-aec6-4b8a-b689-adbb219c2267 +522e2abe-ad96-4d1a-b5a5-748faa997531 bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +522e2abe-ad96-4d1a-b5a5-748faa997531 a221198d-000e-497b-8b70-bb4d37f7bbe1 +522e2abe-ad96-4d1a-b5a5-748faa997531 a1228f20-7c50-4d3a-b88c-2d277ca79d79 +522e2abe-ad96-4d1a-b5a5-748faa997531 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +522e2abe-ad96-4d1a-b5a5-748faa997531 88b16960-bfff-41d0-81e4-9a4630834a00 +522e2abe-ad96-4d1a-b5a5-748faa997531 522e2abe-ad96-4d1a-b5a5-748faa997531 +522e2abe-ad96-4d1a-b5a5-748faa997531 4cfe72fe-21a0-4201-87b6-ef93695d2495 +522e2abe-ad96-4d1a-b5a5-748faa997531 498f4b18-bd4c-470d-9cde-2fca70ec8964 +522e2abe-ad96-4d1a-b5a5-748faa997531 202131ae-78bb-4104-89b0-fb90645760f6 +522e2abe-ad96-4d1a-b5a5-748faa997531 11dafd5c-85e7-4275-a147-89a63641e35e +522e2abe-ad96-4d1a-b5a5-748faa997531 02b993c0-b358-481c-b0d0-0767387feb9f +54c750aa-570a-4e8e-9a02-418781923e39 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +54c750aa-570a-4e8e-9a02-418781923e39 e8eba267-fecb-477e-9625-771fbcfc3cba +54c750aa-570a-4e8e-9a02-418781923e39 e8c41168-a093-40eb-8baa-6802d24f554a +54c750aa-570a-4e8e-9a02-418781923e39 e26ae29a-213a-4f79-98c2-cc81cf2f451d +54c750aa-570a-4e8e-9a02-418781923e39 e0dfbc08-45ad-4a81-b648-7e65c066f673 +54c750aa-570a-4e8e-9a02-418781923e39 d535b213-2abc-438f-aa6a-c06476d60b09 +54c750aa-570a-4e8e-9a02-418781923e39 c892b1d7-7485-407d-8883-54fb7fecebc1 +54c750aa-570a-4e8e-9a02-418781923e39 b6900c21-d310-4fb4-8b8f-176a09c91989 +54c750aa-570a-4e8e-9a02-418781923e39 b00df815-7528-4967-bfe2-311130d91c21 +54c750aa-570a-4e8e-9a02-418781923e39 add6d754-a075-4693-a33a-c061c9a368ff +54c750aa-570a-4e8e-9a02-418781923e39 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +54c750aa-570a-4e8e-9a02-418781923e39 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +54c750aa-570a-4e8e-9a02-418781923e39 8a1908f7-47cc-4f82-859c-781a193e9901 +54c750aa-570a-4e8e-9a02-418781923e39 8743e69b-17aa-44ff-b8fa-4f582665efc6 +54c750aa-570a-4e8e-9a02-418781923e39 721e4027-a080-40d8-bc4a-43cd33477611 +54c750aa-570a-4e8e-9a02-418781923e39 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +54c750aa-570a-4e8e-9a02-418781923e39 54c750aa-570a-4e8e-9a02-418781923e39 +54c750aa-570a-4e8e-9a02-418781923e39 50eac25a-3761-4de3-8a69-aae52e658300 +54c750aa-570a-4e8e-9a02-418781923e39 4a464bb5-9373-4f1b-9c03-053c64797114 +54c750aa-570a-4e8e-9a02-418781923e39 4506aa76-9d0d-40e4-85bc-5735f74522a0 +54c750aa-570a-4e8e-9a02-418781923e39 42a9a333-d09d-4b9e-af96-1f02af26bac3 +54c750aa-570a-4e8e-9a02-418781923e39 2fc75f11-2097-4e1e-8390-dbff3e844b7a +54c750aa-570a-4e8e-9a02-418781923e39 274a2e49-9170-4945-bca2-ab6ef4bded75 +54c750aa-570a-4e8e-9a02-418781923e39 0364073f-91d8-47a1-b8b0-107c6318a691 +55f17f44-287f-41aa-a1bc-d1c0104af36e 984067fc-3dfc-47b7-8ee0-4d9b27d43860 +55f17f44-287f-41aa-a1bc-d1c0104af36e 55f17f44-287f-41aa-a1bc-d1c0104af36e +55f17f44-287f-41aa-a1bc-d1c0104af36e 2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5 +56999896-e36b-4e63-a6b6-dbb85dfe1936 e1c2ad9f-6f61-4f16-805a-2f405b63659a +56999896-e36b-4e63-a6b6-dbb85dfe1936 d6988116-3c63-44f8-9f94-9e9d51cc5a37 +56999896-e36b-4e63-a6b6-dbb85dfe1936 cfcbe34a-edc5-4442-bc05-5927fa00336b +56999896-e36b-4e63-a6b6-dbb85dfe1936 c90aae7e-5704-4e13-8794-41ab377193bd +56999896-e36b-4e63-a6b6-dbb85dfe1936 bf895c48-1d52-4780-8e9a-532d69356d28 +56999896-e36b-4e63-a6b6-dbb85dfe1936 ae9e663e-8c15-43c9-8a88-52b61cbf07a9 +56999896-e36b-4e63-a6b6-dbb85dfe1936 56999896-e36b-4e63-a6b6-dbb85dfe1936 +56999896-e36b-4e63-a6b6-dbb85dfe1936 51241857-a7a4-4517-96e3-21f797581f89 +56999896-e36b-4e63-a6b6-dbb85dfe1936 4cd95073-6db3-4eb2-ac4b-0aacb182b352 +56999896-e36b-4e63-a6b6-dbb85dfe1936 4148ac36-1dcb-4e96-89cd-355e7e8f919b +56999896-e36b-4e63-a6b6-dbb85dfe1936 2e8eb256-84c9-499a-8bf6-bb39504373e1 +56999896-e36b-4e63-a6b6-dbb85dfe1936 07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 +578cc35c-0982-4d72-a729-b304c026f075 a2c3ee1d-ec35-4b26-9bab-12de3f47d604 +578cc35c-0982-4d72-a729-b304c026f075 86415df5-7e47-4637-9e09-aaad9e7628d1 +578cc35c-0982-4d72-a729-b304c026f075 749babeb-6883-4bd4-92a5-bec52769071c +578cc35c-0982-4d72-a729-b304c026f075 578cc35c-0982-4d72-a729-b304c026f075 +578cc35c-0982-4d72-a729-b304c026f075 0250a217-a663-403b-a708-5d14eadf0c40 +5abe533b-ae5f-47ad-8746-f60caf7483c0 f883f2ca-d523-4c9f-86b2-0add137901de +5abe533b-ae5f-47ad-8746-f60caf7483c0 f28931e5-1f35-4f9f-a02e-78398735ea67 +5abe533b-ae5f-47ad-8746-f60caf7483c0 e7f68d35-ae55-4fa4-b0be-0672a5a7186a +5abe533b-ae5f-47ad-8746-f60caf7483c0 85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b +5abe533b-ae5f-47ad-8746-f60caf7483c0 7ac56d5a-32a7-4b40-a956-356e3006faed +5abe533b-ae5f-47ad-8746-f60caf7483c0 72788e7f-1bf1-40b5-a1f1-27c669dc7278 +5abe533b-ae5f-47ad-8746-f60caf7483c0 6c84348c-5065-4e89-9412-bfda023683f2 +5abe533b-ae5f-47ad-8746-f60caf7483c0 5abe533b-ae5f-47ad-8746-f60caf7483c0 +5abe533b-ae5f-47ad-8746-f60caf7483c0 4f14ca7f-5332-4263-a7b2-f0a838380309 +5abe533b-ae5f-47ad-8746-f60caf7483c0 359cb842-c25b-429f-ba9b-d52f171e5631 +5cedf18a-fc44-4c39-a80f-2106a0d76934 f29330d0-5b32-4f75-b558-a1c7f05c0b4a +5cedf18a-fc44-4c39-a80f-2106a0d76934 d90676d2-ce74-4867-a00f-6dbffa7c00be +5cedf18a-fc44-4c39-a80f-2106a0d76934 cc56af6d-25c6-480e-9767-da02a55551da +5cedf18a-fc44-4c39-a80f-2106a0d76934 ca7f269d-3794-4994-8c46-d8e9f2aa6378 +5cedf18a-fc44-4c39-a80f-2106a0d76934 baf48725-f0f9-4357-a71d-b1104910deae +5cedf18a-fc44-4c39-a80f-2106a0d76934 9ed8703e-3b40-424c-9e98-b3b404051f99 +5cedf18a-fc44-4c39-a80f-2106a0d76934 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +5cedf18a-fc44-4c39-a80f-2106a0d76934 8be81657-8ba6-4ec5-8ff9-4809367096ea +5cedf18a-fc44-4c39-a80f-2106a0d76934 7f410064-638a-4477-85c4-97fc2bb14a49 +5cedf18a-fc44-4c39-a80f-2106a0d76934 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +5cedf18a-fc44-4c39-a80f-2106a0d76934 5cedf18a-fc44-4c39-a80f-2106a0d76934 +5cedf18a-fc44-4c39-a80f-2106a0d76934 4f342a71-dec3-403e-970b-e4c21b9eb98b +5cedf18a-fc44-4c39-a80f-2106a0d76934 4db144d3-a596-4718-a50a-8ac9175ad386 +5cedf18a-fc44-4c39-a80f-2106a0d76934 4215b5d0-bdd9-4222-a04a-81bb637d60af +5cedf18a-fc44-4c39-a80f-2106a0d76934 29d4e919-2bd2-44e6-bb8a-380a780f60ff +5cedf18a-fc44-4c39-a80f-2106a0d76934 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +5cedf18a-fc44-4c39-a80f-2106a0d76934 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +5cedf18a-fc44-4c39-a80f-2106a0d76934 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +5cedf18a-fc44-4c39-a80f-2106a0d76934 102e16c5-4920-4dad-b142-0b280b2aacad +5cedf18a-fc44-4c39-a80f-2106a0d76934 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +5e1fe0f2-4517-462b-8287-7824f9a60f4f 5e1fe0f2-4517-462b-8287-7824f9a60f4f +5f64131b-d410-449a-9de6-35415919fec5 fcc916dd-5e4f-4bf7-9aef-c906a36d7301 +5f64131b-d410-449a-9de6-35415919fec5 c57d51a7-45de-41b9-92fc-efd0634effaf +5f64131b-d410-449a-9de6-35415919fec5 ae7ddaef-4911-418c-8401-d978617e145b +5f64131b-d410-449a-9de6-35415919fec5 acb2329d-b1c3-45c3-ad28-91a09aa521e1 +5f64131b-d410-449a-9de6-35415919fec5 a89c0c42-c19f-4d7f-a15e-d04e043c92f6 +5f64131b-d410-449a-9de6-35415919fec5 836b2e59-fb97-4014-ab0c-d5a5dc750969 +5f64131b-d410-449a-9de6-35415919fec5 683714ad-5194-49e7-9b8f-4e306cf68ae1 +5f64131b-d410-449a-9de6-35415919fec5 5f64131b-d410-449a-9de6-35415919fec5 +5f64131b-d410-449a-9de6-35415919fec5 4f3d1e93-a28f-446e-91af-2baf0168b82b +5f64131b-d410-449a-9de6-35415919fec5 1db1c71b-9eeb-4a98-abc1-eb699b38510c +62059efc-7607-41c9-a3ba-70948f6a8a1d f77a8561-5adc-452a-ac9a-76273b6a6678 +62059efc-7607-41c9-a3ba-70948f6a8a1d d79728e1-8834-4f4a-8edc-ce18aca18be6 +62059efc-7607-41c9-a3ba-70948f6a8a1d cf67a8d4-48e2-4dc9-a521-6aabcff0330b +62059efc-7607-41c9-a3ba-70948f6a8a1d bb1a7816-4798-4f58-9ce4-c5bd3af682a8 +62059efc-7607-41c9-a3ba-70948f6a8a1d b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 +62059efc-7607-41c9-a3ba-70948f6a8a1d a6cb423a-7571-46df-83e2-ccc6820adb36 +62059efc-7607-41c9-a3ba-70948f6a8a1d 7e7322a6-7912-4101-b3be-d134245a0626 +62059efc-7607-41c9-a3ba-70948f6a8a1d 777aa5f2-2a09-4aed-92ea-912694e28b48 +62059efc-7607-41c9-a3ba-70948f6a8a1d 6e187f47-91a1-4217-a185-31b6f01db225 +62059efc-7607-41c9-a3ba-70948f6a8a1d 62059efc-7607-41c9-a3ba-70948f6a8a1d +62059efc-7607-41c9-a3ba-70948f6a8a1d 2d2e07ec-e697-4384-a679-867e93740921 +62059efc-7607-41c9-a3ba-70948f6a8a1d 20fbcb6e-d793-4b05-8e29-8ff82572b0db +62059efc-7607-41c9-a3ba-70948f6a8a1d 1fd714a6-48b4-425a-99b3-ba5c1a995cce +634f0889-3a83-484a-bcc8-86f48e333c7b fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +634f0889-3a83-484a-bcc8-86f48e333c7b ee24bf89-81a3-4259-a382-86917f3829d4 +634f0889-3a83-484a-bcc8-86f48e333c7b e363eb67-64c7-440f-93fd-5c8787c69a85 +634f0889-3a83-484a-bcc8-86f48e333c7b dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +634f0889-3a83-484a-bcc8-86f48e333c7b db79f75d-af3c-4f82-b4e5-9b5d26598eef +634f0889-3a83-484a-bcc8-86f48e333c7b d7a48a26-7731-4046-bf22-78f0b8084eb9 +634f0889-3a83-484a-bcc8-86f48e333c7b d080c116-681a-494a-a928-45924109b49d +634f0889-3a83-484a-bcc8-86f48e333c7b cdb8493c-e3b0-447f-b16b-e58dd64660f3 +634f0889-3a83-484a-bcc8-86f48e333c7b c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +634f0889-3a83-484a-bcc8-86f48e333c7b a6b084fb-ada7-480a-9adc-f180d4eb1e2a +634f0889-3a83-484a-bcc8-86f48e333c7b 990c21ab-433b-4920-873e-f9dfc7103d9d +634f0889-3a83-484a-bcc8-86f48e333c7b 922e2443-9cc5-421f-8310-9cd23f6e9f2d +634f0889-3a83-484a-bcc8-86f48e333c7b 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +634f0889-3a83-484a-bcc8-86f48e333c7b 8089a9ac-9e71-4487-a231-f720e4bc8d3e +634f0889-3a83-484a-bcc8-86f48e333c7b 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +634f0889-3a83-484a-bcc8-86f48e333c7b 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +634f0889-3a83-484a-bcc8-86f48e333c7b 6d47a2fe-3645-4c5c-bebe-1b822eff197f +634f0889-3a83-484a-bcc8-86f48e333c7b 67fc12fc-bb9f-40f1-9051-127a788b3081 +634f0889-3a83-484a-bcc8-86f48e333c7b 634f0889-3a83-484a-bcc8-86f48e333c7b +634f0889-3a83-484a-bcc8-86f48e333c7b 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +634f0889-3a83-484a-bcc8-86f48e333c7b 4adcaf72-5241-4877-b61c-f34060576c50 +634f0889-3a83-484a-bcc8-86f48e333c7b 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +634f0889-3a83-484a-bcc8-86f48e333c7b 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +634f0889-3a83-484a-bcc8-86f48e333c7b 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +634f0889-3a83-484a-bcc8-86f48e333c7b 374fd699-6b74-4500-9d60-2473c7e0364f +634f0889-3a83-484a-bcc8-86f48e333c7b 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +634f0889-3a83-484a-bcc8-86f48e333c7b 308eba53-29fa-489a-97dc-741b077841a7 +634f0889-3a83-484a-bcc8-86f48e333c7b 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +634f0889-3a83-484a-bcc8-86f48e333c7b 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +634f0889-3a83-484a-bcc8-86f48e333c7b 21995497-0eb8-4c0b-8c23-e6a829f3d596 +634f0889-3a83-484a-bcc8-86f48e333c7b 18ebf5ea-b0a2-4333-9e9c-40217de809ff +634f0889-3a83-484a-bcc8-86f48e333c7b 151e3048-66ad-4411-8753-677877e3bf0a +634f0889-3a83-484a-bcc8-86f48e333c7b 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +634f0889-3a83-484a-bcc8-86f48e333c7b 0af4a77e-892e-4b3f-9110-4c5224782250 +67e5cf95-236b-4f75-abc5-034ca2966448 fc771883-3bd6-46d9-9626-a130e1b902de +67e5cf95-236b-4f75-abc5-034ca2966448 e2387a81-5ff5-4daf-b2e8-881998d0d917 +67e5cf95-236b-4f75-abc5-034ca2966448 b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa +67e5cf95-236b-4f75-abc5-034ca2966448 b1d4dc41-07ae-44db-ae17-1df86c5a62cf +67e5cf95-236b-4f75-abc5-034ca2966448 67e5cf95-236b-4f75-abc5-034ca2966448 +67e5cf95-236b-4f75-abc5-034ca2966448 48cc2275-a478-4ade-b076-cf38e1d16017 +67e5cf95-236b-4f75-abc5-034ca2966448 41d26b1c-57b9-408c-b955-bf0ee1db4809 +67e5cf95-236b-4f75-abc5-034ca2966448 3ddf46fe-b5be-456d-810f-ea6a7402236d +67e5cf95-236b-4f75-abc5-034ca2966448 3397a796-894d-409c-97de-a9e6f3f88198 +67e5cf95-236b-4f75-abc5-034ca2966448 19502b5a-2031-487d-9d9c-2001f959408b +67fc12fc-bb9f-40f1-9051-127a788b3081 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +67fc12fc-bb9f-40f1-9051-127a788b3081 ee24bf89-81a3-4259-a382-86917f3829d4 +67fc12fc-bb9f-40f1-9051-127a788b3081 e363eb67-64c7-440f-93fd-5c8787c69a85 +67fc12fc-bb9f-40f1-9051-127a788b3081 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +67fc12fc-bb9f-40f1-9051-127a788b3081 db79f75d-af3c-4f82-b4e5-9b5d26598eef +67fc12fc-bb9f-40f1-9051-127a788b3081 d7a48a26-7731-4046-bf22-78f0b8084eb9 +67fc12fc-bb9f-40f1-9051-127a788b3081 d080c116-681a-494a-a928-45924109b49d +67fc12fc-bb9f-40f1-9051-127a788b3081 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +67fc12fc-bb9f-40f1-9051-127a788b3081 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +67fc12fc-bb9f-40f1-9051-127a788b3081 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +67fc12fc-bb9f-40f1-9051-127a788b3081 990c21ab-433b-4920-873e-f9dfc7103d9d +67fc12fc-bb9f-40f1-9051-127a788b3081 922e2443-9cc5-421f-8310-9cd23f6e9f2d +67fc12fc-bb9f-40f1-9051-127a788b3081 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +67fc12fc-bb9f-40f1-9051-127a788b3081 8089a9ac-9e71-4487-a231-f720e4bc8d3e +67fc12fc-bb9f-40f1-9051-127a788b3081 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +67fc12fc-bb9f-40f1-9051-127a788b3081 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +67fc12fc-bb9f-40f1-9051-127a788b3081 6d47a2fe-3645-4c5c-bebe-1b822eff197f +67fc12fc-bb9f-40f1-9051-127a788b3081 67fc12fc-bb9f-40f1-9051-127a788b3081 +67fc12fc-bb9f-40f1-9051-127a788b3081 634f0889-3a83-484a-bcc8-86f48e333c7b +67fc12fc-bb9f-40f1-9051-127a788b3081 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +67fc12fc-bb9f-40f1-9051-127a788b3081 4adcaf72-5241-4877-b61c-f34060576c50 +67fc12fc-bb9f-40f1-9051-127a788b3081 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +67fc12fc-bb9f-40f1-9051-127a788b3081 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +67fc12fc-bb9f-40f1-9051-127a788b3081 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +67fc12fc-bb9f-40f1-9051-127a788b3081 374fd699-6b74-4500-9d60-2473c7e0364f +67fc12fc-bb9f-40f1-9051-127a788b3081 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +67fc12fc-bb9f-40f1-9051-127a788b3081 308eba53-29fa-489a-97dc-741b077841a7 +67fc12fc-bb9f-40f1-9051-127a788b3081 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +67fc12fc-bb9f-40f1-9051-127a788b3081 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +67fc12fc-bb9f-40f1-9051-127a788b3081 21995497-0eb8-4c0b-8c23-e6a829f3d596 +67fc12fc-bb9f-40f1-9051-127a788b3081 18ebf5ea-b0a2-4333-9e9c-40217de809ff +67fc12fc-bb9f-40f1-9051-127a788b3081 151e3048-66ad-4411-8753-677877e3bf0a +67fc12fc-bb9f-40f1-9051-127a788b3081 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +67fc12fc-bb9f-40f1-9051-127a788b3081 0af4a77e-892e-4b3f-9110-4c5224782250 +683714ad-5194-49e7-9b8f-4e306cf68ae1 fcc916dd-5e4f-4bf7-9aef-c906a36d7301 +683714ad-5194-49e7-9b8f-4e306cf68ae1 c57d51a7-45de-41b9-92fc-efd0634effaf +683714ad-5194-49e7-9b8f-4e306cf68ae1 ae7ddaef-4911-418c-8401-d978617e145b +683714ad-5194-49e7-9b8f-4e306cf68ae1 acb2329d-b1c3-45c3-ad28-91a09aa521e1 +683714ad-5194-49e7-9b8f-4e306cf68ae1 a89c0c42-c19f-4d7f-a15e-d04e043c92f6 +683714ad-5194-49e7-9b8f-4e306cf68ae1 836b2e59-fb97-4014-ab0c-d5a5dc750969 +683714ad-5194-49e7-9b8f-4e306cf68ae1 683714ad-5194-49e7-9b8f-4e306cf68ae1 +683714ad-5194-49e7-9b8f-4e306cf68ae1 5f64131b-d410-449a-9de6-35415919fec5 +683714ad-5194-49e7-9b8f-4e306cf68ae1 4f3d1e93-a28f-446e-91af-2baf0168b82b +683714ad-5194-49e7-9b8f-4e306cf68ae1 1db1c71b-9eeb-4a98-abc1-eb699b38510c +6954c5c2-dd77-490a-9152-f1d78eff913d 6954c5c2-dd77-490a-9152-f1d78eff913d +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 e8eba267-fecb-477e-9625-771fbcfc3cba +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 e8c41168-a093-40eb-8baa-6802d24f554a +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 e26ae29a-213a-4f79-98c2-cc81cf2f451d +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 e0dfbc08-45ad-4a81-b648-7e65c066f673 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 d535b213-2abc-438f-aa6a-c06476d60b09 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 c892b1d7-7485-407d-8883-54fb7fecebc1 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 b6900c21-d310-4fb4-8b8f-176a09c91989 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 b00df815-7528-4967-bfe2-311130d91c21 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 add6d754-a075-4693-a33a-c061c9a368ff +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 8a1908f7-47cc-4f82-859c-781a193e9901 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 8743e69b-17aa-44ff-b8fa-4f582665efc6 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 721e4027-a080-40d8-bc4a-43cd33477611 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 54c750aa-570a-4e8e-9a02-418781923e39 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 50eac25a-3761-4de3-8a69-aae52e658300 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 4a464bb5-9373-4f1b-9c03-053c64797114 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 4506aa76-9d0d-40e4-85bc-5735f74522a0 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 42a9a333-d09d-4b9e-af96-1f02af26bac3 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 2fc75f11-2097-4e1e-8390-dbff3e844b7a +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 274a2e49-9170-4945-bca2-ab6ef4bded75 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 0364073f-91d8-47a1-b8b0-107c6318a691 +6af73cae-2e4e-485f-a74d-6f4307eb3af3 f29330d0-5b32-4f75-b558-a1c7f05c0b4a +6af73cae-2e4e-485f-a74d-6f4307eb3af3 d90676d2-ce74-4867-a00f-6dbffa7c00be +6af73cae-2e4e-485f-a74d-6f4307eb3af3 cc56af6d-25c6-480e-9767-da02a55551da +6af73cae-2e4e-485f-a74d-6f4307eb3af3 ca7f269d-3794-4994-8c46-d8e9f2aa6378 +6af73cae-2e4e-485f-a74d-6f4307eb3af3 baf48725-f0f9-4357-a71d-b1104910deae +6af73cae-2e4e-485f-a74d-6f4307eb3af3 9ed8703e-3b40-424c-9e98-b3b404051f99 +6af73cae-2e4e-485f-a74d-6f4307eb3af3 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +6af73cae-2e4e-485f-a74d-6f4307eb3af3 8be81657-8ba6-4ec5-8ff9-4809367096ea +6af73cae-2e4e-485f-a74d-6f4307eb3af3 7f410064-638a-4477-85c4-97fc2bb14a49 +6af73cae-2e4e-485f-a74d-6f4307eb3af3 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +6af73cae-2e4e-485f-a74d-6f4307eb3af3 5cedf18a-fc44-4c39-a80f-2106a0d76934 +6af73cae-2e4e-485f-a74d-6f4307eb3af3 4f342a71-dec3-403e-970b-e4c21b9eb98b +6af73cae-2e4e-485f-a74d-6f4307eb3af3 4db144d3-a596-4718-a50a-8ac9175ad386 +6af73cae-2e4e-485f-a74d-6f4307eb3af3 4215b5d0-bdd9-4222-a04a-81bb637d60af +6af73cae-2e4e-485f-a74d-6f4307eb3af3 29d4e919-2bd2-44e6-bb8a-380a780f60ff +6af73cae-2e4e-485f-a74d-6f4307eb3af3 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +6af73cae-2e4e-485f-a74d-6f4307eb3af3 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +6af73cae-2e4e-485f-a74d-6f4307eb3af3 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +6af73cae-2e4e-485f-a74d-6f4307eb3af3 102e16c5-4920-4dad-b142-0b280b2aacad +6af73cae-2e4e-485f-a74d-6f4307eb3af3 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +6c84348c-5065-4e89-9412-bfda023683f2 f883f2ca-d523-4c9f-86b2-0add137901de +6c84348c-5065-4e89-9412-bfda023683f2 f28931e5-1f35-4f9f-a02e-78398735ea67 +6c84348c-5065-4e89-9412-bfda023683f2 e7f68d35-ae55-4fa4-b0be-0672a5a7186a +6c84348c-5065-4e89-9412-bfda023683f2 85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b +6c84348c-5065-4e89-9412-bfda023683f2 7ac56d5a-32a7-4b40-a956-356e3006faed +6c84348c-5065-4e89-9412-bfda023683f2 72788e7f-1bf1-40b5-a1f1-27c669dc7278 +6c84348c-5065-4e89-9412-bfda023683f2 6c84348c-5065-4e89-9412-bfda023683f2 +6c84348c-5065-4e89-9412-bfda023683f2 5abe533b-ae5f-47ad-8746-f60caf7483c0 +6c84348c-5065-4e89-9412-bfda023683f2 4f14ca7f-5332-4263-a7b2-f0a838380309 +6c84348c-5065-4e89-9412-bfda023683f2 359cb842-c25b-429f-ba9b-d52f171e5631 +6d47a2fe-3645-4c5c-bebe-1b822eff197f fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +6d47a2fe-3645-4c5c-bebe-1b822eff197f ee24bf89-81a3-4259-a382-86917f3829d4 +6d47a2fe-3645-4c5c-bebe-1b822eff197f e363eb67-64c7-440f-93fd-5c8787c69a85 +6d47a2fe-3645-4c5c-bebe-1b822eff197f dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +6d47a2fe-3645-4c5c-bebe-1b822eff197f db79f75d-af3c-4f82-b4e5-9b5d26598eef +6d47a2fe-3645-4c5c-bebe-1b822eff197f d7a48a26-7731-4046-bf22-78f0b8084eb9 +6d47a2fe-3645-4c5c-bebe-1b822eff197f d080c116-681a-494a-a928-45924109b49d +6d47a2fe-3645-4c5c-bebe-1b822eff197f cdb8493c-e3b0-447f-b16b-e58dd64660f3 +6d47a2fe-3645-4c5c-bebe-1b822eff197f c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +6d47a2fe-3645-4c5c-bebe-1b822eff197f a6b084fb-ada7-480a-9adc-f180d4eb1e2a +6d47a2fe-3645-4c5c-bebe-1b822eff197f 990c21ab-433b-4920-873e-f9dfc7103d9d +6d47a2fe-3645-4c5c-bebe-1b822eff197f 922e2443-9cc5-421f-8310-9cd23f6e9f2d +6d47a2fe-3645-4c5c-bebe-1b822eff197f 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +6d47a2fe-3645-4c5c-bebe-1b822eff197f 8089a9ac-9e71-4487-a231-f720e4bc8d3e +6d47a2fe-3645-4c5c-bebe-1b822eff197f 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +6d47a2fe-3645-4c5c-bebe-1b822eff197f 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +6d47a2fe-3645-4c5c-bebe-1b822eff197f 6d47a2fe-3645-4c5c-bebe-1b822eff197f +6d47a2fe-3645-4c5c-bebe-1b822eff197f 67fc12fc-bb9f-40f1-9051-127a788b3081 +6d47a2fe-3645-4c5c-bebe-1b822eff197f 634f0889-3a83-484a-bcc8-86f48e333c7b +6d47a2fe-3645-4c5c-bebe-1b822eff197f 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +6d47a2fe-3645-4c5c-bebe-1b822eff197f 4adcaf72-5241-4877-b61c-f34060576c50 +6d47a2fe-3645-4c5c-bebe-1b822eff197f 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +6d47a2fe-3645-4c5c-bebe-1b822eff197f 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +6d47a2fe-3645-4c5c-bebe-1b822eff197f 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +6d47a2fe-3645-4c5c-bebe-1b822eff197f 374fd699-6b74-4500-9d60-2473c7e0364f +6d47a2fe-3645-4c5c-bebe-1b822eff197f 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +6d47a2fe-3645-4c5c-bebe-1b822eff197f 308eba53-29fa-489a-97dc-741b077841a7 +6d47a2fe-3645-4c5c-bebe-1b822eff197f 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +6d47a2fe-3645-4c5c-bebe-1b822eff197f 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +6d47a2fe-3645-4c5c-bebe-1b822eff197f 21995497-0eb8-4c0b-8c23-e6a829f3d596 +6d47a2fe-3645-4c5c-bebe-1b822eff197f 18ebf5ea-b0a2-4333-9e9c-40217de809ff +6d47a2fe-3645-4c5c-bebe-1b822eff197f 151e3048-66ad-4411-8753-677877e3bf0a +6d47a2fe-3645-4c5c-bebe-1b822eff197f 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +6d47a2fe-3645-4c5c-bebe-1b822eff197f 0af4a77e-892e-4b3f-9110-4c5224782250 +6e187f47-91a1-4217-a185-31b6f01db225 f77a8561-5adc-452a-ac9a-76273b6a6678 +6e187f47-91a1-4217-a185-31b6f01db225 d79728e1-8834-4f4a-8edc-ce18aca18be6 +6e187f47-91a1-4217-a185-31b6f01db225 cf67a8d4-48e2-4dc9-a521-6aabcff0330b +6e187f47-91a1-4217-a185-31b6f01db225 bb1a7816-4798-4f58-9ce4-c5bd3af682a8 +6e187f47-91a1-4217-a185-31b6f01db225 b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 +6e187f47-91a1-4217-a185-31b6f01db225 a6cb423a-7571-46df-83e2-ccc6820adb36 +6e187f47-91a1-4217-a185-31b6f01db225 7e7322a6-7912-4101-b3be-d134245a0626 +6e187f47-91a1-4217-a185-31b6f01db225 777aa5f2-2a09-4aed-92ea-912694e28b48 +6e187f47-91a1-4217-a185-31b6f01db225 6e187f47-91a1-4217-a185-31b6f01db225 +6e187f47-91a1-4217-a185-31b6f01db225 62059efc-7607-41c9-a3ba-70948f6a8a1d +6e187f47-91a1-4217-a185-31b6f01db225 2d2e07ec-e697-4384-a679-867e93740921 +6e187f47-91a1-4217-a185-31b6f01db225 20fbcb6e-d793-4b05-8e29-8ff82572b0db +6e187f47-91a1-4217-a185-31b6f01db225 1fd714a6-48b4-425a-99b3-ba5c1a995cce +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 f3aa491a-4dbd-4436-b674-18b2177dcf18 +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 e1040d58-53bc-4467-8101-ca53b772cede +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 d05461d6-13bf-402c-890d-db6404933f7c +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 b7772635-1801-48e4-a442-f4aaa6860544 +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 b07fb98d-2da4-423a-83b4-7a673de93096 +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 97b42d67-8691-433d-8a31-dada076162ae +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 720f7ed6-7b2e-41dc-a0e0-724a3332aa24 +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 2ffd6142-b75b-406f-bc06-cdb1c02eaefc +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 224e538d-3622-4f5e-b772-211ed358d8de +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 16a3408d-c927-4d02-a1ae-cad32419caab +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 04d88eb4-fb1b-4fa1-802a-5624f4c61b32 +721e4027-a080-40d8-bc4a-43cd33477611 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +721e4027-a080-40d8-bc4a-43cd33477611 e8eba267-fecb-477e-9625-771fbcfc3cba +721e4027-a080-40d8-bc4a-43cd33477611 e8c41168-a093-40eb-8baa-6802d24f554a +721e4027-a080-40d8-bc4a-43cd33477611 e26ae29a-213a-4f79-98c2-cc81cf2f451d +721e4027-a080-40d8-bc4a-43cd33477611 e0dfbc08-45ad-4a81-b648-7e65c066f673 +721e4027-a080-40d8-bc4a-43cd33477611 d535b213-2abc-438f-aa6a-c06476d60b09 +721e4027-a080-40d8-bc4a-43cd33477611 c892b1d7-7485-407d-8883-54fb7fecebc1 +721e4027-a080-40d8-bc4a-43cd33477611 b6900c21-d310-4fb4-8b8f-176a09c91989 +721e4027-a080-40d8-bc4a-43cd33477611 b00df815-7528-4967-bfe2-311130d91c21 +721e4027-a080-40d8-bc4a-43cd33477611 add6d754-a075-4693-a33a-c061c9a368ff +721e4027-a080-40d8-bc4a-43cd33477611 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +721e4027-a080-40d8-bc4a-43cd33477611 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +721e4027-a080-40d8-bc4a-43cd33477611 8a1908f7-47cc-4f82-859c-781a193e9901 +721e4027-a080-40d8-bc4a-43cd33477611 8743e69b-17aa-44ff-b8fa-4f582665efc6 +721e4027-a080-40d8-bc4a-43cd33477611 721e4027-a080-40d8-bc4a-43cd33477611 +721e4027-a080-40d8-bc4a-43cd33477611 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +721e4027-a080-40d8-bc4a-43cd33477611 54c750aa-570a-4e8e-9a02-418781923e39 +721e4027-a080-40d8-bc4a-43cd33477611 50eac25a-3761-4de3-8a69-aae52e658300 +721e4027-a080-40d8-bc4a-43cd33477611 4a464bb5-9373-4f1b-9c03-053c64797114 +721e4027-a080-40d8-bc4a-43cd33477611 4506aa76-9d0d-40e4-85bc-5735f74522a0 +721e4027-a080-40d8-bc4a-43cd33477611 42a9a333-d09d-4b9e-af96-1f02af26bac3 +721e4027-a080-40d8-bc4a-43cd33477611 2fc75f11-2097-4e1e-8390-dbff3e844b7a +721e4027-a080-40d8-bc4a-43cd33477611 274a2e49-9170-4945-bca2-ab6ef4bded75 +721e4027-a080-40d8-bc4a-43cd33477611 0364073f-91d8-47a1-b8b0-107c6318a691 +72788e7f-1bf1-40b5-a1f1-27c669dc7278 f883f2ca-d523-4c9f-86b2-0add137901de +72788e7f-1bf1-40b5-a1f1-27c669dc7278 f28931e5-1f35-4f9f-a02e-78398735ea67 +72788e7f-1bf1-40b5-a1f1-27c669dc7278 e7f68d35-ae55-4fa4-b0be-0672a5a7186a +72788e7f-1bf1-40b5-a1f1-27c669dc7278 85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b +72788e7f-1bf1-40b5-a1f1-27c669dc7278 7ac56d5a-32a7-4b40-a956-356e3006faed +72788e7f-1bf1-40b5-a1f1-27c669dc7278 72788e7f-1bf1-40b5-a1f1-27c669dc7278 +72788e7f-1bf1-40b5-a1f1-27c669dc7278 6c84348c-5065-4e89-9412-bfda023683f2 +72788e7f-1bf1-40b5-a1f1-27c669dc7278 5abe533b-ae5f-47ad-8746-f60caf7483c0 +72788e7f-1bf1-40b5-a1f1-27c669dc7278 4f14ca7f-5332-4263-a7b2-f0a838380309 +72788e7f-1bf1-40b5-a1f1-27c669dc7278 359cb842-c25b-429f-ba9b-d52f171e5631 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e ee24bf89-81a3-4259-a382-86917f3829d4 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e e363eb67-64c7-440f-93fd-5c8787c69a85 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e db79f75d-af3c-4f82-b4e5-9b5d26598eef +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e d7a48a26-7731-4046-bf22-78f0b8084eb9 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e d080c116-681a-494a-a928-45924109b49d +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e cdb8493c-e3b0-447f-b16b-e58dd64660f3 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e a6b084fb-ada7-480a-9adc-f180d4eb1e2a +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 990c21ab-433b-4920-873e-f9dfc7103d9d +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 922e2443-9cc5-421f-8310-9cd23f6e9f2d +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 8089a9ac-9e71-4487-a231-f720e4bc8d3e +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 6d47a2fe-3645-4c5c-bebe-1b822eff197f +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 67fc12fc-bb9f-40f1-9051-127a788b3081 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 634f0889-3a83-484a-bcc8-86f48e333c7b +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 4adcaf72-5241-4877-b61c-f34060576c50 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 374fd699-6b74-4500-9d60-2473c7e0364f +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 308eba53-29fa-489a-97dc-741b077841a7 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 21995497-0eb8-4c0b-8c23-e6a829f3d596 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 18ebf5ea-b0a2-4333-9e9c-40217de809ff +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 151e3048-66ad-4411-8753-677877e3bf0a +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 0af4a77e-892e-4b3f-9110-4c5224782250 +749babeb-6883-4bd4-92a5-bec52769071c a2c3ee1d-ec35-4b26-9bab-12de3f47d604 +749babeb-6883-4bd4-92a5-bec52769071c 86415df5-7e47-4637-9e09-aaad9e7628d1 +749babeb-6883-4bd4-92a5-bec52769071c 749babeb-6883-4bd4-92a5-bec52769071c +749babeb-6883-4bd4-92a5-bec52769071c 578cc35c-0982-4d72-a729-b304c026f075 +749babeb-6883-4bd4-92a5-bec52769071c 0250a217-a663-403b-a708-5d14eadf0c40 +75a09d4f-eef2-4404-a386-dfcb408ec9ed bfc93cb7-e53a-49f7-b86e-81d6a13cfdea +75a09d4f-eef2-4404-a386-dfcb408ec9ed a6790cbd-8349-432e-a976-5dfc07451203 +75a09d4f-eef2-4404-a386-dfcb408ec9ed 9b2eb632-6f1a-4e97-912b-3c8378a1b11c +75a09d4f-eef2-4404-a386-dfcb408ec9ed 85c997bf-63d9-4ebb-8e0b-320de3dddc6c +75a09d4f-eef2-4404-a386-dfcb408ec9ed 85481b3f-c75e-40cd-bacd-b0afb79e893c +75a09d4f-eef2-4404-a386-dfcb408ec9ed 75a09d4f-eef2-4404-a386-dfcb408ec9ed +75a09d4f-eef2-4404-a386-dfcb408ec9ed 30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 +75a09d4f-eef2-4404-a386-dfcb408ec9ed 2ee2ab26-09fb-4e12-bf21-9fed3b4c38de +777aa5f2-2a09-4aed-92ea-912694e28b48 f77a8561-5adc-452a-ac9a-76273b6a6678 +777aa5f2-2a09-4aed-92ea-912694e28b48 d79728e1-8834-4f4a-8edc-ce18aca18be6 +777aa5f2-2a09-4aed-92ea-912694e28b48 cf67a8d4-48e2-4dc9-a521-6aabcff0330b +777aa5f2-2a09-4aed-92ea-912694e28b48 bb1a7816-4798-4f58-9ce4-c5bd3af682a8 +777aa5f2-2a09-4aed-92ea-912694e28b48 b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 +777aa5f2-2a09-4aed-92ea-912694e28b48 a6cb423a-7571-46df-83e2-ccc6820adb36 +777aa5f2-2a09-4aed-92ea-912694e28b48 7e7322a6-7912-4101-b3be-d134245a0626 +777aa5f2-2a09-4aed-92ea-912694e28b48 777aa5f2-2a09-4aed-92ea-912694e28b48 +777aa5f2-2a09-4aed-92ea-912694e28b48 6e187f47-91a1-4217-a185-31b6f01db225 +777aa5f2-2a09-4aed-92ea-912694e28b48 62059efc-7607-41c9-a3ba-70948f6a8a1d +777aa5f2-2a09-4aed-92ea-912694e28b48 2d2e07ec-e697-4384-a679-867e93740921 +777aa5f2-2a09-4aed-92ea-912694e28b48 20fbcb6e-d793-4b05-8e29-8ff82572b0db +777aa5f2-2a09-4aed-92ea-912694e28b48 1fd714a6-48b4-425a-99b3-ba5c1a995cce +79be3b69-23d8-4408-8f01-68d993e63b6c b0d337c5-3555-4817-ba59-4ceea5ad8a91 +79be3b69-23d8-4408-8f01-68d993e63b6c 79be3b69-23d8-4408-8f01-68d993e63b6c +79be3b69-23d8-4408-8f01-68d993e63b6c 04945715-8ad5-4d8f-bfda-ae26662a3610 +7ac56d5a-32a7-4b40-a956-356e3006faed f883f2ca-d523-4c9f-86b2-0add137901de +7ac56d5a-32a7-4b40-a956-356e3006faed f28931e5-1f35-4f9f-a02e-78398735ea67 +7ac56d5a-32a7-4b40-a956-356e3006faed e7f68d35-ae55-4fa4-b0be-0672a5a7186a +7ac56d5a-32a7-4b40-a956-356e3006faed 85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b +7ac56d5a-32a7-4b40-a956-356e3006faed 7ac56d5a-32a7-4b40-a956-356e3006faed +7ac56d5a-32a7-4b40-a956-356e3006faed 72788e7f-1bf1-40b5-a1f1-27c669dc7278 +7ac56d5a-32a7-4b40-a956-356e3006faed 6c84348c-5065-4e89-9412-bfda023683f2 +7ac56d5a-32a7-4b40-a956-356e3006faed 5abe533b-ae5f-47ad-8746-f60caf7483c0 +7ac56d5a-32a7-4b40-a956-356e3006faed 4f14ca7f-5332-4263-a7b2-f0a838380309 +7ac56d5a-32a7-4b40-a956-356e3006faed 359cb842-c25b-429f-ba9b-d52f171e5631 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 ee24bf89-81a3-4259-a382-86917f3829d4 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 e363eb67-64c7-440f-93fd-5c8787c69a85 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 db79f75d-af3c-4f82-b4e5-9b5d26598eef +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 d7a48a26-7731-4046-bf22-78f0b8084eb9 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 d080c116-681a-494a-a928-45924109b49d +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 990c21ab-433b-4920-873e-f9dfc7103d9d +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 922e2443-9cc5-421f-8310-9cd23f6e9f2d +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 8089a9ac-9e71-4487-a231-f720e4bc8d3e +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 6d47a2fe-3645-4c5c-bebe-1b822eff197f +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 67fc12fc-bb9f-40f1-9051-127a788b3081 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 634f0889-3a83-484a-bcc8-86f48e333c7b +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 4adcaf72-5241-4877-b61c-f34060576c50 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 374fd699-6b74-4500-9d60-2473c7e0364f +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 308eba53-29fa-489a-97dc-741b077841a7 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 21995497-0eb8-4c0b-8c23-e6a829f3d596 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 18ebf5ea-b0a2-4333-9e9c-40217de809ff +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 151e3048-66ad-4411-8753-677877e3bf0a +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 0af4a77e-892e-4b3f-9110-4c5224782250 +7e7322a6-7912-4101-b3be-d134245a0626 f77a8561-5adc-452a-ac9a-76273b6a6678 +7e7322a6-7912-4101-b3be-d134245a0626 d79728e1-8834-4f4a-8edc-ce18aca18be6 +7e7322a6-7912-4101-b3be-d134245a0626 cf67a8d4-48e2-4dc9-a521-6aabcff0330b +7e7322a6-7912-4101-b3be-d134245a0626 bb1a7816-4798-4f58-9ce4-c5bd3af682a8 +7e7322a6-7912-4101-b3be-d134245a0626 b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 +7e7322a6-7912-4101-b3be-d134245a0626 a6cb423a-7571-46df-83e2-ccc6820adb36 +7e7322a6-7912-4101-b3be-d134245a0626 7e7322a6-7912-4101-b3be-d134245a0626 +7e7322a6-7912-4101-b3be-d134245a0626 777aa5f2-2a09-4aed-92ea-912694e28b48 +7e7322a6-7912-4101-b3be-d134245a0626 6e187f47-91a1-4217-a185-31b6f01db225 +7e7322a6-7912-4101-b3be-d134245a0626 62059efc-7607-41c9-a3ba-70948f6a8a1d +7e7322a6-7912-4101-b3be-d134245a0626 2d2e07ec-e697-4384-a679-867e93740921 +7e7322a6-7912-4101-b3be-d134245a0626 20fbcb6e-d793-4b05-8e29-8ff82572b0db +7e7322a6-7912-4101-b3be-d134245a0626 1fd714a6-48b4-425a-99b3-ba5c1a995cce +7ea43fc6-b1f7-46be-a308-5ecb275a1081 bac374c0-50e8-4a5c-947b-82a8e9a747a9 +7ea43fc6-b1f7-46be-a308-5ecb275a1081 8bc7af32-e5ad-4849-ba4d-b446db833ab4 +7ea43fc6-b1f7-46be-a308-5ecb275a1081 7ea43fc6-b1f7-46be-a308-5ecb275a1081 +7ea43fc6-b1f7-46be-a308-5ecb275a1081 2de1f829-c9d2-4f22-bf39-536dcb82fc3e +7ea43fc6-b1f7-46be-a308-5ecb275a1081 2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 +7ea43fc6-b1f7-46be-a308-5ecb275a1081 23780032-f3bb-4b40-af2e-3fa6b1677376 +7ea43fc6-b1f7-46be-a308-5ecb275a1081 21c6e0fc-bf47-4f80-b1ff-85b940445bdf +7ea43fc6-b1f7-46be-a308-5ecb275a1081 12b212d8-bc6e-415a-93b0-594381726668 +7f410064-638a-4477-85c4-97fc2bb14a49 f29330d0-5b32-4f75-b558-a1c7f05c0b4a +7f410064-638a-4477-85c4-97fc2bb14a49 d90676d2-ce74-4867-a00f-6dbffa7c00be +7f410064-638a-4477-85c4-97fc2bb14a49 cc56af6d-25c6-480e-9767-da02a55551da +7f410064-638a-4477-85c4-97fc2bb14a49 ca7f269d-3794-4994-8c46-d8e9f2aa6378 +7f410064-638a-4477-85c4-97fc2bb14a49 baf48725-f0f9-4357-a71d-b1104910deae +7f410064-638a-4477-85c4-97fc2bb14a49 9ed8703e-3b40-424c-9e98-b3b404051f99 +7f410064-638a-4477-85c4-97fc2bb14a49 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +7f410064-638a-4477-85c4-97fc2bb14a49 8be81657-8ba6-4ec5-8ff9-4809367096ea +7f410064-638a-4477-85c4-97fc2bb14a49 7f410064-638a-4477-85c4-97fc2bb14a49 +7f410064-638a-4477-85c4-97fc2bb14a49 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +7f410064-638a-4477-85c4-97fc2bb14a49 5cedf18a-fc44-4c39-a80f-2106a0d76934 +7f410064-638a-4477-85c4-97fc2bb14a49 4f342a71-dec3-403e-970b-e4c21b9eb98b +7f410064-638a-4477-85c4-97fc2bb14a49 4db144d3-a596-4718-a50a-8ac9175ad386 +7f410064-638a-4477-85c4-97fc2bb14a49 4215b5d0-bdd9-4222-a04a-81bb637d60af +7f410064-638a-4477-85c4-97fc2bb14a49 29d4e919-2bd2-44e6-bb8a-380a780f60ff +7f410064-638a-4477-85c4-97fc2bb14a49 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +7f410064-638a-4477-85c4-97fc2bb14a49 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +7f410064-638a-4477-85c4-97fc2bb14a49 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +7f410064-638a-4477-85c4-97fc2bb14a49 102e16c5-4920-4dad-b142-0b280b2aacad +7f410064-638a-4477-85c4-97fc2bb14a49 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +8089a9ac-9e71-4487-a231-f720e4bc8d3e fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +8089a9ac-9e71-4487-a231-f720e4bc8d3e ee24bf89-81a3-4259-a382-86917f3829d4 +8089a9ac-9e71-4487-a231-f720e4bc8d3e e363eb67-64c7-440f-93fd-5c8787c69a85 +8089a9ac-9e71-4487-a231-f720e4bc8d3e dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +8089a9ac-9e71-4487-a231-f720e4bc8d3e db79f75d-af3c-4f82-b4e5-9b5d26598eef +8089a9ac-9e71-4487-a231-f720e4bc8d3e d7a48a26-7731-4046-bf22-78f0b8084eb9 +8089a9ac-9e71-4487-a231-f720e4bc8d3e d080c116-681a-494a-a928-45924109b49d +8089a9ac-9e71-4487-a231-f720e4bc8d3e cdb8493c-e3b0-447f-b16b-e58dd64660f3 +8089a9ac-9e71-4487-a231-f720e4bc8d3e c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +8089a9ac-9e71-4487-a231-f720e4bc8d3e a6b084fb-ada7-480a-9adc-f180d4eb1e2a +8089a9ac-9e71-4487-a231-f720e4bc8d3e 990c21ab-433b-4920-873e-f9dfc7103d9d +8089a9ac-9e71-4487-a231-f720e4bc8d3e 922e2443-9cc5-421f-8310-9cd23f6e9f2d +8089a9ac-9e71-4487-a231-f720e4bc8d3e 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +8089a9ac-9e71-4487-a231-f720e4bc8d3e 8089a9ac-9e71-4487-a231-f720e4bc8d3e +8089a9ac-9e71-4487-a231-f720e4bc8d3e 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +8089a9ac-9e71-4487-a231-f720e4bc8d3e 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +8089a9ac-9e71-4487-a231-f720e4bc8d3e 6d47a2fe-3645-4c5c-bebe-1b822eff197f +8089a9ac-9e71-4487-a231-f720e4bc8d3e 67fc12fc-bb9f-40f1-9051-127a788b3081 +8089a9ac-9e71-4487-a231-f720e4bc8d3e 634f0889-3a83-484a-bcc8-86f48e333c7b +8089a9ac-9e71-4487-a231-f720e4bc8d3e 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +8089a9ac-9e71-4487-a231-f720e4bc8d3e 4adcaf72-5241-4877-b61c-f34060576c50 +8089a9ac-9e71-4487-a231-f720e4bc8d3e 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +8089a9ac-9e71-4487-a231-f720e4bc8d3e 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +8089a9ac-9e71-4487-a231-f720e4bc8d3e 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +8089a9ac-9e71-4487-a231-f720e4bc8d3e 374fd699-6b74-4500-9d60-2473c7e0364f +8089a9ac-9e71-4487-a231-f720e4bc8d3e 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +8089a9ac-9e71-4487-a231-f720e4bc8d3e 308eba53-29fa-489a-97dc-741b077841a7 +8089a9ac-9e71-4487-a231-f720e4bc8d3e 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +8089a9ac-9e71-4487-a231-f720e4bc8d3e 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +8089a9ac-9e71-4487-a231-f720e4bc8d3e 21995497-0eb8-4c0b-8c23-e6a829f3d596 +8089a9ac-9e71-4487-a231-f720e4bc8d3e 18ebf5ea-b0a2-4333-9e9c-40217de809ff +8089a9ac-9e71-4487-a231-f720e4bc8d3e 151e3048-66ad-4411-8753-677877e3bf0a +8089a9ac-9e71-4487-a231-f720e4bc8d3e 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +8089a9ac-9e71-4487-a231-f720e4bc8d3e 0af4a77e-892e-4b3f-9110-4c5224782250 +836b2e59-fb97-4014-ab0c-d5a5dc750969 fcc916dd-5e4f-4bf7-9aef-c906a36d7301 +836b2e59-fb97-4014-ab0c-d5a5dc750969 c57d51a7-45de-41b9-92fc-efd0634effaf +836b2e59-fb97-4014-ab0c-d5a5dc750969 ae7ddaef-4911-418c-8401-d978617e145b +836b2e59-fb97-4014-ab0c-d5a5dc750969 acb2329d-b1c3-45c3-ad28-91a09aa521e1 +836b2e59-fb97-4014-ab0c-d5a5dc750969 a89c0c42-c19f-4d7f-a15e-d04e043c92f6 +836b2e59-fb97-4014-ab0c-d5a5dc750969 836b2e59-fb97-4014-ab0c-d5a5dc750969 +836b2e59-fb97-4014-ab0c-d5a5dc750969 683714ad-5194-49e7-9b8f-4e306cf68ae1 +836b2e59-fb97-4014-ab0c-d5a5dc750969 5f64131b-d410-449a-9de6-35415919fec5 +836b2e59-fb97-4014-ab0c-d5a5dc750969 4f3d1e93-a28f-446e-91af-2baf0168b82b +836b2e59-fb97-4014-ab0c-d5a5dc750969 1db1c71b-9eeb-4a98-abc1-eb699b38510c +85481b3f-c75e-40cd-bacd-b0afb79e893c bfc93cb7-e53a-49f7-b86e-81d6a13cfdea +85481b3f-c75e-40cd-bacd-b0afb79e893c a6790cbd-8349-432e-a976-5dfc07451203 +85481b3f-c75e-40cd-bacd-b0afb79e893c 9b2eb632-6f1a-4e97-912b-3c8378a1b11c +85481b3f-c75e-40cd-bacd-b0afb79e893c 85c997bf-63d9-4ebb-8e0b-320de3dddc6c +85481b3f-c75e-40cd-bacd-b0afb79e893c 85481b3f-c75e-40cd-bacd-b0afb79e893c +85481b3f-c75e-40cd-bacd-b0afb79e893c 75a09d4f-eef2-4404-a386-dfcb408ec9ed +85481b3f-c75e-40cd-bacd-b0afb79e893c 30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 +85481b3f-c75e-40cd-bacd-b0afb79e893c 2ee2ab26-09fb-4e12-bf21-9fed3b4c38de +85c997bf-63d9-4ebb-8e0b-320de3dddc6c bfc93cb7-e53a-49f7-b86e-81d6a13cfdea +85c997bf-63d9-4ebb-8e0b-320de3dddc6c a6790cbd-8349-432e-a976-5dfc07451203 +85c997bf-63d9-4ebb-8e0b-320de3dddc6c 9b2eb632-6f1a-4e97-912b-3c8378a1b11c +85c997bf-63d9-4ebb-8e0b-320de3dddc6c 85c997bf-63d9-4ebb-8e0b-320de3dddc6c +85c997bf-63d9-4ebb-8e0b-320de3dddc6c 85481b3f-c75e-40cd-bacd-b0afb79e893c +85c997bf-63d9-4ebb-8e0b-320de3dddc6c 75a09d4f-eef2-4404-a386-dfcb408ec9ed +85c997bf-63d9-4ebb-8e0b-320de3dddc6c 30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 +85c997bf-63d9-4ebb-8e0b-320de3dddc6c 2ee2ab26-09fb-4e12-bf21-9fed3b4c38de +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b f883f2ca-d523-4c9f-86b2-0add137901de +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b f28931e5-1f35-4f9f-a02e-78398735ea67 +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b e7f68d35-ae55-4fa4-b0be-0672a5a7186a +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b 85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b 7ac56d5a-32a7-4b40-a956-356e3006faed +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b 72788e7f-1bf1-40b5-a1f1-27c669dc7278 +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b 6c84348c-5065-4e89-9412-bfda023683f2 +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b 5abe533b-ae5f-47ad-8746-f60caf7483c0 +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b 4f14ca7f-5332-4263-a7b2-f0a838380309 +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b 359cb842-c25b-429f-ba9b-d52f171e5631 +86415df5-7e47-4637-9e09-aaad9e7628d1 a2c3ee1d-ec35-4b26-9bab-12de3f47d604 +86415df5-7e47-4637-9e09-aaad9e7628d1 86415df5-7e47-4637-9e09-aaad9e7628d1 +86415df5-7e47-4637-9e09-aaad9e7628d1 749babeb-6883-4bd4-92a5-bec52769071c +86415df5-7e47-4637-9e09-aaad9e7628d1 578cc35c-0982-4d72-a729-b304c026f075 +86415df5-7e47-4637-9e09-aaad9e7628d1 0250a217-a663-403b-a708-5d14eadf0c40 +8743e69b-17aa-44ff-b8fa-4f582665efc6 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +8743e69b-17aa-44ff-b8fa-4f582665efc6 e8eba267-fecb-477e-9625-771fbcfc3cba +8743e69b-17aa-44ff-b8fa-4f582665efc6 e8c41168-a093-40eb-8baa-6802d24f554a +8743e69b-17aa-44ff-b8fa-4f582665efc6 e26ae29a-213a-4f79-98c2-cc81cf2f451d +8743e69b-17aa-44ff-b8fa-4f582665efc6 e0dfbc08-45ad-4a81-b648-7e65c066f673 +8743e69b-17aa-44ff-b8fa-4f582665efc6 d535b213-2abc-438f-aa6a-c06476d60b09 +8743e69b-17aa-44ff-b8fa-4f582665efc6 c892b1d7-7485-407d-8883-54fb7fecebc1 +8743e69b-17aa-44ff-b8fa-4f582665efc6 b6900c21-d310-4fb4-8b8f-176a09c91989 +8743e69b-17aa-44ff-b8fa-4f582665efc6 b00df815-7528-4967-bfe2-311130d91c21 +8743e69b-17aa-44ff-b8fa-4f582665efc6 add6d754-a075-4693-a33a-c061c9a368ff +8743e69b-17aa-44ff-b8fa-4f582665efc6 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +8743e69b-17aa-44ff-b8fa-4f582665efc6 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +8743e69b-17aa-44ff-b8fa-4f582665efc6 8a1908f7-47cc-4f82-859c-781a193e9901 +8743e69b-17aa-44ff-b8fa-4f582665efc6 8743e69b-17aa-44ff-b8fa-4f582665efc6 +8743e69b-17aa-44ff-b8fa-4f582665efc6 721e4027-a080-40d8-bc4a-43cd33477611 +8743e69b-17aa-44ff-b8fa-4f582665efc6 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +8743e69b-17aa-44ff-b8fa-4f582665efc6 54c750aa-570a-4e8e-9a02-418781923e39 +8743e69b-17aa-44ff-b8fa-4f582665efc6 50eac25a-3761-4de3-8a69-aae52e658300 +8743e69b-17aa-44ff-b8fa-4f582665efc6 4a464bb5-9373-4f1b-9c03-053c64797114 +8743e69b-17aa-44ff-b8fa-4f582665efc6 4506aa76-9d0d-40e4-85bc-5735f74522a0 +8743e69b-17aa-44ff-b8fa-4f582665efc6 42a9a333-d09d-4b9e-af96-1f02af26bac3 +8743e69b-17aa-44ff-b8fa-4f582665efc6 2fc75f11-2097-4e1e-8390-dbff3e844b7a +8743e69b-17aa-44ff-b8fa-4f582665efc6 274a2e49-9170-4945-bca2-ab6ef4bded75 +8743e69b-17aa-44ff-b8fa-4f582665efc6 0364073f-91d8-47a1-b8b0-107c6318a691 +87b04a97-1d33-4548-aec9-3d2856070702 c3ec1f2e-1411-41e4-8370-acd5a8f68cf4 +87b04a97-1d33-4548-aec9-3d2856070702 87b04a97-1d33-4548-aec9-3d2856070702 +87b04a97-1d33-4548-aec9-3d2856070702 2bddd98c-86e3-4ae0-9dc6-87da16ab6267 +88b16960-bfff-41d0-81e4-9a4630834a00 fce20464-ff98-4051-8714-6b9584e52740 +88b16960-bfff-41d0-81e4-9a4630834a00 f3eb0c38-2571-44e7-be39-732eb52cf1df +88b16960-bfff-41d0-81e4-9a4630834a00 f3c2f842-6aa3-42c9-a0f3-895c40456868 +88b16960-bfff-41d0-81e4-9a4630834a00 ede7745a-8f3e-4e20-9f16-33756be7ab6c +88b16960-bfff-41d0-81e4-9a4630834a00 ca3e0486-fd6b-4a13-a741-3a47760b412d +88b16960-bfff-41d0-81e4-9a4630834a00 c5c6eed5-aec6-4b8a-b689-adbb219c2267 +88b16960-bfff-41d0-81e4-9a4630834a00 bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +88b16960-bfff-41d0-81e4-9a4630834a00 a221198d-000e-497b-8b70-bb4d37f7bbe1 +88b16960-bfff-41d0-81e4-9a4630834a00 a1228f20-7c50-4d3a-b88c-2d277ca79d79 +88b16960-bfff-41d0-81e4-9a4630834a00 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +88b16960-bfff-41d0-81e4-9a4630834a00 88b16960-bfff-41d0-81e4-9a4630834a00 +88b16960-bfff-41d0-81e4-9a4630834a00 522e2abe-ad96-4d1a-b5a5-748faa997531 +88b16960-bfff-41d0-81e4-9a4630834a00 4cfe72fe-21a0-4201-87b6-ef93695d2495 +88b16960-bfff-41d0-81e4-9a4630834a00 498f4b18-bd4c-470d-9cde-2fca70ec8964 +88b16960-bfff-41d0-81e4-9a4630834a00 202131ae-78bb-4104-89b0-fb90645760f6 +88b16960-bfff-41d0-81e4-9a4630834a00 11dafd5c-85e7-4275-a147-89a63641e35e +88b16960-bfff-41d0-81e4-9a4630834a00 02b993c0-b358-481c-b0d0-0767387feb9f +89f260bb-68b4-4dec-91f4-d52e673e0ff7 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +89f260bb-68b4-4dec-91f4-d52e673e0ff7 ee24bf89-81a3-4259-a382-86917f3829d4 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 e363eb67-64c7-440f-93fd-5c8787c69a85 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 db79f75d-af3c-4f82-b4e5-9b5d26598eef +89f260bb-68b4-4dec-91f4-d52e673e0ff7 d7a48a26-7731-4046-bf22-78f0b8084eb9 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 d080c116-681a-494a-a928-45924109b49d +89f260bb-68b4-4dec-91f4-d52e673e0ff7 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +89f260bb-68b4-4dec-91f4-d52e673e0ff7 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +89f260bb-68b4-4dec-91f4-d52e673e0ff7 990c21ab-433b-4920-873e-f9dfc7103d9d +89f260bb-68b4-4dec-91f4-d52e673e0ff7 922e2443-9cc5-421f-8310-9cd23f6e9f2d +89f260bb-68b4-4dec-91f4-d52e673e0ff7 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 8089a9ac-9e71-4487-a231-f720e4bc8d3e +89f260bb-68b4-4dec-91f4-d52e673e0ff7 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +89f260bb-68b4-4dec-91f4-d52e673e0ff7 6d47a2fe-3645-4c5c-bebe-1b822eff197f +89f260bb-68b4-4dec-91f4-d52e673e0ff7 67fc12fc-bb9f-40f1-9051-127a788b3081 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 634f0889-3a83-484a-bcc8-86f48e333c7b +89f260bb-68b4-4dec-91f4-d52e673e0ff7 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 4adcaf72-5241-4877-b61c-f34060576c50 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +89f260bb-68b4-4dec-91f4-d52e673e0ff7 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 374fd699-6b74-4500-9d60-2473c7e0364f +89f260bb-68b4-4dec-91f4-d52e673e0ff7 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 308eba53-29fa-489a-97dc-741b077841a7 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +89f260bb-68b4-4dec-91f4-d52e673e0ff7 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 21995497-0eb8-4c0b-8c23-e6a829f3d596 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 18ebf5ea-b0a2-4333-9e9c-40217de809ff +89f260bb-68b4-4dec-91f4-d52e673e0ff7 151e3048-66ad-4411-8753-677877e3bf0a +89f260bb-68b4-4dec-91f4-d52e673e0ff7 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +89f260bb-68b4-4dec-91f4-d52e673e0ff7 0af4a77e-892e-4b3f-9110-4c5224782250 +8a1908f7-47cc-4f82-859c-781a193e9901 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +8a1908f7-47cc-4f82-859c-781a193e9901 e8eba267-fecb-477e-9625-771fbcfc3cba +8a1908f7-47cc-4f82-859c-781a193e9901 e8c41168-a093-40eb-8baa-6802d24f554a +8a1908f7-47cc-4f82-859c-781a193e9901 e26ae29a-213a-4f79-98c2-cc81cf2f451d +8a1908f7-47cc-4f82-859c-781a193e9901 e0dfbc08-45ad-4a81-b648-7e65c066f673 +8a1908f7-47cc-4f82-859c-781a193e9901 d535b213-2abc-438f-aa6a-c06476d60b09 +8a1908f7-47cc-4f82-859c-781a193e9901 c892b1d7-7485-407d-8883-54fb7fecebc1 +8a1908f7-47cc-4f82-859c-781a193e9901 b6900c21-d310-4fb4-8b8f-176a09c91989 +8a1908f7-47cc-4f82-859c-781a193e9901 b00df815-7528-4967-bfe2-311130d91c21 +8a1908f7-47cc-4f82-859c-781a193e9901 add6d754-a075-4693-a33a-c061c9a368ff +8a1908f7-47cc-4f82-859c-781a193e9901 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +8a1908f7-47cc-4f82-859c-781a193e9901 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +8a1908f7-47cc-4f82-859c-781a193e9901 8a1908f7-47cc-4f82-859c-781a193e9901 +8a1908f7-47cc-4f82-859c-781a193e9901 8743e69b-17aa-44ff-b8fa-4f582665efc6 +8a1908f7-47cc-4f82-859c-781a193e9901 721e4027-a080-40d8-bc4a-43cd33477611 +8a1908f7-47cc-4f82-859c-781a193e9901 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +8a1908f7-47cc-4f82-859c-781a193e9901 54c750aa-570a-4e8e-9a02-418781923e39 +8a1908f7-47cc-4f82-859c-781a193e9901 50eac25a-3761-4de3-8a69-aae52e658300 +8a1908f7-47cc-4f82-859c-781a193e9901 4a464bb5-9373-4f1b-9c03-053c64797114 +8a1908f7-47cc-4f82-859c-781a193e9901 4506aa76-9d0d-40e4-85bc-5735f74522a0 +8a1908f7-47cc-4f82-859c-781a193e9901 42a9a333-d09d-4b9e-af96-1f02af26bac3 +8a1908f7-47cc-4f82-859c-781a193e9901 2fc75f11-2097-4e1e-8390-dbff3e844b7a +8a1908f7-47cc-4f82-859c-781a193e9901 274a2e49-9170-4945-bca2-ab6ef4bded75 +8a1908f7-47cc-4f82-859c-781a193e9901 0364073f-91d8-47a1-b8b0-107c6318a691 +8bc7af32-e5ad-4849-ba4d-b446db833ab4 bac374c0-50e8-4a5c-947b-82a8e9a747a9 +8bc7af32-e5ad-4849-ba4d-b446db833ab4 8bc7af32-e5ad-4849-ba4d-b446db833ab4 +8bc7af32-e5ad-4849-ba4d-b446db833ab4 7ea43fc6-b1f7-46be-a308-5ecb275a1081 +8bc7af32-e5ad-4849-ba4d-b446db833ab4 2de1f829-c9d2-4f22-bf39-536dcb82fc3e +8bc7af32-e5ad-4849-ba4d-b446db833ab4 2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 +8bc7af32-e5ad-4849-ba4d-b446db833ab4 23780032-f3bb-4b40-af2e-3fa6b1677376 +8bc7af32-e5ad-4849-ba4d-b446db833ab4 21c6e0fc-bf47-4f80-b1ff-85b940445bdf +8bc7af32-e5ad-4849-ba4d-b446db833ab4 12b212d8-bc6e-415a-93b0-594381726668 +8be81657-8ba6-4ec5-8ff9-4809367096ea f29330d0-5b32-4f75-b558-a1c7f05c0b4a +8be81657-8ba6-4ec5-8ff9-4809367096ea d90676d2-ce74-4867-a00f-6dbffa7c00be +8be81657-8ba6-4ec5-8ff9-4809367096ea cc56af6d-25c6-480e-9767-da02a55551da +8be81657-8ba6-4ec5-8ff9-4809367096ea ca7f269d-3794-4994-8c46-d8e9f2aa6378 +8be81657-8ba6-4ec5-8ff9-4809367096ea baf48725-f0f9-4357-a71d-b1104910deae +8be81657-8ba6-4ec5-8ff9-4809367096ea 9ed8703e-3b40-424c-9e98-b3b404051f99 +8be81657-8ba6-4ec5-8ff9-4809367096ea 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +8be81657-8ba6-4ec5-8ff9-4809367096ea 8be81657-8ba6-4ec5-8ff9-4809367096ea +8be81657-8ba6-4ec5-8ff9-4809367096ea 7f410064-638a-4477-85c4-97fc2bb14a49 +8be81657-8ba6-4ec5-8ff9-4809367096ea 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +8be81657-8ba6-4ec5-8ff9-4809367096ea 5cedf18a-fc44-4c39-a80f-2106a0d76934 +8be81657-8ba6-4ec5-8ff9-4809367096ea 4f342a71-dec3-403e-970b-e4c21b9eb98b +8be81657-8ba6-4ec5-8ff9-4809367096ea 4db144d3-a596-4718-a50a-8ac9175ad386 +8be81657-8ba6-4ec5-8ff9-4809367096ea 4215b5d0-bdd9-4222-a04a-81bb637d60af +8be81657-8ba6-4ec5-8ff9-4809367096ea 29d4e919-2bd2-44e6-bb8a-380a780f60ff +8be81657-8ba6-4ec5-8ff9-4809367096ea 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +8be81657-8ba6-4ec5-8ff9-4809367096ea 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +8be81657-8ba6-4ec5-8ff9-4809367096ea 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +8be81657-8ba6-4ec5-8ff9-4809367096ea 102e16c5-4920-4dad-b142-0b280b2aacad +8be81657-8ba6-4ec5-8ff9-4809367096ea 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +8dc2ce26-aec7-43c0-9771-350af4257ad8 f19eefca-c1ad-4860-bdda-4674a631d464 +8dc2ce26-aec7-43c0-9771-350af4257ad8 f18b08bd-40da-43c1-87ec-4ba23542635f +8dc2ce26-aec7-43c0-9771-350af4257ad8 b1ccd55a-6b88-4240-b20c-ca893f36e23b +8dc2ce26-aec7-43c0-9771-350af4257ad8 900ce9fe-02d3-476b-902a-9467767ecdcf +8dc2ce26-aec7-43c0-9771-350af4257ad8 8dc2ce26-aec7-43c0-9771-350af4257ad8 +8dc2ce26-aec7-43c0-9771-350af4257ad8 1f3443ee-d15e-4894-b18e-185cfadfcdea +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 fce20464-ff98-4051-8714-6b9584e52740 +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 f3eb0c38-2571-44e7-be39-732eb52cf1df +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 f3c2f842-6aa3-42c9-a0f3-895c40456868 +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 ede7745a-8f3e-4e20-9f16-33756be7ab6c +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 ca3e0486-fd6b-4a13-a741-3a47760b412d +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 c5c6eed5-aec6-4b8a-b689-adbb219c2267 +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 a221198d-000e-497b-8b70-bb4d37f7bbe1 +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 a1228f20-7c50-4d3a-b88c-2d277ca79d79 +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 88b16960-bfff-41d0-81e4-9a4630834a00 +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 522e2abe-ad96-4d1a-b5a5-748faa997531 +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 4cfe72fe-21a0-4201-87b6-ef93695d2495 +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 498f4b18-bd4c-470d-9cde-2fca70ec8964 +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 202131ae-78bb-4104-89b0-fb90645760f6 +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 11dafd5c-85e7-4275-a147-89a63641e35e +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 02b993c0-b358-481c-b0d0-0767387feb9f +8f76d729-9f74-4cfd-be2a-8b033b568e18 fcab9efe-fb05-42d6-b366-ce6db1cc4093 +8f76d729-9f74-4cfd-be2a-8b033b568e18 f1293c5e-c997-4ba9-8bfd-438e7a840dc8 +8f76d729-9f74-4cfd-be2a-8b033b568e18 e4358f28-62a8-4e06-b2bd-cb00d3310349 +8f76d729-9f74-4cfd-be2a-8b033b568e18 8f76d729-9f74-4cfd-be2a-8b033b568e18 +8f76d729-9f74-4cfd-be2a-8b033b568e18 39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 +8f76d729-9f74-4cfd-be2a-8b033b568e18 31d4c712-75b1-4ecb-9d1a-ccc4b8453826 +8f76d729-9f74-4cfd-be2a-8b033b568e18 24c4dd5b-748b-4165-a0cc-2867b76c2dc2 +8f76d729-9f74-4cfd-be2a-8b033b568e18 16d280a2-c61f-487f-9034-22e884158969 +8f76d729-9f74-4cfd-be2a-8b033b568e18 0134901a-4a69-4b39-92ea-d6f48ec83c6e +900ce9fe-02d3-476b-902a-9467767ecdcf f19eefca-c1ad-4860-bdda-4674a631d464 +900ce9fe-02d3-476b-902a-9467767ecdcf f18b08bd-40da-43c1-87ec-4ba23542635f +900ce9fe-02d3-476b-902a-9467767ecdcf b1ccd55a-6b88-4240-b20c-ca893f36e23b +900ce9fe-02d3-476b-902a-9467767ecdcf 900ce9fe-02d3-476b-902a-9467767ecdcf +900ce9fe-02d3-476b-902a-9467767ecdcf 8dc2ce26-aec7-43c0-9771-350af4257ad8 +900ce9fe-02d3-476b-902a-9467767ecdcf 1f3443ee-d15e-4894-b18e-185cfadfcdea +922e2443-9cc5-421f-8310-9cd23f6e9f2d fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +922e2443-9cc5-421f-8310-9cd23f6e9f2d ee24bf89-81a3-4259-a382-86917f3829d4 +922e2443-9cc5-421f-8310-9cd23f6e9f2d e363eb67-64c7-440f-93fd-5c8787c69a85 +922e2443-9cc5-421f-8310-9cd23f6e9f2d dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +922e2443-9cc5-421f-8310-9cd23f6e9f2d db79f75d-af3c-4f82-b4e5-9b5d26598eef +922e2443-9cc5-421f-8310-9cd23f6e9f2d d7a48a26-7731-4046-bf22-78f0b8084eb9 +922e2443-9cc5-421f-8310-9cd23f6e9f2d d080c116-681a-494a-a928-45924109b49d +922e2443-9cc5-421f-8310-9cd23f6e9f2d cdb8493c-e3b0-447f-b16b-e58dd64660f3 +922e2443-9cc5-421f-8310-9cd23f6e9f2d c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +922e2443-9cc5-421f-8310-9cd23f6e9f2d a6b084fb-ada7-480a-9adc-f180d4eb1e2a +922e2443-9cc5-421f-8310-9cd23f6e9f2d 990c21ab-433b-4920-873e-f9dfc7103d9d +922e2443-9cc5-421f-8310-9cd23f6e9f2d 922e2443-9cc5-421f-8310-9cd23f6e9f2d +922e2443-9cc5-421f-8310-9cd23f6e9f2d 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +922e2443-9cc5-421f-8310-9cd23f6e9f2d 8089a9ac-9e71-4487-a231-f720e4bc8d3e +922e2443-9cc5-421f-8310-9cd23f6e9f2d 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +922e2443-9cc5-421f-8310-9cd23f6e9f2d 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +922e2443-9cc5-421f-8310-9cd23f6e9f2d 6d47a2fe-3645-4c5c-bebe-1b822eff197f +922e2443-9cc5-421f-8310-9cd23f6e9f2d 67fc12fc-bb9f-40f1-9051-127a788b3081 +922e2443-9cc5-421f-8310-9cd23f6e9f2d 634f0889-3a83-484a-bcc8-86f48e333c7b +922e2443-9cc5-421f-8310-9cd23f6e9f2d 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +922e2443-9cc5-421f-8310-9cd23f6e9f2d 4adcaf72-5241-4877-b61c-f34060576c50 +922e2443-9cc5-421f-8310-9cd23f6e9f2d 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +922e2443-9cc5-421f-8310-9cd23f6e9f2d 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +922e2443-9cc5-421f-8310-9cd23f6e9f2d 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +922e2443-9cc5-421f-8310-9cd23f6e9f2d 374fd699-6b74-4500-9d60-2473c7e0364f +922e2443-9cc5-421f-8310-9cd23f6e9f2d 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +922e2443-9cc5-421f-8310-9cd23f6e9f2d 308eba53-29fa-489a-97dc-741b077841a7 +922e2443-9cc5-421f-8310-9cd23f6e9f2d 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +922e2443-9cc5-421f-8310-9cd23f6e9f2d 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +922e2443-9cc5-421f-8310-9cd23f6e9f2d 21995497-0eb8-4c0b-8c23-e6a829f3d596 +922e2443-9cc5-421f-8310-9cd23f6e9f2d 18ebf5ea-b0a2-4333-9e9c-40217de809ff +922e2443-9cc5-421f-8310-9cd23f6e9f2d 151e3048-66ad-4411-8753-677877e3bf0a +922e2443-9cc5-421f-8310-9cd23f6e9f2d 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +922e2443-9cc5-421f-8310-9cd23f6e9f2d 0af4a77e-892e-4b3f-9110-4c5224782250 +96b2282d-1384-4cfb-9958-f009fb501626 def5fd23-2b74-4c64-85e9-fac27e626bb7 +96b2282d-1384-4cfb-9958-f009fb501626 96b2282d-1384-4cfb-9958-f009fb501626 +96b2282d-1384-4cfb-9958-f009fb501626 17bcf2ea-d921-4715-91c5-6b15226b33d3 +97b42d67-8691-433d-8a31-dada076162ae f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 +97b42d67-8691-433d-8a31-dada076162ae f3aa491a-4dbd-4436-b674-18b2177dcf18 +97b42d67-8691-433d-8a31-dada076162ae e1040d58-53bc-4467-8101-ca53b772cede +97b42d67-8691-433d-8a31-dada076162ae d05461d6-13bf-402c-890d-db6404933f7c +97b42d67-8691-433d-8a31-dada076162ae b7772635-1801-48e4-a442-f4aaa6860544 +97b42d67-8691-433d-8a31-dada076162ae b07fb98d-2da4-423a-83b4-7a673de93096 +97b42d67-8691-433d-8a31-dada076162ae 97b42d67-8691-433d-8a31-dada076162ae +97b42d67-8691-433d-8a31-dada076162ae 720f7ed6-7b2e-41dc-a0e0-724a3332aa24 +97b42d67-8691-433d-8a31-dada076162ae 4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 +97b42d67-8691-433d-8a31-dada076162ae 2ffd6142-b75b-406f-bc06-cdb1c02eaefc +97b42d67-8691-433d-8a31-dada076162ae 224e538d-3622-4f5e-b772-211ed358d8de +97b42d67-8691-433d-8a31-dada076162ae 16a3408d-c927-4d02-a1ae-cad32419caab +97b42d67-8691-433d-8a31-dada076162ae 04d88eb4-fb1b-4fa1-802a-5624f4c61b32 +984067fc-3dfc-47b7-8ee0-4d9b27d43860 984067fc-3dfc-47b7-8ee0-4d9b27d43860 +984067fc-3dfc-47b7-8ee0-4d9b27d43860 55f17f44-287f-41aa-a1bc-d1c0104af36e +984067fc-3dfc-47b7-8ee0-4d9b27d43860 2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5 +990c21ab-433b-4920-873e-f9dfc7103d9d fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +990c21ab-433b-4920-873e-f9dfc7103d9d ee24bf89-81a3-4259-a382-86917f3829d4 +990c21ab-433b-4920-873e-f9dfc7103d9d e363eb67-64c7-440f-93fd-5c8787c69a85 +990c21ab-433b-4920-873e-f9dfc7103d9d dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +990c21ab-433b-4920-873e-f9dfc7103d9d db79f75d-af3c-4f82-b4e5-9b5d26598eef +990c21ab-433b-4920-873e-f9dfc7103d9d d7a48a26-7731-4046-bf22-78f0b8084eb9 +990c21ab-433b-4920-873e-f9dfc7103d9d d080c116-681a-494a-a928-45924109b49d +990c21ab-433b-4920-873e-f9dfc7103d9d cdb8493c-e3b0-447f-b16b-e58dd64660f3 +990c21ab-433b-4920-873e-f9dfc7103d9d c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +990c21ab-433b-4920-873e-f9dfc7103d9d a6b084fb-ada7-480a-9adc-f180d4eb1e2a +990c21ab-433b-4920-873e-f9dfc7103d9d 990c21ab-433b-4920-873e-f9dfc7103d9d +990c21ab-433b-4920-873e-f9dfc7103d9d 922e2443-9cc5-421f-8310-9cd23f6e9f2d +990c21ab-433b-4920-873e-f9dfc7103d9d 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +990c21ab-433b-4920-873e-f9dfc7103d9d 8089a9ac-9e71-4487-a231-f720e4bc8d3e +990c21ab-433b-4920-873e-f9dfc7103d9d 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +990c21ab-433b-4920-873e-f9dfc7103d9d 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +990c21ab-433b-4920-873e-f9dfc7103d9d 6d47a2fe-3645-4c5c-bebe-1b822eff197f +990c21ab-433b-4920-873e-f9dfc7103d9d 67fc12fc-bb9f-40f1-9051-127a788b3081 +990c21ab-433b-4920-873e-f9dfc7103d9d 634f0889-3a83-484a-bcc8-86f48e333c7b +990c21ab-433b-4920-873e-f9dfc7103d9d 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +990c21ab-433b-4920-873e-f9dfc7103d9d 4adcaf72-5241-4877-b61c-f34060576c50 +990c21ab-433b-4920-873e-f9dfc7103d9d 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +990c21ab-433b-4920-873e-f9dfc7103d9d 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +990c21ab-433b-4920-873e-f9dfc7103d9d 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +990c21ab-433b-4920-873e-f9dfc7103d9d 374fd699-6b74-4500-9d60-2473c7e0364f +990c21ab-433b-4920-873e-f9dfc7103d9d 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +990c21ab-433b-4920-873e-f9dfc7103d9d 308eba53-29fa-489a-97dc-741b077841a7 +990c21ab-433b-4920-873e-f9dfc7103d9d 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +990c21ab-433b-4920-873e-f9dfc7103d9d 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +990c21ab-433b-4920-873e-f9dfc7103d9d 21995497-0eb8-4c0b-8c23-e6a829f3d596 +990c21ab-433b-4920-873e-f9dfc7103d9d 18ebf5ea-b0a2-4333-9e9c-40217de809ff +990c21ab-433b-4920-873e-f9dfc7103d9d 151e3048-66ad-4411-8753-677877e3bf0a +990c21ab-433b-4920-873e-f9dfc7103d9d 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +990c21ab-433b-4920-873e-f9dfc7103d9d 0af4a77e-892e-4b3f-9110-4c5224782250 +99fc3558-79f3-4d14-9aa1-ff63f621ecd9 99fc3558-79f3-4d14-9aa1-ff63f621ecd9 +99fc3558-79f3-4d14-9aa1-ff63f621ecd9 2aff9edd-def2-487a-b435-a162e11a303c +9b2eb632-6f1a-4e97-912b-3c8378a1b11c bfc93cb7-e53a-49f7-b86e-81d6a13cfdea +9b2eb632-6f1a-4e97-912b-3c8378a1b11c a6790cbd-8349-432e-a976-5dfc07451203 +9b2eb632-6f1a-4e97-912b-3c8378a1b11c 9b2eb632-6f1a-4e97-912b-3c8378a1b11c +9b2eb632-6f1a-4e97-912b-3c8378a1b11c 85c997bf-63d9-4ebb-8e0b-320de3dddc6c +9b2eb632-6f1a-4e97-912b-3c8378a1b11c 85481b3f-c75e-40cd-bacd-b0afb79e893c +9b2eb632-6f1a-4e97-912b-3c8378a1b11c 75a09d4f-eef2-4404-a386-dfcb408ec9ed +9b2eb632-6f1a-4e97-912b-3c8378a1b11c 30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 +9b2eb632-6f1a-4e97-912b-3c8378a1b11c 2ee2ab26-09fb-4e12-bf21-9fed3b4c38de +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 f29330d0-5b32-4f75-b558-a1c7f05c0b4a +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 d90676d2-ce74-4867-a00f-6dbffa7c00be +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 cc56af6d-25c6-480e-9767-da02a55551da +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 ca7f269d-3794-4994-8c46-d8e9f2aa6378 +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 baf48725-f0f9-4357-a71d-b1104910deae +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 9ed8703e-3b40-424c-9e98-b3b404051f99 +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 8be81657-8ba6-4ec5-8ff9-4809367096ea +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 7f410064-638a-4477-85c4-97fc2bb14a49 +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 5cedf18a-fc44-4c39-a80f-2106a0d76934 +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 4f342a71-dec3-403e-970b-e4c21b9eb98b +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 4db144d3-a596-4718-a50a-8ac9175ad386 +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 4215b5d0-bdd9-4222-a04a-81bb637d60af +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 29d4e919-2bd2-44e6-bb8a-380a780f60ff +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 102e16c5-4920-4dad-b142-0b280b2aacad +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +9bc516d4-7c82-4340-a90c-bb493f96fbe4 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +9bc516d4-7c82-4340-a90c-bb493f96fbe4 e8eba267-fecb-477e-9625-771fbcfc3cba +9bc516d4-7c82-4340-a90c-bb493f96fbe4 e8c41168-a093-40eb-8baa-6802d24f554a +9bc516d4-7c82-4340-a90c-bb493f96fbe4 e26ae29a-213a-4f79-98c2-cc81cf2f451d +9bc516d4-7c82-4340-a90c-bb493f96fbe4 e0dfbc08-45ad-4a81-b648-7e65c066f673 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 d535b213-2abc-438f-aa6a-c06476d60b09 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 c892b1d7-7485-407d-8883-54fb7fecebc1 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 b6900c21-d310-4fb4-8b8f-176a09c91989 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 b00df815-7528-4967-bfe2-311130d91c21 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 add6d754-a075-4693-a33a-c061c9a368ff +9bc516d4-7c82-4340-a90c-bb493f96fbe4 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 8a1908f7-47cc-4f82-859c-781a193e9901 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 8743e69b-17aa-44ff-b8fa-4f582665efc6 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 721e4027-a080-40d8-bc4a-43cd33477611 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 54c750aa-570a-4e8e-9a02-418781923e39 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 50eac25a-3761-4de3-8a69-aae52e658300 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 4a464bb5-9373-4f1b-9c03-053c64797114 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 4506aa76-9d0d-40e4-85bc-5735f74522a0 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 42a9a333-d09d-4b9e-af96-1f02af26bac3 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 2fc75f11-2097-4e1e-8390-dbff3e844b7a +9bc516d4-7c82-4340-a90c-bb493f96fbe4 274a2e49-9170-4945-bca2-ab6ef4bded75 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 0364073f-91d8-47a1-b8b0-107c6318a691 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 e8eba267-fecb-477e-9625-771fbcfc3cba +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 e8c41168-a093-40eb-8baa-6802d24f554a +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 e26ae29a-213a-4f79-98c2-cc81cf2f451d +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 e0dfbc08-45ad-4a81-b648-7e65c066f673 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 d535b213-2abc-438f-aa6a-c06476d60b09 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 c892b1d7-7485-407d-8883-54fb7fecebc1 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 b6900c21-d310-4fb4-8b8f-176a09c91989 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 b00df815-7528-4967-bfe2-311130d91c21 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 add6d754-a075-4693-a33a-c061c9a368ff +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 8a1908f7-47cc-4f82-859c-781a193e9901 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 8743e69b-17aa-44ff-b8fa-4f582665efc6 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 721e4027-a080-40d8-bc4a-43cd33477611 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 54c750aa-570a-4e8e-9a02-418781923e39 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 50eac25a-3761-4de3-8a69-aae52e658300 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 4a464bb5-9373-4f1b-9c03-053c64797114 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 4506aa76-9d0d-40e4-85bc-5735f74522a0 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 42a9a333-d09d-4b9e-af96-1f02af26bac3 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 2fc75f11-2097-4e1e-8390-dbff3e844b7a +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 274a2e49-9170-4945-bca2-ab6ef4bded75 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 0364073f-91d8-47a1-b8b0-107c6318a691 +9ed8703e-3b40-424c-9e98-b3b404051f99 f29330d0-5b32-4f75-b558-a1c7f05c0b4a +9ed8703e-3b40-424c-9e98-b3b404051f99 d90676d2-ce74-4867-a00f-6dbffa7c00be +9ed8703e-3b40-424c-9e98-b3b404051f99 cc56af6d-25c6-480e-9767-da02a55551da +9ed8703e-3b40-424c-9e98-b3b404051f99 ca7f269d-3794-4994-8c46-d8e9f2aa6378 +9ed8703e-3b40-424c-9e98-b3b404051f99 baf48725-f0f9-4357-a71d-b1104910deae +9ed8703e-3b40-424c-9e98-b3b404051f99 9ed8703e-3b40-424c-9e98-b3b404051f99 +9ed8703e-3b40-424c-9e98-b3b404051f99 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +9ed8703e-3b40-424c-9e98-b3b404051f99 8be81657-8ba6-4ec5-8ff9-4809367096ea +9ed8703e-3b40-424c-9e98-b3b404051f99 7f410064-638a-4477-85c4-97fc2bb14a49 +9ed8703e-3b40-424c-9e98-b3b404051f99 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +9ed8703e-3b40-424c-9e98-b3b404051f99 5cedf18a-fc44-4c39-a80f-2106a0d76934 +9ed8703e-3b40-424c-9e98-b3b404051f99 4f342a71-dec3-403e-970b-e4c21b9eb98b +9ed8703e-3b40-424c-9e98-b3b404051f99 4db144d3-a596-4718-a50a-8ac9175ad386 +9ed8703e-3b40-424c-9e98-b3b404051f99 4215b5d0-bdd9-4222-a04a-81bb637d60af +9ed8703e-3b40-424c-9e98-b3b404051f99 29d4e919-2bd2-44e6-bb8a-380a780f60ff +9ed8703e-3b40-424c-9e98-b3b404051f99 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +9ed8703e-3b40-424c-9e98-b3b404051f99 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +9ed8703e-3b40-424c-9e98-b3b404051f99 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +9ed8703e-3b40-424c-9e98-b3b404051f99 102e16c5-4920-4dad-b142-0b280b2aacad +9ed8703e-3b40-424c-9e98-b3b404051f99 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +a1228f20-7c50-4d3a-b88c-2d277ca79d79 fce20464-ff98-4051-8714-6b9584e52740 +a1228f20-7c50-4d3a-b88c-2d277ca79d79 f3eb0c38-2571-44e7-be39-732eb52cf1df +a1228f20-7c50-4d3a-b88c-2d277ca79d79 f3c2f842-6aa3-42c9-a0f3-895c40456868 +a1228f20-7c50-4d3a-b88c-2d277ca79d79 ede7745a-8f3e-4e20-9f16-33756be7ab6c +a1228f20-7c50-4d3a-b88c-2d277ca79d79 ca3e0486-fd6b-4a13-a741-3a47760b412d +a1228f20-7c50-4d3a-b88c-2d277ca79d79 c5c6eed5-aec6-4b8a-b689-adbb219c2267 +a1228f20-7c50-4d3a-b88c-2d277ca79d79 bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +a1228f20-7c50-4d3a-b88c-2d277ca79d79 a221198d-000e-497b-8b70-bb4d37f7bbe1 +a1228f20-7c50-4d3a-b88c-2d277ca79d79 a1228f20-7c50-4d3a-b88c-2d277ca79d79 +a1228f20-7c50-4d3a-b88c-2d277ca79d79 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +a1228f20-7c50-4d3a-b88c-2d277ca79d79 88b16960-bfff-41d0-81e4-9a4630834a00 +a1228f20-7c50-4d3a-b88c-2d277ca79d79 522e2abe-ad96-4d1a-b5a5-748faa997531 +a1228f20-7c50-4d3a-b88c-2d277ca79d79 4cfe72fe-21a0-4201-87b6-ef93695d2495 +a1228f20-7c50-4d3a-b88c-2d277ca79d79 498f4b18-bd4c-470d-9cde-2fca70ec8964 +a1228f20-7c50-4d3a-b88c-2d277ca79d79 202131ae-78bb-4104-89b0-fb90645760f6 +a1228f20-7c50-4d3a-b88c-2d277ca79d79 11dafd5c-85e7-4275-a147-89a63641e35e +a1228f20-7c50-4d3a-b88c-2d277ca79d79 02b993c0-b358-481c-b0d0-0767387feb9f +a221198d-000e-497b-8b70-bb4d37f7bbe1 fce20464-ff98-4051-8714-6b9584e52740 +a221198d-000e-497b-8b70-bb4d37f7bbe1 f3eb0c38-2571-44e7-be39-732eb52cf1df +a221198d-000e-497b-8b70-bb4d37f7bbe1 f3c2f842-6aa3-42c9-a0f3-895c40456868 +a221198d-000e-497b-8b70-bb4d37f7bbe1 ede7745a-8f3e-4e20-9f16-33756be7ab6c +a221198d-000e-497b-8b70-bb4d37f7bbe1 ca3e0486-fd6b-4a13-a741-3a47760b412d +a221198d-000e-497b-8b70-bb4d37f7bbe1 c5c6eed5-aec6-4b8a-b689-adbb219c2267 +a221198d-000e-497b-8b70-bb4d37f7bbe1 bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +a221198d-000e-497b-8b70-bb4d37f7bbe1 a221198d-000e-497b-8b70-bb4d37f7bbe1 +a221198d-000e-497b-8b70-bb4d37f7bbe1 a1228f20-7c50-4d3a-b88c-2d277ca79d79 +a221198d-000e-497b-8b70-bb4d37f7bbe1 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +a221198d-000e-497b-8b70-bb4d37f7bbe1 88b16960-bfff-41d0-81e4-9a4630834a00 +a221198d-000e-497b-8b70-bb4d37f7bbe1 522e2abe-ad96-4d1a-b5a5-748faa997531 +a221198d-000e-497b-8b70-bb4d37f7bbe1 4cfe72fe-21a0-4201-87b6-ef93695d2495 +a221198d-000e-497b-8b70-bb4d37f7bbe1 498f4b18-bd4c-470d-9cde-2fca70ec8964 +a221198d-000e-497b-8b70-bb4d37f7bbe1 202131ae-78bb-4104-89b0-fb90645760f6 +a221198d-000e-497b-8b70-bb4d37f7bbe1 11dafd5c-85e7-4275-a147-89a63641e35e +a221198d-000e-497b-8b70-bb4d37f7bbe1 02b993c0-b358-481c-b0d0-0767387feb9f +a2c3ee1d-ec35-4b26-9bab-12de3f47d604 a2c3ee1d-ec35-4b26-9bab-12de3f47d604 +a2c3ee1d-ec35-4b26-9bab-12de3f47d604 86415df5-7e47-4637-9e09-aaad9e7628d1 +a2c3ee1d-ec35-4b26-9bab-12de3f47d604 749babeb-6883-4bd4-92a5-bec52769071c +a2c3ee1d-ec35-4b26-9bab-12de3f47d604 578cc35c-0982-4d72-a729-b304c026f075 +a2c3ee1d-ec35-4b26-9bab-12de3f47d604 0250a217-a663-403b-a708-5d14eadf0c40 +a6790cbd-8349-432e-a976-5dfc07451203 bfc93cb7-e53a-49f7-b86e-81d6a13cfdea +a6790cbd-8349-432e-a976-5dfc07451203 a6790cbd-8349-432e-a976-5dfc07451203 +a6790cbd-8349-432e-a976-5dfc07451203 9b2eb632-6f1a-4e97-912b-3c8378a1b11c +a6790cbd-8349-432e-a976-5dfc07451203 85c997bf-63d9-4ebb-8e0b-320de3dddc6c +a6790cbd-8349-432e-a976-5dfc07451203 85481b3f-c75e-40cd-bacd-b0afb79e893c +a6790cbd-8349-432e-a976-5dfc07451203 75a09d4f-eef2-4404-a386-dfcb408ec9ed +a6790cbd-8349-432e-a976-5dfc07451203 30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 +a6790cbd-8349-432e-a976-5dfc07451203 2ee2ab26-09fb-4e12-bf21-9fed3b4c38de +a6b084fb-ada7-480a-9adc-f180d4eb1e2a fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +a6b084fb-ada7-480a-9adc-f180d4eb1e2a ee24bf89-81a3-4259-a382-86917f3829d4 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a e363eb67-64c7-440f-93fd-5c8787c69a85 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a db79f75d-af3c-4f82-b4e5-9b5d26598eef +a6b084fb-ada7-480a-9adc-f180d4eb1e2a d7a48a26-7731-4046-bf22-78f0b8084eb9 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a d080c116-681a-494a-a928-45924109b49d +a6b084fb-ada7-480a-9adc-f180d4eb1e2a cdb8493c-e3b0-447f-b16b-e58dd64660f3 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +a6b084fb-ada7-480a-9adc-f180d4eb1e2a a6b084fb-ada7-480a-9adc-f180d4eb1e2a +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 990c21ab-433b-4920-873e-f9dfc7103d9d +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 922e2443-9cc5-421f-8310-9cd23f6e9f2d +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 8089a9ac-9e71-4487-a231-f720e4bc8d3e +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 6d47a2fe-3645-4c5c-bebe-1b822eff197f +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 67fc12fc-bb9f-40f1-9051-127a788b3081 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 634f0889-3a83-484a-bcc8-86f48e333c7b +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 4adcaf72-5241-4877-b61c-f34060576c50 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 374fd699-6b74-4500-9d60-2473c7e0364f +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 308eba53-29fa-489a-97dc-741b077841a7 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 21995497-0eb8-4c0b-8c23-e6a829f3d596 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 18ebf5ea-b0a2-4333-9e9c-40217de809ff +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 151e3048-66ad-4411-8753-677877e3bf0a +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 0af4a77e-892e-4b3f-9110-4c5224782250 +a6cb423a-7571-46df-83e2-ccc6820adb36 f77a8561-5adc-452a-ac9a-76273b6a6678 +a6cb423a-7571-46df-83e2-ccc6820adb36 d79728e1-8834-4f4a-8edc-ce18aca18be6 +a6cb423a-7571-46df-83e2-ccc6820adb36 cf67a8d4-48e2-4dc9-a521-6aabcff0330b +a6cb423a-7571-46df-83e2-ccc6820adb36 bb1a7816-4798-4f58-9ce4-c5bd3af682a8 +a6cb423a-7571-46df-83e2-ccc6820adb36 b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 +a6cb423a-7571-46df-83e2-ccc6820adb36 a6cb423a-7571-46df-83e2-ccc6820adb36 +a6cb423a-7571-46df-83e2-ccc6820adb36 7e7322a6-7912-4101-b3be-d134245a0626 +a6cb423a-7571-46df-83e2-ccc6820adb36 777aa5f2-2a09-4aed-92ea-912694e28b48 +a6cb423a-7571-46df-83e2-ccc6820adb36 6e187f47-91a1-4217-a185-31b6f01db225 +a6cb423a-7571-46df-83e2-ccc6820adb36 62059efc-7607-41c9-a3ba-70948f6a8a1d +a6cb423a-7571-46df-83e2-ccc6820adb36 2d2e07ec-e697-4384-a679-867e93740921 +a6cb423a-7571-46df-83e2-ccc6820adb36 20fbcb6e-d793-4b05-8e29-8ff82572b0db +a6cb423a-7571-46df-83e2-ccc6820adb36 1fd714a6-48b4-425a-99b3-ba5c1a995cce +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 fcc916dd-5e4f-4bf7-9aef-c906a36d7301 +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 c57d51a7-45de-41b9-92fc-efd0634effaf +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 ae7ddaef-4911-418c-8401-d978617e145b +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 acb2329d-b1c3-45c3-ad28-91a09aa521e1 +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 a89c0c42-c19f-4d7f-a15e-d04e043c92f6 +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 836b2e59-fb97-4014-ab0c-d5a5dc750969 +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 683714ad-5194-49e7-9b8f-4e306cf68ae1 +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 5f64131b-d410-449a-9de6-35415919fec5 +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 4f3d1e93-a28f-446e-91af-2baf0168b82b +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 1db1c71b-9eeb-4a98-abc1-eb699b38510c +acb2329d-b1c3-45c3-ad28-91a09aa521e1 fcc916dd-5e4f-4bf7-9aef-c906a36d7301 +acb2329d-b1c3-45c3-ad28-91a09aa521e1 c57d51a7-45de-41b9-92fc-efd0634effaf +acb2329d-b1c3-45c3-ad28-91a09aa521e1 ae7ddaef-4911-418c-8401-d978617e145b +acb2329d-b1c3-45c3-ad28-91a09aa521e1 acb2329d-b1c3-45c3-ad28-91a09aa521e1 +acb2329d-b1c3-45c3-ad28-91a09aa521e1 a89c0c42-c19f-4d7f-a15e-d04e043c92f6 +acb2329d-b1c3-45c3-ad28-91a09aa521e1 836b2e59-fb97-4014-ab0c-d5a5dc750969 +acb2329d-b1c3-45c3-ad28-91a09aa521e1 683714ad-5194-49e7-9b8f-4e306cf68ae1 +acb2329d-b1c3-45c3-ad28-91a09aa521e1 5f64131b-d410-449a-9de6-35415919fec5 +acb2329d-b1c3-45c3-ad28-91a09aa521e1 4f3d1e93-a28f-446e-91af-2baf0168b82b +acb2329d-b1c3-45c3-ad28-91a09aa521e1 1db1c71b-9eeb-4a98-abc1-eb699b38510c +add6d754-a075-4693-a33a-c061c9a368ff f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +add6d754-a075-4693-a33a-c061c9a368ff e8eba267-fecb-477e-9625-771fbcfc3cba +add6d754-a075-4693-a33a-c061c9a368ff e8c41168-a093-40eb-8baa-6802d24f554a +add6d754-a075-4693-a33a-c061c9a368ff e26ae29a-213a-4f79-98c2-cc81cf2f451d +add6d754-a075-4693-a33a-c061c9a368ff e0dfbc08-45ad-4a81-b648-7e65c066f673 +add6d754-a075-4693-a33a-c061c9a368ff d535b213-2abc-438f-aa6a-c06476d60b09 +add6d754-a075-4693-a33a-c061c9a368ff c892b1d7-7485-407d-8883-54fb7fecebc1 +add6d754-a075-4693-a33a-c061c9a368ff b6900c21-d310-4fb4-8b8f-176a09c91989 +add6d754-a075-4693-a33a-c061c9a368ff b00df815-7528-4967-bfe2-311130d91c21 +add6d754-a075-4693-a33a-c061c9a368ff add6d754-a075-4693-a33a-c061c9a368ff +add6d754-a075-4693-a33a-c061c9a368ff 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +add6d754-a075-4693-a33a-c061c9a368ff 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +add6d754-a075-4693-a33a-c061c9a368ff 8a1908f7-47cc-4f82-859c-781a193e9901 +add6d754-a075-4693-a33a-c061c9a368ff 8743e69b-17aa-44ff-b8fa-4f582665efc6 +add6d754-a075-4693-a33a-c061c9a368ff 721e4027-a080-40d8-bc4a-43cd33477611 +add6d754-a075-4693-a33a-c061c9a368ff 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +add6d754-a075-4693-a33a-c061c9a368ff 54c750aa-570a-4e8e-9a02-418781923e39 +add6d754-a075-4693-a33a-c061c9a368ff 50eac25a-3761-4de3-8a69-aae52e658300 +add6d754-a075-4693-a33a-c061c9a368ff 4a464bb5-9373-4f1b-9c03-053c64797114 +add6d754-a075-4693-a33a-c061c9a368ff 4506aa76-9d0d-40e4-85bc-5735f74522a0 +add6d754-a075-4693-a33a-c061c9a368ff 42a9a333-d09d-4b9e-af96-1f02af26bac3 +add6d754-a075-4693-a33a-c061c9a368ff 2fc75f11-2097-4e1e-8390-dbff3e844b7a +add6d754-a075-4693-a33a-c061c9a368ff 274a2e49-9170-4945-bca2-ab6ef4bded75 +add6d754-a075-4693-a33a-c061c9a368ff 0364073f-91d8-47a1-b8b0-107c6318a691 +ae7ddaef-4911-418c-8401-d978617e145b fcc916dd-5e4f-4bf7-9aef-c906a36d7301 +ae7ddaef-4911-418c-8401-d978617e145b c57d51a7-45de-41b9-92fc-efd0634effaf +ae7ddaef-4911-418c-8401-d978617e145b ae7ddaef-4911-418c-8401-d978617e145b +ae7ddaef-4911-418c-8401-d978617e145b acb2329d-b1c3-45c3-ad28-91a09aa521e1 +ae7ddaef-4911-418c-8401-d978617e145b a89c0c42-c19f-4d7f-a15e-d04e043c92f6 +ae7ddaef-4911-418c-8401-d978617e145b 836b2e59-fb97-4014-ab0c-d5a5dc750969 +ae7ddaef-4911-418c-8401-d978617e145b 683714ad-5194-49e7-9b8f-4e306cf68ae1 +ae7ddaef-4911-418c-8401-d978617e145b 5f64131b-d410-449a-9de6-35415919fec5 +ae7ddaef-4911-418c-8401-d978617e145b 4f3d1e93-a28f-446e-91af-2baf0168b82b +ae7ddaef-4911-418c-8401-d978617e145b 1db1c71b-9eeb-4a98-abc1-eb699b38510c +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 e1c2ad9f-6f61-4f16-805a-2f405b63659a +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 d6988116-3c63-44f8-9f94-9e9d51cc5a37 +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 cfcbe34a-edc5-4442-bc05-5927fa00336b +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 c90aae7e-5704-4e13-8794-41ab377193bd +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 bf895c48-1d52-4780-8e9a-532d69356d28 +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 ae9e663e-8c15-43c9-8a88-52b61cbf07a9 +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 56999896-e36b-4e63-a6b6-dbb85dfe1936 +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 51241857-a7a4-4517-96e3-21f797581f89 +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 4cd95073-6db3-4eb2-ac4b-0aacb182b352 +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 4148ac36-1dcb-4e96-89cd-355e7e8f919b +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 2e8eb256-84c9-499a-8bf6-bb39504373e1 +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 +b00df815-7528-4967-bfe2-311130d91c21 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +b00df815-7528-4967-bfe2-311130d91c21 e8eba267-fecb-477e-9625-771fbcfc3cba +b00df815-7528-4967-bfe2-311130d91c21 e8c41168-a093-40eb-8baa-6802d24f554a +b00df815-7528-4967-bfe2-311130d91c21 e26ae29a-213a-4f79-98c2-cc81cf2f451d +b00df815-7528-4967-bfe2-311130d91c21 e0dfbc08-45ad-4a81-b648-7e65c066f673 +b00df815-7528-4967-bfe2-311130d91c21 d535b213-2abc-438f-aa6a-c06476d60b09 +b00df815-7528-4967-bfe2-311130d91c21 c892b1d7-7485-407d-8883-54fb7fecebc1 +b00df815-7528-4967-bfe2-311130d91c21 b6900c21-d310-4fb4-8b8f-176a09c91989 +b00df815-7528-4967-bfe2-311130d91c21 b00df815-7528-4967-bfe2-311130d91c21 +b00df815-7528-4967-bfe2-311130d91c21 add6d754-a075-4693-a33a-c061c9a368ff +b00df815-7528-4967-bfe2-311130d91c21 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +b00df815-7528-4967-bfe2-311130d91c21 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +b00df815-7528-4967-bfe2-311130d91c21 8a1908f7-47cc-4f82-859c-781a193e9901 +b00df815-7528-4967-bfe2-311130d91c21 8743e69b-17aa-44ff-b8fa-4f582665efc6 +b00df815-7528-4967-bfe2-311130d91c21 721e4027-a080-40d8-bc4a-43cd33477611 +b00df815-7528-4967-bfe2-311130d91c21 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +b00df815-7528-4967-bfe2-311130d91c21 54c750aa-570a-4e8e-9a02-418781923e39 +b00df815-7528-4967-bfe2-311130d91c21 50eac25a-3761-4de3-8a69-aae52e658300 +b00df815-7528-4967-bfe2-311130d91c21 4a464bb5-9373-4f1b-9c03-053c64797114 +b00df815-7528-4967-bfe2-311130d91c21 4506aa76-9d0d-40e4-85bc-5735f74522a0 +b00df815-7528-4967-bfe2-311130d91c21 42a9a333-d09d-4b9e-af96-1f02af26bac3 +b00df815-7528-4967-bfe2-311130d91c21 2fc75f11-2097-4e1e-8390-dbff3e844b7a +b00df815-7528-4967-bfe2-311130d91c21 274a2e49-9170-4945-bca2-ab6ef4bded75 +b00df815-7528-4967-bfe2-311130d91c21 0364073f-91d8-47a1-b8b0-107c6318a691 +b07fb98d-2da4-423a-83b4-7a673de93096 f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 +b07fb98d-2da4-423a-83b4-7a673de93096 f3aa491a-4dbd-4436-b674-18b2177dcf18 +b07fb98d-2da4-423a-83b4-7a673de93096 e1040d58-53bc-4467-8101-ca53b772cede +b07fb98d-2da4-423a-83b4-7a673de93096 d05461d6-13bf-402c-890d-db6404933f7c +b07fb98d-2da4-423a-83b4-7a673de93096 b7772635-1801-48e4-a442-f4aaa6860544 +b07fb98d-2da4-423a-83b4-7a673de93096 b07fb98d-2da4-423a-83b4-7a673de93096 +b07fb98d-2da4-423a-83b4-7a673de93096 97b42d67-8691-433d-8a31-dada076162ae +b07fb98d-2da4-423a-83b4-7a673de93096 720f7ed6-7b2e-41dc-a0e0-724a3332aa24 +b07fb98d-2da4-423a-83b4-7a673de93096 4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 +b07fb98d-2da4-423a-83b4-7a673de93096 2ffd6142-b75b-406f-bc06-cdb1c02eaefc +b07fb98d-2da4-423a-83b4-7a673de93096 224e538d-3622-4f5e-b772-211ed358d8de +b07fb98d-2da4-423a-83b4-7a673de93096 16a3408d-c927-4d02-a1ae-cad32419caab +b07fb98d-2da4-423a-83b4-7a673de93096 04d88eb4-fb1b-4fa1-802a-5624f4c61b32 +b0d337c5-3555-4817-ba59-4ceea5ad8a91 b0d337c5-3555-4817-ba59-4ceea5ad8a91 +b0d337c5-3555-4817-ba59-4ceea5ad8a91 79be3b69-23d8-4408-8f01-68d993e63b6c +b0d337c5-3555-4817-ba59-4ceea5ad8a91 04945715-8ad5-4d8f-bfda-ae26662a3610 +b1ccd55a-6b88-4240-b20c-ca893f36e23b f19eefca-c1ad-4860-bdda-4674a631d464 +b1ccd55a-6b88-4240-b20c-ca893f36e23b f18b08bd-40da-43c1-87ec-4ba23542635f +b1ccd55a-6b88-4240-b20c-ca893f36e23b b1ccd55a-6b88-4240-b20c-ca893f36e23b +b1ccd55a-6b88-4240-b20c-ca893f36e23b 900ce9fe-02d3-476b-902a-9467767ecdcf +b1ccd55a-6b88-4240-b20c-ca893f36e23b 8dc2ce26-aec7-43c0-9771-350af4257ad8 +b1ccd55a-6b88-4240-b20c-ca893f36e23b 1f3443ee-d15e-4894-b18e-185cfadfcdea +b1d4dc41-07ae-44db-ae17-1df86c5a62cf fc771883-3bd6-46d9-9626-a130e1b902de +b1d4dc41-07ae-44db-ae17-1df86c5a62cf e2387a81-5ff5-4daf-b2e8-881998d0d917 +b1d4dc41-07ae-44db-ae17-1df86c5a62cf b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa +b1d4dc41-07ae-44db-ae17-1df86c5a62cf b1d4dc41-07ae-44db-ae17-1df86c5a62cf +b1d4dc41-07ae-44db-ae17-1df86c5a62cf 67e5cf95-236b-4f75-abc5-034ca2966448 +b1d4dc41-07ae-44db-ae17-1df86c5a62cf 48cc2275-a478-4ade-b076-cf38e1d16017 +b1d4dc41-07ae-44db-ae17-1df86c5a62cf 41d26b1c-57b9-408c-b955-bf0ee1db4809 +b1d4dc41-07ae-44db-ae17-1df86c5a62cf 3ddf46fe-b5be-456d-810f-ea6a7402236d +b1d4dc41-07ae-44db-ae17-1df86c5a62cf 3397a796-894d-409c-97de-a9e6f3f88198 +b1d4dc41-07ae-44db-ae17-1df86c5a62cf 19502b5a-2031-487d-9d9c-2001f959408b +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa fc771883-3bd6-46d9-9626-a130e1b902de +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa e2387a81-5ff5-4daf-b2e8-881998d0d917 +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa b1d4dc41-07ae-44db-ae17-1df86c5a62cf +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa 67e5cf95-236b-4f75-abc5-034ca2966448 +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa 48cc2275-a478-4ade-b076-cf38e1d16017 +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa 41d26b1c-57b9-408c-b955-bf0ee1db4809 +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa 3ddf46fe-b5be-456d-810f-ea6a7402236d +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa 3397a796-894d-409c-97de-a9e6f3f88198 +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa 19502b5a-2031-487d-9d9c-2001f959408b +b6900c21-d310-4fb4-8b8f-176a09c91989 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +b6900c21-d310-4fb4-8b8f-176a09c91989 e8eba267-fecb-477e-9625-771fbcfc3cba +b6900c21-d310-4fb4-8b8f-176a09c91989 e8c41168-a093-40eb-8baa-6802d24f554a +b6900c21-d310-4fb4-8b8f-176a09c91989 e26ae29a-213a-4f79-98c2-cc81cf2f451d +b6900c21-d310-4fb4-8b8f-176a09c91989 e0dfbc08-45ad-4a81-b648-7e65c066f673 +b6900c21-d310-4fb4-8b8f-176a09c91989 d535b213-2abc-438f-aa6a-c06476d60b09 +b6900c21-d310-4fb4-8b8f-176a09c91989 c892b1d7-7485-407d-8883-54fb7fecebc1 +b6900c21-d310-4fb4-8b8f-176a09c91989 b6900c21-d310-4fb4-8b8f-176a09c91989 +b6900c21-d310-4fb4-8b8f-176a09c91989 b00df815-7528-4967-bfe2-311130d91c21 +b6900c21-d310-4fb4-8b8f-176a09c91989 add6d754-a075-4693-a33a-c061c9a368ff +b6900c21-d310-4fb4-8b8f-176a09c91989 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +b6900c21-d310-4fb4-8b8f-176a09c91989 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +b6900c21-d310-4fb4-8b8f-176a09c91989 8a1908f7-47cc-4f82-859c-781a193e9901 +b6900c21-d310-4fb4-8b8f-176a09c91989 8743e69b-17aa-44ff-b8fa-4f582665efc6 +b6900c21-d310-4fb4-8b8f-176a09c91989 721e4027-a080-40d8-bc4a-43cd33477611 +b6900c21-d310-4fb4-8b8f-176a09c91989 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +b6900c21-d310-4fb4-8b8f-176a09c91989 54c750aa-570a-4e8e-9a02-418781923e39 +b6900c21-d310-4fb4-8b8f-176a09c91989 50eac25a-3761-4de3-8a69-aae52e658300 +b6900c21-d310-4fb4-8b8f-176a09c91989 4a464bb5-9373-4f1b-9c03-053c64797114 +b6900c21-d310-4fb4-8b8f-176a09c91989 4506aa76-9d0d-40e4-85bc-5735f74522a0 +b6900c21-d310-4fb4-8b8f-176a09c91989 42a9a333-d09d-4b9e-af96-1f02af26bac3 +b6900c21-d310-4fb4-8b8f-176a09c91989 2fc75f11-2097-4e1e-8390-dbff3e844b7a +b6900c21-d310-4fb4-8b8f-176a09c91989 274a2e49-9170-4945-bca2-ab6ef4bded75 +b6900c21-d310-4fb4-8b8f-176a09c91989 0364073f-91d8-47a1-b8b0-107c6318a691 +b7772635-1801-48e4-a442-f4aaa6860544 f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 +b7772635-1801-48e4-a442-f4aaa6860544 f3aa491a-4dbd-4436-b674-18b2177dcf18 +b7772635-1801-48e4-a442-f4aaa6860544 e1040d58-53bc-4467-8101-ca53b772cede +b7772635-1801-48e4-a442-f4aaa6860544 d05461d6-13bf-402c-890d-db6404933f7c +b7772635-1801-48e4-a442-f4aaa6860544 b7772635-1801-48e4-a442-f4aaa6860544 +b7772635-1801-48e4-a442-f4aaa6860544 b07fb98d-2da4-423a-83b4-7a673de93096 +b7772635-1801-48e4-a442-f4aaa6860544 97b42d67-8691-433d-8a31-dada076162ae +b7772635-1801-48e4-a442-f4aaa6860544 720f7ed6-7b2e-41dc-a0e0-724a3332aa24 +b7772635-1801-48e4-a442-f4aaa6860544 4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 +b7772635-1801-48e4-a442-f4aaa6860544 2ffd6142-b75b-406f-bc06-cdb1c02eaefc +b7772635-1801-48e4-a442-f4aaa6860544 224e538d-3622-4f5e-b772-211ed358d8de +b7772635-1801-48e4-a442-f4aaa6860544 16a3408d-c927-4d02-a1ae-cad32419caab +b7772635-1801-48e4-a442-f4aaa6860544 04d88eb4-fb1b-4fa1-802a-5624f4c61b32 +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 f77a8561-5adc-452a-ac9a-76273b6a6678 +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 d79728e1-8834-4f4a-8edc-ce18aca18be6 +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 cf67a8d4-48e2-4dc9-a521-6aabcff0330b +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 bb1a7816-4798-4f58-9ce4-c5bd3af682a8 +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 a6cb423a-7571-46df-83e2-ccc6820adb36 +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 7e7322a6-7912-4101-b3be-d134245a0626 +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 777aa5f2-2a09-4aed-92ea-912694e28b48 +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 6e187f47-91a1-4217-a185-31b6f01db225 +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 62059efc-7607-41c9-a3ba-70948f6a8a1d +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 2d2e07ec-e697-4384-a679-867e93740921 +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 20fbcb6e-d793-4b05-8e29-8ff82572b0db +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 1fd714a6-48b4-425a-99b3-ba5c1a995cce +bac374c0-50e8-4a5c-947b-82a8e9a747a9 bac374c0-50e8-4a5c-947b-82a8e9a747a9 +bac374c0-50e8-4a5c-947b-82a8e9a747a9 8bc7af32-e5ad-4849-ba4d-b446db833ab4 +bac374c0-50e8-4a5c-947b-82a8e9a747a9 7ea43fc6-b1f7-46be-a308-5ecb275a1081 +bac374c0-50e8-4a5c-947b-82a8e9a747a9 2de1f829-c9d2-4f22-bf39-536dcb82fc3e +bac374c0-50e8-4a5c-947b-82a8e9a747a9 2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 +bac374c0-50e8-4a5c-947b-82a8e9a747a9 23780032-f3bb-4b40-af2e-3fa6b1677376 +bac374c0-50e8-4a5c-947b-82a8e9a747a9 21c6e0fc-bf47-4f80-b1ff-85b940445bdf +bac374c0-50e8-4a5c-947b-82a8e9a747a9 12b212d8-bc6e-415a-93b0-594381726668 +baf48725-f0f9-4357-a71d-b1104910deae f29330d0-5b32-4f75-b558-a1c7f05c0b4a +baf48725-f0f9-4357-a71d-b1104910deae d90676d2-ce74-4867-a00f-6dbffa7c00be +baf48725-f0f9-4357-a71d-b1104910deae cc56af6d-25c6-480e-9767-da02a55551da +baf48725-f0f9-4357-a71d-b1104910deae ca7f269d-3794-4994-8c46-d8e9f2aa6378 +baf48725-f0f9-4357-a71d-b1104910deae baf48725-f0f9-4357-a71d-b1104910deae +baf48725-f0f9-4357-a71d-b1104910deae 9ed8703e-3b40-424c-9e98-b3b404051f99 +baf48725-f0f9-4357-a71d-b1104910deae 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +baf48725-f0f9-4357-a71d-b1104910deae 8be81657-8ba6-4ec5-8ff9-4809367096ea +baf48725-f0f9-4357-a71d-b1104910deae 7f410064-638a-4477-85c4-97fc2bb14a49 +baf48725-f0f9-4357-a71d-b1104910deae 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +baf48725-f0f9-4357-a71d-b1104910deae 5cedf18a-fc44-4c39-a80f-2106a0d76934 +baf48725-f0f9-4357-a71d-b1104910deae 4f342a71-dec3-403e-970b-e4c21b9eb98b +baf48725-f0f9-4357-a71d-b1104910deae 4db144d3-a596-4718-a50a-8ac9175ad386 +baf48725-f0f9-4357-a71d-b1104910deae 4215b5d0-bdd9-4222-a04a-81bb637d60af +baf48725-f0f9-4357-a71d-b1104910deae 29d4e919-2bd2-44e6-bb8a-380a780f60ff +baf48725-f0f9-4357-a71d-b1104910deae 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +baf48725-f0f9-4357-a71d-b1104910deae 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +baf48725-f0f9-4357-a71d-b1104910deae 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +baf48725-f0f9-4357-a71d-b1104910deae 102e16c5-4920-4dad-b142-0b280b2aacad +baf48725-f0f9-4357-a71d-b1104910deae 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 f77a8561-5adc-452a-ac9a-76273b6a6678 +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 d79728e1-8834-4f4a-8edc-ce18aca18be6 +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 cf67a8d4-48e2-4dc9-a521-6aabcff0330b +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 bb1a7816-4798-4f58-9ce4-c5bd3af682a8 +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 a6cb423a-7571-46df-83e2-ccc6820adb36 +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 7e7322a6-7912-4101-b3be-d134245a0626 +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 777aa5f2-2a09-4aed-92ea-912694e28b48 +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 6e187f47-91a1-4217-a185-31b6f01db225 +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 62059efc-7607-41c9-a3ba-70948f6a8a1d +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 2d2e07ec-e697-4384-a679-867e93740921 +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 20fbcb6e-d793-4b05-8e29-8ff82572b0db +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 1fd714a6-48b4-425a-99b3-ba5c1a995cce +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb fce20464-ff98-4051-8714-6b9584e52740 +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb f3eb0c38-2571-44e7-be39-732eb52cf1df +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb f3c2f842-6aa3-42c9-a0f3-895c40456868 +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb ede7745a-8f3e-4e20-9f16-33756be7ab6c +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb ca3e0486-fd6b-4a13-a741-3a47760b412d +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb c5c6eed5-aec6-4b8a-b689-adbb219c2267 +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb a221198d-000e-497b-8b70-bb4d37f7bbe1 +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb a1228f20-7c50-4d3a-b88c-2d277ca79d79 +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb 88b16960-bfff-41d0-81e4-9a4630834a00 +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb 522e2abe-ad96-4d1a-b5a5-748faa997531 +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb 4cfe72fe-21a0-4201-87b6-ef93695d2495 +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb 498f4b18-bd4c-470d-9cde-2fca70ec8964 +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb 202131ae-78bb-4104-89b0-fb90645760f6 +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb 11dafd5c-85e7-4275-a147-89a63641e35e +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb 02b993c0-b358-481c-b0d0-0767387feb9f +bf895c48-1d52-4780-8e9a-532d69356d28 e1c2ad9f-6f61-4f16-805a-2f405b63659a +bf895c48-1d52-4780-8e9a-532d69356d28 d6988116-3c63-44f8-9f94-9e9d51cc5a37 +bf895c48-1d52-4780-8e9a-532d69356d28 cfcbe34a-edc5-4442-bc05-5927fa00336b +bf895c48-1d52-4780-8e9a-532d69356d28 c90aae7e-5704-4e13-8794-41ab377193bd +bf895c48-1d52-4780-8e9a-532d69356d28 bf895c48-1d52-4780-8e9a-532d69356d28 +bf895c48-1d52-4780-8e9a-532d69356d28 ae9e663e-8c15-43c9-8a88-52b61cbf07a9 +bf895c48-1d52-4780-8e9a-532d69356d28 56999896-e36b-4e63-a6b6-dbb85dfe1936 +bf895c48-1d52-4780-8e9a-532d69356d28 51241857-a7a4-4517-96e3-21f797581f89 +bf895c48-1d52-4780-8e9a-532d69356d28 4cd95073-6db3-4eb2-ac4b-0aacb182b352 +bf895c48-1d52-4780-8e9a-532d69356d28 4148ac36-1dcb-4e96-89cd-355e7e8f919b +bf895c48-1d52-4780-8e9a-532d69356d28 2e8eb256-84c9-499a-8bf6-bb39504373e1 +bf895c48-1d52-4780-8e9a-532d69356d28 07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 +bfc93cb7-e53a-49f7-b86e-81d6a13cfdea bfc93cb7-e53a-49f7-b86e-81d6a13cfdea +bfc93cb7-e53a-49f7-b86e-81d6a13cfdea a6790cbd-8349-432e-a976-5dfc07451203 +bfc93cb7-e53a-49f7-b86e-81d6a13cfdea 9b2eb632-6f1a-4e97-912b-3c8378a1b11c +bfc93cb7-e53a-49f7-b86e-81d6a13cfdea 85c997bf-63d9-4ebb-8e0b-320de3dddc6c +bfc93cb7-e53a-49f7-b86e-81d6a13cfdea 85481b3f-c75e-40cd-bacd-b0afb79e893c +bfc93cb7-e53a-49f7-b86e-81d6a13cfdea 75a09d4f-eef2-4404-a386-dfcb408ec9ed +bfc93cb7-e53a-49f7-b86e-81d6a13cfdea 30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 +bfc93cb7-e53a-49f7-b86e-81d6a13cfdea 2ee2ab26-09fb-4e12-bf21-9fed3b4c38de +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a ee24bf89-81a3-4259-a382-86917f3829d4 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a e363eb67-64c7-440f-93fd-5c8787c69a85 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a db79f75d-af3c-4f82-b4e5-9b5d26598eef +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a d7a48a26-7731-4046-bf22-78f0b8084eb9 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a d080c116-681a-494a-a928-45924109b49d +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a cdb8493c-e3b0-447f-b16b-e58dd64660f3 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a a6b084fb-ada7-480a-9adc-f180d4eb1e2a +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 990c21ab-433b-4920-873e-f9dfc7103d9d +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 922e2443-9cc5-421f-8310-9cd23f6e9f2d +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 8089a9ac-9e71-4487-a231-f720e4bc8d3e +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 6d47a2fe-3645-4c5c-bebe-1b822eff197f +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 67fc12fc-bb9f-40f1-9051-127a788b3081 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 634f0889-3a83-484a-bcc8-86f48e333c7b +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 4adcaf72-5241-4877-b61c-f34060576c50 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 374fd699-6b74-4500-9d60-2473c7e0364f +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 308eba53-29fa-489a-97dc-741b077841a7 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 21995497-0eb8-4c0b-8c23-e6a829f3d596 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 18ebf5ea-b0a2-4333-9e9c-40217de809ff +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 151e3048-66ad-4411-8753-677877e3bf0a +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 0af4a77e-892e-4b3f-9110-4c5224782250 +c3ec1f2e-1411-41e4-8370-acd5a8f68cf4 c3ec1f2e-1411-41e4-8370-acd5a8f68cf4 +c3ec1f2e-1411-41e4-8370-acd5a8f68cf4 87b04a97-1d33-4548-aec9-3d2856070702 +c3ec1f2e-1411-41e4-8370-acd5a8f68cf4 2bddd98c-86e3-4ae0-9dc6-87da16ab6267 +c57d51a7-45de-41b9-92fc-efd0634effaf fcc916dd-5e4f-4bf7-9aef-c906a36d7301 +c57d51a7-45de-41b9-92fc-efd0634effaf c57d51a7-45de-41b9-92fc-efd0634effaf +c57d51a7-45de-41b9-92fc-efd0634effaf ae7ddaef-4911-418c-8401-d978617e145b +c57d51a7-45de-41b9-92fc-efd0634effaf acb2329d-b1c3-45c3-ad28-91a09aa521e1 +c57d51a7-45de-41b9-92fc-efd0634effaf a89c0c42-c19f-4d7f-a15e-d04e043c92f6 +c57d51a7-45de-41b9-92fc-efd0634effaf 836b2e59-fb97-4014-ab0c-d5a5dc750969 +c57d51a7-45de-41b9-92fc-efd0634effaf 683714ad-5194-49e7-9b8f-4e306cf68ae1 +c57d51a7-45de-41b9-92fc-efd0634effaf 5f64131b-d410-449a-9de6-35415919fec5 +c57d51a7-45de-41b9-92fc-efd0634effaf 4f3d1e93-a28f-446e-91af-2baf0168b82b +c57d51a7-45de-41b9-92fc-efd0634effaf 1db1c71b-9eeb-4a98-abc1-eb699b38510c +c5c6eed5-aec6-4b8a-b689-adbb219c2267 fce20464-ff98-4051-8714-6b9584e52740 +c5c6eed5-aec6-4b8a-b689-adbb219c2267 f3eb0c38-2571-44e7-be39-732eb52cf1df +c5c6eed5-aec6-4b8a-b689-adbb219c2267 f3c2f842-6aa3-42c9-a0f3-895c40456868 +c5c6eed5-aec6-4b8a-b689-adbb219c2267 ede7745a-8f3e-4e20-9f16-33756be7ab6c +c5c6eed5-aec6-4b8a-b689-adbb219c2267 ca3e0486-fd6b-4a13-a741-3a47760b412d +c5c6eed5-aec6-4b8a-b689-adbb219c2267 c5c6eed5-aec6-4b8a-b689-adbb219c2267 +c5c6eed5-aec6-4b8a-b689-adbb219c2267 bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +c5c6eed5-aec6-4b8a-b689-adbb219c2267 a221198d-000e-497b-8b70-bb4d37f7bbe1 +c5c6eed5-aec6-4b8a-b689-adbb219c2267 a1228f20-7c50-4d3a-b88c-2d277ca79d79 +c5c6eed5-aec6-4b8a-b689-adbb219c2267 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +c5c6eed5-aec6-4b8a-b689-adbb219c2267 88b16960-bfff-41d0-81e4-9a4630834a00 +c5c6eed5-aec6-4b8a-b689-adbb219c2267 522e2abe-ad96-4d1a-b5a5-748faa997531 +c5c6eed5-aec6-4b8a-b689-adbb219c2267 4cfe72fe-21a0-4201-87b6-ef93695d2495 +c5c6eed5-aec6-4b8a-b689-adbb219c2267 498f4b18-bd4c-470d-9cde-2fca70ec8964 +c5c6eed5-aec6-4b8a-b689-adbb219c2267 202131ae-78bb-4104-89b0-fb90645760f6 +c5c6eed5-aec6-4b8a-b689-adbb219c2267 11dafd5c-85e7-4275-a147-89a63641e35e +c5c6eed5-aec6-4b8a-b689-adbb219c2267 02b993c0-b358-481c-b0d0-0767387feb9f +c892b1d7-7485-407d-8883-54fb7fecebc1 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +c892b1d7-7485-407d-8883-54fb7fecebc1 e8eba267-fecb-477e-9625-771fbcfc3cba +c892b1d7-7485-407d-8883-54fb7fecebc1 e8c41168-a093-40eb-8baa-6802d24f554a +c892b1d7-7485-407d-8883-54fb7fecebc1 e26ae29a-213a-4f79-98c2-cc81cf2f451d +c892b1d7-7485-407d-8883-54fb7fecebc1 e0dfbc08-45ad-4a81-b648-7e65c066f673 +c892b1d7-7485-407d-8883-54fb7fecebc1 d535b213-2abc-438f-aa6a-c06476d60b09 +c892b1d7-7485-407d-8883-54fb7fecebc1 c892b1d7-7485-407d-8883-54fb7fecebc1 +c892b1d7-7485-407d-8883-54fb7fecebc1 b6900c21-d310-4fb4-8b8f-176a09c91989 +c892b1d7-7485-407d-8883-54fb7fecebc1 b00df815-7528-4967-bfe2-311130d91c21 +c892b1d7-7485-407d-8883-54fb7fecebc1 add6d754-a075-4693-a33a-c061c9a368ff +c892b1d7-7485-407d-8883-54fb7fecebc1 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +c892b1d7-7485-407d-8883-54fb7fecebc1 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +c892b1d7-7485-407d-8883-54fb7fecebc1 8a1908f7-47cc-4f82-859c-781a193e9901 +c892b1d7-7485-407d-8883-54fb7fecebc1 8743e69b-17aa-44ff-b8fa-4f582665efc6 +c892b1d7-7485-407d-8883-54fb7fecebc1 721e4027-a080-40d8-bc4a-43cd33477611 +c892b1d7-7485-407d-8883-54fb7fecebc1 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +c892b1d7-7485-407d-8883-54fb7fecebc1 54c750aa-570a-4e8e-9a02-418781923e39 +c892b1d7-7485-407d-8883-54fb7fecebc1 50eac25a-3761-4de3-8a69-aae52e658300 +c892b1d7-7485-407d-8883-54fb7fecebc1 4a464bb5-9373-4f1b-9c03-053c64797114 +c892b1d7-7485-407d-8883-54fb7fecebc1 4506aa76-9d0d-40e4-85bc-5735f74522a0 +c892b1d7-7485-407d-8883-54fb7fecebc1 42a9a333-d09d-4b9e-af96-1f02af26bac3 +c892b1d7-7485-407d-8883-54fb7fecebc1 2fc75f11-2097-4e1e-8390-dbff3e844b7a +c892b1d7-7485-407d-8883-54fb7fecebc1 274a2e49-9170-4945-bca2-ab6ef4bded75 +c892b1d7-7485-407d-8883-54fb7fecebc1 0364073f-91d8-47a1-b8b0-107c6318a691 +c90aae7e-5704-4e13-8794-41ab377193bd e1c2ad9f-6f61-4f16-805a-2f405b63659a +c90aae7e-5704-4e13-8794-41ab377193bd d6988116-3c63-44f8-9f94-9e9d51cc5a37 +c90aae7e-5704-4e13-8794-41ab377193bd cfcbe34a-edc5-4442-bc05-5927fa00336b +c90aae7e-5704-4e13-8794-41ab377193bd c90aae7e-5704-4e13-8794-41ab377193bd +c90aae7e-5704-4e13-8794-41ab377193bd bf895c48-1d52-4780-8e9a-532d69356d28 +c90aae7e-5704-4e13-8794-41ab377193bd ae9e663e-8c15-43c9-8a88-52b61cbf07a9 +c90aae7e-5704-4e13-8794-41ab377193bd 56999896-e36b-4e63-a6b6-dbb85dfe1936 +c90aae7e-5704-4e13-8794-41ab377193bd 51241857-a7a4-4517-96e3-21f797581f89 +c90aae7e-5704-4e13-8794-41ab377193bd 4cd95073-6db3-4eb2-ac4b-0aacb182b352 +c90aae7e-5704-4e13-8794-41ab377193bd 4148ac36-1dcb-4e96-89cd-355e7e8f919b +c90aae7e-5704-4e13-8794-41ab377193bd 2e8eb256-84c9-499a-8bf6-bb39504373e1 +c90aae7e-5704-4e13-8794-41ab377193bd 07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 +ca3e0486-fd6b-4a13-a741-3a47760b412d fce20464-ff98-4051-8714-6b9584e52740 +ca3e0486-fd6b-4a13-a741-3a47760b412d f3eb0c38-2571-44e7-be39-732eb52cf1df +ca3e0486-fd6b-4a13-a741-3a47760b412d f3c2f842-6aa3-42c9-a0f3-895c40456868 +ca3e0486-fd6b-4a13-a741-3a47760b412d ede7745a-8f3e-4e20-9f16-33756be7ab6c +ca3e0486-fd6b-4a13-a741-3a47760b412d ca3e0486-fd6b-4a13-a741-3a47760b412d +ca3e0486-fd6b-4a13-a741-3a47760b412d c5c6eed5-aec6-4b8a-b689-adbb219c2267 +ca3e0486-fd6b-4a13-a741-3a47760b412d bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +ca3e0486-fd6b-4a13-a741-3a47760b412d a221198d-000e-497b-8b70-bb4d37f7bbe1 +ca3e0486-fd6b-4a13-a741-3a47760b412d a1228f20-7c50-4d3a-b88c-2d277ca79d79 +ca3e0486-fd6b-4a13-a741-3a47760b412d 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +ca3e0486-fd6b-4a13-a741-3a47760b412d 88b16960-bfff-41d0-81e4-9a4630834a00 +ca3e0486-fd6b-4a13-a741-3a47760b412d 522e2abe-ad96-4d1a-b5a5-748faa997531 +ca3e0486-fd6b-4a13-a741-3a47760b412d 4cfe72fe-21a0-4201-87b6-ef93695d2495 +ca3e0486-fd6b-4a13-a741-3a47760b412d 498f4b18-bd4c-470d-9cde-2fca70ec8964 +ca3e0486-fd6b-4a13-a741-3a47760b412d 202131ae-78bb-4104-89b0-fb90645760f6 +ca3e0486-fd6b-4a13-a741-3a47760b412d 11dafd5c-85e7-4275-a147-89a63641e35e +ca3e0486-fd6b-4a13-a741-3a47760b412d 02b993c0-b358-481c-b0d0-0767387feb9f +ca7f269d-3794-4994-8c46-d8e9f2aa6378 f29330d0-5b32-4f75-b558-a1c7f05c0b4a +ca7f269d-3794-4994-8c46-d8e9f2aa6378 d90676d2-ce74-4867-a00f-6dbffa7c00be +ca7f269d-3794-4994-8c46-d8e9f2aa6378 cc56af6d-25c6-480e-9767-da02a55551da +ca7f269d-3794-4994-8c46-d8e9f2aa6378 ca7f269d-3794-4994-8c46-d8e9f2aa6378 +ca7f269d-3794-4994-8c46-d8e9f2aa6378 baf48725-f0f9-4357-a71d-b1104910deae +ca7f269d-3794-4994-8c46-d8e9f2aa6378 9ed8703e-3b40-424c-9e98-b3b404051f99 +ca7f269d-3794-4994-8c46-d8e9f2aa6378 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +ca7f269d-3794-4994-8c46-d8e9f2aa6378 8be81657-8ba6-4ec5-8ff9-4809367096ea +ca7f269d-3794-4994-8c46-d8e9f2aa6378 7f410064-638a-4477-85c4-97fc2bb14a49 +ca7f269d-3794-4994-8c46-d8e9f2aa6378 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +ca7f269d-3794-4994-8c46-d8e9f2aa6378 5cedf18a-fc44-4c39-a80f-2106a0d76934 +ca7f269d-3794-4994-8c46-d8e9f2aa6378 4f342a71-dec3-403e-970b-e4c21b9eb98b +ca7f269d-3794-4994-8c46-d8e9f2aa6378 4db144d3-a596-4718-a50a-8ac9175ad386 +ca7f269d-3794-4994-8c46-d8e9f2aa6378 4215b5d0-bdd9-4222-a04a-81bb637d60af +ca7f269d-3794-4994-8c46-d8e9f2aa6378 29d4e919-2bd2-44e6-bb8a-380a780f60ff +ca7f269d-3794-4994-8c46-d8e9f2aa6378 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +ca7f269d-3794-4994-8c46-d8e9f2aa6378 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +ca7f269d-3794-4994-8c46-d8e9f2aa6378 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +ca7f269d-3794-4994-8c46-d8e9f2aa6378 102e16c5-4920-4dad-b142-0b280b2aacad +ca7f269d-3794-4994-8c46-d8e9f2aa6378 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +cc56af6d-25c6-480e-9767-da02a55551da f29330d0-5b32-4f75-b558-a1c7f05c0b4a +cc56af6d-25c6-480e-9767-da02a55551da d90676d2-ce74-4867-a00f-6dbffa7c00be +cc56af6d-25c6-480e-9767-da02a55551da cc56af6d-25c6-480e-9767-da02a55551da +cc56af6d-25c6-480e-9767-da02a55551da ca7f269d-3794-4994-8c46-d8e9f2aa6378 +cc56af6d-25c6-480e-9767-da02a55551da baf48725-f0f9-4357-a71d-b1104910deae +cc56af6d-25c6-480e-9767-da02a55551da 9ed8703e-3b40-424c-9e98-b3b404051f99 +cc56af6d-25c6-480e-9767-da02a55551da 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +cc56af6d-25c6-480e-9767-da02a55551da 8be81657-8ba6-4ec5-8ff9-4809367096ea +cc56af6d-25c6-480e-9767-da02a55551da 7f410064-638a-4477-85c4-97fc2bb14a49 +cc56af6d-25c6-480e-9767-da02a55551da 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +cc56af6d-25c6-480e-9767-da02a55551da 5cedf18a-fc44-4c39-a80f-2106a0d76934 +cc56af6d-25c6-480e-9767-da02a55551da 4f342a71-dec3-403e-970b-e4c21b9eb98b +cc56af6d-25c6-480e-9767-da02a55551da 4db144d3-a596-4718-a50a-8ac9175ad386 +cc56af6d-25c6-480e-9767-da02a55551da 4215b5d0-bdd9-4222-a04a-81bb637d60af +cc56af6d-25c6-480e-9767-da02a55551da 29d4e919-2bd2-44e6-bb8a-380a780f60ff +cc56af6d-25c6-480e-9767-da02a55551da 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +cc56af6d-25c6-480e-9767-da02a55551da 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +cc56af6d-25c6-480e-9767-da02a55551da 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +cc56af6d-25c6-480e-9767-da02a55551da 102e16c5-4920-4dad-b142-0b280b2aacad +cc56af6d-25c6-480e-9767-da02a55551da 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +cdb8493c-e3b0-447f-b16b-e58dd64660f3 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +cdb8493c-e3b0-447f-b16b-e58dd64660f3 ee24bf89-81a3-4259-a382-86917f3829d4 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 e363eb67-64c7-440f-93fd-5c8787c69a85 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 db79f75d-af3c-4f82-b4e5-9b5d26598eef +cdb8493c-e3b0-447f-b16b-e58dd64660f3 d7a48a26-7731-4046-bf22-78f0b8084eb9 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 d080c116-681a-494a-a928-45924109b49d +cdb8493c-e3b0-447f-b16b-e58dd64660f3 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +cdb8493c-e3b0-447f-b16b-e58dd64660f3 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +cdb8493c-e3b0-447f-b16b-e58dd64660f3 990c21ab-433b-4920-873e-f9dfc7103d9d +cdb8493c-e3b0-447f-b16b-e58dd64660f3 922e2443-9cc5-421f-8310-9cd23f6e9f2d +cdb8493c-e3b0-447f-b16b-e58dd64660f3 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 8089a9ac-9e71-4487-a231-f720e4bc8d3e +cdb8493c-e3b0-447f-b16b-e58dd64660f3 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +cdb8493c-e3b0-447f-b16b-e58dd64660f3 6d47a2fe-3645-4c5c-bebe-1b822eff197f +cdb8493c-e3b0-447f-b16b-e58dd64660f3 67fc12fc-bb9f-40f1-9051-127a788b3081 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 634f0889-3a83-484a-bcc8-86f48e333c7b +cdb8493c-e3b0-447f-b16b-e58dd64660f3 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 4adcaf72-5241-4877-b61c-f34060576c50 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +cdb8493c-e3b0-447f-b16b-e58dd64660f3 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 374fd699-6b74-4500-9d60-2473c7e0364f +cdb8493c-e3b0-447f-b16b-e58dd64660f3 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 308eba53-29fa-489a-97dc-741b077841a7 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +cdb8493c-e3b0-447f-b16b-e58dd64660f3 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 21995497-0eb8-4c0b-8c23-e6a829f3d596 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 18ebf5ea-b0a2-4333-9e9c-40217de809ff +cdb8493c-e3b0-447f-b16b-e58dd64660f3 151e3048-66ad-4411-8753-677877e3bf0a +cdb8493c-e3b0-447f-b16b-e58dd64660f3 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +cdb8493c-e3b0-447f-b16b-e58dd64660f3 0af4a77e-892e-4b3f-9110-4c5224782250 +cf67a8d4-48e2-4dc9-a521-6aabcff0330b f77a8561-5adc-452a-ac9a-76273b6a6678 +cf67a8d4-48e2-4dc9-a521-6aabcff0330b d79728e1-8834-4f4a-8edc-ce18aca18be6 +cf67a8d4-48e2-4dc9-a521-6aabcff0330b cf67a8d4-48e2-4dc9-a521-6aabcff0330b +cf67a8d4-48e2-4dc9-a521-6aabcff0330b bb1a7816-4798-4f58-9ce4-c5bd3af682a8 +cf67a8d4-48e2-4dc9-a521-6aabcff0330b b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 +cf67a8d4-48e2-4dc9-a521-6aabcff0330b a6cb423a-7571-46df-83e2-ccc6820adb36 +cf67a8d4-48e2-4dc9-a521-6aabcff0330b 7e7322a6-7912-4101-b3be-d134245a0626 +cf67a8d4-48e2-4dc9-a521-6aabcff0330b 777aa5f2-2a09-4aed-92ea-912694e28b48 +cf67a8d4-48e2-4dc9-a521-6aabcff0330b 6e187f47-91a1-4217-a185-31b6f01db225 +cf67a8d4-48e2-4dc9-a521-6aabcff0330b 62059efc-7607-41c9-a3ba-70948f6a8a1d +cf67a8d4-48e2-4dc9-a521-6aabcff0330b 2d2e07ec-e697-4384-a679-867e93740921 +cf67a8d4-48e2-4dc9-a521-6aabcff0330b 20fbcb6e-d793-4b05-8e29-8ff82572b0db +cf67a8d4-48e2-4dc9-a521-6aabcff0330b 1fd714a6-48b4-425a-99b3-ba5c1a995cce +cfcbe34a-edc5-4442-bc05-5927fa00336b e1c2ad9f-6f61-4f16-805a-2f405b63659a +cfcbe34a-edc5-4442-bc05-5927fa00336b d6988116-3c63-44f8-9f94-9e9d51cc5a37 +cfcbe34a-edc5-4442-bc05-5927fa00336b cfcbe34a-edc5-4442-bc05-5927fa00336b +cfcbe34a-edc5-4442-bc05-5927fa00336b c90aae7e-5704-4e13-8794-41ab377193bd +cfcbe34a-edc5-4442-bc05-5927fa00336b bf895c48-1d52-4780-8e9a-532d69356d28 +cfcbe34a-edc5-4442-bc05-5927fa00336b ae9e663e-8c15-43c9-8a88-52b61cbf07a9 +cfcbe34a-edc5-4442-bc05-5927fa00336b 56999896-e36b-4e63-a6b6-dbb85dfe1936 +cfcbe34a-edc5-4442-bc05-5927fa00336b 51241857-a7a4-4517-96e3-21f797581f89 +cfcbe34a-edc5-4442-bc05-5927fa00336b 4cd95073-6db3-4eb2-ac4b-0aacb182b352 +cfcbe34a-edc5-4442-bc05-5927fa00336b 4148ac36-1dcb-4e96-89cd-355e7e8f919b +cfcbe34a-edc5-4442-bc05-5927fa00336b 2e8eb256-84c9-499a-8bf6-bb39504373e1 +cfcbe34a-edc5-4442-bc05-5927fa00336b 07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 +d05461d6-13bf-402c-890d-db6404933f7c f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 +d05461d6-13bf-402c-890d-db6404933f7c f3aa491a-4dbd-4436-b674-18b2177dcf18 +d05461d6-13bf-402c-890d-db6404933f7c e1040d58-53bc-4467-8101-ca53b772cede +d05461d6-13bf-402c-890d-db6404933f7c d05461d6-13bf-402c-890d-db6404933f7c +d05461d6-13bf-402c-890d-db6404933f7c b7772635-1801-48e4-a442-f4aaa6860544 +d05461d6-13bf-402c-890d-db6404933f7c b07fb98d-2da4-423a-83b4-7a673de93096 +d05461d6-13bf-402c-890d-db6404933f7c 97b42d67-8691-433d-8a31-dada076162ae +d05461d6-13bf-402c-890d-db6404933f7c 720f7ed6-7b2e-41dc-a0e0-724a3332aa24 +d05461d6-13bf-402c-890d-db6404933f7c 4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 +d05461d6-13bf-402c-890d-db6404933f7c 2ffd6142-b75b-406f-bc06-cdb1c02eaefc +d05461d6-13bf-402c-890d-db6404933f7c 224e538d-3622-4f5e-b772-211ed358d8de +d05461d6-13bf-402c-890d-db6404933f7c 16a3408d-c927-4d02-a1ae-cad32419caab +d05461d6-13bf-402c-890d-db6404933f7c 04d88eb4-fb1b-4fa1-802a-5624f4c61b32 +d080c116-681a-494a-a928-45924109b49d fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +d080c116-681a-494a-a928-45924109b49d ee24bf89-81a3-4259-a382-86917f3829d4 +d080c116-681a-494a-a928-45924109b49d e363eb67-64c7-440f-93fd-5c8787c69a85 +d080c116-681a-494a-a928-45924109b49d dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +d080c116-681a-494a-a928-45924109b49d db79f75d-af3c-4f82-b4e5-9b5d26598eef +d080c116-681a-494a-a928-45924109b49d d7a48a26-7731-4046-bf22-78f0b8084eb9 +d080c116-681a-494a-a928-45924109b49d d080c116-681a-494a-a928-45924109b49d +d080c116-681a-494a-a928-45924109b49d cdb8493c-e3b0-447f-b16b-e58dd64660f3 +d080c116-681a-494a-a928-45924109b49d c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +d080c116-681a-494a-a928-45924109b49d a6b084fb-ada7-480a-9adc-f180d4eb1e2a +d080c116-681a-494a-a928-45924109b49d 990c21ab-433b-4920-873e-f9dfc7103d9d +d080c116-681a-494a-a928-45924109b49d 922e2443-9cc5-421f-8310-9cd23f6e9f2d +d080c116-681a-494a-a928-45924109b49d 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +d080c116-681a-494a-a928-45924109b49d 8089a9ac-9e71-4487-a231-f720e4bc8d3e +d080c116-681a-494a-a928-45924109b49d 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +d080c116-681a-494a-a928-45924109b49d 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +d080c116-681a-494a-a928-45924109b49d 6d47a2fe-3645-4c5c-bebe-1b822eff197f +d080c116-681a-494a-a928-45924109b49d 67fc12fc-bb9f-40f1-9051-127a788b3081 +d080c116-681a-494a-a928-45924109b49d 634f0889-3a83-484a-bcc8-86f48e333c7b +d080c116-681a-494a-a928-45924109b49d 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +d080c116-681a-494a-a928-45924109b49d 4adcaf72-5241-4877-b61c-f34060576c50 +d080c116-681a-494a-a928-45924109b49d 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +d080c116-681a-494a-a928-45924109b49d 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +d080c116-681a-494a-a928-45924109b49d 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +d080c116-681a-494a-a928-45924109b49d 374fd699-6b74-4500-9d60-2473c7e0364f +d080c116-681a-494a-a928-45924109b49d 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +d080c116-681a-494a-a928-45924109b49d 308eba53-29fa-489a-97dc-741b077841a7 +d080c116-681a-494a-a928-45924109b49d 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +d080c116-681a-494a-a928-45924109b49d 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +d080c116-681a-494a-a928-45924109b49d 21995497-0eb8-4c0b-8c23-e6a829f3d596 +d080c116-681a-494a-a928-45924109b49d 18ebf5ea-b0a2-4333-9e9c-40217de809ff +d080c116-681a-494a-a928-45924109b49d 151e3048-66ad-4411-8753-677877e3bf0a +d080c116-681a-494a-a928-45924109b49d 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +d080c116-681a-494a-a928-45924109b49d 0af4a77e-892e-4b3f-9110-4c5224782250 +d535b213-2abc-438f-aa6a-c06476d60b09 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +d535b213-2abc-438f-aa6a-c06476d60b09 e8eba267-fecb-477e-9625-771fbcfc3cba +d535b213-2abc-438f-aa6a-c06476d60b09 e8c41168-a093-40eb-8baa-6802d24f554a +d535b213-2abc-438f-aa6a-c06476d60b09 e26ae29a-213a-4f79-98c2-cc81cf2f451d +d535b213-2abc-438f-aa6a-c06476d60b09 e0dfbc08-45ad-4a81-b648-7e65c066f673 +d535b213-2abc-438f-aa6a-c06476d60b09 d535b213-2abc-438f-aa6a-c06476d60b09 +d535b213-2abc-438f-aa6a-c06476d60b09 c892b1d7-7485-407d-8883-54fb7fecebc1 +d535b213-2abc-438f-aa6a-c06476d60b09 b6900c21-d310-4fb4-8b8f-176a09c91989 +d535b213-2abc-438f-aa6a-c06476d60b09 b00df815-7528-4967-bfe2-311130d91c21 +d535b213-2abc-438f-aa6a-c06476d60b09 add6d754-a075-4693-a33a-c061c9a368ff +d535b213-2abc-438f-aa6a-c06476d60b09 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +d535b213-2abc-438f-aa6a-c06476d60b09 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +d535b213-2abc-438f-aa6a-c06476d60b09 8a1908f7-47cc-4f82-859c-781a193e9901 +d535b213-2abc-438f-aa6a-c06476d60b09 8743e69b-17aa-44ff-b8fa-4f582665efc6 +d535b213-2abc-438f-aa6a-c06476d60b09 721e4027-a080-40d8-bc4a-43cd33477611 +d535b213-2abc-438f-aa6a-c06476d60b09 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +d535b213-2abc-438f-aa6a-c06476d60b09 54c750aa-570a-4e8e-9a02-418781923e39 +d535b213-2abc-438f-aa6a-c06476d60b09 50eac25a-3761-4de3-8a69-aae52e658300 +d535b213-2abc-438f-aa6a-c06476d60b09 4a464bb5-9373-4f1b-9c03-053c64797114 +d535b213-2abc-438f-aa6a-c06476d60b09 4506aa76-9d0d-40e4-85bc-5735f74522a0 +d535b213-2abc-438f-aa6a-c06476d60b09 42a9a333-d09d-4b9e-af96-1f02af26bac3 +d535b213-2abc-438f-aa6a-c06476d60b09 2fc75f11-2097-4e1e-8390-dbff3e844b7a +d535b213-2abc-438f-aa6a-c06476d60b09 274a2e49-9170-4945-bca2-ab6ef4bded75 +d535b213-2abc-438f-aa6a-c06476d60b09 0364073f-91d8-47a1-b8b0-107c6318a691 +d6988116-3c63-44f8-9f94-9e9d51cc5a37 e1c2ad9f-6f61-4f16-805a-2f405b63659a +d6988116-3c63-44f8-9f94-9e9d51cc5a37 d6988116-3c63-44f8-9f94-9e9d51cc5a37 +d6988116-3c63-44f8-9f94-9e9d51cc5a37 cfcbe34a-edc5-4442-bc05-5927fa00336b +d6988116-3c63-44f8-9f94-9e9d51cc5a37 c90aae7e-5704-4e13-8794-41ab377193bd +d6988116-3c63-44f8-9f94-9e9d51cc5a37 bf895c48-1d52-4780-8e9a-532d69356d28 +d6988116-3c63-44f8-9f94-9e9d51cc5a37 ae9e663e-8c15-43c9-8a88-52b61cbf07a9 +d6988116-3c63-44f8-9f94-9e9d51cc5a37 56999896-e36b-4e63-a6b6-dbb85dfe1936 +d6988116-3c63-44f8-9f94-9e9d51cc5a37 51241857-a7a4-4517-96e3-21f797581f89 +d6988116-3c63-44f8-9f94-9e9d51cc5a37 4cd95073-6db3-4eb2-ac4b-0aacb182b352 +d6988116-3c63-44f8-9f94-9e9d51cc5a37 4148ac36-1dcb-4e96-89cd-355e7e8f919b +d6988116-3c63-44f8-9f94-9e9d51cc5a37 2e8eb256-84c9-499a-8bf6-bb39504373e1 +d6988116-3c63-44f8-9f94-9e9d51cc5a37 07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 +d79728e1-8834-4f4a-8edc-ce18aca18be6 f77a8561-5adc-452a-ac9a-76273b6a6678 +d79728e1-8834-4f4a-8edc-ce18aca18be6 d79728e1-8834-4f4a-8edc-ce18aca18be6 +d79728e1-8834-4f4a-8edc-ce18aca18be6 cf67a8d4-48e2-4dc9-a521-6aabcff0330b +d79728e1-8834-4f4a-8edc-ce18aca18be6 bb1a7816-4798-4f58-9ce4-c5bd3af682a8 +d79728e1-8834-4f4a-8edc-ce18aca18be6 b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 +d79728e1-8834-4f4a-8edc-ce18aca18be6 a6cb423a-7571-46df-83e2-ccc6820adb36 +d79728e1-8834-4f4a-8edc-ce18aca18be6 7e7322a6-7912-4101-b3be-d134245a0626 +d79728e1-8834-4f4a-8edc-ce18aca18be6 777aa5f2-2a09-4aed-92ea-912694e28b48 +d79728e1-8834-4f4a-8edc-ce18aca18be6 6e187f47-91a1-4217-a185-31b6f01db225 +d79728e1-8834-4f4a-8edc-ce18aca18be6 62059efc-7607-41c9-a3ba-70948f6a8a1d +d79728e1-8834-4f4a-8edc-ce18aca18be6 2d2e07ec-e697-4384-a679-867e93740921 +d79728e1-8834-4f4a-8edc-ce18aca18be6 20fbcb6e-d793-4b05-8e29-8ff82572b0db +d79728e1-8834-4f4a-8edc-ce18aca18be6 1fd714a6-48b4-425a-99b3-ba5c1a995cce +d7a48a26-7731-4046-bf22-78f0b8084eb9 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +d7a48a26-7731-4046-bf22-78f0b8084eb9 ee24bf89-81a3-4259-a382-86917f3829d4 +d7a48a26-7731-4046-bf22-78f0b8084eb9 e363eb67-64c7-440f-93fd-5c8787c69a85 +d7a48a26-7731-4046-bf22-78f0b8084eb9 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +d7a48a26-7731-4046-bf22-78f0b8084eb9 db79f75d-af3c-4f82-b4e5-9b5d26598eef +d7a48a26-7731-4046-bf22-78f0b8084eb9 d7a48a26-7731-4046-bf22-78f0b8084eb9 +d7a48a26-7731-4046-bf22-78f0b8084eb9 d080c116-681a-494a-a928-45924109b49d +d7a48a26-7731-4046-bf22-78f0b8084eb9 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +d7a48a26-7731-4046-bf22-78f0b8084eb9 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +d7a48a26-7731-4046-bf22-78f0b8084eb9 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +d7a48a26-7731-4046-bf22-78f0b8084eb9 990c21ab-433b-4920-873e-f9dfc7103d9d +d7a48a26-7731-4046-bf22-78f0b8084eb9 922e2443-9cc5-421f-8310-9cd23f6e9f2d +d7a48a26-7731-4046-bf22-78f0b8084eb9 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +d7a48a26-7731-4046-bf22-78f0b8084eb9 8089a9ac-9e71-4487-a231-f720e4bc8d3e +d7a48a26-7731-4046-bf22-78f0b8084eb9 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +d7a48a26-7731-4046-bf22-78f0b8084eb9 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +d7a48a26-7731-4046-bf22-78f0b8084eb9 6d47a2fe-3645-4c5c-bebe-1b822eff197f +d7a48a26-7731-4046-bf22-78f0b8084eb9 67fc12fc-bb9f-40f1-9051-127a788b3081 +d7a48a26-7731-4046-bf22-78f0b8084eb9 634f0889-3a83-484a-bcc8-86f48e333c7b +d7a48a26-7731-4046-bf22-78f0b8084eb9 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +d7a48a26-7731-4046-bf22-78f0b8084eb9 4adcaf72-5241-4877-b61c-f34060576c50 +d7a48a26-7731-4046-bf22-78f0b8084eb9 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +d7a48a26-7731-4046-bf22-78f0b8084eb9 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +d7a48a26-7731-4046-bf22-78f0b8084eb9 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +d7a48a26-7731-4046-bf22-78f0b8084eb9 374fd699-6b74-4500-9d60-2473c7e0364f +d7a48a26-7731-4046-bf22-78f0b8084eb9 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +d7a48a26-7731-4046-bf22-78f0b8084eb9 308eba53-29fa-489a-97dc-741b077841a7 +d7a48a26-7731-4046-bf22-78f0b8084eb9 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +d7a48a26-7731-4046-bf22-78f0b8084eb9 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +d7a48a26-7731-4046-bf22-78f0b8084eb9 21995497-0eb8-4c0b-8c23-e6a829f3d596 +d7a48a26-7731-4046-bf22-78f0b8084eb9 18ebf5ea-b0a2-4333-9e9c-40217de809ff +d7a48a26-7731-4046-bf22-78f0b8084eb9 151e3048-66ad-4411-8753-677877e3bf0a +d7a48a26-7731-4046-bf22-78f0b8084eb9 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +d7a48a26-7731-4046-bf22-78f0b8084eb9 0af4a77e-892e-4b3f-9110-4c5224782250 +d90676d2-ce74-4867-a00f-6dbffa7c00be f29330d0-5b32-4f75-b558-a1c7f05c0b4a +d90676d2-ce74-4867-a00f-6dbffa7c00be d90676d2-ce74-4867-a00f-6dbffa7c00be +d90676d2-ce74-4867-a00f-6dbffa7c00be cc56af6d-25c6-480e-9767-da02a55551da +d90676d2-ce74-4867-a00f-6dbffa7c00be ca7f269d-3794-4994-8c46-d8e9f2aa6378 +d90676d2-ce74-4867-a00f-6dbffa7c00be baf48725-f0f9-4357-a71d-b1104910deae +d90676d2-ce74-4867-a00f-6dbffa7c00be 9ed8703e-3b40-424c-9e98-b3b404051f99 +d90676d2-ce74-4867-a00f-6dbffa7c00be 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +d90676d2-ce74-4867-a00f-6dbffa7c00be 8be81657-8ba6-4ec5-8ff9-4809367096ea +d90676d2-ce74-4867-a00f-6dbffa7c00be 7f410064-638a-4477-85c4-97fc2bb14a49 +d90676d2-ce74-4867-a00f-6dbffa7c00be 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +d90676d2-ce74-4867-a00f-6dbffa7c00be 5cedf18a-fc44-4c39-a80f-2106a0d76934 +d90676d2-ce74-4867-a00f-6dbffa7c00be 4f342a71-dec3-403e-970b-e4c21b9eb98b +d90676d2-ce74-4867-a00f-6dbffa7c00be 4db144d3-a596-4718-a50a-8ac9175ad386 +d90676d2-ce74-4867-a00f-6dbffa7c00be 4215b5d0-bdd9-4222-a04a-81bb637d60af +d90676d2-ce74-4867-a00f-6dbffa7c00be 29d4e919-2bd2-44e6-bb8a-380a780f60ff +d90676d2-ce74-4867-a00f-6dbffa7c00be 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +d90676d2-ce74-4867-a00f-6dbffa7c00be 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +d90676d2-ce74-4867-a00f-6dbffa7c00be 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +d90676d2-ce74-4867-a00f-6dbffa7c00be 102e16c5-4920-4dad-b142-0b280b2aacad +d90676d2-ce74-4867-a00f-6dbffa7c00be 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2 d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2 +d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2 395019b6-84e8-4919-8b8b-ac64e4a6eade +db79f75d-af3c-4f82-b4e5-9b5d26598eef fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +db79f75d-af3c-4f82-b4e5-9b5d26598eef ee24bf89-81a3-4259-a382-86917f3829d4 +db79f75d-af3c-4f82-b4e5-9b5d26598eef e363eb67-64c7-440f-93fd-5c8787c69a85 +db79f75d-af3c-4f82-b4e5-9b5d26598eef dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +db79f75d-af3c-4f82-b4e5-9b5d26598eef db79f75d-af3c-4f82-b4e5-9b5d26598eef +db79f75d-af3c-4f82-b4e5-9b5d26598eef d7a48a26-7731-4046-bf22-78f0b8084eb9 +db79f75d-af3c-4f82-b4e5-9b5d26598eef d080c116-681a-494a-a928-45924109b49d +db79f75d-af3c-4f82-b4e5-9b5d26598eef cdb8493c-e3b0-447f-b16b-e58dd64660f3 +db79f75d-af3c-4f82-b4e5-9b5d26598eef c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +db79f75d-af3c-4f82-b4e5-9b5d26598eef a6b084fb-ada7-480a-9adc-f180d4eb1e2a +db79f75d-af3c-4f82-b4e5-9b5d26598eef 990c21ab-433b-4920-873e-f9dfc7103d9d +db79f75d-af3c-4f82-b4e5-9b5d26598eef 922e2443-9cc5-421f-8310-9cd23f6e9f2d +db79f75d-af3c-4f82-b4e5-9b5d26598eef 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +db79f75d-af3c-4f82-b4e5-9b5d26598eef 8089a9ac-9e71-4487-a231-f720e4bc8d3e +db79f75d-af3c-4f82-b4e5-9b5d26598eef 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +db79f75d-af3c-4f82-b4e5-9b5d26598eef 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +db79f75d-af3c-4f82-b4e5-9b5d26598eef 6d47a2fe-3645-4c5c-bebe-1b822eff197f +db79f75d-af3c-4f82-b4e5-9b5d26598eef 67fc12fc-bb9f-40f1-9051-127a788b3081 +db79f75d-af3c-4f82-b4e5-9b5d26598eef 634f0889-3a83-484a-bcc8-86f48e333c7b +db79f75d-af3c-4f82-b4e5-9b5d26598eef 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +db79f75d-af3c-4f82-b4e5-9b5d26598eef 4adcaf72-5241-4877-b61c-f34060576c50 +db79f75d-af3c-4f82-b4e5-9b5d26598eef 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +db79f75d-af3c-4f82-b4e5-9b5d26598eef 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +db79f75d-af3c-4f82-b4e5-9b5d26598eef 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +db79f75d-af3c-4f82-b4e5-9b5d26598eef 374fd699-6b74-4500-9d60-2473c7e0364f +db79f75d-af3c-4f82-b4e5-9b5d26598eef 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +db79f75d-af3c-4f82-b4e5-9b5d26598eef 308eba53-29fa-489a-97dc-741b077841a7 +db79f75d-af3c-4f82-b4e5-9b5d26598eef 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +db79f75d-af3c-4f82-b4e5-9b5d26598eef 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +db79f75d-af3c-4f82-b4e5-9b5d26598eef 21995497-0eb8-4c0b-8c23-e6a829f3d596 +db79f75d-af3c-4f82-b4e5-9b5d26598eef 18ebf5ea-b0a2-4333-9e9c-40217de809ff +db79f75d-af3c-4f82-b4e5-9b5d26598eef 151e3048-66ad-4411-8753-677877e3bf0a +db79f75d-af3c-4f82-b4e5-9b5d26598eef 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +db79f75d-af3c-4f82-b4e5-9b5d26598eef 0af4a77e-892e-4b3f-9110-4c5224782250 +def5fd23-2b74-4c64-85e9-fac27e626bb7 def5fd23-2b74-4c64-85e9-fac27e626bb7 +def5fd23-2b74-4c64-85e9-fac27e626bb7 96b2282d-1384-4cfb-9958-f009fb501626 +def5fd23-2b74-4c64-85e9-fac27e626bb7 17bcf2ea-d921-4715-91c5-6b15226b33d3 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 ee24bf89-81a3-4259-a382-86917f3829d4 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 e363eb67-64c7-440f-93fd-5c8787c69a85 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 db79f75d-af3c-4f82-b4e5-9b5d26598eef +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 d7a48a26-7731-4046-bf22-78f0b8084eb9 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 d080c116-681a-494a-a928-45924109b49d +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 990c21ab-433b-4920-873e-f9dfc7103d9d +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 922e2443-9cc5-421f-8310-9cd23f6e9f2d +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 8089a9ac-9e71-4487-a231-f720e4bc8d3e +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 6d47a2fe-3645-4c5c-bebe-1b822eff197f +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 67fc12fc-bb9f-40f1-9051-127a788b3081 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 634f0889-3a83-484a-bcc8-86f48e333c7b +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 4adcaf72-5241-4877-b61c-f34060576c50 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 374fd699-6b74-4500-9d60-2473c7e0364f +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 308eba53-29fa-489a-97dc-741b077841a7 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 21995497-0eb8-4c0b-8c23-e6a829f3d596 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 18ebf5ea-b0a2-4333-9e9c-40217de809ff +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 151e3048-66ad-4411-8753-677877e3bf0a +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 0af4a77e-892e-4b3f-9110-4c5224782250 +e0dfbc08-45ad-4a81-b648-7e65c066f673 f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +e0dfbc08-45ad-4a81-b648-7e65c066f673 e8eba267-fecb-477e-9625-771fbcfc3cba +e0dfbc08-45ad-4a81-b648-7e65c066f673 e8c41168-a093-40eb-8baa-6802d24f554a +e0dfbc08-45ad-4a81-b648-7e65c066f673 e26ae29a-213a-4f79-98c2-cc81cf2f451d +e0dfbc08-45ad-4a81-b648-7e65c066f673 e0dfbc08-45ad-4a81-b648-7e65c066f673 +e0dfbc08-45ad-4a81-b648-7e65c066f673 d535b213-2abc-438f-aa6a-c06476d60b09 +e0dfbc08-45ad-4a81-b648-7e65c066f673 c892b1d7-7485-407d-8883-54fb7fecebc1 +e0dfbc08-45ad-4a81-b648-7e65c066f673 b6900c21-d310-4fb4-8b8f-176a09c91989 +e0dfbc08-45ad-4a81-b648-7e65c066f673 b00df815-7528-4967-bfe2-311130d91c21 +e0dfbc08-45ad-4a81-b648-7e65c066f673 add6d754-a075-4693-a33a-c061c9a368ff +e0dfbc08-45ad-4a81-b648-7e65c066f673 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +e0dfbc08-45ad-4a81-b648-7e65c066f673 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +e0dfbc08-45ad-4a81-b648-7e65c066f673 8a1908f7-47cc-4f82-859c-781a193e9901 +e0dfbc08-45ad-4a81-b648-7e65c066f673 8743e69b-17aa-44ff-b8fa-4f582665efc6 +e0dfbc08-45ad-4a81-b648-7e65c066f673 721e4027-a080-40d8-bc4a-43cd33477611 +e0dfbc08-45ad-4a81-b648-7e65c066f673 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +e0dfbc08-45ad-4a81-b648-7e65c066f673 54c750aa-570a-4e8e-9a02-418781923e39 +e0dfbc08-45ad-4a81-b648-7e65c066f673 50eac25a-3761-4de3-8a69-aae52e658300 +e0dfbc08-45ad-4a81-b648-7e65c066f673 4a464bb5-9373-4f1b-9c03-053c64797114 +e0dfbc08-45ad-4a81-b648-7e65c066f673 4506aa76-9d0d-40e4-85bc-5735f74522a0 +e0dfbc08-45ad-4a81-b648-7e65c066f673 42a9a333-d09d-4b9e-af96-1f02af26bac3 +e0dfbc08-45ad-4a81-b648-7e65c066f673 2fc75f11-2097-4e1e-8390-dbff3e844b7a +e0dfbc08-45ad-4a81-b648-7e65c066f673 274a2e49-9170-4945-bca2-ab6ef4bded75 +e0dfbc08-45ad-4a81-b648-7e65c066f673 0364073f-91d8-47a1-b8b0-107c6318a691 +e1040d58-53bc-4467-8101-ca53b772cede f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 +e1040d58-53bc-4467-8101-ca53b772cede f3aa491a-4dbd-4436-b674-18b2177dcf18 +e1040d58-53bc-4467-8101-ca53b772cede e1040d58-53bc-4467-8101-ca53b772cede +e1040d58-53bc-4467-8101-ca53b772cede d05461d6-13bf-402c-890d-db6404933f7c +e1040d58-53bc-4467-8101-ca53b772cede b7772635-1801-48e4-a442-f4aaa6860544 +e1040d58-53bc-4467-8101-ca53b772cede b07fb98d-2da4-423a-83b4-7a673de93096 +e1040d58-53bc-4467-8101-ca53b772cede 97b42d67-8691-433d-8a31-dada076162ae +e1040d58-53bc-4467-8101-ca53b772cede 720f7ed6-7b2e-41dc-a0e0-724a3332aa24 +e1040d58-53bc-4467-8101-ca53b772cede 4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 +e1040d58-53bc-4467-8101-ca53b772cede 2ffd6142-b75b-406f-bc06-cdb1c02eaefc +e1040d58-53bc-4467-8101-ca53b772cede 224e538d-3622-4f5e-b772-211ed358d8de +e1040d58-53bc-4467-8101-ca53b772cede 16a3408d-c927-4d02-a1ae-cad32419caab +e1040d58-53bc-4467-8101-ca53b772cede 04d88eb4-fb1b-4fa1-802a-5624f4c61b32 +e1c2ad9f-6f61-4f16-805a-2f405b63659a e1c2ad9f-6f61-4f16-805a-2f405b63659a +e1c2ad9f-6f61-4f16-805a-2f405b63659a d6988116-3c63-44f8-9f94-9e9d51cc5a37 +e1c2ad9f-6f61-4f16-805a-2f405b63659a cfcbe34a-edc5-4442-bc05-5927fa00336b +e1c2ad9f-6f61-4f16-805a-2f405b63659a c90aae7e-5704-4e13-8794-41ab377193bd +e1c2ad9f-6f61-4f16-805a-2f405b63659a bf895c48-1d52-4780-8e9a-532d69356d28 +e1c2ad9f-6f61-4f16-805a-2f405b63659a ae9e663e-8c15-43c9-8a88-52b61cbf07a9 +e1c2ad9f-6f61-4f16-805a-2f405b63659a 56999896-e36b-4e63-a6b6-dbb85dfe1936 +e1c2ad9f-6f61-4f16-805a-2f405b63659a 51241857-a7a4-4517-96e3-21f797581f89 +e1c2ad9f-6f61-4f16-805a-2f405b63659a 4cd95073-6db3-4eb2-ac4b-0aacb182b352 +e1c2ad9f-6f61-4f16-805a-2f405b63659a 4148ac36-1dcb-4e96-89cd-355e7e8f919b +e1c2ad9f-6f61-4f16-805a-2f405b63659a 2e8eb256-84c9-499a-8bf6-bb39504373e1 +e1c2ad9f-6f61-4f16-805a-2f405b63659a 07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 +e2387a81-5ff5-4daf-b2e8-881998d0d917 fc771883-3bd6-46d9-9626-a130e1b902de +e2387a81-5ff5-4daf-b2e8-881998d0d917 e2387a81-5ff5-4daf-b2e8-881998d0d917 +e2387a81-5ff5-4daf-b2e8-881998d0d917 b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa +e2387a81-5ff5-4daf-b2e8-881998d0d917 b1d4dc41-07ae-44db-ae17-1df86c5a62cf +e2387a81-5ff5-4daf-b2e8-881998d0d917 67e5cf95-236b-4f75-abc5-034ca2966448 +e2387a81-5ff5-4daf-b2e8-881998d0d917 48cc2275-a478-4ade-b076-cf38e1d16017 +e2387a81-5ff5-4daf-b2e8-881998d0d917 41d26b1c-57b9-408c-b955-bf0ee1db4809 +e2387a81-5ff5-4daf-b2e8-881998d0d917 3ddf46fe-b5be-456d-810f-ea6a7402236d +e2387a81-5ff5-4daf-b2e8-881998d0d917 3397a796-894d-409c-97de-a9e6f3f88198 +e2387a81-5ff5-4daf-b2e8-881998d0d917 19502b5a-2031-487d-9d9c-2001f959408b +e26ae29a-213a-4f79-98c2-cc81cf2f451d f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +e26ae29a-213a-4f79-98c2-cc81cf2f451d e8eba267-fecb-477e-9625-771fbcfc3cba +e26ae29a-213a-4f79-98c2-cc81cf2f451d e8c41168-a093-40eb-8baa-6802d24f554a +e26ae29a-213a-4f79-98c2-cc81cf2f451d e26ae29a-213a-4f79-98c2-cc81cf2f451d +e26ae29a-213a-4f79-98c2-cc81cf2f451d e0dfbc08-45ad-4a81-b648-7e65c066f673 +e26ae29a-213a-4f79-98c2-cc81cf2f451d d535b213-2abc-438f-aa6a-c06476d60b09 +e26ae29a-213a-4f79-98c2-cc81cf2f451d c892b1d7-7485-407d-8883-54fb7fecebc1 +e26ae29a-213a-4f79-98c2-cc81cf2f451d b6900c21-d310-4fb4-8b8f-176a09c91989 +e26ae29a-213a-4f79-98c2-cc81cf2f451d b00df815-7528-4967-bfe2-311130d91c21 +e26ae29a-213a-4f79-98c2-cc81cf2f451d add6d754-a075-4693-a33a-c061c9a368ff +e26ae29a-213a-4f79-98c2-cc81cf2f451d 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +e26ae29a-213a-4f79-98c2-cc81cf2f451d 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +e26ae29a-213a-4f79-98c2-cc81cf2f451d 8a1908f7-47cc-4f82-859c-781a193e9901 +e26ae29a-213a-4f79-98c2-cc81cf2f451d 8743e69b-17aa-44ff-b8fa-4f582665efc6 +e26ae29a-213a-4f79-98c2-cc81cf2f451d 721e4027-a080-40d8-bc4a-43cd33477611 +e26ae29a-213a-4f79-98c2-cc81cf2f451d 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +e26ae29a-213a-4f79-98c2-cc81cf2f451d 54c750aa-570a-4e8e-9a02-418781923e39 +e26ae29a-213a-4f79-98c2-cc81cf2f451d 50eac25a-3761-4de3-8a69-aae52e658300 +e26ae29a-213a-4f79-98c2-cc81cf2f451d 4a464bb5-9373-4f1b-9c03-053c64797114 +e26ae29a-213a-4f79-98c2-cc81cf2f451d 4506aa76-9d0d-40e4-85bc-5735f74522a0 +e26ae29a-213a-4f79-98c2-cc81cf2f451d 42a9a333-d09d-4b9e-af96-1f02af26bac3 +e26ae29a-213a-4f79-98c2-cc81cf2f451d 2fc75f11-2097-4e1e-8390-dbff3e844b7a +e26ae29a-213a-4f79-98c2-cc81cf2f451d 274a2e49-9170-4945-bca2-ab6ef4bded75 +e26ae29a-213a-4f79-98c2-cc81cf2f451d 0364073f-91d8-47a1-b8b0-107c6318a691 +e363eb67-64c7-440f-93fd-5c8787c69a85 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +e363eb67-64c7-440f-93fd-5c8787c69a85 ee24bf89-81a3-4259-a382-86917f3829d4 +e363eb67-64c7-440f-93fd-5c8787c69a85 e363eb67-64c7-440f-93fd-5c8787c69a85 +e363eb67-64c7-440f-93fd-5c8787c69a85 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +e363eb67-64c7-440f-93fd-5c8787c69a85 db79f75d-af3c-4f82-b4e5-9b5d26598eef +e363eb67-64c7-440f-93fd-5c8787c69a85 d7a48a26-7731-4046-bf22-78f0b8084eb9 +e363eb67-64c7-440f-93fd-5c8787c69a85 d080c116-681a-494a-a928-45924109b49d +e363eb67-64c7-440f-93fd-5c8787c69a85 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +e363eb67-64c7-440f-93fd-5c8787c69a85 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +e363eb67-64c7-440f-93fd-5c8787c69a85 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +e363eb67-64c7-440f-93fd-5c8787c69a85 990c21ab-433b-4920-873e-f9dfc7103d9d +e363eb67-64c7-440f-93fd-5c8787c69a85 922e2443-9cc5-421f-8310-9cd23f6e9f2d +e363eb67-64c7-440f-93fd-5c8787c69a85 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +e363eb67-64c7-440f-93fd-5c8787c69a85 8089a9ac-9e71-4487-a231-f720e4bc8d3e +e363eb67-64c7-440f-93fd-5c8787c69a85 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +e363eb67-64c7-440f-93fd-5c8787c69a85 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +e363eb67-64c7-440f-93fd-5c8787c69a85 6d47a2fe-3645-4c5c-bebe-1b822eff197f +e363eb67-64c7-440f-93fd-5c8787c69a85 67fc12fc-bb9f-40f1-9051-127a788b3081 +e363eb67-64c7-440f-93fd-5c8787c69a85 634f0889-3a83-484a-bcc8-86f48e333c7b +e363eb67-64c7-440f-93fd-5c8787c69a85 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +e363eb67-64c7-440f-93fd-5c8787c69a85 4adcaf72-5241-4877-b61c-f34060576c50 +e363eb67-64c7-440f-93fd-5c8787c69a85 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +e363eb67-64c7-440f-93fd-5c8787c69a85 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +e363eb67-64c7-440f-93fd-5c8787c69a85 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +e363eb67-64c7-440f-93fd-5c8787c69a85 374fd699-6b74-4500-9d60-2473c7e0364f +e363eb67-64c7-440f-93fd-5c8787c69a85 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +e363eb67-64c7-440f-93fd-5c8787c69a85 308eba53-29fa-489a-97dc-741b077841a7 +e363eb67-64c7-440f-93fd-5c8787c69a85 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +e363eb67-64c7-440f-93fd-5c8787c69a85 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +e363eb67-64c7-440f-93fd-5c8787c69a85 21995497-0eb8-4c0b-8c23-e6a829f3d596 +e363eb67-64c7-440f-93fd-5c8787c69a85 18ebf5ea-b0a2-4333-9e9c-40217de809ff +e363eb67-64c7-440f-93fd-5c8787c69a85 151e3048-66ad-4411-8753-677877e3bf0a +e363eb67-64c7-440f-93fd-5c8787c69a85 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +e363eb67-64c7-440f-93fd-5c8787c69a85 0af4a77e-892e-4b3f-9110-4c5224782250 +e4358f28-62a8-4e06-b2bd-cb00d3310349 fcab9efe-fb05-42d6-b366-ce6db1cc4093 +e4358f28-62a8-4e06-b2bd-cb00d3310349 f1293c5e-c997-4ba9-8bfd-438e7a840dc8 +e4358f28-62a8-4e06-b2bd-cb00d3310349 e4358f28-62a8-4e06-b2bd-cb00d3310349 +e4358f28-62a8-4e06-b2bd-cb00d3310349 8f76d729-9f74-4cfd-be2a-8b033b568e18 +e4358f28-62a8-4e06-b2bd-cb00d3310349 39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 +e4358f28-62a8-4e06-b2bd-cb00d3310349 31d4c712-75b1-4ecb-9d1a-ccc4b8453826 +e4358f28-62a8-4e06-b2bd-cb00d3310349 24c4dd5b-748b-4165-a0cc-2867b76c2dc2 +e4358f28-62a8-4e06-b2bd-cb00d3310349 16d280a2-c61f-487f-9034-22e884158969 +e4358f28-62a8-4e06-b2bd-cb00d3310349 0134901a-4a69-4b39-92ea-d6f48ec83c6e +e7f68d35-ae55-4fa4-b0be-0672a5a7186a f883f2ca-d523-4c9f-86b2-0add137901de +e7f68d35-ae55-4fa4-b0be-0672a5a7186a f28931e5-1f35-4f9f-a02e-78398735ea67 +e7f68d35-ae55-4fa4-b0be-0672a5a7186a e7f68d35-ae55-4fa4-b0be-0672a5a7186a +e7f68d35-ae55-4fa4-b0be-0672a5a7186a 85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b +e7f68d35-ae55-4fa4-b0be-0672a5a7186a 7ac56d5a-32a7-4b40-a956-356e3006faed +e7f68d35-ae55-4fa4-b0be-0672a5a7186a 72788e7f-1bf1-40b5-a1f1-27c669dc7278 +e7f68d35-ae55-4fa4-b0be-0672a5a7186a 6c84348c-5065-4e89-9412-bfda023683f2 +e7f68d35-ae55-4fa4-b0be-0672a5a7186a 5abe533b-ae5f-47ad-8746-f60caf7483c0 +e7f68d35-ae55-4fa4-b0be-0672a5a7186a 4f14ca7f-5332-4263-a7b2-f0a838380309 +e7f68d35-ae55-4fa4-b0be-0672a5a7186a 359cb842-c25b-429f-ba9b-d52f171e5631 +e8c41168-a093-40eb-8baa-6802d24f554a f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +e8c41168-a093-40eb-8baa-6802d24f554a e8eba267-fecb-477e-9625-771fbcfc3cba +e8c41168-a093-40eb-8baa-6802d24f554a e8c41168-a093-40eb-8baa-6802d24f554a +e8c41168-a093-40eb-8baa-6802d24f554a e26ae29a-213a-4f79-98c2-cc81cf2f451d +e8c41168-a093-40eb-8baa-6802d24f554a e0dfbc08-45ad-4a81-b648-7e65c066f673 +e8c41168-a093-40eb-8baa-6802d24f554a d535b213-2abc-438f-aa6a-c06476d60b09 +e8c41168-a093-40eb-8baa-6802d24f554a c892b1d7-7485-407d-8883-54fb7fecebc1 +e8c41168-a093-40eb-8baa-6802d24f554a b6900c21-d310-4fb4-8b8f-176a09c91989 +e8c41168-a093-40eb-8baa-6802d24f554a b00df815-7528-4967-bfe2-311130d91c21 +e8c41168-a093-40eb-8baa-6802d24f554a add6d754-a075-4693-a33a-c061c9a368ff +e8c41168-a093-40eb-8baa-6802d24f554a 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +e8c41168-a093-40eb-8baa-6802d24f554a 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +e8c41168-a093-40eb-8baa-6802d24f554a 8a1908f7-47cc-4f82-859c-781a193e9901 +e8c41168-a093-40eb-8baa-6802d24f554a 8743e69b-17aa-44ff-b8fa-4f582665efc6 +e8c41168-a093-40eb-8baa-6802d24f554a 721e4027-a080-40d8-bc4a-43cd33477611 +e8c41168-a093-40eb-8baa-6802d24f554a 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +e8c41168-a093-40eb-8baa-6802d24f554a 54c750aa-570a-4e8e-9a02-418781923e39 +e8c41168-a093-40eb-8baa-6802d24f554a 50eac25a-3761-4de3-8a69-aae52e658300 +e8c41168-a093-40eb-8baa-6802d24f554a 4a464bb5-9373-4f1b-9c03-053c64797114 +e8c41168-a093-40eb-8baa-6802d24f554a 4506aa76-9d0d-40e4-85bc-5735f74522a0 +e8c41168-a093-40eb-8baa-6802d24f554a 42a9a333-d09d-4b9e-af96-1f02af26bac3 +e8c41168-a093-40eb-8baa-6802d24f554a 2fc75f11-2097-4e1e-8390-dbff3e844b7a +e8c41168-a093-40eb-8baa-6802d24f554a 274a2e49-9170-4945-bca2-ab6ef4bded75 +e8c41168-a093-40eb-8baa-6802d24f554a 0364073f-91d8-47a1-b8b0-107c6318a691 +e8eba267-fecb-477e-9625-771fbcfc3cba f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +e8eba267-fecb-477e-9625-771fbcfc3cba e8eba267-fecb-477e-9625-771fbcfc3cba +e8eba267-fecb-477e-9625-771fbcfc3cba e8c41168-a093-40eb-8baa-6802d24f554a +e8eba267-fecb-477e-9625-771fbcfc3cba e26ae29a-213a-4f79-98c2-cc81cf2f451d +e8eba267-fecb-477e-9625-771fbcfc3cba e0dfbc08-45ad-4a81-b648-7e65c066f673 +e8eba267-fecb-477e-9625-771fbcfc3cba d535b213-2abc-438f-aa6a-c06476d60b09 +e8eba267-fecb-477e-9625-771fbcfc3cba c892b1d7-7485-407d-8883-54fb7fecebc1 +e8eba267-fecb-477e-9625-771fbcfc3cba b6900c21-d310-4fb4-8b8f-176a09c91989 +e8eba267-fecb-477e-9625-771fbcfc3cba b00df815-7528-4967-bfe2-311130d91c21 +e8eba267-fecb-477e-9625-771fbcfc3cba add6d754-a075-4693-a33a-c061c9a368ff +e8eba267-fecb-477e-9625-771fbcfc3cba 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +e8eba267-fecb-477e-9625-771fbcfc3cba 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +e8eba267-fecb-477e-9625-771fbcfc3cba 8a1908f7-47cc-4f82-859c-781a193e9901 +e8eba267-fecb-477e-9625-771fbcfc3cba 8743e69b-17aa-44ff-b8fa-4f582665efc6 +e8eba267-fecb-477e-9625-771fbcfc3cba 721e4027-a080-40d8-bc4a-43cd33477611 +e8eba267-fecb-477e-9625-771fbcfc3cba 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +e8eba267-fecb-477e-9625-771fbcfc3cba 54c750aa-570a-4e8e-9a02-418781923e39 +e8eba267-fecb-477e-9625-771fbcfc3cba 50eac25a-3761-4de3-8a69-aae52e658300 +e8eba267-fecb-477e-9625-771fbcfc3cba 4a464bb5-9373-4f1b-9c03-053c64797114 +e8eba267-fecb-477e-9625-771fbcfc3cba 4506aa76-9d0d-40e4-85bc-5735f74522a0 +e8eba267-fecb-477e-9625-771fbcfc3cba 42a9a333-d09d-4b9e-af96-1f02af26bac3 +e8eba267-fecb-477e-9625-771fbcfc3cba 2fc75f11-2097-4e1e-8390-dbff3e844b7a +e8eba267-fecb-477e-9625-771fbcfc3cba 274a2e49-9170-4945-bca2-ab6ef4bded75 +e8eba267-fecb-477e-9625-771fbcfc3cba 0364073f-91d8-47a1-b8b0-107c6318a691 +ede7745a-8f3e-4e20-9f16-33756be7ab6c fce20464-ff98-4051-8714-6b9584e52740 +ede7745a-8f3e-4e20-9f16-33756be7ab6c f3eb0c38-2571-44e7-be39-732eb52cf1df +ede7745a-8f3e-4e20-9f16-33756be7ab6c f3c2f842-6aa3-42c9-a0f3-895c40456868 +ede7745a-8f3e-4e20-9f16-33756be7ab6c ede7745a-8f3e-4e20-9f16-33756be7ab6c +ede7745a-8f3e-4e20-9f16-33756be7ab6c ca3e0486-fd6b-4a13-a741-3a47760b412d +ede7745a-8f3e-4e20-9f16-33756be7ab6c c5c6eed5-aec6-4b8a-b689-adbb219c2267 +ede7745a-8f3e-4e20-9f16-33756be7ab6c bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +ede7745a-8f3e-4e20-9f16-33756be7ab6c a221198d-000e-497b-8b70-bb4d37f7bbe1 +ede7745a-8f3e-4e20-9f16-33756be7ab6c a1228f20-7c50-4d3a-b88c-2d277ca79d79 +ede7745a-8f3e-4e20-9f16-33756be7ab6c 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +ede7745a-8f3e-4e20-9f16-33756be7ab6c 88b16960-bfff-41d0-81e4-9a4630834a00 +ede7745a-8f3e-4e20-9f16-33756be7ab6c 522e2abe-ad96-4d1a-b5a5-748faa997531 +ede7745a-8f3e-4e20-9f16-33756be7ab6c 4cfe72fe-21a0-4201-87b6-ef93695d2495 +ede7745a-8f3e-4e20-9f16-33756be7ab6c 498f4b18-bd4c-470d-9cde-2fca70ec8964 +ede7745a-8f3e-4e20-9f16-33756be7ab6c 202131ae-78bb-4104-89b0-fb90645760f6 +ede7745a-8f3e-4e20-9f16-33756be7ab6c 11dafd5c-85e7-4275-a147-89a63641e35e +ede7745a-8f3e-4e20-9f16-33756be7ab6c 02b993c0-b358-481c-b0d0-0767387feb9f +ee24bf89-81a3-4259-a382-86917f3829d4 fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +ee24bf89-81a3-4259-a382-86917f3829d4 ee24bf89-81a3-4259-a382-86917f3829d4 +ee24bf89-81a3-4259-a382-86917f3829d4 e363eb67-64c7-440f-93fd-5c8787c69a85 +ee24bf89-81a3-4259-a382-86917f3829d4 dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +ee24bf89-81a3-4259-a382-86917f3829d4 db79f75d-af3c-4f82-b4e5-9b5d26598eef +ee24bf89-81a3-4259-a382-86917f3829d4 d7a48a26-7731-4046-bf22-78f0b8084eb9 +ee24bf89-81a3-4259-a382-86917f3829d4 d080c116-681a-494a-a928-45924109b49d +ee24bf89-81a3-4259-a382-86917f3829d4 cdb8493c-e3b0-447f-b16b-e58dd64660f3 +ee24bf89-81a3-4259-a382-86917f3829d4 c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +ee24bf89-81a3-4259-a382-86917f3829d4 a6b084fb-ada7-480a-9adc-f180d4eb1e2a +ee24bf89-81a3-4259-a382-86917f3829d4 990c21ab-433b-4920-873e-f9dfc7103d9d +ee24bf89-81a3-4259-a382-86917f3829d4 922e2443-9cc5-421f-8310-9cd23f6e9f2d +ee24bf89-81a3-4259-a382-86917f3829d4 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +ee24bf89-81a3-4259-a382-86917f3829d4 8089a9ac-9e71-4487-a231-f720e4bc8d3e +ee24bf89-81a3-4259-a382-86917f3829d4 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +ee24bf89-81a3-4259-a382-86917f3829d4 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +ee24bf89-81a3-4259-a382-86917f3829d4 6d47a2fe-3645-4c5c-bebe-1b822eff197f +ee24bf89-81a3-4259-a382-86917f3829d4 67fc12fc-bb9f-40f1-9051-127a788b3081 +ee24bf89-81a3-4259-a382-86917f3829d4 634f0889-3a83-484a-bcc8-86f48e333c7b +ee24bf89-81a3-4259-a382-86917f3829d4 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +ee24bf89-81a3-4259-a382-86917f3829d4 4adcaf72-5241-4877-b61c-f34060576c50 +ee24bf89-81a3-4259-a382-86917f3829d4 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +ee24bf89-81a3-4259-a382-86917f3829d4 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +ee24bf89-81a3-4259-a382-86917f3829d4 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +ee24bf89-81a3-4259-a382-86917f3829d4 374fd699-6b74-4500-9d60-2473c7e0364f +ee24bf89-81a3-4259-a382-86917f3829d4 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +ee24bf89-81a3-4259-a382-86917f3829d4 308eba53-29fa-489a-97dc-741b077841a7 +ee24bf89-81a3-4259-a382-86917f3829d4 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +ee24bf89-81a3-4259-a382-86917f3829d4 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +ee24bf89-81a3-4259-a382-86917f3829d4 21995497-0eb8-4c0b-8c23-e6a829f3d596 +ee24bf89-81a3-4259-a382-86917f3829d4 18ebf5ea-b0a2-4333-9e9c-40217de809ff +ee24bf89-81a3-4259-a382-86917f3829d4 151e3048-66ad-4411-8753-677877e3bf0a +ee24bf89-81a3-4259-a382-86917f3829d4 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +ee24bf89-81a3-4259-a382-86917f3829d4 0af4a77e-892e-4b3f-9110-4c5224782250 +f1293c5e-c997-4ba9-8bfd-438e7a840dc8 fcab9efe-fb05-42d6-b366-ce6db1cc4093 +f1293c5e-c997-4ba9-8bfd-438e7a840dc8 f1293c5e-c997-4ba9-8bfd-438e7a840dc8 +f1293c5e-c997-4ba9-8bfd-438e7a840dc8 e4358f28-62a8-4e06-b2bd-cb00d3310349 +f1293c5e-c997-4ba9-8bfd-438e7a840dc8 8f76d729-9f74-4cfd-be2a-8b033b568e18 +f1293c5e-c997-4ba9-8bfd-438e7a840dc8 39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 +f1293c5e-c997-4ba9-8bfd-438e7a840dc8 31d4c712-75b1-4ecb-9d1a-ccc4b8453826 +f1293c5e-c997-4ba9-8bfd-438e7a840dc8 24c4dd5b-748b-4165-a0cc-2867b76c2dc2 +f1293c5e-c997-4ba9-8bfd-438e7a840dc8 16d280a2-c61f-487f-9034-22e884158969 +f1293c5e-c997-4ba9-8bfd-438e7a840dc8 0134901a-4a69-4b39-92ea-d6f48ec83c6e +f18b08bd-40da-43c1-87ec-4ba23542635f f19eefca-c1ad-4860-bdda-4674a631d464 +f18b08bd-40da-43c1-87ec-4ba23542635f f18b08bd-40da-43c1-87ec-4ba23542635f +f18b08bd-40da-43c1-87ec-4ba23542635f b1ccd55a-6b88-4240-b20c-ca893f36e23b +f18b08bd-40da-43c1-87ec-4ba23542635f 900ce9fe-02d3-476b-902a-9467767ecdcf +f18b08bd-40da-43c1-87ec-4ba23542635f 8dc2ce26-aec7-43c0-9771-350af4257ad8 +f18b08bd-40da-43c1-87ec-4ba23542635f 1f3443ee-d15e-4894-b18e-185cfadfcdea +f19eefca-c1ad-4860-bdda-4674a631d464 f19eefca-c1ad-4860-bdda-4674a631d464 +f19eefca-c1ad-4860-bdda-4674a631d464 f18b08bd-40da-43c1-87ec-4ba23542635f +f19eefca-c1ad-4860-bdda-4674a631d464 b1ccd55a-6b88-4240-b20c-ca893f36e23b +f19eefca-c1ad-4860-bdda-4674a631d464 900ce9fe-02d3-476b-902a-9467767ecdcf +f19eefca-c1ad-4860-bdda-4674a631d464 8dc2ce26-aec7-43c0-9771-350af4257ad8 +f19eefca-c1ad-4860-bdda-4674a631d464 1f3443ee-d15e-4894-b18e-185cfadfcdea +f28931e5-1f35-4f9f-a02e-78398735ea67 f883f2ca-d523-4c9f-86b2-0add137901de +f28931e5-1f35-4f9f-a02e-78398735ea67 f28931e5-1f35-4f9f-a02e-78398735ea67 +f28931e5-1f35-4f9f-a02e-78398735ea67 e7f68d35-ae55-4fa4-b0be-0672a5a7186a +f28931e5-1f35-4f9f-a02e-78398735ea67 85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b +f28931e5-1f35-4f9f-a02e-78398735ea67 7ac56d5a-32a7-4b40-a956-356e3006faed +f28931e5-1f35-4f9f-a02e-78398735ea67 72788e7f-1bf1-40b5-a1f1-27c669dc7278 +f28931e5-1f35-4f9f-a02e-78398735ea67 6c84348c-5065-4e89-9412-bfda023683f2 +f28931e5-1f35-4f9f-a02e-78398735ea67 5abe533b-ae5f-47ad-8746-f60caf7483c0 +f28931e5-1f35-4f9f-a02e-78398735ea67 4f14ca7f-5332-4263-a7b2-f0a838380309 +f28931e5-1f35-4f9f-a02e-78398735ea67 359cb842-c25b-429f-ba9b-d52f171e5631 +f29330d0-5b32-4f75-b558-a1c7f05c0b4a f29330d0-5b32-4f75-b558-a1c7f05c0b4a +f29330d0-5b32-4f75-b558-a1c7f05c0b4a d90676d2-ce74-4867-a00f-6dbffa7c00be +f29330d0-5b32-4f75-b558-a1c7f05c0b4a cc56af6d-25c6-480e-9767-da02a55551da +f29330d0-5b32-4f75-b558-a1c7f05c0b4a ca7f269d-3794-4994-8c46-d8e9f2aa6378 +f29330d0-5b32-4f75-b558-a1c7f05c0b4a baf48725-f0f9-4357-a71d-b1104910deae +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 9ed8703e-3b40-424c-9e98-b3b404051f99 +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 8be81657-8ba6-4ec5-8ff9-4809367096ea +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 7f410064-638a-4477-85c4-97fc2bb14a49 +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 6af73cae-2e4e-485f-a74d-6f4307eb3af3 +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 5cedf18a-fc44-4c39-a80f-2106a0d76934 +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 4f342a71-dec3-403e-970b-e4c21b9eb98b +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 4db144d3-a596-4718-a50a-8ac9175ad386 +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 4215b5d0-bdd9-4222-a04a-81bb637d60af +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 29d4e919-2bd2-44e6-bb8a-380a780f60ff +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 2830e99a-e6b0-411b-a051-7ea1e9f815d1 +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 1cb88e7a-3db6-4a88-9b68-b0e1492114bc +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 12df1bed-5472-4c48-8e88-2cbb3e5eab33 +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 102e16c5-4920-4dad-b142-0b280b2aacad +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 01ac1476-0c9c-4cd7-82a6-4ff526018e9a +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c f2c9ca81-8b1e-4873-bd8a-1a6e1411193c +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c e8eba267-fecb-477e-9625-771fbcfc3cba +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c e8c41168-a093-40eb-8baa-6802d24f554a +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c e26ae29a-213a-4f79-98c2-cc81cf2f451d +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c e0dfbc08-45ad-4a81-b648-7e65c066f673 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c d535b213-2abc-438f-aa6a-c06476d60b09 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c c892b1d7-7485-407d-8883-54fb7fecebc1 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c b6900c21-d310-4fb4-8b8f-176a09c91989 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c b00df815-7528-4967-bfe2-311130d91c21 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c add6d754-a075-4693-a33a-c061c9a368ff +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c 9e0b8dc3-65c4-490d-97c0-d5e3db944de5 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c 9bc516d4-7c82-4340-a90c-bb493f96fbe4 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c 8a1908f7-47cc-4f82-859c-781a193e9901 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c 8743e69b-17aa-44ff-b8fa-4f582665efc6 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c 721e4027-a080-40d8-bc4a-43cd33477611 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c 6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c 54c750aa-570a-4e8e-9a02-418781923e39 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c 50eac25a-3761-4de3-8a69-aae52e658300 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c 4a464bb5-9373-4f1b-9c03-053c64797114 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c 4506aa76-9d0d-40e4-85bc-5735f74522a0 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c 42a9a333-d09d-4b9e-af96-1f02af26bac3 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c 2fc75f11-2097-4e1e-8390-dbff3e844b7a +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c 274a2e49-9170-4945-bca2-ab6ef4bded75 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c 0364073f-91d8-47a1-b8b0-107c6318a691 +f3aa491a-4dbd-4436-b674-18b2177dcf18 f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 +f3aa491a-4dbd-4436-b674-18b2177dcf18 f3aa491a-4dbd-4436-b674-18b2177dcf18 +f3aa491a-4dbd-4436-b674-18b2177dcf18 e1040d58-53bc-4467-8101-ca53b772cede +f3aa491a-4dbd-4436-b674-18b2177dcf18 d05461d6-13bf-402c-890d-db6404933f7c +f3aa491a-4dbd-4436-b674-18b2177dcf18 b7772635-1801-48e4-a442-f4aaa6860544 +f3aa491a-4dbd-4436-b674-18b2177dcf18 b07fb98d-2da4-423a-83b4-7a673de93096 +f3aa491a-4dbd-4436-b674-18b2177dcf18 97b42d67-8691-433d-8a31-dada076162ae +f3aa491a-4dbd-4436-b674-18b2177dcf18 720f7ed6-7b2e-41dc-a0e0-724a3332aa24 +f3aa491a-4dbd-4436-b674-18b2177dcf18 4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 +f3aa491a-4dbd-4436-b674-18b2177dcf18 2ffd6142-b75b-406f-bc06-cdb1c02eaefc +f3aa491a-4dbd-4436-b674-18b2177dcf18 224e538d-3622-4f5e-b772-211ed358d8de +f3aa491a-4dbd-4436-b674-18b2177dcf18 16a3408d-c927-4d02-a1ae-cad32419caab +f3aa491a-4dbd-4436-b674-18b2177dcf18 04d88eb4-fb1b-4fa1-802a-5624f4c61b32 +f3c2f842-6aa3-42c9-a0f3-895c40456868 fce20464-ff98-4051-8714-6b9584e52740 +f3c2f842-6aa3-42c9-a0f3-895c40456868 f3eb0c38-2571-44e7-be39-732eb52cf1df +f3c2f842-6aa3-42c9-a0f3-895c40456868 f3c2f842-6aa3-42c9-a0f3-895c40456868 +f3c2f842-6aa3-42c9-a0f3-895c40456868 ede7745a-8f3e-4e20-9f16-33756be7ab6c +f3c2f842-6aa3-42c9-a0f3-895c40456868 ca3e0486-fd6b-4a13-a741-3a47760b412d +f3c2f842-6aa3-42c9-a0f3-895c40456868 c5c6eed5-aec6-4b8a-b689-adbb219c2267 +f3c2f842-6aa3-42c9-a0f3-895c40456868 bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +f3c2f842-6aa3-42c9-a0f3-895c40456868 a221198d-000e-497b-8b70-bb4d37f7bbe1 +f3c2f842-6aa3-42c9-a0f3-895c40456868 a1228f20-7c50-4d3a-b88c-2d277ca79d79 +f3c2f842-6aa3-42c9-a0f3-895c40456868 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +f3c2f842-6aa3-42c9-a0f3-895c40456868 88b16960-bfff-41d0-81e4-9a4630834a00 +f3c2f842-6aa3-42c9-a0f3-895c40456868 522e2abe-ad96-4d1a-b5a5-748faa997531 +f3c2f842-6aa3-42c9-a0f3-895c40456868 4cfe72fe-21a0-4201-87b6-ef93695d2495 +f3c2f842-6aa3-42c9-a0f3-895c40456868 498f4b18-bd4c-470d-9cde-2fca70ec8964 +f3c2f842-6aa3-42c9-a0f3-895c40456868 202131ae-78bb-4104-89b0-fb90645760f6 +f3c2f842-6aa3-42c9-a0f3-895c40456868 11dafd5c-85e7-4275-a147-89a63641e35e +f3c2f842-6aa3-42c9-a0f3-895c40456868 02b993c0-b358-481c-b0d0-0767387feb9f +f3eb0c38-2571-44e7-be39-732eb52cf1df fce20464-ff98-4051-8714-6b9584e52740 +f3eb0c38-2571-44e7-be39-732eb52cf1df f3eb0c38-2571-44e7-be39-732eb52cf1df +f3eb0c38-2571-44e7-be39-732eb52cf1df f3c2f842-6aa3-42c9-a0f3-895c40456868 +f3eb0c38-2571-44e7-be39-732eb52cf1df ede7745a-8f3e-4e20-9f16-33756be7ab6c +f3eb0c38-2571-44e7-be39-732eb52cf1df ca3e0486-fd6b-4a13-a741-3a47760b412d +f3eb0c38-2571-44e7-be39-732eb52cf1df c5c6eed5-aec6-4b8a-b689-adbb219c2267 +f3eb0c38-2571-44e7-be39-732eb52cf1df bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +f3eb0c38-2571-44e7-be39-732eb52cf1df a221198d-000e-497b-8b70-bb4d37f7bbe1 +f3eb0c38-2571-44e7-be39-732eb52cf1df a1228f20-7c50-4d3a-b88c-2d277ca79d79 +f3eb0c38-2571-44e7-be39-732eb52cf1df 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +f3eb0c38-2571-44e7-be39-732eb52cf1df 88b16960-bfff-41d0-81e4-9a4630834a00 +f3eb0c38-2571-44e7-be39-732eb52cf1df 522e2abe-ad96-4d1a-b5a5-748faa997531 +f3eb0c38-2571-44e7-be39-732eb52cf1df 4cfe72fe-21a0-4201-87b6-ef93695d2495 +f3eb0c38-2571-44e7-be39-732eb52cf1df 498f4b18-bd4c-470d-9cde-2fca70ec8964 +f3eb0c38-2571-44e7-be39-732eb52cf1df 202131ae-78bb-4104-89b0-fb90645760f6 +f3eb0c38-2571-44e7-be39-732eb52cf1df 11dafd5c-85e7-4275-a147-89a63641e35e +f3eb0c38-2571-44e7-be39-732eb52cf1df 02b993c0-b358-481c-b0d0-0767387feb9f +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 f3aa491a-4dbd-4436-b674-18b2177dcf18 +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 e1040d58-53bc-4467-8101-ca53b772cede +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 d05461d6-13bf-402c-890d-db6404933f7c +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 b7772635-1801-48e4-a442-f4aaa6860544 +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 b07fb98d-2da4-423a-83b4-7a673de93096 +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 97b42d67-8691-433d-8a31-dada076162ae +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 720f7ed6-7b2e-41dc-a0e0-724a3332aa24 +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 2ffd6142-b75b-406f-bc06-cdb1c02eaefc +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 224e538d-3622-4f5e-b772-211ed358d8de +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 16a3408d-c927-4d02-a1ae-cad32419caab +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 04d88eb4-fb1b-4fa1-802a-5624f4c61b32 +f77a8561-5adc-452a-ac9a-76273b6a6678 f77a8561-5adc-452a-ac9a-76273b6a6678 +f77a8561-5adc-452a-ac9a-76273b6a6678 d79728e1-8834-4f4a-8edc-ce18aca18be6 +f77a8561-5adc-452a-ac9a-76273b6a6678 cf67a8d4-48e2-4dc9-a521-6aabcff0330b +f77a8561-5adc-452a-ac9a-76273b6a6678 bb1a7816-4798-4f58-9ce4-c5bd3af682a8 +f77a8561-5adc-452a-ac9a-76273b6a6678 b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 +f77a8561-5adc-452a-ac9a-76273b6a6678 a6cb423a-7571-46df-83e2-ccc6820adb36 +f77a8561-5adc-452a-ac9a-76273b6a6678 7e7322a6-7912-4101-b3be-d134245a0626 +f77a8561-5adc-452a-ac9a-76273b6a6678 777aa5f2-2a09-4aed-92ea-912694e28b48 +f77a8561-5adc-452a-ac9a-76273b6a6678 6e187f47-91a1-4217-a185-31b6f01db225 +f77a8561-5adc-452a-ac9a-76273b6a6678 62059efc-7607-41c9-a3ba-70948f6a8a1d +f77a8561-5adc-452a-ac9a-76273b6a6678 2d2e07ec-e697-4384-a679-867e93740921 +f77a8561-5adc-452a-ac9a-76273b6a6678 20fbcb6e-d793-4b05-8e29-8ff82572b0db +f77a8561-5adc-452a-ac9a-76273b6a6678 1fd714a6-48b4-425a-99b3-ba5c1a995cce +f883f2ca-d523-4c9f-86b2-0add137901de f883f2ca-d523-4c9f-86b2-0add137901de +f883f2ca-d523-4c9f-86b2-0add137901de f28931e5-1f35-4f9f-a02e-78398735ea67 +f883f2ca-d523-4c9f-86b2-0add137901de e7f68d35-ae55-4fa4-b0be-0672a5a7186a +f883f2ca-d523-4c9f-86b2-0add137901de 85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b +f883f2ca-d523-4c9f-86b2-0add137901de 7ac56d5a-32a7-4b40-a956-356e3006faed +f883f2ca-d523-4c9f-86b2-0add137901de 72788e7f-1bf1-40b5-a1f1-27c669dc7278 +f883f2ca-d523-4c9f-86b2-0add137901de 6c84348c-5065-4e89-9412-bfda023683f2 +f883f2ca-d523-4c9f-86b2-0add137901de 5abe533b-ae5f-47ad-8746-f60caf7483c0 +f883f2ca-d523-4c9f-86b2-0add137901de 4f14ca7f-5332-4263-a7b2-f0a838380309 +f883f2ca-d523-4c9f-86b2-0add137901de 359cb842-c25b-429f-ba9b-d52f171e5631 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c ee24bf89-81a3-4259-a382-86917f3829d4 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c e363eb67-64c7-440f-93fd-5c8787c69a85 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c db79f75d-af3c-4f82-b4e5-9b5d26598eef +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c d7a48a26-7731-4046-bf22-78f0b8084eb9 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c d080c116-681a-494a-a928-45924109b49d +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c cdb8493c-e3b0-447f-b16b-e58dd64660f3 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c c06a9f9b-90fa-4bd6-9581-0c6950f03e4a +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c a6b084fb-ada7-480a-9adc-f180d4eb1e2a +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 990c21ab-433b-4920-873e-f9dfc7103d9d +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 922e2443-9cc5-421f-8310-9cd23f6e9f2d +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 89f260bb-68b4-4dec-91f4-d52e673e0ff7 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 8089a9ac-9e71-4487-a231-f720e4bc8d3e +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 72c611bb-471e-40f2-a6a8-0ae7b7b6b63e +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 6d47a2fe-3645-4c5c-bebe-1b822eff197f +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 67fc12fc-bb9f-40f1-9051-127a788b3081 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 634f0889-3a83-484a-bcc8-86f48e333c7b +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 4adcaf72-5241-4877-b61c-f34060576c50 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 45b25ced-6eed-4fca-8ec1-b7d32ea13efe +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 42aa48c7-68cc-4bee-9730-cff29f0e36c5 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 374fd699-6b74-4500-9d60-2473c7e0364f +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 308eba53-29fa-489a-97dc-741b077841a7 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 2bd766e5-60e4-4469-bb97-54f0503a1eb0 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 21995497-0eb8-4c0b-8c23-e6a829f3d596 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 18ebf5ea-b0a2-4333-9e9c-40217de809ff +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 151e3048-66ad-4411-8753-677877e3bf0a +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 0c315d37-c758-4cf5-a7cb-cbf712fa7dfd +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 0af4a77e-892e-4b3f-9110-4c5224782250 +fc771883-3bd6-46d9-9626-a130e1b902de fc771883-3bd6-46d9-9626-a130e1b902de +fc771883-3bd6-46d9-9626-a130e1b902de e2387a81-5ff5-4daf-b2e8-881998d0d917 +fc771883-3bd6-46d9-9626-a130e1b902de b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa +fc771883-3bd6-46d9-9626-a130e1b902de b1d4dc41-07ae-44db-ae17-1df86c5a62cf +fc771883-3bd6-46d9-9626-a130e1b902de 67e5cf95-236b-4f75-abc5-034ca2966448 +fc771883-3bd6-46d9-9626-a130e1b902de 48cc2275-a478-4ade-b076-cf38e1d16017 +fc771883-3bd6-46d9-9626-a130e1b902de 41d26b1c-57b9-408c-b955-bf0ee1db4809 +fc771883-3bd6-46d9-9626-a130e1b902de 3ddf46fe-b5be-456d-810f-ea6a7402236d +fc771883-3bd6-46d9-9626-a130e1b902de 3397a796-894d-409c-97de-a9e6f3f88198 +fc771883-3bd6-46d9-9626-a130e1b902de 19502b5a-2031-487d-9d9c-2001f959408b +fcab9efe-fb05-42d6-b366-ce6db1cc4093 fcab9efe-fb05-42d6-b366-ce6db1cc4093 +fcab9efe-fb05-42d6-b366-ce6db1cc4093 f1293c5e-c997-4ba9-8bfd-438e7a840dc8 +fcab9efe-fb05-42d6-b366-ce6db1cc4093 e4358f28-62a8-4e06-b2bd-cb00d3310349 +fcab9efe-fb05-42d6-b366-ce6db1cc4093 8f76d729-9f74-4cfd-be2a-8b033b568e18 +fcab9efe-fb05-42d6-b366-ce6db1cc4093 39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 +fcab9efe-fb05-42d6-b366-ce6db1cc4093 31d4c712-75b1-4ecb-9d1a-ccc4b8453826 +fcab9efe-fb05-42d6-b366-ce6db1cc4093 24c4dd5b-748b-4165-a0cc-2867b76c2dc2 +fcab9efe-fb05-42d6-b366-ce6db1cc4093 16d280a2-c61f-487f-9034-22e884158969 +fcab9efe-fb05-42d6-b366-ce6db1cc4093 0134901a-4a69-4b39-92ea-d6f48ec83c6e +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 fcc916dd-5e4f-4bf7-9aef-c906a36d7301 +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 c57d51a7-45de-41b9-92fc-efd0634effaf +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 ae7ddaef-4911-418c-8401-d978617e145b +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 acb2329d-b1c3-45c3-ad28-91a09aa521e1 +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 a89c0c42-c19f-4d7f-a15e-d04e043c92f6 +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 836b2e59-fb97-4014-ab0c-d5a5dc750969 +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 683714ad-5194-49e7-9b8f-4e306cf68ae1 +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 5f64131b-d410-449a-9de6-35415919fec5 +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 4f3d1e93-a28f-446e-91af-2baf0168b82b +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 1db1c71b-9eeb-4a98-abc1-eb699b38510c +fce20464-ff98-4051-8714-6b9584e52740 fce20464-ff98-4051-8714-6b9584e52740 +fce20464-ff98-4051-8714-6b9584e52740 f3eb0c38-2571-44e7-be39-732eb52cf1df +fce20464-ff98-4051-8714-6b9584e52740 f3c2f842-6aa3-42c9-a0f3-895c40456868 +fce20464-ff98-4051-8714-6b9584e52740 ede7745a-8f3e-4e20-9f16-33756be7ab6c +fce20464-ff98-4051-8714-6b9584e52740 ca3e0486-fd6b-4a13-a741-3a47760b412d +fce20464-ff98-4051-8714-6b9584e52740 c5c6eed5-aec6-4b8a-b689-adbb219c2267 +fce20464-ff98-4051-8714-6b9584e52740 bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb +fce20464-ff98-4051-8714-6b9584e52740 a221198d-000e-497b-8b70-bb4d37f7bbe1 +fce20464-ff98-4051-8714-6b9584e52740 a1228f20-7c50-4d3a-b88c-2d277ca79d79 +fce20464-ff98-4051-8714-6b9584e52740 8e9e848c-0547-4f2c-8b5f-e33d79bbed67 +fce20464-ff98-4051-8714-6b9584e52740 88b16960-bfff-41d0-81e4-9a4630834a00 +fce20464-ff98-4051-8714-6b9584e52740 522e2abe-ad96-4d1a-b5a5-748faa997531 +fce20464-ff98-4051-8714-6b9584e52740 4cfe72fe-21a0-4201-87b6-ef93695d2495 +fce20464-ff98-4051-8714-6b9584e52740 498f4b18-bd4c-470d-9cde-2fca70ec8964 +fce20464-ff98-4051-8714-6b9584e52740 202131ae-78bb-4104-89b0-fb90645760f6 +fce20464-ff98-4051-8714-6b9584e52740 11dafd5c-85e7-4275-a147-89a63641e35e +fce20464-ff98-4051-8714-6b9584e52740 02b993c0-b358-481c-b0d0-0767387feb9f diff --git a/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingPolymorphicResolve.csv b/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingPolymorphicResolve.csv deleted file mode 100644 index ab4a9c29ba..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingPolymorphicResolve.csv +++ /dev/null @@ -1,217 +0,0 @@ -0134901a-4a69-4b39-92ea-d6f48ec83c6e,false -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,false -0250a217-a663-403b-a708-5d14eadf0c40,true -02b993c0-b358-481c-b0d0-0767387feb9f,false -0364073f-91d8-47a1-b8b0-107c6318a691,false -04945715-8ad5-4d8f-bfda-ae26662a3610,false -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,false -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,false -0af4a77e-892e-4b3f-9110-4c5224782250,false -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,false -102e16c5-4920-4dad-b142-0b280b2aacad,false -11dafd5c-85e7-4275-a147-89a63641e35e,false -12b212d8-bc6e-415a-93b0-594381726668,true -12df1bed-5472-4c48-8e88-2cbb3e5eab33,false -151e3048-66ad-4411-8753-677877e3bf0a,false -16a3408d-c927-4d02-a1ae-cad32419caab,false -16d280a2-c61f-487f-9034-22e884158969,false -17bcf2ea-d921-4715-91c5-6b15226b33d3,false -18ebf5ea-b0a2-4333-9e9c-40217de809ff,false -19502b5a-2031-487d-9d9c-2001f959408b,false -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,false -1db1c71b-9eeb-4a98-abc1-eb699b38510c,false -1f3443ee-d15e-4894-b18e-185cfadfcdea,false -1fd714a6-48b4-425a-99b3-ba5c1a995cce,false -202131ae-78bb-4104-89b0-fb90645760f6,false -20fbcb6e-d793-4b05-8e29-8ff82572b0db,false -21995497-0eb8-4c0b-8c23-e6a829f3d596,false -21c6e0fc-bf47-4f80-b1ff-85b940445bdf,false -224e538d-3622-4f5e-b772-211ed358d8de,false -23780032-f3bb-4b40-af2e-3fa6b1677376,true -24c4dd5b-748b-4165-a0cc-2867b76c2dc2,false -2514cdf0-1216-4c5e-bfa8-0c11c20c6b93,false -274a2e49-9170-4945-bca2-ab6ef4bded75,false -2830e99a-e6b0-411b-a051-7ea1e9f815d1,false -29d4e919-2bd2-44e6-bb8a-380a780f60ff,false -2aff9edd-def2-487a-b435-a162e11a303c,true -2bd766e5-60e4-4469-bb97-54f0503a1eb0,false -2bddd98c-86e3-4ae0-9dc6-87da16ab6267,false -2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5,false -2d2e07ec-e697-4384-a679-867e93740921,false -2de1f829-c9d2-4f22-bf39-536dcb82fc3e,false -2e8eb256-84c9-499a-8bf6-bb39504373e1,false -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,false -2ee2ab26-09fb-4e12-bf21-9fed3b4c38de,false -2fc75f11-2097-4e1e-8390-dbff3e844b7a,false -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,false -308eba53-29fa-489a-97dc-741b077841a7,false -30ae4a13-0cc6-4d12-a5dd-fa9288efcc09,false -31d4c712-75b1-4ecb-9d1a-ccc4b8453826,false -3397a796-894d-409c-97de-a9e6f3f88198,false -359cb842-c25b-429f-ba9b-d52f171e5631,false -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,false -374fd699-6b74-4500-9d60-2473c7e0364f,false -395019b6-84e8-4919-8b8b-ac64e4a6eade,false -39efc9b8-a75f-43a3-98a2-5bdc1c8207d2,false -3ddf46fe-b5be-456d-810f-ea6a7402236d,false -4148ac36-1dcb-4e96-89cd-355e7e8f919b,false -41d26b1c-57b9-408c-b955-bf0ee1db4809,false -4215b5d0-bdd9-4222-a04a-81bb637d60af,false -42a9a333-d09d-4b9e-af96-1f02af26bac3,false -42aa48c7-68cc-4bee-9730-cff29f0e36c5,false -4506aa76-9d0d-40e4-85bc-5735f74522a0,false -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,false -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,false -48cc2275-a478-4ade-b076-cf38e1d16017,false -498f4b18-bd4c-470d-9cde-2fca70ec8964,false -4a464bb5-9373-4f1b-9c03-053c64797114,false -4adcaf72-5241-4877-b61c-f34060576c50,false -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,false -4cd95073-6db3-4eb2-ac4b-0aacb182b352,false -4cfe72fe-21a0-4201-87b6-ef93695d2495,false -4db144d3-a596-4718-a50a-8ac9175ad386,false -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,false -4f14ca7f-5332-4263-a7b2-f0a838380309,false -4f342a71-dec3-403e-970b-e4c21b9eb98b,false -4f3d1e93-a28f-446e-91af-2baf0168b82b,false -50eac25a-3761-4de3-8a69-aae52e658300,false -51241857-a7a4-4517-96e3-21f797581f89,false -522e2abe-ad96-4d1a-b5a5-748faa997531,false -54c750aa-570a-4e8e-9a02-418781923e39,false -55f17f44-287f-41aa-a1bc-d1c0104af36e,false -56999896-e36b-4e63-a6b6-dbb85dfe1936,false -578cc35c-0982-4d72-a729-b304c026f075,true -5abe533b-ae5f-47ad-8746-f60caf7483c0,false -5cedf18a-fc44-4c39-a80f-2106a0d76934,false -5e1fe0f2-4517-462b-8287-7824f9a60f4f,false -5f64131b-d410-449a-9de6-35415919fec5,false -62059efc-7607-41c9-a3ba-70948f6a8a1d,false -634f0889-3a83-484a-bcc8-86f48e333c7b,false -67e5cf95-236b-4f75-abc5-034ca2966448,false -67fc12fc-bb9f-40f1-9051-127a788b3081,false -683714ad-5194-49e7-9b8f-4e306cf68ae1,false -6954c5c2-dd77-490a-9152-f1d78eff913d,false -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,false -6af73cae-2e4e-485f-a74d-6f4307eb3af3,false -6c84348c-5065-4e89-9412-bfda023683f2,false -6d47a2fe-3645-4c5c-bebe-1b822eff197f,false -6e187f47-91a1-4217-a185-31b6f01db225,false -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,false -721e4027-a080-40d8-bc4a-43cd33477611,false -72788e7f-1bf1-40b5-a1f1-27c669dc7278,false -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,false -749babeb-6883-4bd4-92a5-bec52769071c,true -75a09d4f-eef2-4404-a386-dfcb408ec9ed,false -777aa5f2-2a09-4aed-92ea-912694e28b48,false -79be3b69-23d8-4408-8f01-68d993e63b6c,false -7ac56d5a-32a7-4b40-a956-356e3006faed,false -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,false -7e7322a6-7912-4101-b3be-d134245a0626,false -7ea43fc6-b1f7-46be-a308-5ecb275a1081,true -7f410064-638a-4477-85c4-97fc2bb14a49,false -8089a9ac-9e71-4487-a231-f720e4bc8d3e,false -836b2e59-fb97-4014-ab0c-d5a5dc750969,false -85481b3f-c75e-40cd-bacd-b0afb79e893c,false -85c997bf-63d9-4ebb-8e0b-320de3dddc6c,false -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,false -86415df5-7e47-4637-9e09-aaad9e7628d1,true -8743e69b-17aa-44ff-b8fa-4f582665efc6,false -87b04a97-1d33-4548-aec9-3d2856070702,false -88b16960-bfff-41d0-81e4-9a4630834a00,false -89f260bb-68b4-4dec-91f4-d52e673e0ff7,false -8a1908f7-47cc-4f82-859c-781a193e9901,false -8bc7af32-e5ad-4849-ba4d-b446db833ab4,true -8be81657-8ba6-4ec5-8ff9-4809367096ea,false -8dc2ce26-aec7-43c0-9771-350af4257ad8,false -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,false -8f76d729-9f74-4cfd-be2a-8b033b568e18,false -900ce9fe-02d3-476b-902a-9467767ecdcf,false -922e2443-9cc5-421f-8310-9cd23f6e9f2d,false -96b2282d-1384-4cfb-9958-f009fb501626,false -97b42d67-8691-433d-8a31-dada076162ae,false -984067fc-3dfc-47b7-8ee0-4d9b27d43860,false -990c21ab-433b-4920-873e-f9dfc7103d9d,false -99fc3558-79f3-4d14-9aa1-ff63f621ecd9,true -9b2eb632-6f1a-4e97-912b-3c8378a1b11c,false -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,false -9bc516d4-7c82-4340-a90c-bb493f96fbe4,false -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,false -9ed8703e-3b40-424c-9e98-b3b404051f99,false -a1228f20-7c50-4d3a-b88c-2d277ca79d79,false -a221198d-000e-497b-8b70-bb4d37f7bbe1,false -a2c3ee1d-ec35-4b26-9bab-12de3f47d604,true -a6790cbd-8349-432e-a976-5dfc07451203,false -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,false -a6cb423a-7571-46df-83e2-ccc6820adb36,false -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,false -acb2329d-b1c3-45c3-ad28-91a09aa521e1,false -add6d754-a075-4693-a33a-c061c9a368ff,false -ae7ddaef-4911-418c-8401-d978617e145b,false -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,false -b00df815-7528-4967-bfe2-311130d91c21,false -b07fb98d-2da4-423a-83b4-7a673de93096,false -b0d337c5-3555-4817-ba59-4ceea5ad8a91,false -b1ccd55a-6b88-4240-b20c-ca893f36e23b,false -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,false -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,false -b6900c21-d310-4fb4-8b8f-176a09c91989,false -b7772635-1801-48e4-a442-f4aaa6860544,false -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,false -bac374c0-50e8-4a5c-947b-82a8e9a747a9,false -baf48725-f0f9-4357-a71d-b1104910deae,false -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,false -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,false -bf895c48-1d52-4780-8e9a-532d69356d28,false -bfc93cb7-e53a-49f7-b86e-81d6a13cfdea,false -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,false -c3ec1f2e-1411-41e4-8370-acd5a8f68cf4,false -c57d51a7-45de-41b9-92fc-efd0634effaf,false -c5c6eed5-aec6-4b8a-b689-adbb219c2267,false -c892b1d7-7485-407d-8883-54fb7fecebc1,false -c90aae7e-5704-4e13-8794-41ab377193bd,false -ca3e0486-fd6b-4a13-a741-3a47760b412d,false -ca7f269d-3794-4994-8c46-d8e9f2aa6378,false -cc56af6d-25c6-480e-9767-da02a55551da,false -cdb8493c-e3b0-447f-b16b-e58dd64660f3,false -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,false -cfcbe34a-edc5-4442-bc05-5927fa00336b,false -d05461d6-13bf-402c-890d-db6404933f7c,false -d080c116-681a-494a-a928-45924109b49d,false -d535b213-2abc-438f-aa6a-c06476d60b09,false -d6988116-3c63-44f8-9f94-9e9d51cc5a37,false -d79728e1-8834-4f4a-8edc-ce18aca18be6,false -d7a48a26-7731-4046-bf22-78f0b8084eb9,false -d90676d2-ce74-4867-a00f-6dbffa7c00be,false -d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2,false -db79f75d-af3c-4f82-b4e5-9b5d26598eef,false -def5fd23-2b74-4c64-85e9-fac27e626bb7,false -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,false -e0dfbc08-45ad-4a81-b648-7e65c066f673,false -e1040d58-53bc-4467-8101-ca53b772cede,false -e1c2ad9f-6f61-4f16-805a-2f405b63659a,false -e2387a81-5ff5-4daf-b2e8-881998d0d917,false -e26ae29a-213a-4f79-98c2-cc81cf2f451d,false -e363eb67-64c7-440f-93fd-5c8787c69a85,false -e4358f28-62a8-4e06-b2bd-cb00d3310349,false -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,false -e8c41168-a093-40eb-8baa-6802d24f554a,false -e8eba267-fecb-477e-9625-771fbcfc3cba,false -ede7745a-8f3e-4e20-9f16-33756be7ab6c,false -ee24bf89-81a3-4259-a382-86917f3829d4,false -f1293c5e-c997-4ba9-8bfd-438e7a840dc8,false -f18b08bd-40da-43c1-87ec-4ba23542635f,false -f19eefca-c1ad-4860-bdda-4674a631d464,false -f28931e5-1f35-4f9f-a02e-78398735ea67,false -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,false -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,false -f3aa491a-4dbd-4436-b674-18b2177dcf18,false -f3c2f842-6aa3-42c9-a0f3-895c40456868,false -f3eb0c38-2571-44e7-be39-732eb52cf1df,false -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,false -f77a8561-5adc-452a-ac9a-76273b6a6678,false -f883f2ca-d523-4c9f-86b2-0add137901de,false -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,false -fc771883-3bd6-46d9-9626-a130e1b902de,false -fcab9efe-fb05-42d6-b366-ce6db1cc4093,false -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,false -fce20464-ff98-4051-8714-6b9584e52740,false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingPolymorphicResolve.tsv b/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingPolymorphicResolve.tsv new file mode 100644 index 0000000000..5dc211fff4 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingPolymorphicResolve.tsv @@ -0,0 +1,217 @@ +0134901a-4a69-4b39-92ea-d6f48ec83c6e false +01ac1476-0c9c-4cd7-82a6-4ff526018e9a false +0250a217-a663-403b-a708-5d14eadf0c40 true +02b993c0-b358-481c-b0d0-0767387feb9f false +0364073f-91d8-47a1-b8b0-107c6318a691 false +04945715-8ad5-4d8f-bfda-ae26662a3610 false +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 false +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 false +0af4a77e-892e-4b3f-9110-4c5224782250 false +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd false +102e16c5-4920-4dad-b142-0b280b2aacad false +11dafd5c-85e7-4275-a147-89a63641e35e false +12b212d8-bc6e-415a-93b0-594381726668 true +12df1bed-5472-4c48-8e88-2cbb3e5eab33 false +151e3048-66ad-4411-8753-677877e3bf0a false +16a3408d-c927-4d02-a1ae-cad32419caab false +16d280a2-c61f-487f-9034-22e884158969 false +17bcf2ea-d921-4715-91c5-6b15226b33d3 false +18ebf5ea-b0a2-4333-9e9c-40217de809ff false +19502b5a-2031-487d-9d9c-2001f959408b false +1cb88e7a-3db6-4a88-9b68-b0e1492114bc false +1db1c71b-9eeb-4a98-abc1-eb699b38510c false +1f3443ee-d15e-4894-b18e-185cfadfcdea false +1fd714a6-48b4-425a-99b3-ba5c1a995cce false +202131ae-78bb-4104-89b0-fb90645760f6 false +20fbcb6e-d793-4b05-8e29-8ff82572b0db false +21995497-0eb8-4c0b-8c23-e6a829f3d596 false +21c6e0fc-bf47-4f80-b1ff-85b940445bdf false +224e538d-3622-4f5e-b772-211ed358d8de false +23780032-f3bb-4b40-af2e-3fa6b1677376 true +24c4dd5b-748b-4165-a0cc-2867b76c2dc2 false +2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 false +274a2e49-9170-4945-bca2-ab6ef4bded75 false +2830e99a-e6b0-411b-a051-7ea1e9f815d1 false +29d4e919-2bd2-44e6-bb8a-380a780f60ff false +2aff9edd-def2-487a-b435-a162e11a303c true +2bd766e5-60e4-4469-bb97-54f0503a1eb0 false +2bddd98c-86e3-4ae0-9dc6-87da16ab6267 false +2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5 false +2d2e07ec-e697-4384-a679-867e93740921 false +2de1f829-c9d2-4f22-bf39-536dcb82fc3e false +2e8eb256-84c9-499a-8bf6-bb39504373e1 false +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f false +2ee2ab26-09fb-4e12-bf21-9fed3b4c38de false +2fc75f11-2097-4e1e-8390-dbff3e844b7a false +2ffd6142-b75b-406f-bc06-cdb1c02eaefc false +308eba53-29fa-489a-97dc-741b077841a7 false +30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 false +31d4c712-75b1-4ecb-9d1a-ccc4b8453826 false +3397a796-894d-409c-97de-a9e6f3f88198 false +359cb842-c25b-429f-ba9b-d52f171e5631 false +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 false +374fd699-6b74-4500-9d60-2473c7e0364f false +395019b6-84e8-4919-8b8b-ac64e4a6eade false +39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 false +3ddf46fe-b5be-456d-810f-ea6a7402236d false +4148ac36-1dcb-4e96-89cd-355e7e8f919b false +41d26b1c-57b9-408c-b955-bf0ee1db4809 false +4215b5d0-bdd9-4222-a04a-81bb637d60af false +42a9a333-d09d-4b9e-af96-1f02af26bac3 false +42aa48c7-68cc-4bee-9730-cff29f0e36c5 false +4506aa76-9d0d-40e4-85bc-5735f74522a0 false +45b25ced-6eed-4fca-8ec1-b7d32ea13efe false +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 false +48cc2275-a478-4ade-b076-cf38e1d16017 false +498f4b18-bd4c-470d-9cde-2fca70ec8964 false +4a464bb5-9373-4f1b-9c03-053c64797114 false +4adcaf72-5241-4877-b61c-f34060576c50 false +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 false +4cd95073-6db3-4eb2-ac4b-0aacb182b352 false +4cfe72fe-21a0-4201-87b6-ef93695d2495 false +4db144d3-a596-4718-a50a-8ac9175ad386 false +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 false +4f14ca7f-5332-4263-a7b2-f0a838380309 false +4f342a71-dec3-403e-970b-e4c21b9eb98b false +4f3d1e93-a28f-446e-91af-2baf0168b82b false +50eac25a-3761-4de3-8a69-aae52e658300 false +51241857-a7a4-4517-96e3-21f797581f89 false +522e2abe-ad96-4d1a-b5a5-748faa997531 false +54c750aa-570a-4e8e-9a02-418781923e39 false +55f17f44-287f-41aa-a1bc-d1c0104af36e false +56999896-e36b-4e63-a6b6-dbb85dfe1936 false +578cc35c-0982-4d72-a729-b304c026f075 true +5abe533b-ae5f-47ad-8746-f60caf7483c0 false +5cedf18a-fc44-4c39-a80f-2106a0d76934 false +5e1fe0f2-4517-462b-8287-7824f9a60f4f false +5f64131b-d410-449a-9de6-35415919fec5 false +62059efc-7607-41c9-a3ba-70948f6a8a1d false +634f0889-3a83-484a-bcc8-86f48e333c7b false +67e5cf95-236b-4f75-abc5-034ca2966448 false +67fc12fc-bb9f-40f1-9051-127a788b3081 false +683714ad-5194-49e7-9b8f-4e306cf68ae1 false +6954c5c2-dd77-490a-9152-f1d78eff913d false +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 false +6af73cae-2e4e-485f-a74d-6f4307eb3af3 false +6c84348c-5065-4e89-9412-bfda023683f2 false +6d47a2fe-3645-4c5c-bebe-1b822eff197f false +6e187f47-91a1-4217-a185-31b6f01db225 false +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 false +721e4027-a080-40d8-bc4a-43cd33477611 false +72788e7f-1bf1-40b5-a1f1-27c669dc7278 false +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e false +749babeb-6883-4bd4-92a5-bec52769071c true +75a09d4f-eef2-4404-a386-dfcb408ec9ed false +777aa5f2-2a09-4aed-92ea-912694e28b48 false +79be3b69-23d8-4408-8f01-68d993e63b6c false +7ac56d5a-32a7-4b40-a956-356e3006faed false +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 false +7e7322a6-7912-4101-b3be-d134245a0626 false +7ea43fc6-b1f7-46be-a308-5ecb275a1081 true +7f410064-638a-4477-85c4-97fc2bb14a49 false +8089a9ac-9e71-4487-a231-f720e4bc8d3e false +836b2e59-fb97-4014-ab0c-d5a5dc750969 false +85481b3f-c75e-40cd-bacd-b0afb79e893c false +85c997bf-63d9-4ebb-8e0b-320de3dddc6c false +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b false +86415df5-7e47-4637-9e09-aaad9e7628d1 true +8743e69b-17aa-44ff-b8fa-4f582665efc6 false +87b04a97-1d33-4548-aec9-3d2856070702 false +88b16960-bfff-41d0-81e4-9a4630834a00 false +89f260bb-68b4-4dec-91f4-d52e673e0ff7 false +8a1908f7-47cc-4f82-859c-781a193e9901 false +8bc7af32-e5ad-4849-ba4d-b446db833ab4 true +8be81657-8ba6-4ec5-8ff9-4809367096ea false +8dc2ce26-aec7-43c0-9771-350af4257ad8 false +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 false +8f76d729-9f74-4cfd-be2a-8b033b568e18 false +900ce9fe-02d3-476b-902a-9467767ecdcf false +922e2443-9cc5-421f-8310-9cd23f6e9f2d false +96b2282d-1384-4cfb-9958-f009fb501626 false +97b42d67-8691-433d-8a31-dada076162ae false +984067fc-3dfc-47b7-8ee0-4d9b27d43860 false +990c21ab-433b-4920-873e-f9dfc7103d9d false +99fc3558-79f3-4d14-9aa1-ff63f621ecd9 true +9b2eb632-6f1a-4e97-912b-3c8378a1b11c false +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 false +9bc516d4-7c82-4340-a90c-bb493f96fbe4 false +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 false +9ed8703e-3b40-424c-9e98-b3b404051f99 false +a1228f20-7c50-4d3a-b88c-2d277ca79d79 false +a221198d-000e-497b-8b70-bb4d37f7bbe1 false +a2c3ee1d-ec35-4b26-9bab-12de3f47d604 true +a6790cbd-8349-432e-a976-5dfc07451203 false +a6b084fb-ada7-480a-9adc-f180d4eb1e2a false +a6cb423a-7571-46df-83e2-ccc6820adb36 false +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 false +acb2329d-b1c3-45c3-ad28-91a09aa521e1 false +add6d754-a075-4693-a33a-c061c9a368ff false +ae7ddaef-4911-418c-8401-d978617e145b false +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 false +b00df815-7528-4967-bfe2-311130d91c21 false +b07fb98d-2da4-423a-83b4-7a673de93096 false +b0d337c5-3555-4817-ba59-4ceea5ad8a91 false +b1ccd55a-6b88-4240-b20c-ca893f36e23b false +b1d4dc41-07ae-44db-ae17-1df86c5a62cf false +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa false +b6900c21-d310-4fb4-8b8f-176a09c91989 false +b7772635-1801-48e4-a442-f4aaa6860544 false +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 false +bac374c0-50e8-4a5c-947b-82a8e9a747a9 false +baf48725-f0f9-4357-a71d-b1104910deae false +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 false +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb false +bf895c48-1d52-4780-8e9a-532d69356d28 false +bfc93cb7-e53a-49f7-b86e-81d6a13cfdea false +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a false +c3ec1f2e-1411-41e4-8370-acd5a8f68cf4 false +c57d51a7-45de-41b9-92fc-efd0634effaf false +c5c6eed5-aec6-4b8a-b689-adbb219c2267 false +c892b1d7-7485-407d-8883-54fb7fecebc1 false +c90aae7e-5704-4e13-8794-41ab377193bd false +ca3e0486-fd6b-4a13-a741-3a47760b412d false +ca7f269d-3794-4994-8c46-d8e9f2aa6378 false +cc56af6d-25c6-480e-9767-da02a55551da false +cdb8493c-e3b0-447f-b16b-e58dd64660f3 false +cf67a8d4-48e2-4dc9-a521-6aabcff0330b false +cfcbe34a-edc5-4442-bc05-5927fa00336b false +d05461d6-13bf-402c-890d-db6404933f7c false +d080c116-681a-494a-a928-45924109b49d false +d535b213-2abc-438f-aa6a-c06476d60b09 false +d6988116-3c63-44f8-9f94-9e9d51cc5a37 false +d79728e1-8834-4f4a-8edc-ce18aca18be6 false +d7a48a26-7731-4046-bf22-78f0b8084eb9 false +d90676d2-ce74-4867-a00f-6dbffa7c00be false +d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2 false +db79f75d-af3c-4f82-b4e5-9b5d26598eef false +def5fd23-2b74-4c64-85e9-fac27e626bb7 false +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 false +e0dfbc08-45ad-4a81-b648-7e65c066f673 false +e1040d58-53bc-4467-8101-ca53b772cede false +e1c2ad9f-6f61-4f16-805a-2f405b63659a false +e2387a81-5ff5-4daf-b2e8-881998d0d917 false +e26ae29a-213a-4f79-98c2-cc81cf2f451d false +e363eb67-64c7-440f-93fd-5c8787c69a85 false +e4358f28-62a8-4e06-b2bd-cb00d3310349 false +e7f68d35-ae55-4fa4-b0be-0672a5a7186a false +e8c41168-a093-40eb-8baa-6802d24f554a false +e8eba267-fecb-477e-9625-771fbcfc3cba false +ede7745a-8f3e-4e20-9f16-33756be7ab6c false +ee24bf89-81a3-4259-a382-86917f3829d4 false +f1293c5e-c997-4ba9-8bfd-438e7a840dc8 false +f18b08bd-40da-43c1-87ec-4ba23542635f false +f19eefca-c1ad-4860-bdda-4674a631d464 false +f28931e5-1f35-4f9f-a02e-78398735ea67 false +f29330d0-5b32-4f75-b558-a1c7f05c0b4a false +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c false +f3aa491a-4dbd-4436-b674-18b2177dcf18 false +f3c2f842-6aa3-42c9-a0f3-895c40456868 false +f3eb0c38-2571-44e7-be39-732eb52cf1df false +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 false +f77a8561-5adc-452a-ac9a-76273b6a6678 false +f883f2ca-d523-4c9f-86b2-0add137901de false +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c false +fc771883-3bd6-46d9-9626-a130e1b902de false +fcab9efe-fb05-42d6-b366-ce6db1cc4093 false +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 false +fce20464-ff98-4051-8714-6b9584e52740 false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingReverseResolve.csv b/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingReverseResolve.csv deleted file mode 100644 index e0689b9adb..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingReverseResolve.csv +++ /dev/null @@ -1,217 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,54c45763-20f4-4a54-a506-7be0b3405836 -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,1f7f64e2-db15-4821-ae11-cfc16ee328bf -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,3d4f9569-1a5d-40b2-9383-91de37db9af7 -121503c8-9564-4b48-9086-a22df717948e,d8ca2cfd-94cc-4ebc-b49f-22534a662e86 -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,f433abac-e782-4b76-b03a-f39e090ffba3 -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,d8c3ae50-a099-48b0-bc8d-c1fe98219afe -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,05fd5547-18ab-406b-9da1-5eea3e787e7f -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,542857ec-8ddf-4a4a-bc8c-ffd0a349101b -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,9f6e34e4-c9f7-44dd-8ad9-17ec9f31e4b3 -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,a855f6cc-0bb7-4e7b-ab51-db570c3e2b4e -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,7ab71676-8460-434c-bd00-f1164820cc7d -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,92788782-57e9-4089-b462-c26de46781a6 -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,dcca1188-e211-47fb-aa6a-269014f58bb7 -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,d77ffb18-21b6-4c2a-8b6c-0c2411870c2d -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,122ca2d5-d026-4952-929b-8df056a4a157 -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,ee50d60f-0488-465b-939c-f27a9376cb1a -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,b1775b0d-230c-4f65-963d-91ab3a446d7c -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,7a7f1ab8-d356-49ca-8514-99f31ee27ef3 -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,bb8cc17c-93c4-4f36-894f-fc7f192a6fab -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,01b9ab89-fa4e-40cb-a020-d544b82ea31d -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,a0161707-e016-4d92-a681-c1c358d8d37b -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,017e4feb-9b7f-4e5c-b8d8-f09b01145224 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,5a807931-7736-47a7-a250-b7067b461f2b -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,ca000fdd-4559-4fed-9b51-3e68a4af25d4 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,bd244a5f-9c8b-44ab-8a80-dec44de03dc9 -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,9be4c8a3-b0e8-4301-919a-89da2cfff37d -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" diff --git a/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingReverseResolve.tsv b/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingReverseResolve.tsv new file mode 100644 index 0000000000..cf881fbf48 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testReverseResolveFollowingReverseResolve.tsv @@ -0,0 +1,217 @@ +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 54c45763-20f4-4a54-a506-7be0b3405836 +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 1f7f64e2-db15-4821-ae11-cfc16ee328bf +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 3d4f9569-1a5d-40b2-9383-91de37db9af7 +121503c8-9564-4b48-9086-a22df717948e d8ca2cfd-94cc-4ebc-b49f-22534a662e86 +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e f433abac-e782-4b76-b03a-f39e090ffba3 +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e d8c3ae50-a099-48b0-bc8d-c1fe98219afe +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 05fd5547-18ab-406b-9da1-5eea3e787e7f +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 542857ec-8ddf-4a4a-bc8c-ffd0a349101b +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 9f6e34e4-c9f7-44dd-8ad9-17ec9f31e4b3 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 a855f6cc-0bb7-4e7b-ab51-db570c3e2b4e +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d 7ab71676-8460-434c-bd00-f1164820cc7d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 92788782-57e9-4089-b462-c26de46781a6 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 dcca1188-e211-47fb-aa6a-269014f58bb7 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 d77ffb18-21b6-4c2a-8b6c-0c2411870c2d +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 122ca2d5-d026-4952-929b-8df056a4a157 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 ee50d60f-0488-465b-939c-f27a9376cb1a +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 b1775b0d-230c-4f65-963d-91ab3a446d7c +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 7a7f1ab8-d356-49ca-8514-99f31ee27ef3 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 bb8cc17c-93c4-4f36-894f-fc7f192a6fab +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 01b9ab89-fa4e-40cb-a020-d544b82ea31d +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 a0161707-e016-4d92-a681-c1c358d8d37b +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 017e4feb-9b7f-4e5c-b8d8-f09b01145224 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 5a807931-7736-47a7-a250-b7067b461f2b +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 ca000fdd-4559-4fed-9b51-3e68a4af25d4 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 bd244a5f-9c8b-44ab-8a80-dec44de03dc9 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 9be4c8a3-b0e8-4301-919a-89da2cfff37d +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy-empty.csv b/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy-empty.csv deleted file mode 100644 index 313f542a1a..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy-empty.csv +++ /dev/null @@ -1,71 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -2b36c1e2-bbe1-45ae-8124-4adad2677702,false -2b36c1e2-bbe1-45ae-8124-4adad2677702,false -2b36c1e2-bbe1-45ae-8124-4adad2677702,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,true -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -beff242e-580b-47c0-9844-c1a68c36c5bf,false -beff242e-580b-47c0-9844-c1a68c36c5bf,false -beff242e-580b-47c0-9844-c1a68c36c5bf,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy-empty.tsv b/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy-empty.tsv new file mode 100644 index 0000000000..a12d112df8 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy-empty.tsv @@ -0,0 +1,71 @@ +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +2b36c1e2-bbe1-45ae-8124-4adad2677702 false +2b36c1e2-bbe1-45ae-8124-4adad2677702 false +2b36c1e2-bbe1-45ae-8124-4adad2677702 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 true +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +beff242e-580b-47c0-9844-c1a68c36c5bf false +beff242e-580b-47c0-9844-c1a68c36c5bf false +beff242e-580b-47c0-9844-c1a68c36c5bf false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy.csv b/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy.csv deleted file mode 100644 index 313f542a1a..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy.csv +++ /dev/null @@ -1,71 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -2b36c1e2-bbe1-45ae-8124-4adad2677702,false -2b36c1e2-bbe1-45ae-8124-4adad2677702,false -2b36c1e2-bbe1-45ae-8124-4adad2677702,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,true -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -beff242e-580b-47c0-9844-c1a68c36c5bf,false -beff242e-580b-47c0-9844-c1a68c36c5bf,false -beff242e-580b-47c0-9844-c1a68c36c5bf,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy.tsv b/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy.tsv new file mode 100644 index 0000000000..a12d112df8 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy.tsv @@ -0,0 +1,71 @@ +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +2b36c1e2-bbe1-45ae-8124-4adad2677702 false +2b36c1e2-bbe1-45ae-8124-4adad2677702 false +2b36c1e2-bbe1-45ae-8124-4adad2677702 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 true +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +beff242e-580b-47c0-9844-c1a68c36c5bf false +beff242e-580b-47c0-9844-c1a68c36c5bf false +beff242e-580b-47c0-9844-c1a68c36c5bf false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-empty.csv b/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-empty.csv deleted file mode 100644 index 313f542a1a..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-empty.csv +++ /dev/null @@ -1,71 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -2b36c1e2-bbe1-45ae-8124-4adad2677702,false -2b36c1e2-bbe1-45ae-8124-4adad2677702,false -2b36c1e2-bbe1-45ae-8124-4adad2677702,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,true -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -beff242e-580b-47c0-9844-c1a68c36c5bf,false -beff242e-580b-47c0-9844-c1a68c36c5bf,false -beff242e-580b-47c0-9844-c1a68c36c5bf,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-empty.tsv b/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-empty.tsv new file mode 100644 index 0000000000..a12d112df8 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-empty.tsv @@ -0,0 +1,71 @@ +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +2b36c1e2-bbe1-45ae-8124-4adad2677702 false +2b36c1e2-bbe1-45ae-8124-4adad2677702 false +2b36c1e2-bbe1-45ae-8124-4adad2677702 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 true +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +beff242e-580b-47c0-9844-c1a68c36c5bf false +beff242e-580b-47c0-9844-c1a68c36c5bf false +beff242e-580b-47c0-9844-c1a68c36c5bf false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-self.csv b/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-self.csv deleted file mode 100644 index 9c4223e5e6..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-self.csv +++ /dev/null @@ -1,71 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,true -121503c8-9564-4b48-9086-a22df717948e,true -121503c8-9564-4b48-9086-a22df717948e,true -121503c8-9564-4b48-9086-a22df717948e,true -121503c8-9564-4b48-9086-a22df717948e,true -121503c8-9564-4b48-9086-a22df717948e,true -121503c8-9564-4b48-9086-a22df717948e,true -121503c8-9564-4b48-9086-a22df717948e,true -121503c8-9564-4b48-9086-a22df717948e,true -121503c8-9564-4b48-9086-a22df717948e,true -2b36c1e2-bbe1-45ae-8124-4adad2677702,true -2b36c1e2-bbe1-45ae-8124-4adad2677702,true -2b36c1e2-bbe1-45ae-8124-4adad2677702,true -7001ad9c-34d2-4eb5-8165-5fdc2147f469,true -7001ad9c-34d2-4eb5-8165-5fdc2147f469,true -7001ad9c-34d2-4eb5-8165-5fdc2147f469,true -7001ad9c-34d2-4eb5-8165-5fdc2147f469,true -7001ad9c-34d2-4eb5-8165-5fdc2147f469,true -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,true -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,true -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,true -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,true -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,true -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,true -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,true -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -a7eb2ce7-1075-426c-addd-957b861b0e55,true -a7eb2ce7-1075-426c-addd-957b861b0e55,true -a7eb2ce7-1075-426c-addd-957b861b0e55,true -a7eb2ce7-1075-426c-addd-957b861b0e55,true -a7eb2ce7-1075-426c-addd-957b861b0e55,true -a7eb2ce7-1075-426c-addd-957b861b0e55,true -a7eb2ce7-1075-426c-addd-957b861b0e55,true -a7eb2ce7-1075-426c-addd-957b861b0e55,true -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,true -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,true -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,true -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,true -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,true -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,true -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,true -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,true -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,true -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,true -beff242e-580b-47c0-9844-c1a68c36c5bf,true -beff242e-580b-47c0-9844-c1a68c36c5bf,true -beff242e-580b-47c0-9844-c1a68c36c5bf,true -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,true -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,true -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,true -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,true -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,true -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,true -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,true -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,true diff --git a/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-self.tsv b/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-self.tsv new file mode 100644 index 0000000000..bd101b5f92 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-self.tsv @@ -0,0 +1,71 @@ +121503c8-9564-4b48-9086-a22df717948e true +121503c8-9564-4b48-9086-a22df717948e true +121503c8-9564-4b48-9086-a22df717948e true +121503c8-9564-4b48-9086-a22df717948e true +121503c8-9564-4b48-9086-a22df717948e true +121503c8-9564-4b48-9086-a22df717948e true +121503c8-9564-4b48-9086-a22df717948e true +121503c8-9564-4b48-9086-a22df717948e true +121503c8-9564-4b48-9086-a22df717948e true +121503c8-9564-4b48-9086-a22df717948e true +2b36c1e2-bbe1-45ae-8124-4adad2677702 true +2b36c1e2-bbe1-45ae-8124-4adad2677702 true +2b36c1e2-bbe1-45ae-8124-4adad2677702 true +7001ad9c-34d2-4eb5-8165-5fdc2147f469 true +7001ad9c-34d2-4eb5-8165-5fdc2147f469 true +7001ad9c-34d2-4eb5-8165-5fdc2147f469 true +7001ad9c-34d2-4eb5-8165-5fdc2147f469 true +7001ad9c-34d2-4eb5-8165-5fdc2147f469 true +8ee183e2-b3c0-4151-be94-b945d6aa8c6d true +8ee183e2-b3c0-4151-be94-b945d6aa8c6d true +8ee183e2-b3c0-4151-be94-b945d6aa8c6d true +8ee183e2-b3c0-4151-be94-b945d6aa8c6d true +8ee183e2-b3c0-4151-be94-b945d6aa8c6d true +8ee183e2-b3c0-4151-be94-b945d6aa8c6d true +8ee183e2-b3c0-4151-be94-b945d6aa8c6d true +8ee183e2-b3c0-4151-be94-b945d6aa8c6d true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +a7eb2ce7-1075-426c-addd-957b861b0e55 true +a7eb2ce7-1075-426c-addd-957b861b0e55 true +a7eb2ce7-1075-426c-addd-957b861b0e55 true +a7eb2ce7-1075-426c-addd-957b861b0e55 true +a7eb2ce7-1075-426c-addd-957b861b0e55 true +a7eb2ce7-1075-426c-addd-957b861b0e55 true +a7eb2ce7-1075-426c-addd-957b861b0e55 true +a7eb2ce7-1075-426c-addd-957b861b0e55 true +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 true +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 true +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 true +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 true +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 true +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 true +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 true +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 true +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 true +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 true +beff242e-580b-47c0-9844-c1a68c36c5bf true +beff242e-580b-47c0-9844-c1a68c36c5bf true +beff242e-580b-47c0-9844-c1a68c36c5bf true +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 true +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 true +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 true +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 true +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 true +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 true +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 true +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 true diff --git a/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes.csv b/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes.csv deleted file mode 100644 index 0519b39e3d..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes.csv +++ /dev/null @@ -1,71 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,true -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -121503c8-9564-4b48-9086-a22df717948e,false -2b36c1e2-bbe1-45ae-8124-4adad2677702,false -2b36c1e2-bbe1-45ae-8124-4adad2677702,false -2b36c1e2-bbe1-45ae-8124-4adad2677702,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,true -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,true -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,true -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,false -9360820c-8602-4335-8b50-c88d627a0c20,true -9360820c-8602-4335-8b50-c88d627a0c20,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,true -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,true -a7eb2ce7-1075-426c-addd-957b861b0e55,false -a7eb2ce7-1075-426c-addd-957b861b0e55,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -beff242e-580b-47c0-9844-c1a68c36c5bf,true -beff242e-580b-47c0-9844-c1a68c36c5bf,true -beff242e-580b-47c0-9844-c1a68c36c5bf,true -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,true -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes.tsv b/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes.tsv new file mode 100644 index 0000000000..88a7588a83 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testSubsumesAndSubsumedBy-subsumes.tsv @@ -0,0 +1,71 @@ +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e true +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +121503c8-9564-4b48-9086-a22df717948e false +2b36c1e2-bbe1-45ae-8124-4adad2677702 false +2b36c1e2-bbe1-45ae-8124-4adad2677702 false +2b36c1e2-bbe1-45ae-8124-4adad2677702 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 true +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d true +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d true +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 false +9360820c-8602-4335-8b50-c88d627a0c20 true +9360820c-8602-4335-8b50-c88d627a0c20 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 true +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 true +a7eb2ce7-1075-426c-addd-957b861b0e55 false +a7eb2ce7-1075-426c-addd-957b861b0e55 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +beff242e-580b-47c0-9844-c1a68c36c5bf true +beff242e-580b-47c0-9844-c1a68c36c5bf true +beff242e-580b-47c0-9844-c1a68c36c5bf true +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 true +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 false diff --git a/fhir-server/src/test/resources/responses/ParserTest/testTranslateFunction.csv b/fhir-server/src/test/resources/responses/ParserTest/testTranslateFunction.csv deleted file mode 100644 index e6b162404b..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testTranslateFunction.csv +++ /dev/null @@ -1,96 +0,0 @@ -01fac610-c309-46f8-9a0f-6b0f55c9915a,"" -031f1c86-16ec-42e1-a155-81456e778fed,444814009-0 -031f1c86-16ec-42e1-a155-81456e778fed,444814009-1 -0447de38-1f2a-411c-9fd2-a9c62ab1f221,444814009-0 -0447de38-1f2a-411c-9fd2-a9c62ab1f221,444814009-1 -061521f0-7f7e-41a5-ad35-b30fe958dea7,195662009-0 -061521f0-7f7e-41a5-ad35-b30fe958dea7,195662009-1 -061521f0-7f7e-41a5-ad35-b30fe958dea7,195662009-2 -06e446da-6d66-4d97-a457-4abf2a0d2e24,"" -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,195662009-0 -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,195662009-1 -08b4f1b6-f99b-400d-90f1-ec5cc6ed1556,195662009-2 -1146cf52-573f-4667-97c3-abf235f9a83b,"" -1452c50c-b0b9-472c-afb7-2f9e5a3c4717,"" -171ad058-c082-46ab-b9cc-d7bba6e4e3cd,"" -1cea8432-f8a0-4746-ab5c-8f195fc07c55,"" -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,444814009-0 -1dcedab1-cf3c-46dc-859b-cc3fb63e8375,444814009-1 -21372b39-2be0-4d0d-85c4-5d7e250d8f78,"" -248e951c-e0ce-4e4b-afae-6273fd109c9d,"" -251ddff9-1588-4790-8f40-cd6ab1d9b7fc,"" -258191b8-2b42-4473-9ba2-be0ba3561767,"" -29386b12-9af9-4c21-9f82-858998b388bb,"" -31393667-dd7f-4c94-90c3-6cde75c67d3e,"" -31925a68-7a48-412c-b4f8-aef92498dda1,444814009-0 -31925a68-7a48-412c-b4f8-aef92498dda1,444814009-1 -38171e22-b35d-4161-be15-80243be37b99,195662009-0 -38171e22-b35d-4161-be15-80243be37b99,195662009-1 -38171e22-b35d-4161-be15-80243be37b99,195662009-2 -39170d67-8205-4636-84d7-d8575115d14c,"" -4025ceb3-04a6-41fb-8569-16a8dcce7ccc,"" -46d69851-eca3-42e2-b142-88c5578f6cff,444814009-0 -46d69851-eca3-42e2-b142-88c5578f6cff,444814009-1 -475461a2-3bd2-43fd-a5aa-7ce424203ae8,444814009-0 -475461a2-3bd2-43fd-a5aa-7ce424203ae8,444814009-1 -4dd7824b-4b41-4402-bc42-de016e1bfcd9,"" -5300158c-92cb-40fe-bc14-a449d7b3c1c5,"" -5378726d-7f2b-4c83-9762-eaf385915fa7,"" -549d3ef5-a05e-4b8e-acba-3d70f26a82f6,"" -55cde383-2eb1-42d9-b5c6-698d6eade389,444814009-0 -55cde383-2eb1-42d9-b5c6-698d6eade389,444814009-1 -5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325,"" -5ef84858-0480-4a34-8f43-fc962fe627b2,"" -6464e20b-55ec-4685-be3e-55bc3b9602d4,195662009-0 -6464e20b-55ec-4685-be3e-55bc3b9602d4,195662009-1 -6464e20b-55ec-4685-be3e-55bc3b9602d4,195662009-2 -66b38727-96ea-43ad-bff7-5f4df5d779a9,444814009-0 -66b38727-96ea-43ad-bff7-5f4df5d779a9,444814009-1 -6794fbaf-e715-4e22-bafa-b7e594550ff7,"" -77fc5f45-e51d-41e2-8356-daa27ebd8268,"" -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,195662009-0 -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,195662009-1 -7d18555a-54c9-4c0a-bf3b-6305d0392a2a,195662009-2 -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,444814009-0 -7ed34fb1-0bbf-4c41-a101-1316ec483aa7,444814009-1 -80787532-559e-4999-ac25-75c462ce4ef1,"" -8922ff1b-4bc5-41b4-8512-5aad1517e2eb,"" -8ddb74fc-46d5-4e94-a0ee-6761b292ae95,"" -8dfd419b-0b83-4ba7-8fe6-324e2f382bd6,"" -8f3ad6ad-a457-484e-a455-f0711e77b2ba,444814009-0 -8f3ad6ad-a457-484e-a455-f0711e77b2ba,444814009-1 -9e598086-27bb-4e50-8988-9a40eb3c178f,195662009-0 -9e598086-27bb-4e50-8988-9a40eb3c178f,195662009-1 -9e598086-27bb-4e50-8988-9a40eb3c178f,195662009-2 -9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc,"" -a16f311d-d6dc-486e-8eb0-7cd53a5333ee,"" -aa5a32b3-3993-4eb5-afce-489d0e7413cf,"" -abecd476-3911-4a6b-a1c6-b8ecea2fe63e,"" -ad679e19-3e17-4c67-8b0d-dd4f7d862207,"" -b77b04ef-5eda-418c-a6fb-528a2d0a171a,444814009-0 -b77b04ef-5eda-418c-a6fb-528a2d0a171a,444814009-1 -b8eccdce-7261-4402-9aa0-7360ae6bf53b,"" -b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc,"" -badbd940-9839-4472-aa66-3382d8823c80,"" -bb9c4fc1-795a-4492-b065-1f497fe18bb2,"" -be4b757d-70f1-464c-bb29-6f9a0f6cb05e,"" -c39927f7-d23a-475c-b325-c1de4b51e280,"" -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,444814009-0 -c879c300-7fdf-4b53-aa6a-a2b4a266b30c,444814009-1 -c892bf2f-a7bc-407e-9508-c778a9b8761c,"" -d12274a5-9e03-4c78-ae3e-6cc27e91d737,"" -d554ba19-e081-4979-9c0c-a72cd6e5e8ea,"" -da8db730-f051-42d0-a2db-a3577d96e9bd,"" -db8c0b3e-48f0-43ac-aa03-9ae9a251cb82,"" -df98a2ea-8129-4d1a-9a9f-13a0292f6d1d,"" -e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76,"" -e35f5823-4533-49e5-9652-79d733be6bef,"" -e620d7ee-6cfe-4f04-ba06-1d0b39f7624d,"" -e87fbe4b-74ef-44e4-8e36-70b2abb48895,"" -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,444814009-0 -eaee8845-e4ed-42e1-9098-99b3f7e14dc3,444814009-1 -eaf99c09-419d-4162-8417-5a9d7e042cd4,"" -eb373264-da60-4e98-af7c-e3021fdd8d4b,"" -f1dd0d5f-c410-484b-984c-2ba38cee79ba,"" -f1fd855c-802c-417a-ab7c-14a3c9daafc6,"" -f41b6458-2358-43e7-ae34-d2a0fbe083d8,"" diff --git a/fhir-server/src/test/resources/responses/ParserTest/testTranslateFunction.tsv b/fhir-server/src/test/resources/responses/ParserTest/testTranslateFunction.tsv new file mode 100644 index 0000000000..6245c54b10 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testTranslateFunction.tsv @@ -0,0 +1,96 @@ +01fac610-c309-46f8-9a0f-6b0f55c9915a +031f1c86-16ec-42e1-a155-81456e778fed 444814009-0 +031f1c86-16ec-42e1-a155-81456e778fed 444814009-1 +0447de38-1f2a-411c-9fd2-a9c62ab1f221 444814009-0 +0447de38-1f2a-411c-9fd2-a9c62ab1f221 444814009-1 +061521f0-7f7e-41a5-ad35-b30fe958dea7 195662009-0 +061521f0-7f7e-41a5-ad35-b30fe958dea7 195662009-1 +061521f0-7f7e-41a5-ad35-b30fe958dea7 195662009-2 +06e446da-6d66-4d97-a457-4abf2a0d2e24 +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 195662009-0 +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 195662009-1 +08b4f1b6-f99b-400d-90f1-ec5cc6ed1556 195662009-2 +1146cf52-573f-4667-97c3-abf235f9a83b +1452c50c-b0b9-472c-afb7-2f9e5a3c4717 +171ad058-c082-46ab-b9cc-d7bba6e4e3cd +1cea8432-f8a0-4746-ab5c-8f195fc07c55 +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 444814009-0 +1dcedab1-cf3c-46dc-859b-cc3fb63e8375 444814009-1 +21372b39-2be0-4d0d-85c4-5d7e250d8f78 +248e951c-e0ce-4e4b-afae-6273fd109c9d +251ddff9-1588-4790-8f40-cd6ab1d9b7fc +258191b8-2b42-4473-9ba2-be0ba3561767 +29386b12-9af9-4c21-9f82-858998b388bb +31393667-dd7f-4c94-90c3-6cde75c67d3e +31925a68-7a48-412c-b4f8-aef92498dda1 444814009-0 +31925a68-7a48-412c-b4f8-aef92498dda1 444814009-1 +38171e22-b35d-4161-be15-80243be37b99 195662009-0 +38171e22-b35d-4161-be15-80243be37b99 195662009-1 +38171e22-b35d-4161-be15-80243be37b99 195662009-2 +39170d67-8205-4636-84d7-d8575115d14c +4025ceb3-04a6-41fb-8569-16a8dcce7ccc +46d69851-eca3-42e2-b142-88c5578f6cff 444814009-0 +46d69851-eca3-42e2-b142-88c5578f6cff 444814009-1 +475461a2-3bd2-43fd-a5aa-7ce424203ae8 444814009-0 +475461a2-3bd2-43fd-a5aa-7ce424203ae8 444814009-1 +4dd7824b-4b41-4402-bc42-de016e1bfcd9 +5300158c-92cb-40fe-bc14-a449d7b3c1c5 +5378726d-7f2b-4c83-9762-eaf385915fa7 +549d3ef5-a05e-4b8e-acba-3d70f26a82f6 +55cde383-2eb1-42d9-b5c6-698d6eade389 444814009-0 +55cde383-2eb1-42d9-b5c6-698d6eade389 444814009-1 +5a406b78-ae23-4b4f-a3c8-9d9ed2f9d325 +5ef84858-0480-4a34-8f43-fc962fe627b2 +6464e20b-55ec-4685-be3e-55bc3b9602d4 195662009-0 +6464e20b-55ec-4685-be3e-55bc3b9602d4 195662009-1 +6464e20b-55ec-4685-be3e-55bc3b9602d4 195662009-2 +66b38727-96ea-43ad-bff7-5f4df5d779a9 444814009-0 +66b38727-96ea-43ad-bff7-5f4df5d779a9 444814009-1 +6794fbaf-e715-4e22-bafa-b7e594550ff7 +77fc5f45-e51d-41e2-8356-daa27ebd8268 +7d18555a-54c9-4c0a-bf3b-6305d0392a2a 195662009-0 +7d18555a-54c9-4c0a-bf3b-6305d0392a2a 195662009-1 +7d18555a-54c9-4c0a-bf3b-6305d0392a2a 195662009-2 +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 444814009-0 +7ed34fb1-0bbf-4c41-a101-1316ec483aa7 444814009-1 +80787532-559e-4999-ac25-75c462ce4ef1 +8922ff1b-4bc5-41b4-8512-5aad1517e2eb +8ddb74fc-46d5-4e94-a0ee-6761b292ae95 +8dfd419b-0b83-4ba7-8fe6-324e2f382bd6 +8f3ad6ad-a457-484e-a455-f0711e77b2ba 444814009-0 +8f3ad6ad-a457-484e-a455-f0711e77b2ba 444814009-1 +9e598086-27bb-4e50-8988-9a40eb3c178f 195662009-0 +9e598086-27bb-4e50-8988-9a40eb3c178f 195662009-1 +9e598086-27bb-4e50-8988-9a40eb3c178f 195662009-2 +9ed0f743-5a93-46ba-bf3f-e00c6f8c5bbc +a16f311d-d6dc-486e-8eb0-7cd53a5333ee +aa5a32b3-3993-4eb5-afce-489d0e7413cf +abecd476-3911-4a6b-a1c6-b8ecea2fe63e +ad679e19-3e17-4c67-8b0d-dd4f7d862207 +b77b04ef-5eda-418c-a6fb-528a2d0a171a 444814009-0 +b77b04ef-5eda-418c-a6fb-528a2d0a171a 444814009-1 +b8eccdce-7261-4402-9aa0-7360ae6bf53b +b972a4a8-4f7e-4e9b-8e2c-71140bf0c6cc +badbd940-9839-4472-aa66-3382d8823c80 +bb9c4fc1-795a-4492-b065-1f497fe18bb2 +be4b757d-70f1-464c-bb29-6f9a0f6cb05e +c39927f7-d23a-475c-b325-c1de4b51e280 +c879c300-7fdf-4b53-aa6a-a2b4a266b30c 444814009-0 +c879c300-7fdf-4b53-aa6a-a2b4a266b30c 444814009-1 +c892bf2f-a7bc-407e-9508-c778a9b8761c +d12274a5-9e03-4c78-ae3e-6cc27e91d737 +d554ba19-e081-4979-9c0c-a72cd6e5e8ea +da8db730-f051-42d0-a2db-a3577d96e9bd +db8c0b3e-48f0-43ac-aa03-9ae9a251cb82 +df98a2ea-8129-4d1a-9a9f-13a0292f6d1d +e2f8e85b-ed68-42a1-bae2-b5d7c2a69a76 +e35f5823-4533-49e5-9652-79d733be6bef +e620d7ee-6cfe-4f04-ba06-1d0b39f7624d +e87fbe4b-74ef-44e4-8e36-70b2abb48895 +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 444814009-0 +eaee8845-e4ed-42e1-9098-99b3f7e14dc3 444814009-1 +eaf99c09-419d-4162-8417-5a9d7e042cd4 +eb373264-da60-4e98-af7c-e3021fdd8d4b +f1dd0d5f-c410-484b-984c-2ba38cee79ba +f1fd855c-802c-417a-ab7c-14a3c9daafc6 +f41b6458-2358-43e7-ae34-d2a0fbe083d8 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testTranslateWithWhereAndTranslate.csv b/fhir-server/src/test/resources/responses/ParserTest/testTranslateWithWhereAndTranslate.csv deleted file mode 100644 index 2631f34bbc..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testTranslateWithWhereAndTranslate.csv +++ /dev/null @@ -1,4 +0,0 @@ -01fac610-c309-46f8-9a0f-6b0f55c9915a,"" -031f1c86-16ec-42e1-a155-81456e778fed,444814009-0 -031f1c86-16ec-42e1-a155-81456e778fed,"" -061521f0-7f7e-41a5-ad35-b30fe958dea7,"" diff --git a/fhir-server/src/test/resources/responses/ParserTest/testTranslateWithWhereAndTranslate.tsv b/fhir-server/src/test/resources/responses/ParserTest/testTranslateWithWhereAndTranslate.tsv new file mode 100644 index 0000000000..c187968a69 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testTranslateWithWhereAndTranslate.tsv @@ -0,0 +1,4 @@ +01fac610-c309-46f8-9a0f-6b0f55c9915a +031f1c86-16ec-42e1-a155-81456e778fed 444814009-0 +031f1c86-16ec-42e1-a155-81456e778fed +061521f0-7f7e-41a5-ad35-b30fe958dea7 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testUntilFunction.csv b/fhir-server/src/test/resources/responses/ParserTest/testUntilFunction.csv deleted file mode 100644 index 43e711ecb7..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testUntilFunction.csv +++ /dev/null @@ -1,217 +0,0 @@ -0134901a-4a69-4b39-92ea-d6f48ec83c6e,19 -01ac1476-0c9c-4cd7-82a6-4ff526018e9a,56 -0250a217-a663-403b-a708-5d14eadf0c40,42 -02b993c0-b358-481c-b0d0-0767387feb9f,0 -0364073f-91d8-47a1-b8b0-107c6318a691,58 -04945715-8ad5-4d8f-bfda-ae26662a3610,28 -04d88eb4-fb1b-4fa1-802a-5624f4c61b32,49 -07bb3bb3-09e6-4abf-85f4-8ad113e7afa5,46 -0af4a77e-892e-4b3f-9110-4c5224782250,51 -0c315d37-c758-4cf5-a7cb-cbf712fa7dfd,50 -102e16c5-4920-4dad-b142-0b280b2aacad,56 -11dafd5c-85e7-4275-a147-89a63641e35e,1 -12b212d8-bc6e-415a-93b0-594381726668,44 -12df1bed-5472-4c48-8e88-2cbb3e5eab33,50 -151e3048-66ad-4411-8753-677877e3bf0a,43 -16a3408d-c927-4d02-a1ae-cad32419caab,56 -16d280a2-c61f-487f-9034-22e884158969,15 -17bcf2ea-d921-4715-91c5-6b15226b33d3,17 -18ebf5ea-b0a2-4333-9e9c-40217de809ff,43 -19502b5a-2031-487d-9d9c-2001f959408b,51 -1cb88e7a-3db6-4a88-9b68-b0e1492114bc,56 -1db1c71b-9eeb-4a98-abc1-eb699b38510c,25 -1f3443ee-d15e-4894-b18e-185cfadfcdea,48 -1fd714a6-48b4-425a-99b3-ba5c1a995cce,58 -202131ae-78bb-4104-89b0-fb90645760f6,0 -20fbcb6e-d793-4b05-8e29-8ff82572b0db,59 -21995497-0eb8-4c0b-8c23-e6a829f3d596,50 -21c6e0fc-bf47-4f80-b1ff-85b940445bdf,49 -224e538d-3622-4f5e-b772-211ed358d8de,48 -23780032-f3bb-4b40-af2e-3fa6b1677376,45 -24c4dd5b-748b-4165-a0cc-2867b76c2dc2,13 -2514cdf0-1216-4c5e-bfa8-0c11c20c6b93,48 -274a2e49-9170-4945-bca2-ab6ef4bded75,26 -2830e99a-e6b0-411b-a051-7ea1e9f815d1,56 -29d4e919-2bd2-44e6-bb8a-380a780f60ff,54 -2aff9edd-def2-487a-b435-a162e11a303c,11 -2bd766e5-60e4-4469-bb97-54f0503a1eb0,43 -2bddd98c-86e3-4ae0-9dc6-87da16ab6267,54 -2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5,50 -2d2e07ec-e697-4384-a679-867e93740921,40 -2de1f829-c9d2-4f22-bf39-536dcb82fc3e,48 -2e8eb256-84c9-499a-8bf6-bb39504373e1,51 -2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f,50 -2ee2ab26-09fb-4e12-bf21-9fed3b4c38de,51 -2fc75f11-2097-4e1e-8390-dbff3e844b7a,3 -2ffd6142-b75b-406f-bc06-cdb1c02eaefc,44 -308eba53-29fa-489a-97dc-741b077841a7,49 -30ae4a13-0cc6-4d12-a5dd-fa9288efcc09,37 -31d4c712-75b1-4ecb-9d1a-ccc4b8453826,12 -3397a796-894d-409c-97de-a9e6f3f88198,50 -359cb842-c25b-429f-ba9b-d52f171e5631,47 -36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7,49 -374fd699-6b74-4500-9d60-2473c7e0364f,54 -395019b6-84e8-4919-8b8b-ac64e4a6eade,43 -39efc9b8-a75f-43a3-98a2-5bdc1c8207d2,11 -3ddf46fe-b5be-456d-810f-ea6a7402236d,54 -4148ac36-1dcb-4e96-89cd-355e7e8f919b,54 -41d26b1c-57b9-408c-b955-bf0ee1db4809,55 -4215b5d0-bdd9-4222-a04a-81bb637d60af,56 -42a9a333-d09d-4b9e-af96-1f02af26bac3,60 -42aa48c7-68cc-4bee-9730-cff29f0e36c5,43 -4506aa76-9d0d-40e4-85bc-5735f74522a0,53 -45b25ced-6eed-4fca-8ec1-b7d32ea13efe,50 -45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3,12 -48cc2275-a478-4ade-b076-cf38e1d16017,58 -498f4b18-bd4c-470d-9cde-2fca70ec8964,3 -4a464bb5-9373-4f1b-9c03-053c64797114,2 -4adcaf72-5241-4877-b61c-f34060576c50,50 -4cc605d0-d0c6-4de3-849a-f5f92b35d2a0,20 -4cd95073-6db3-4eb2-ac4b-0aacb182b352,53 -4cfe72fe-21a0-4201-87b6-ef93695d2495,1 -4db144d3-a596-4718-a50a-8ac9175ad386,56 -4e6b69c7-61f9-4ff5-8c33-ad400edb9b06,43 -4f14ca7f-5332-4263-a7b2-f0a838380309,46 -4f342a71-dec3-403e-970b-e4c21b9eb98b,55 -4f3d1e93-a28f-446e-91af-2baf0168b82b,45 -50eac25a-3761-4de3-8a69-aae52e658300,60 -51241857-a7a4-4517-96e3-21f797581f89,50 -522e2abe-ad96-4d1a-b5a5-748faa997531,0 -54c750aa-570a-4e8e-9a02-418781923e39,3 -55f17f44-287f-41aa-a1bc-d1c0104af36e,48 -56999896-e36b-4e63-a6b6-dbb85dfe1936,59 -578cc35c-0982-4d72-a729-b304c026f075,48 -5abe533b-ae5f-47ad-8746-f60caf7483c0,40 -5cedf18a-fc44-4c39-a80f-2106a0d76934,53 -5e1fe0f2-4517-462b-8287-7824f9a60f4f,58 -5f64131b-d410-449a-9de6-35415919fec5,46 -62059efc-7607-41c9-a3ba-70948f6a8a1d,54 -634f0889-3a83-484a-bcc8-86f48e333c7b,49 -67e5cf95-236b-4f75-abc5-034ca2966448,22 -67fc12fc-bb9f-40f1-9051-127a788b3081,39 -683714ad-5194-49e7-9b8f-4e306cf68ae1,46 -6954c5c2-dd77-490a-9152-f1d78eff913d,52 -6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0,10 -6af73cae-2e4e-485f-a74d-6f4307eb3af3,52 -6c84348c-5065-4e89-9412-bfda023683f2,42 -6d47a2fe-3645-4c5c-bebe-1b822eff197f,50 -6e187f47-91a1-4217-a185-31b6f01db225,57 -720f7ed6-7b2e-41dc-a0e0-724a3332aa24,50 -721e4027-a080-40d8-bc4a-43cd33477611,26 -72788e7f-1bf1-40b5-a1f1-27c669dc7278,46 -72c611bb-471e-40f2-a6a8-0ae7b7b6b63e,45 -749babeb-6883-4bd4-92a5-bec52769071c,44 -75a09d4f-eef2-4404-a386-dfcb408ec9ed,18 -777aa5f2-2a09-4aed-92ea-912694e28b48,53 -79be3b69-23d8-4408-8f01-68d993e63b6c,34 -7ac56d5a-32a7-4b40-a956-356e3006faed,17 -7e6fe8ca-a95e-4102-8b18-e6e858cd15a8,49 -7e7322a6-7912-4101-b3be-d134245a0626,55 -7ea43fc6-b1f7-46be-a308-5ecb275a1081,40 -7f410064-638a-4477-85c4-97fc2bb14a49,2 -8089a9ac-9e71-4487-a231-f720e4bc8d3e,50 -836b2e59-fb97-4014-ab0c-d5a5dc750969,0 -85481b3f-c75e-40cd-bacd-b0afb79e893c,50 -85c997bf-63d9-4ebb-8e0b-320de3dddc6c,48 -85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b,42 -86415df5-7e47-4637-9e09-aaad9e7628d1,46 -8743e69b-17aa-44ff-b8fa-4f582665efc6,8 -87b04a97-1d33-4548-aec9-3d2856070702,56 -88b16960-bfff-41d0-81e4-9a4630834a00,2 -89f260bb-68b4-4dec-91f4-d52e673e0ff7,43 -8a1908f7-47cc-4f82-859c-781a193e9901,54 -8bc7af32-e5ad-4849-ba4d-b446db833ab4,44 -8be81657-8ba6-4ec5-8ff9-4809367096ea,54 -8dc2ce26-aec7-43c0-9771-350af4257ad8,50 -8e9e848c-0547-4f2c-8b5f-e33d79bbed67,3 -8f76d729-9f74-4cfd-be2a-8b033b568e18,18 -900ce9fe-02d3-476b-902a-9467767ecdcf,46 -922e2443-9cc5-421f-8310-9cd23f6e9f2d,48 -96b2282d-1384-4cfb-9958-f009fb501626,18 -97b42d67-8691-433d-8a31-dada076162ae,46 -984067fc-3dfc-47b7-8ee0-4d9b27d43860,44 -990c21ab-433b-4920-873e-f9dfc7103d9d,49 -99fc3558-79f3-4d14-9aa1-ff63f621ecd9,45 -9b2eb632-6f1a-4e97-912b-3c8378a1b11c,42 -9b4f52b0-51fa-4ea6-b02c-2ed8478066d3,51 -9bc516d4-7c82-4340-a90c-bb493f96fbe4,53 -9e0b8dc3-65c4-490d-97c0-d5e3db944de5,56 -9ed8703e-3b40-424c-9e98-b3b404051f99,52 -a1228f20-7c50-4d3a-b88c-2d277ca79d79,9 -a221198d-000e-497b-8b70-bb4d37f7bbe1,6 -a2c3ee1d-ec35-4b26-9bab-12de3f47d604,40 -a6790cbd-8349-432e-a976-5dfc07451203,44 -a6b084fb-ada7-480a-9adc-f180d4eb1e2a,50 -a6cb423a-7571-46df-83e2-ccc6820adb36,46 -a89c0c42-c19f-4d7f-a15e-d04e043c92f6,0 -acb2329d-b1c3-45c3-ad28-91a09aa521e1,40 -add6d754-a075-4693-a33a-c061c9a368ff,58 -ae7ddaef-4911-418c-8401-d978617e145b,23 -ae9e663e-8c15-43c9-8a88-52b61cbf07a9,56 -b00df815-7528-4967-bfe2-311130d91c21,26 -b07fb98d-2da4-423a-83b4-7a673de93096,48 -b0d337c5-3555-4817-ba59-4ceea5ad8a91,31 -b1ccd55a-6b88-4240-b20c-ca893f36e23b,42 -b1d4dc41-07ae-44db-ae17-1df86c5a62cf,56 -b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa,52 -b6900c21-d310-4fb4-8b8f-176a09c91989,4 -b7772635-1801-48e4-a442-f4aaa6860544,41 -b870d2cb-ea6a-4b67-8de5-ce1224a18bf2,48 -bac374c0-50e8-4a5c-947b-82a8e9a747a9,50 -baf48725-f0f9-4357-a71d-b1104910deae,50 -bb1a7816-4798-4f58-9ce4-c5bd3af682a8,61 -bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb,2 -bf895c48-1d52-4780-8e9a-532d69356d28,18 -bfc93cb7-e53a-49f7-b86e-81d6a13cfdea,46 -c06a9f9b-90fa-4bd6-9581-0c6950f03e4a,44 -c3ec1f2e-1411-41e4-8370-acd5a8f68cf4,54 -c57d51a7-45de-41b9-92fc-efd0634effaf,5 -c5c6eed5-aec6-4b8a-b689-adbb219c2267,4 -c892b1d7-7485-407d-8883-54fb7fecebc1,1 -c90aae7e-5704-4e13-8794-41ab377193bd,58 -ca3e0486-fd6b-4a13-a741-3a47760b412d,5 -ca7f269d-3794-4994-8c46-d8e9f2aa6378,56 -cc56af6d-25c6-480e-9767-da02a55551da,56 -cdb8493c-e3b0-447f-b16b-e58dd64660f3,55 -cf67a8d4-48e2-4dc9-a521-6aabcff0330b,56 -cfcbe34a-edc5-4442-bc05-5927fa00336b,55 -d05461d6-13bf-402c-890d-db6404933f7c,49 -d080c116-681a-494a-a928-45924109b49d,59 -d535b213-2abc-438f-aa6a-c06476d60b09,3 -d6988116-3c63-44f8-9f94-9e9d51cc5a37,52 -d79728e1-8834-4f4a-8edc-ce18aca18be6,60 -d7a48a26-7731-4046-bf22-78f0b8084eb9,43 -d90676d2-ce74-4867-a00f-6dbffa7c00be,56 -d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2,50 -db79f75d-af3c-4f82-b4e5-9b5d26598eef,42 -def5fd23-2b74-4c64-85e9-fac27e626bb7,18 -dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8,43 -e0dfbc08-45ad-4a81-b648-7e65c066f673,1 -e1040d58-53bc-4467-8101-ca53b772cede,45 -e1c2ad9f-6f61-4f16-805a-2f405b63659a,57 -e2387a81-5ff5-4daf-b2e8-881998d0d917,57 -e26ae29a-213a-4f79-98c2-cc81cf2f451d,1 -e363eb67-64c7-440f-93fd-5c8787c69a85,49 -e4358f28-62a8-4e06-b2bd-cb00d3310349,16 -e7f68d35-ae55-4fa4-b0be-0672a5a7186a,45 -e8c41168-a093-40eb-8baa-6802d24f554a,59 -e8eba267-fecb-477e-9625-771fbcfc3cba,56 -ede7745a-8f3e-4e20-9f16-33756be7ab6c,0 -ee24bf89-81a3-4259-a382-86917f3829d4,50 -f1293c5e-c997-4ba9-8bfd-438e7a840dc8,17 -f18b08bd-40da-43c1-87ec-4ba23542635f,44 -f19eefca-c1ad-4860-bdda-4674a631d464,28 -f28931e5-1f35-4f9f-a02e-78398735ea67,46 -f29330d0-5b32-4f75-b558-a1c7f05c0b4a,56 -f2c9ca81-8b1e-4873-bd8a-1a6e1411193c,30 -f3aa491a-4dbd-4436-b674-18b2177dcf18,54 -f3c2f842-6aa3-42c9-a0f3-895c40456868,0 -f3eb0c38-2571-44e7-be39-732eb52cf1df,1 -f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5,47 -f77a8561-5adc-452a-ac9a-76273b6a6678,62 -f883f2ca-d523-4c9f-86b2-0add137901de,23 -fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c,43 -fc771883-3bd6-46d9-9626-a130e1b902de,53 -fcab9efe-fb05-42d6-b366-ce6db1cc4093,14 -fcc916dd-5e4f-4bf7-9aef-c906a36d7301,50 -fce20464-ff98-4051-8714-6b9584e52740,7 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testUntilFunction.tsv b/fhir-server/src/test/resources/responses/ParserTest/testUntilFunction.tsv new file mode 100644 index 0000000000..71b9c24355 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testUntilFunction.tsv @@ -0,0 +1,217 @@ +0134901a-4a69-4b39-92ea-d6f48ec83c6e 19 +01ac1476-0c9c-4cd7-82a6-4ff526018e9a 56 +0250a217-a663-403b-a708-5d14eadf0c40 42 +02b993c0-b358-481c-b0d0-0767387feb9f 0 +0364073f-91d8-47a1-b8b0-107c6318a691 58 +04945715-8ad5-4d8f-bfda-ae26662a3610 28 +04d88eb4-fb1b-4fa1-802a-5624f4c61b32 49 +07bb3bb3-09e6-4abf-85f4-8ad113e7afa5 46 +0af4a77e-892e-4b3f-9110-4c5224782250 51 +0c315d37-c758-4cf5-a7cb-cbf712fa7dfd 50 +102e16c5-4920-4dad-b142-0b280b2aacad 56 +11dafd5c-85e7-4275-a147-89a63641e35e 1 +12b212d8-bc6e-415a-93b0-594381726668 44 +12df1bed-5472-4c48-8e88-2cbb3e5eab33 50 +151e3048-66ad-4411-8753-677877e3bf0a 43 +16a3408d-c927-4d02-a1ae-cad32419caab 56 +16d280a2-c61f-487f-9034-22e884158969 15 +17bcf2ea-d921-4715-91c5-6b15226b33d3 17 +18ebf5ea-b0a2-4333-9e9c-40217de809ff 43 +19502b5a-2031-487d-9d9c-2001f959408b 51 +1cb88e7a-3db6-4a88-9b68-b0e1492114bc 56 +1db1c71b-9eeb-4a98-abc1-eb699b38510c 25 +1f3443ee-d15e-4894-b18e-185cfadfcdea 48 +1fd714a6-48b4-425a-99b3-ba5c1a995cce 58 +202131ae-78bb-4104-89b0-fb90645760f6 0 +20fbcb6e-d793-4b05-8e29-8ff82572b0db 59 +21995497-0eb8-4c0b-8c23-e6a829f3d596 50 +21c6e0fc-bf47-4f80-b1ff-85b940445bdf 49 +224e538d-3622-4f5e-b772-211ed358d8de 48 +23780032-f3bb-4b40-af2e-3fa6b1677376 45 +24c4dd5b-748b-4165-a0cc-2867b76c2dc2 13 +2514cdf0-1216-4c5e-bfa8-0c11c20c6b93 48 +274a2e49-9170-4945-bca2-ab6ef4bded75 26 +2830e99a-e6b0-411b-a051-7ea1e9f815d1 56 +29d4e919-2bd2-44e6-bb8a-380a780f60ff 54 +2aff9edd-def2-487a-b435-a162e11a303c 11 +2bd766e5-60e4-4469-bb97-54f0503a1eb0 43 +2bddd98c-86e3-4ae0-9dc6-87da16ab6267 54 +2cd8b9e4-2881-45a4-b1a6-c3a38e9d59d5 50 +2d2e07ec-e697-4384-a679-867e93740921 40 +2de1f829-c9d2-4f22-bf39-536dcb82fc3e 48 +2e8eb256-84c9-499a-8bf6-bb39504373e1 51 +2ec33cf5-d22d-414b-bf5d-e4b4e9997c0f 50 +2ee2ab26-09fb-4e12-bf21-9fed3b4c38de 51 +2fc75f11-2097-4e1e-8390-dbff3e844b7a 3 +2ffd6142-b75b-406f-bc06-cdb1c02eaefc 44 +308eba53-29fa-489a-97dc-741b077841a7 49 +30ae4a13-0cc6-4d12-a5dd-fa9288efcc09 37 +31d4c712-75b1-4ecb-9d1a-ccc4b8453826 12 +3397a796-894d-409c-97de-a9e6f3f88198 50 +359cb842-c25b-429f-ba9b-d52f171e5631 47 +36160225-76a2-4cf1-8bf8-5a3d3f2ec9d7 49 +374fd699-6b74-4500-9d60-2473c7e0364f 54 +395019b6-84e8-4919-8b8b-ac64e4a6eade 43 +39efc9b8-a75f-43a3-98a2-5bdc1c8207d2 11 +3ddf46fe-b5be-456d-810f-ea6a7402236d 54 +4148ac36-1dcb-4e96-89cd-355e7e8f919b 54 +41d26b1c-57b9-408c-b955-bf0ee1db4809 55 +4215b5d0-bdd9-4222-a04a-81bb637d60af 56 +42a9a333-d09d-4b9e-af96-1f02af26bac3 60 +42aa48c7-68cc-4bee-9730-cff29f0e36c5 43 +4506aa76-9d0d-40e4-85bc-5735f74522a0 53 +45b25ced-6eed-4fca-8ec1-b7d32ea13efe 50 +45ec3b9e-eb82-48b4-b4b6-bd305afdf8b3 12 +48cc2275-a478-4ade-b076-cf38e1d16017 58 +498f4b18-bd4c-470d-9cde-2fca70ec8964 3 +4a464bb5-9373-4f1b-9c03-053c64797114 2 +4adcaf72-5241-4877-b61c-f34060576c50 50 +4cc605d0-d0c6-4de3-849a-f5f92b35d2a0 20 +4cd95073-6db3-4eb2-ac4b-0aacb182b352 53 +4cfe72fe-21a0-4201-87b6-ef93695d2495 1 +4db144d3-a596-4718-a50a-8ac9175ad386 56 +4e6b69c7-61f9-4ff5-8c33-ad400edb9b06 43 +4f14ca7f-5332-4263-a7b2-f0a838380309 46 +4f342a71-dec3-403e-970b-e4c21b9eb98b 55 +4f3d1e93-a28f-446e-91af-2baf0168b82b 45 +50eac25a-3761-4de3-8a69-aae52e658300 60 +51241857-a7a4-4517-96e3-21f797581f89 50 +522e2abe-ad96-4d1a-b5a5-748faa997531 0 +54c750aa-570a-4e8e-9a02-418781923e39 3 +55f17f44-287f-41aa-a1bc-d1c0104af36e 48 +56999896-e36b-4e63-a6b6-dbb85dfe1936 59 +578cc35c-0982-4d72-a729-b304c026f075 48 +5abe533b-ae5f-47ad-8746-f60caf7483c0 40 +5cedf18a-fc44-4c39-a80f-2106a0d76934 53 +5e1fe0f2-4517-462b-8287-7824f9a60f4f 58 +5f64131b-d410-449a-9de6-35415919fec5 46 +62059efc-7607-41c9-a3ba-70948f6a8a1d 54 +634f0889-3a83-484a-bcc8-86f48e333c7b 49 +67e5cf95-236b-4f75-abc5-034ca2966448 22 +67fc12fc-bb9f-40f1-9051-127a788b3081 39 +683714ad-5194-49e7-9b8f-4e306cf68ae1 46 +6954c5c2-dd77-490a-9152-f1d78eff913d 52 +6a5d8a68-bbe0-4408-ada5-a843ee6bd9b0 10 +6af73cae-2e4e-485f-a74d-6f4307eb3af3 52 +6c84348c-5065-4e89-9412-bfda023683f2 42 +6d47a2fe-3645-4c5c-bebe-1b822eff197f 50 +6e187f47-91a1-4217-a185-31b6f01db225 57 +720f7ed6-7b2e-41dc-a0e0-724a3332aa24 50 +721e4027-a080-40d8-bc4a-43cd33477611 26 +72788e7f-1bf1-40b5-a1f1-27c669dc7278 46 +72c611bb-471e-40f2-a6a8-0ae7b7b6b63e 45 +749babeb-6883-4bd4-92a5-bec52769071c 44 +75a09d4f-eef2-4404-a386-dfcb408ec9ed 18 +777aa5f2-2a09-4aed-92ea-912694e28b48 53 +79be3b69-23d8-4408-8f01-68d993e63b6c 34 +7ac56d5a-32a7-4b40-a956-356e3006faed 17 +7e6fe8ca-a95e-4102-8b18-e6e858cd15a8 49 +7e7322a6-7912-4101-b3be-d134245a0626 55 +7ea43fc6-b1f7-46be-a308-5ecb275a1081 40 +7f410064-638a-4477-85c4-97fc2bb14a49 2 +8089a9ac-9e71-4487-a231-f720e4bc8d3e 50 +836b2e59-fb97-4014-ab0c-d5a5dc750969 0 +85481b3f-c75e-40cd-bacd-b0afb79e893c 50 +85c997bf-63d9-4ebb-8e0b-320de3dddc6c 48 +85e41a01-04e5-44d2-8b3a-4d3ad7b0f87b 42 +86415df5-7e47-4637-9e09-aaad9e7628d1 46 +8743e69b-17aa-44ff-b8fa-4f582665efc6 8 +87b04a97-1d33-4548-aec9-3d2856070702 56 +88b16960-bfff-41d0-81e4-9a4630834a00 2 +89f260bb-68b4-4dec-91f4-d52e673e0ff7 43 +8a1908f7-47cc-4f82-859c-781a193e9901 54 +8bc7af32-e5ad-4849-ba4d-b446db833ab4 44 +8be81657-8ba6-4ec5-8ff9-4809367096ea 54 +8dc2ce26-aec7-43c0-9771-350af4257ad8 50 +8e9e848c-0547-4f2c-8b5f-e33d79bbed67 3 +8f76d729-9f74-4cfd-be2a-8b033b568e18 18 +900ce9fe-02d3-476b-902a-9467767ecdcf 46 +922e2443-9cc5-421f-8310-9cd23f6e9f2d 48 +96b2282d-1384-4cfb-9958-f009fb501626 18 +97b42d67-8691-433d-8a31-dada076162ae 46 +984067fc-3dfc-47b7-8ee0-4d9b27d43860 44 +990c21ab-433b-4920-873e-f9dfc7103d9d 49 +99fc3558-79f3-4d14-9aa1-ff63f621ecd9 45 +9b2eb632-6f1a-4e97-912b-3c8378a1b11c 42 +9b4f52b0-51fa-4ea6-b02c-2ed8478066d3 51 +9bc516d4-7c82-4340-a90c-bb493f96fbe4 53 +9e0b8dc3-65c4-490d-97c0-d5e3db944de5 56 +9ed8703e-3b40-424c-9e98-b3b404051f99 52 +a1228f20-7c50-4d3a-b88c-2d277ca79d79 9 +a221198d-000e-497b-8b70-bb4d37f7bbe1 6 +a2c3ee1d-ec35-4b26-9bab-12de3f47d604 40 +a6790cbd-8349-432e-a976-5dfc07451203 44 +a6b084fb-ada7-480a-9adc-f180d4eb1e2a 50 +a6cb423a-7571-46df-83e2-ccc6820adb36 46 +a89c0c42-c19f-4d7f-a15e-d04e043c92f6 0 +acb2329d-b1c3-45c3-ad28-91a09aa521e1 40 +add6d754-a075-4693-a33a-c061c9a368ff 58 +ae7ddaef-4911-418c-8401-d978617e145b 23 +ae9e663e-8c15-43c9-8a88-52b61cbf07a9 56 +b00df815-7528-4967-bfe2-311130d91c21 26 +b07fb98d-2da4-423a-83b4-7a673de93096 48 +b0d337c5-3555-4817-ba59-4ceea5ad8a91 31 +b1ccd55a-6b88-4240-b20c-ca893f36e23b 42 +b1d4dc41-07ae-44db-ae17-1df86c5a62cf 56 +b5d06aa6-b5c6-4782-a0f8-dbf4121e1baa 52 +b6900c21-d310-4fb4-8b8f-176a09c91989 4 +b7772635-1801-48e4-a442-f4aaa6860544 41 +b870d2cb-ea6a-4b67-8de5-ce1224a18bf2 48 +bac374c0-50e8-4a5c-947b-82a8e9a747a9 50 +baf48725-f0f9-4357-a71d-b1104910deae 50 +bb1a7816-4798-4f58-9ce4-c5bd3af682a8 61 +bbd0f936-0cb9-4cc9-858c-2926b2b1c1eb 2 +bf895c48-1d52-4780-8e9a-532d69356d28 18 +bfc93cb7-e53a-49f7-b86e-81d6a13cfdea 46 +c06a9f9b-90fa-4bd6-9581-0c6950f03e4a 44 +c3ec1f2e-1411-41e4-8370-acd5a8f68cf4 54 +c57d51a7-45de-41b9-92fc-efd0634effaf 5 +c5c6eed5-aec6-4b8a-b689-adbb219c2267 4 +c892b1d7-7485-407d-8883-54fb7fecebc1 1 +c90aae7e-5704-4e13-8794-41ab377193bd 58 +ca3e0486-fd6b-4a13-a741-3a47760b412d 5 +ca7f269d-3794-4994-8c46-d8e9f2aa6378 56 +cc56af6d-25c6-480e-9767-da02a55551da 56 +cdb8493c-e3b0-447f-b16b-e58dd64660f3 55 +cf67a8d4-48e2-4dc9-a521-6aabcff0330b 56 +cfcbe34a-edc5-4442-bc05-5927fa00336b 55 +d05461d6-13bf-402c-890d-db6404933f7c 49 +d080c116-681a-494a-a928-45924109b49d 59 +d535b213-2abc-438f-aa6a-c06476d60b09 3 +d6988116-3c63-44f8-9f94-9e9d51cc5a37 52 +d79728e1-8834-4f4a-8edc-ce18aca18be6 60 +d7a48a26-7731-4046-bf22-78f0b8084eb9 43 +d90676d2-ce74-4867-a00f-6dbffa7c00be 56 +d9e7dd88-2d7c-4e48-8cf9-9e33f729b7c2 50 +db79f75d-af3c-4f82-b4e5-9b5d26598eef 42 +def5fd23-2b74-4c64-85e9-fac27e626bb7 18 +dfe38e2d-23d5-45c6-86bd-f83fe3b6a1d8 43 +e0dfbc08-45ad-4a81-b648-7e65c066f673 1 +e1040d58-53bc-4467-8101-ca53b772cede 45 +e1c2ad9f-6f61-4f16-805a-2f405b63659a 57 +e2387a81-5ff5-4daf-b2e8-881998d0d917 57 +e26ae29a-213a-4f79-98c2-cc81cf2f451d 1 +e363eb67-64c7-440f-93fd-5c8787c69a85 49 +e4358f28-62a8-4e06-b2bd-cb00d3310349 16 +e7f68d35-ae55-4fa4-b0be-0672a5a7186a 45 +e8c41168-a093-40eb-8baa-6802d24f554a 59 +e8eba267-fecb-477e-9625-771fbcfc3cba 56 +ede7745a-8f3e-4e20-9f16-33756be7ab6c 0 +ee24bf89-81a3-4259-a382-86917f3829d4 50 +f1293c5e-c997-4ba9-8bfd-438e7a840dc8 17 +f18b08bd-40da-43c1-87ec-4ba23542635f 44 +f19eefca-c1ad-4860-bdda-4674a631d464 28 +f28931e5-1f35-4f9f-a02e-78398735ea67 46 +f29330d0-5b32-4f75-b558-a1c7f05c0b4a 56 +f2c9ca81-8b1e-4873-bd8a-1a6e1411193c 30 +f3aa491a-4dbd-4436-b674-18b2177dcf18 54 +f3c2f842-6aa3-42c9-a0f3-895c40456868 0 +f3eb0c38-2571-44e7-be39-732eb52cf1df 1 +f6d3ff7b-f6e1-416a-bb05-73dfa8d129b5 47 +f77a8561-5adc-452a-ac9a-76273b6a6678 62 +f883f2ca-d523-4c9f-86b2-0add137901de 23 +fc4d37ec-22b7-4ea0-a34e-dcd4beb2001c 43 +fc771883-3bd6-46d9-9626-a130e1b902de 53 +fcab9efe-fb05-42d6-b366-ce6db1cc4093 14 +fcc916dd-5e4f-4bf7-9aef-c906a36d7301 50 +fce20464-ff98-4051-8714-6b9584e52740 7 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testWhereWithMemberOf.csv b/fhir-server/src/test/resources/responses/ParserTest/testWhereWithMemberOf.csv deleted file mode 100644 index 23accc1956..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testWhereWithMemberOf.csv +++ /dev/null @@ -1,71 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,2016-09-07T09:11:55+00:00 -121503c8-9564-4b48-9086-a22df717948e,"" -121503c8-9564-4b48-9086-a22df717948e,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -2b36c1e2-bbe1-45ae-8124-4adad2677702,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -7001ad9c-34d2-4eb5-8165-5fdc2147f469,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -9360820c-8602-4335-8b50-c88d627a0c20,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -a7eb2ce7-1075-426c-addd-957b861b0e55,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,1959-02-26T08:27:40+00:00 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,1961-04-07T08:27:40+00:00 -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -beff242e-580b-47c0-9844-c1a68c36c5bf,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,"" diff --git a/fhir-server/src/test/resources/responses/ParserTest/testWhereWithMemberOf.tsv b/fhir-server/src/test/resources/responses/ParserTest/testWhereWithMemberOf.tsv new file mode 100644 index 0000000000..972fa8b336 --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testWhereWithMemberOf.tsv @@ -0,0 +1,71 @@ +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e 2016-09-07T09:11:55+00:00 +121503c8-9564-4b48-9086-a22df717948e +121503c8-9564-4b48-9086-a22df717948e +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +2b36c1e2-bbe1-45ae-8124-4adad2677702 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +7001ad9c-34d2-4eb5-8165-5fdc2147f469 +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +8ee183e2-b3c0-4151-be94-b945d6aa8c6d +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +9360820c-8602-4335-8b50-c88d627a0c20 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +a7eb2ce7-1075-426c-addd-957b861b0e55 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 1959-02-26T08:27:40+00:00 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 1961-04-07T08:27:40+00:00 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +beff242e-580b-47c0-9844-c1a68c36c5bf +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 diff --git a/fhir-server/src/test/resources/responses/ParserTest/testWithCodingLiteral.csv b/fhir-server/src/test/resources/responses/ParserTest/testWithCodingLiteral.csv deleted file mode 100644 index db9cf8b91a..0000000000 --- a/fhir-server/src/test/resources/responses/ParserTest/testWithCodingLiteral.csv +++ /dev/null @@ -1,9 +0,0 @@ -121503c8-9564-4b48-9086-a22df717948e,true -2b36c1e2-bbe1-45ae-8124-4adad2677702,false -7001ad9c-34d2-4eb5-8165-5fdc2147f469,true -8ee183e2-b3c0-4151-be94-b945d6aa8c6d,false -9360820c-8602-4335-8b50-c88d627a0c20,false -a7eb2ce7-1075-426c-addd-957b861b0e55,true -bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9,false -beff242e-580b-47c0-9844-c1a68c36c5bf,false -e62e52ae-2d75-4070-a0ae-3cc78d35ed08,true diff --git a/fhir-server/src/test/resources/responses/ParserTest/testWithCodingLiteral.tsv b/fhir-server/src/test/resources/responses/ParserTest/testWithCodingLiteral.tsv new file mode 100644 index 0000000000..b066c60e4a --- /dev/null +++ b/fhir-server/src/test/resources/responses/ParserTest/testWithCodingLiteral.tsv @@ -0,0 +1,9 @@ +121503c8-9564-4b48-9086-a22df717948e true +2b36c1e2-bbe1-45ae-8124-4adad2677702 false +7001ad9c-34d2-4eb5-8165-5fdc2147f469 true +8ee183e2-b3c0-4151-be94-b945d6aa8c6d false +9360820c-8602-4335-8b50-c88d627a0c20 false +a7eb2ce7-1075-426c-addd-957b861b0e55 true +bbd33563-70d9-4f6d-a79a-dd1fc55f5ad9 false +beff242e-580b-47c0-9844-c1a68c36c5bf false +e62e52ae-2d75-4070-a0ae-3cc78d35ed08 true diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java index 7ac51fb44e..477386c22a 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java @@ -66,17 +66,19 @@ public static void assertMatches(@Nonnull final String expectedRegex, } } - public static void assertDatasetAgainstCsv(@Nonnull final SparkSession spark, + public static void assertDatasetAgainstTsv(@Nonnull final SparkSession spark, @Nonnull final String expectedCsvPath, @Nonnull final Dataset actualDataset) { - assertDatasetAgainstCsv(spark, expectedCsvPath, actualDataset, false); + assertDatasetAgainstTsv(spark, expectedCsvPath, actualDataset, false); } - public static void assertDatasetAgainstCsv(@Nonnull final SparkSession spark, + public static void assertDatasetAgainstTsv(@Nonnull final SparkSession spark, @Nonnull final String expectedCsvPath, @Nonnull final Dataset actualDataset, final boolean header) { final URL url = getResourceAsUrl(expectedCsvPath); final String decodedUrl = URLDecoder.decode(url.toString(), StandardCharsets.UTF_8); - final DataFrameReader reader = spark.read().schema(actualDataset.schema()); + final DataFrameReader reader = spark.read() + .schema(actualDataset.schema()) + .option("delimiter", "\t"); if (header) { reader.option("header", true); } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java index 6bc5f7d928..ea11c57fb7 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/DatasetAssert.java @@ -17,7 +17,6 @@ package au.csiro.pathling.test.assertions; -import static au.csiro.pathling.test.assertions.Assertions.assertDatasetAgainstCsv; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -93,7 +92,7 @@ public DatasetAssert hasRows(@Nonnull final SparkSession spark, @Nonnull final String expectedCsvPath, final boolean header) { dataset.explain(); dataset.show(1000, false); - assertDatasetAgainstCsv(spark, expectedCsvPath, dataset, header); + Assertions.assertDatasetAgainstTsv(spark, expectedCsvPath, dataset, header); return this; } diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java index 075c4b227c..334e8ffaa8 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -91,7 +91,7 @@ void test(@Nonnull final TestParameters parameters) { final Dataset result = executor.buildQuery(parameters.getView()); assertThat(result) .hasRows(spark, "results/views/" + - parameters.getRequestFile().getFileName().toString().replace(".json", ".csv"), true); + parameters.getRequestFile().getFileName().toString().replace(".json", ".tsv"), true); } @Value diff --git a/fhirpath/src/test/resources/results/views/elementRelatedUnnesting.csv b/fhirpath/src/test/resources/results/views/elementRelatedUnnesting.csv deleted file mode 100644 index e9f4b71c45..0000000000 --- a/fhirpath/src/test/resources/results/views/elementRelatedUnnesting.csv +++ /dev/null @@ -1,6 +0,0 @@ -id,family_name,given_name -1,Wuckert,Karina -1,Oberbrunner,Karina -2,Towne,Guy -2,Cleveland,Maponos -2,Cleveland,Wilburg diff --git a/fhirpath/src/test/resources/results/views/elementRelatedUnnesting.tsv b/fhirpath/src/test/resources/results/views/elementRelatedUnnesting.tsv new file mode 100644 index 0000000000..ce66c21c14 --- /dev/null +++ b/fhirpath/src/test/resources/results/views/elementRelatedUnnesting.tsv @@ -0,0 +1,6 @@ +id family_name given_name +1 Wuckert Karina +1 Oberbrunner Karina +2 Towne Guy +2 Cleveland Maponos +2 Cleveland Wilburg diff --git a/fhirpath/src/test/resources/results/views/flattenedBloodPressure.csv b/fhirpath/src/test/resources/results/views/flattenedBloodPressure.csv deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/fhirpath/src/test/resources/results/views/flattenedBloodPressure.tsv b/fhirpath/src/test/resources/results/views/flattenedBloodPressure.tsv new file mode 100644 index 0000000000..55d1575585 --- /dev/null +++ b/fhirpath/src/test/resources/results/views/flattenedBloodPressure.tsv @@ -0,0 +1,2 @@ +id patient_id effective_date_time sbp_quantity_system sbp_quantity_code sbp_quantity_display sbp_quantity_value dbp_quantity_system dbp_quantity_code dbp_quantity_display dbp_quantity_value +blood-pressure example 1999-07-02 http://unitsofmeasure.org mm[Hg] mmHg 109 http://unitsofmeasure.org mm[Hg] mmHg 44 diff --git a/fhirpath/src/test/resources/results/views/forEachWithinFrom.csv b/fhirpath/src/test/resources/results/views/forEachWithinFrom.csv deleted file mode 100644 index 8c23df144a..0000000000 --- a/fhirpath/src/test/resources/results/views/forEachWithinFrom.csv +++ /dev/null @@ -1,11 +0,0 @@ -id,name_prefix,given_name -1,Mrs.,Karina -1,Mrs.,Karina -1,Miss.,Karina -1,Miss.,Karina -2,Mr.,Maponos -2,Mr.,Wilburg -2,Mr.,Guy -2,Prof.,Maponos -2,Prof.,Wilburg -2,Prof.,Guy diff --git a/fhirpath/src/test/resources/results/views/forEachWithinFrom.tsv b/fhirpath/src/test/resources/results/views/forEachWithinFrom.tsv new file mode 100644 index 0000000000..3b88f41003 --- /dev/null +++ b/fhirpath/src/test/resources/results/views/forEachWithinFrom.tsv @@ -0,0 +1,11 @@ +id name_prefix given_name +1 Mrs. Karina +1 Mrs. Karina +1 Miss. Karina +1 Miss. Karina +2 Mr. Maponos +2 Mr. Wilburg +2 Mr. Guy +2 Prof. Maponos +2 Prof. Wilburg +2 Prof. Guy diff --git a/fhirpath/src/test/resources/results/views/nestedSingular.csv b/fhirpath/src/test/resources/results/views/nestedSingular.csv deleted file mode 100644 index c1981c06f9..0000000000 --- a/fhirpath/src/test/resources/results/views/nestedSingular.csv +++ /dev/null @@ -1,5 +0,0 @@ -id,name_use,family_name -1,maiden,Wuckert -1,official,Oberbrunner -2,nickname,Cleveland -2,official,Towne diff --git a/fhirpath/src/test/resources/results/views/nestedSingular.tsv b/fhirpath/src/test/resources/results/views/nestedSingular.tsv new file mode 100644 index 0000000000..acfa4dcb0e --- /dev/null +++ b/fhirpath/src/test/resources/results/views/nestedSingular.tsv @@ -0,0 +1,5 @@ +id name_use family_name +1 maiden Wuckert +1 official Oberbrunner +2 nickname Cleveland +2 official Towne diff --git a/fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.csv b/fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.csv deleted file mode 100644 index 34a0606d5e..0000000000 --- a/fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.csv +++ /dev/null @@ -1,7 +0,0 @@ -id,name_prefix,family_name,marital_status_system,marital_status_code -1,Miss.,Wuckert,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M -1,Miss.,Wuckert,http://snomed.info/sct,87915002 -1,Mrs.,Oberbrunner,http://terminology.hl7.org/CodeSystem/v3-MaritalStatus,M -1,Mrs.,Oberbrunner,http://snomed.info/sct,87915002 -2,Mr.,Towne,, -2,Prof.,Cleveland,, diff --git a/fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.tsv b/fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.tsv new file mode 100644 index 0000000000..157a64ee45 --- /dev/null +++ b/fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.tsv @@ -0,0 +1,7 @@ +id name_prefix family_name marital_status_system marital_status_code +1 Miss. Wuckert http://terminology.hl7.org/CodeSystem/v3-MaritalStatus M +1 Miss. Wuckert http://snomed.info/sct 87915002 +1 Mrs. Oberbrunner http://terminology.hl7.org/CodeSystem/v3-MaritalStatus M +1 Mrs. Oberbrunner http://snomed.info/sct 87915002 +2 Mr. Towne +2 Prof. Cleveland diff --git a/fhirpath/src/test/resources/results/views/singularNoUnnesting.csv b/fhirpath/src/test/resources/results/views/singularNoUnnesting.csv deleted file mode 100644 index edd1aa46b7..0000000000 --- a/fhirpath/src/test/resources/results/views/singularNoUnnesting.csv +++ /dev/null @@ -1,3 +0,0 @@ -id,gender,birth_date -1,female,1959-09-27 -2,male,1983-09-06 diff --git a/fhirpath/src/test/resources/results/views/singularNoUnnesting.tsv b/fhirpath/src/test/resources/results/views/singularNoUnnesting.tsv new file mode 100644 index 0000000000..930d70bb76 --- /dev/null +++ b/fhirpath/src/test/resources/results/views/singularNoUnnesting.tsv @@ -0,0 +1,3 @@ +id gender birth_date +1 female 1959-09-27 +2 male 1983-09-06 diff --git a/fhirpath/src/test/resources/results/views/unnestEmptyCollection.csv b/fhirpath/src/test/resources/results/views/unnestEmptyCollection.csv deleted file mode 100644 index 611399a22e..0000000000 --- a/fhirpath/src/test/resources/results/views/unnestEmptyCollection.csv +++ /dev/null @@ -1,3 +0,0 @@ -id,photo_title -1, -2, diff --git a/fhirpath/src/test/resources/results/views/unnestEmptyCollection.tsv b/fhirpath/src/test/resources/results/views/unnestEmptyCollection.tsv new file mode 100644 index 0000000000..260859927b --- /dev/null +++ b/fhirpath/src/test/resources/results/views/unnestEmptyCollection.tsv @@ -0,0 +1,3 @@ +id photo_title +1 +2 From 8e60f629b2a979efe12329ce72189b656a2e7cad Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sun, 6 Aug 2023 21:57:36 +1000 Subject: [PATCH 57/95] Refactor parser to return FhirPathTransformation instead of FhirPath --- .../fhirpath/parser/AbstractParserTest.java | 1 - .../fhirpath/FhirPathTransformation.java | 12 ++ .../fhirpath/function/NamedFunction.java | 46 +--- .../fhirpath/function/NamedFunctionInput.java | 6 +- .../fhirpath/literal/LiteralPath.java | 3 +- .../fhirpath/parser/ConstantReplacer.java | 16 +- .../fhirpath/parser/InvocationVisitor.java | 189 ++++------------- .../fhirpath/parser/LiteralTermVisitor.java | 138 ++++++------ .../pathling/fhirpath/parser/Parser.java | 15 +- .../fhirpath/parser/ParserContext.java | 199 ++---------------- .../pathling/fhirpath/parser/TermVisitor.java | 53 ++--- .../fhirpath/parser/UnnestBehaviour.java | 18 -- .../pathling/fhirpath/parser/Visitor.java | 91 ++++---- 13 files changed, 227 insertions(+), 560 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathTransformation.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/UnnestBehaviour.java diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java index 32b5680661..e47ef9ecf5 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java @@ -17,7 +17,6 @@ package au.csiro.pathling.fhirpath.parser; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; import static org.mockito.Mockito.when; import au.csiro.pathling.encoders.FhirEncoders; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathTransformation.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathTransformation.java new file mode 100644 index 0000000000..2a721eb79d --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathTransformation.java @@ -0,0 +1,12 @@ +package au.csiro.pathling.fhirpath; + +import java.util.function.UnaryOperator; + +/** + * A description of how to take one {@link FhirPath} and transform it into another. + * + * @author John Grimes + */ +public interface FhirPathTransformation extends UnaryOperator { + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java index fd6bbebad4..84827f1b8e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java @@ -30,7 +30,6 @@ import au.csiro.pathling.fhirpath.function.terminology.PropertyFunction; import au.csiro.pathling.fhirpath.function.terminology.SubsumesFunction; import au.csiro.pathling.fhirpath.function.terminology.TranslateFunction; -import au.csiro.pathling.fhirpath.parser.UnnestBehaviour; import com.google.common.collect.ImmutableMap; import java.util.Map; import java.util.stream.Collectors; @@ -75,34 +74,6 @@ public interface NamedFunction { .put("getId", new GetIdFunction()) .build(); - /** - * Mapping of function names to the instances of those functions. - */ - Map NAME_TO_INSTANCE_VIEW_CONTEXT = new ImmutableMap.Builder() - .put("count", new CountFunction()) - .put("ofType", new OfTypeFunction()) - .put("memberOf", new MemberOfFunction()) - .put("where", new WhereFunction()) - .put("subsumes", new SubsumesFunction()) - .put("subsumedBy", new SubsumesFunction(true)) - .put("empty", new EmptyFunction()) - .put("first", new FirstFunction()) - .put("not", new NotFunction()) - .put("iif", new IifFunction()) - .put("translate", new TranslateFunction()) - .put("sum", new SumFunction()) - .put("anyTrue", new BooleansTestFunction(ANY_TRUE)) - .put("anyFalse", new BooleansTestFunction(ANY_FALSE)) - .put("allTrue", new BooleansTestFunction(ALL_TRUE)) - .put("allFalse", new BooleansTestFunction(ALL_FALSE)) - .put("extension", new ExtensionFunction()) - .put("until", new UntilFunction()) - .put("exists", new ExistsFunction()) - .put("display", new DisplayFunction()) - .put("property", new PropertyFunction()) - .put("designation", new DesignationFunction()) - .build(); - /** * The FHIRPath expression for the $this keyword, used to access the current item in the * collection in functions such as {@code where}. @@ -128,22 +99,7 @@ public interface NamedFunction { */ @Nonnull static NamedFunction getInstance(@Nonnull final String name) { - return getInstance(name, UnnestBehaviour.UNNEST); - } - - /** - * Retrieves an instance of the function with the specified name. - * - * @param name The name of the function - * @param context The execution context - * @return An instance of a NamedFunction - */ - @Nonnull - static NamedFunction getInstance(@Nonnull final String name, - @Nonnull final UnnestBehaviour context) { - final NamedFunction function = context == UnnestBehaviour.NOOP - ? NAME_TO_INSTANCE_VIEW_CONTEXT.get(name) - : NAME_TO_INSTANCE.get(name); + final NamedFunction function = NAME_TO_INSTANCE.get(name); checkUserInput(function != null, "Unsupported function: " + name); return function; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunctionInput.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunctionInput.java index b75bc986e8..9a2ffaf31d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunctionInput.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunctionInput.java @@ -39,7 +39,7 @@ public class NamedFunctionInput extends FunctionInput { * of the dot preceding the function invocation. */ @Nonnull - private final NonLiteralPath input; + private final FhirPath input; /** * A list of expressions representing the arguments to the function, i.e. the expressions inside @@ -63,7 +63,7 @@ public class NamedFunctionInput extends FunctionInput { * @param overrideExpression Override expression to use instead of the default one. */ public NamedFunctionInput(@Nonnull final ParserContext context, - @Nonnull final NonLiteralPath input, @Nonnull final List arguments, + @Nonnull final FhirPath input, @Nonnull final List arguments, @Nonnull final String overrideExpression) { super(context); this.input = input; @@ -79,7 +79,7 @@ public NamedFunctionInput(@Nonnull final ParserContext context, * function within the parentheses */ public NamedFunctionInput(@Nonnull final ParserContext context, - @Nonnull final NonLiteralPath input, @Nonnull final List arguments) { + @Nonnull final FhirPath input, @Nonnull final List arguments) { super(context); this.input = input; this.arguments = arguments; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java index ba3285ac8a..43dd04eb8e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java @@ -18,7 +18,6 @@ package au.csiro.pathling.fhirpath.literal; import static au.csiro.pathling.QueryHelpers.getUnionableColumns; -import static au.csiro.pathling.utilities.Strings.randomAlias; import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; @@ -112,7 +111,7 @@ private LiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column i this.value = value; this.dataset = dataset; this.expression = expression; - this.valueColumn = buildValueColumn().alias(randomAlias()); + this.valueColumn = buildValueColumn(); } protected LiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ConstantReplacer.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ConstantReplacer.java index d18d4a85f2..87e5df2085 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ConstantReplacer.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ConstantReplacer.java @@ -17,10 +17,10 @@ package au.csiro.pathling.fhirpath.parser; +import static au.csiro.pathling.fhirpath.parser.Parser.buildParser; import static java.util.Objects.requireNonNull; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; -import au.csiro.pathling.fhirpath.parser.generated.FhirPathLexer; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ExternalConstantContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ExternalConstantTermContext; @@ -28,8 +28,6 @@ import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTree; public class ConstantReplacer extends FhirPathBaseVisitor { @@ -42,17 +40,7 @@ public ConstantReplacer(final Map constants) { @Nonnull public String execute(@Nonnull final String expression) { - final FhirPathLexer lexer = new FhirPathLexer(CharStreams.fromString(expression)); - final CommonTokenStream tokens = new CommonTokenStream(lexer); - final FhirPathParser parser = new FhirPathParser(tokens); - - lexer.removeErrorListeners(); - lexer.addErrorListener(new ParserErrorListener()); - - // Remove the default console error reporter, and add a listener that wraps each parse error in - // an invalid request exception. - parser.removeErrorListeners(); - parser.addErrorListener(new ParserErrorListener()); + final FhirPathParser parser = buildParser(expression); return visit(parser.expression()); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index d80a410327..d488694fbf 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -17,20 +17,18 @@ package au.csiro.pathling.fhirpath.parser; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toList; import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.FhirPathTransformation; import au.csiro.pathling.fhirpath.TypeSpecifier; import au.csiro.pathling.fhirpath.function.NamedFunction; import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.operator.PathTraversalInput; import au.csiro.pathling.fhirpath.operator.PathTraversalOperator; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; -import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ExpressionContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.FunctionInvocationContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.IndexInvocationContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.MemberInvocationContext; @@ -42,10 +40,8 @@ import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.apache.spark.sql.Column; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; /** * This class is invoked on the right-hand side of the invocation expression, and can optionally be @@ -53,14 +49,11 @@ * * @author John Grimes */ -class InvocationVisitor extends FhirPathBaseVisitor { +class InvocationVisitor extends FhirPathBaseVisitor { @Nonnull private final ParserContext context; - @Nullable - private final FhirPath invoker; - /** * This constructor is used when there is no explicit invoker, i.e. an invocation is made without * an expression on the left-hand side of the dot notation. In this case, the invoker is taken to @@ -70,19 +63,6 @@ class InvocationVisitor extends FhirPathBaseVisitor { */ InvocationVisitor(@Nonnull final ParserContext context) { this.context = context; - this.invoker = null; - } - - /** - * This constructor is used when there is an explicit invoker on the left-hand side of the dot - * notation. - * - * @param context The {@link ParserContext} to use when parsing the invocation - * @param invoker A {@link FhirPath} representing the invoking expression - */ - InvocationVisitor(@Nonnull final ParserContext context, @Nonnull final FhirPath invoker) { - this.context = context; - this.invoker = invoker; } /** @@ -90,78 +70,33 @@ class InvocationVisitor extends FhirPathBaseVisitor { * or when an identifier is referred to as a term (e.g. "Encounter" or "type"). * * @param ctx The {@link MemberInvocationContext} - * @return A {@link FhirPath} expression + * @return A {@link FhirPathTransformation} expression */ @Override @Nonnull - public FhirPath visitMemberInvocation(@Nullable final MemberInvocationContext ctx) { + public FhirPathTransformation visitMemberInvocation(@Nullable final MemberInvocationContext ctx) { @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - if (invoker != null) { - // If there is an invoker, we treat this as a path traversal from the invoker. - final PathTraversalInput pathTraversalInput = new PathTraversalInput(context, invoker, - fhirPath); - return new PathTraversalOperator().invoke(pathTraversalInput); - - } else { - // If there is no invoker, we need to interpret what the expression means, based on its - // content and context. - - if (context.getThisContext().isEmpty()) { - // If we're at the root of the expression, this could be: - // (1) a path traversal from the input context; or - // (2) a reference to the subject resource. - - // The only type of resource reference that is allowed at the root a reference to the - // subject resource. - // See https://hl7.org/fhirpath/2018Sep/index.html#path-selection. - if (fhirPath.equals(context.getInputContext().getExpression())) { - return context.getInputContext(); - - } else { - // If the expression is not a reference to the subject resource, treat it as a path - // traversal from the input context. - final PathTraversalInput pathTraversalInput = new PathTraversalInput(context, - context.getInputContext(), fhirPath); - return new PathTraversalOperator().invoke(pathTraversalInput); - } - } else { - // If we're in the context of a function's arguments, there are three valid things this - // could be: - // (1) a path traversal from the "this" context; - // (2) a resource type specifier, or; - // (3) a data type specifier. + return input -> { + try { + // Attempt path traversal. + final PathTraversalInput pathTraversalInput = new PathTraversalInput(context, input, + fhirPath); + return new PathTraversalOperator().invoke(pathTraversalInput); + } catch (final InvalidUserInputError e) { try { - // Check if the expression is a reference to a known resource type. - final ResourceType resourceType = ResourceType.fromCode(fhirPath); - return ResourcePath - .build(context.getFhirContext(), context.getDataSource(), resourceType, fhirPath, - true); + // If it is not a valid path traversal, see if it is a valid type specifier. + final FHIRDefinedType fhirType = FHIRDefinedType.fromCode(fhirPath); + return TypeSpecifier.build(context.getInputContext(), fhirType); - } catch (final FHIRException e) { - try { - // If the expression is not a resource type, attempt path traversal. - final PathTraversalInput pathTraversalInput = new PathTraversalInput(context, - context.getThisContext().get(), fhirPath); - return new PathTraversalOperator().invoke(pathTraversalInput); - - } catch (final InvalidUserInputError e2) { - try { - // If it is not a valid path traversal, see if it is a valid data type specifier. - final FHIRDefinedType fhirType = FHIRDefinedType.fromCode(fhirPath); - return TypeSpecifier.build(context.getInputContext(), fhirType); - - } catch (final FHIRException e3) { - throw new InvalidUserInputError( - "Invocation is not a valid path or type specifier: " + fhirPath); - } - } + } catch (final FHIRException e2) { + throw new InvalidUserInputError( + "Invocation is not a valid path or type specifier: " + fhirPath); } - } - } + }; } /** @@ -169,87 +104,47 @@ public FhirPath visitMemberInvocation(@Nullable final MemberInvocationContext ct * expression. * * @param ctx The {@link FunctionInvocationContext} - * @return A {@link FhirPath} expression + * @return A {@link FhirPathTransformation} expression */ @Override @Nonnull - public FhirPath visitFunctionInvocation(@Nullable final FunctionInvocationContext ctx) { - @Nullable final String functionIdentifier = requireNonNull(ctx).function().identifier() - .getText(); - requireNonNull(functionIdentifier); - final NamedFunction function = NamedFunction.getInstance(functionIdentifier, - context.getUnnestBehaviour()); - - // If there is no invoker, we use either the input context or the this context, depending on - // whether we are in the context of function arguments. - final FhirPath input = invoker == null - ? context.getThisContext().orElse(context.getInputContext()) - : invoker; - - // A literal cannot be used as a function input. - checkUserInput(input instanceof NonLiteralPath, - "Literal expression cannot be used as input to a function invocation: " + input - .getExpression()); - final NonLiteralPath nonLiteral = (NonLiteralPath) input; - + public FhirPathTransformation visitFunctionInvocation( + @Nullable final FunctionInvocationContext ctx) { + final String functionIdentifier = requireNonNull(ctx).function().identifier().getText(); @Nullable final ParamListContext paramList = ctx.function().paramList(); - final List arguments = new ArrayList<>(); - if (paramList != null) { - // The `$this` path will be the same as the input, but with a different expression, and it - // will be singular as it represents a single item. - // NOTE: This works because for $this the context for aggregation grouping on elements - // includes `id` and `this` columns. - - // Create and alias the $this column. - final NonLiteralPath thisPath = nonLiteral.toThisPath(); - - // If the "this" context has an element ID, we need to add this to the grouping columns so - // that aggregations that occur within the arguments are in the context of an element. - // Otherwise, we add the resource ID column to the groupings. - final List argumentGroupings = new ArrayList<>(context.getGroupingColumns()); - thisPath.getOrderingColumn().ifPresentOrElse(argumentGroupings::add, - () -> argumentGroupings.add(thisPath.getIdColumn())); - - // Create a new ParserContext, which includes information about how to evaluate the `$this` - // expression. - ParserContext argumentContext = new ParserContext(context.getInputContext(), - context.getFhirContext(), context.getSparkSession(), context.getDataSource(), - context.getTerminologyServiceFactory(), argumentGroupings, context.getUnnestBehaviour(), - context.getVariables(), context.getNesting(), Optional.empty()); - argumentContext.setThisContext(thisPath); - - for (final ExpressionContext expression : paramList.expression()) { - // Parse each of the expressions passed as arguments to the function. - final FhirPath argumentResult = new Visitor(argumentContext).visit(expression); - arguments.add(argumentResult); - // Update the argument context with the updated dataset, for use in parsing subsequent - // arguments. - argumentContext = argumentContext.withContextDataset(argumentResult.getDataset()); - } - } - - final NamedFunctionInput functionInput = new NamedFunctionInput(context, nonLiteral, arguments); - return function.invoke(functionInput); + return input -> { + final NamedFunction function = NamedFunction.getInstance(functionIdentifier); + final Visitor paramListVisitor = new Visitor(context); + + final List arguments = Optional.ofNullable(paramList) + .map(ParamListContext::expression) + .map(p -> p.stream() + .map(paramListVisitor::visit) + .map(fpt -> fpt.apply(input)) + .collect(toList()) + ).orElse(new ArrayList<>()); + + final NamedFunctionInput functionInput = new NamedFunctionInput(context, input, arguments); + return function.invoke(functionInput); + }; } @Override @Nonnull - public FhirPath visitThisInvocation(@Nullable final ThisInvocationContext ctx) { - checkUserInput(context.getThisContext().isPresent(), - "$this can only be used within the context of arguments to a function"); - return context.getThisContext().get(); + public FhirPathTransformation visitThisInvocation(@Nullable final ThisInvocationContext ctx) { + return input -> context.getInputContext(); } @Override @Nonnull - public FhirPath visitIndexInvocation(@Nullable final IndexInvocationContext ctx) { + public FhirPathTransformation visitIndexInvocation(@Nullable final IndexInvocationContext ctx) { throw new InvalidUserInputError("$index is not supported"); } @Override @Nonnull - public FhirPath visitTotalInvocation(@Nullable final TotalInvocationContext ctx) { + public FhirPathTransformation visitTotalInvocation(@Nullable final TotalInvocationContext ctx) { throw new InvalidUserInputError("$total is not supported"); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java index c6b445a72d..7d0bf3bd3b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java @@ -21,7 +21,7 @@ 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.FhirPathTransformation; import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; import au.csiro.pathling.fhirpath.literal.DateLiteralPath; @@ -43,7 +43,6 @@ import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.StringLiteralContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.TimeLiteralContext; import java.text.ParseException; -import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.antlr.v4.runtime.tree.TerminalNode; @@ -54,131 +53,130 @@ * * @author John Grimes */ -class LiteralTermVisitor extends FhirPathBaseVisitor { - - @Nonnull - private final ParserContext context; - - LiteralTermVisitor(@Nonnull final ParserContext context) { - this.context = context; - } +class LiteralTermVisitor extends FhirPathBaseVisitor { @Override @Nonnull - public FhirPath visitCodingLiteral(@Nullable final CodingLiteralContext ctx) { + public FhirPathTransformation visitCodingLiteral(@Nullable final CodingLiteralContext ctx) { @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - try { - return CodingLiteralPath.fromString(fhirPath, - context.getThisContext().orElse(context.getInputContext())); - } catch (final IllegalArgumentException e) { - throw new InvalidUserInputError(e.getMessage(), e); - } + + return input -> { + try { + return CodingLiteralPath.fromString(fhirPath, input); + } catch (final IllegalArgumentException e) { + throw new InvalidUserInputError(e.getMessage(), e); + } + }; } @Override @Nonnull - public FhirPath visitStringLiteral(@Nullable final StringLiteralContext ctx) { + public FhirPathTransformation visitStringLiteral(@Nullable final StringLiteralContext ctx) { @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return StringLiteralPath.fromString(fhirPath, - context.getThisContext().orElse(context.getInputContext())); + + return input -> StringLiteralPath.fromString(fhirPath, input); } @Override - public FhirPath visitDateLiteral(@Nullable final DateLiteralContext ctx) { + public FhirPathTransformation visitDateLiteral(@Nullable final DateLiteralContext ctx) { @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - try { - return DateLiteralPath.fromString(fhirPath, - context.getThisContext().orElse(context.getInputContext())); - } catch (final ParseException ex) { - throw new InvalidUserInputError("Unable to parse date format: " + fhirPath); - } + + return input -> { + try { + return DateLiteralPath.fromString(fhirPath, input); + } catch (final ParseException e) { + throw new InvalidUserInputError("Unable to parse date format: " + fhirPath); + } + }; } @Override @Nonnull - public FhirPath visitDateTimeLiteral(@Nullable final DateTimeLiteralContext ctx) { + public FhirPathTransformation visitDateTimeLiteral(@Nullable final DateTimeLiteralContext ctx) { @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - try { - return DateTimeLiteralPath.fromString(fhirPath, - context.getThisContext().orElse(context.getInputContext())); - } catch (final ParseException ex) { - throw new InvalidUserInputError("Unable to parse date/time format: " + fhirPath); - } + + return input -> { + try { + return DateTimeLiteralPath.fromString(fhirPath, input); + } catch (final ParseException e) { + throw new InvalidUserInputError("Unable to parse date/time format: " + fhirPath); + } + }; } @Override @Nonnull - public FhirPath visitTimeLiteral(@Nullable final TimeLiteralContext ctx) { + public FhirPathTransformation visitTimeLiteral(@Nullable final TimeLiteralContext ctx) { @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return TimeLiteralPath.fromString(fhirPath, - context.getThisContext().orElse(context.getInputContext())); + + return input -> TimeLiteralPath.fromString(fhirPath, input); } @Override @Nonnull - public FhirPath visitNumberLiteral(@Nullable final NumberLiteralContext ctx) { + public FhirPathTransformation visitNumberLiteral(@Nullable final NumberLiteralContext ctx) { @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - // The FHIRPath grammar lumps these two types together, so we tease them apart by trying to - // parse them. A better way of doing this would be to modify the grammar. - try { - return IntegerLiteralPath.fromString(fhirPath, - context.getThisContext().orElse(context.getInputContext())); - } catch (final NumberFormatException e) { + + return input -> { + // The FHIRPath grammar lumps these two types together, so we tease them apart by trying to + // parse them. try { - return DecimalLiteralPath.fromString(fhirPath, - context.getThisContext().orElse(context.getInputContext())); - } catch (final NumberFormatException ex) { - throw new InvalidUserInputError("Invalid date format: " + fhirPath); + return IntegerLiteralPath.fromString(fhirPath, input); + } catch (final NumberFormatException e) { + try { + return DecimalLiteralPath.fromString(fhirPath, input); + } catch (final NumberFormatException ex) { + throw new InvalidUserInputError("Invalid date format: " + fhirPath); + } } - } + }; } @Override @Nonnull - public FhirPath visitBooleanLiteral(@Nullable final BooleanLiteralContext ctx) { + public FhirPathTransformation visitBooleanLiteral(@Nullable final BooleanLiteralContext ctx) { requireNonNull(ctx); @Nullable final String fhirPath = ctx.getText(); requireNonNull(fhirPath); - return BooleanLiteralPath.fromString(fhirPath, - context.getThisContext().orElse(context.getInputContext())); + + return input -> BooleanLiteralPath.fromString(fhirPath, input); } @Override @Nonnull - public FhirPath visitNullLiteral(@Nullable final NullLiteralContext ctx) { - return NullLiteralPath.build(context.getThisContext().orElse(context.getInputContext())); + public FhirPathTransformation visitNullLiteral(@Nullable final NullLiteralContext ctx) { + return NullLiteralPath::build; } @Override @Nonnull - public FhirPath visitQuantityLiteral(@Nullable final QuantityLiteralContext ctx) { + public FhirPathTransformation visitQuantityLiteral(@Nullable final QuantityLiteralContext ctx) { requireNonNull(ctx); @Nullable final String number = ctx.quantity().NUMBER().getText(); requireNonNull(number); - - final FhirPath resultContext = this.context.getThisContext().orElse(context.getInputContext()); @Nullable final TerminalNode ucumUnit = ctx.quantity().unit().STRING(); - if (ucumUnit == null) { - // Create a calendar duration literal. - final String fhirPath = String.format("%s %s", number, ctx.quantity().unit().getText()); - return QuantityLiteralPath.fromCalendarDurationString(fhirPath, resultContext); - } else { - // Create a UCUM Quantity literal. - final String fhirPath = String.format("%s %s", number, ucumUnit.getText()); - try { - return QuantityLiteralPath.fromUcumString(fhirPath, resultContext, - Ucum.service()); - } catch (final UcumException e) { - throw new RuntimeException(e); + return input -> { + if (ucumUnit == null) { + // Create a calendar duration literal. + final String fhirPath = String.format("%s %s", number, ctx.quantity().unit().getText()); + return QuantityLiteralPath.fromCalendarDurationString(fhirPath, input); + } else { + // Create a UCUM Quantity literal. + final String fhirPath = String.format("%s %s", number, ucumUnit.getText()); + try { + return QuantityLiteralPath.fromUcumString(fhirPath, input, Ucum.service()); + } catch (final UcumException e) { + throw new RuntimeException(e); + } } - } + }; } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java index 26f8a7ffa8..8f306424de 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java @@ -18,6 +18,7 @@ package au.csiro.pathling.fhirpath.parser; import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.FhirPathTransformation; import au.csiro.pathling.fhirpath.parser.generated.FhirPathLexer; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser; import javax.annotation.Nonnull; @@ -52,7 +53,15 @@ public Parser(@Nonnull final ParserContext context) { * @return a new {@link FhirPath} object */ @Nonnull - public FhirPath parse(@Nonnull final String expression) { + public FhirPathTransformation parse(@Nonnull final String expression) { + final FhirPathParser parser = buildParser(expression); + + final Visitor visitor = new Visitor(context); + return visitor.visit(parser.expression()); + } + + @Nonnull + static FhirPathParser buildParser(final @Nonnull String expression) { final FhirPathLexer lexer = new FhirPathLexer(CharStreams.fromString(expression)); final CommonTokenStream tokens = new CommonTokenStream(lexer); final FhirPathParser parser = new FhirPathParser(tokens); @@ -64,9 +73,7 @@ public FhirPath parse(@Nonnull final String expression) { // an invalid request exception. parser.removeErrorListeners(); parser.addErrorListener(new ParserErrorListener()); - - final Visitor visitor = new Visitor(context); - return visitor.visit(parser.expression()); + return parser; } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index e0381d5a50..2717a6a696 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -17,35 +17,19 @@ package au.csiro.pathling.fhirpath.parser; -import static au.csiro.pathling.QueryHelpers.join; -import static au.csiro.pathling.utilities.Preconditions.check; - -import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirPathAndContext; -import au.csiro.pathling.fhirpath.Nesting; -import au.csiro.pathling.fhirpath.NonLiteralPath; +import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyService; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.Optional; import javax.annotation.Nonnull; import lombok.Getter; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; /** - * Context and dependencies used in the parsing of a FHIRPath expression. The parser context can - * change through the parsing of an expression as it passes through different states (e.g. - * aggregation, inside the arguments to a function). Change to the parser context is only permitted - * through a limited set of public setter methods. + * Context and dependencies used in the parsing of a FHIRPath expression. * * @author John Grimes */ @@ -53,8 +37,8 @@ public class ParserContext { /** - * The input context from which the FHIRPath is to be evaluated, which is then referred to through - * {@code %resource} or {@code %context}. + * The input context from which the FHIRPath is to be evaluated, referred to through + * {@code %context}. * * @see Path selection * @see Environment @@ -64,6 +48,18 @@ public class ParserContext { @Nonnull private final FhirPath inputContext; + /** + * The resource that contains the input context, referred to through {@code %resource} or + * {@code %rootResource}. + * + * @see Path selection + * @see Environment + * variables + * @see FHIR Specific Variables + */ + @Nonnull + private final ResourcePath resource; + /** * A FHIR context that can be used to do FHIR stuff. */ @@ -91,29 +87,8 @@ public class ParserContext { private final Optional terminologyServiceFactory; /** - * The context may contain zero or more grouping columns. If the context of evaluation is a single - * resource, the resource identity column should be the one and only column in this list. - * Otherwise, columns that provide the relevant groupings should be in here. - */ - @Nonnull - private final List groupingColumns; - - /** - * When within the context of function arguments, this is the {@link FhirPath} that represents the - * item in a collection currently being iterated over, denoted by the {@code $this} keyword. + * A list of constants and the expressions that they should be replaced with. */ - @Nonnull - private Optional thisContext = Optional.empty(); - - @Nonnull - private final UnnestBehaviour unnestBehaviour; - - @Nonnull - private final Map variables; - - @Nonnull - private final Nesting nesting; - @Nonnull private final Optional constantReplacer; @@ -125,149 +100,21 @@ public class ParserContext { * @param dataSource for retrieving data relating to resource references * @param terminologyServiceFactory a factory for {@link TerminologyService} objects, used for * parallel processing - * @param groupingColumns the list of columns to group on when aggregating paths parsed within - * this context - */ - public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirContext fhirContext, - @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataSource, - @Nonnull final Optional terminologyServiceFactory, - @Nonnull final List groupingColumns, - final Optional constantReplacer) { - this(inputContext, fhirContext, sparkSession, dataSource, terminologyServiceFactory, - groupingColumns, UnnestBehaviour.UNNEST, new HashMap<>(), new Nesting(), constantReplacer); - } - - /** - * @param inputContext the input context from which the FHIRPath is to be evaluated - * @param fhirContext a {@link FhirContext} that can be used to do FHIR stuff - * @param sparkSession a {@link SparkSession} that can be used to resolve Spark queries required - * for this expression - * @param dataSource for retrieving data relating to resource references - * @param terminologyServiceFactory a factory for {@link TerminologyService} objects, used for - * parallel processing - * @param groupingColumns the list of columns to group on when aggregating - * @param unnestBehaviour the execution context to use + * @param constantReplacer a list of constants and the expressions that they should be replaced + * with */ - public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final FhirContext fhirContext, + public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final ResourcePath resource, + @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataSource, @Nonnull final Optional terminologyServiceFactory, - @Nonnull final List groupingColumns, - @Nonnull final UnnestBehaviour unnestBehaviour, - @Nonnull final Map variables, - @Nonnull final Nesting nesting, @Nonnull final Optional constantReplacer) { + @Nonnull final Optional constantReplacer) { this.inputContext = inputContext; + this.resource = resource; this.fhirContext = fhirContext; this.sparkSession = sparkSession; this.dataSource = dataSource; this.terminologyServiceFactory = terminologyServiceFactory; - this.groupingColumns = groupingColumns; - this.unnestBehaviour = unnestBehaviour; - this.variables = variables; - this.nesting = nesting; this.constantReplacer = constantReplacer; } - public void setThisContext(@Nonnull final FhirPath thisContext) { - this.thisContext = Optional.of(thisContext); - } - - public void unsetThisContext() { - this.thisContext = Optional.empty(); - } - - /** - * Creates a copy of the current parser context with the specified unnest behaviour. - * - * @param unnestBehaviour the {@link UnnestBehaviour} to use - * @return a new #{link ParserContext} - */ - public ParserContext withUnnestBehaviour(@Nonnull final UnnestBehaviour unnestBehaviour) { - final ParserContext context = new ParserContext(inputContext, fhirContext, sparkSession, - dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, variables, - nesting, constantReplacer); - thisContext.ifPresent(context::setThisContext); - return context; - } - - /** - * Creates a copy of the current parser context with a different input context. - * - * @return a new {@link ParserContext} - */ - public ParserContext withInputContext(@Nonnull final FhirPath inputContext) { - final ParserContext parserContext = new ParserContext(inputContext, fhirContext, - sparkSession, dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, - variables, nesting, constantReplacer); - thisContext.ifPresent(parserContext::setThisContext); - return parserContext; - } - - /** - * Creates a copy of the current parser context with a different input dataset. - * - * @return a new {@link ParserContext} - */ - public ParserContext withContextDataset(@Nonnull final Dataset contextDataset) { - final FhirPath newInputContext = inputContext.withDataset(contextDataset); - final ParserContext parserContext = new ParserContext(newInputContext, fhirContext, - sparkSession, dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, - variables, nesting, constantReplacer); - thisContext.ifPresent( - thisContext -> parserContext.setThisContext(thisContext.withDataset(contextDataset))); - return parserContext; - } - - /** - * Creates a copy of the current parser context with a different set of grouping columns. - * - * @return a new {@link ParserContext} - */ - public ParserContext withGroupingColumns(@Nonnull final List groupingColumns) { - // Make the input context non-singular - when we are grouping, the input context is a collection - // of resources. - check(inputContext instanceof NonLiteralPath); - final NonLiteralPath nonLiteralInputContext = (NonLiteralPath) inputContext; - final NonLiteralPath nonSingularInputContext = nonLiteralInputContext.copy( - nonLiteralInputContext.getExpression(), - nonLiteralInputContext.getDataset(), nonLiteralInputContext.getIdColumn(), - nonLiteralInputContext.getValueColumn(), nonLiteralInputContext.getOrderingColumn(), false, - nonLiteralInputContext.getThisColumn()); - final ParserContext context = new ParserContext(nonSingularInputContext, fhirContext, - sparkSession, dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, - variables, nesting, constantReplacer); - thisContext.ifPresent(context::setThisContext); - return context; - } - - /** - * Used for the scenario where a dataset is aggregated at the root, and that aggregated result - * needs to be used in onward parsing. Takes the aggregated path and joins it to the input context - * as an additional column. - * - * @return a new {@link ParserContext} - */ - public ParserContext disaggregate(@Nonnull final FhirPath aggregatedPath) { - final Dataset newDataset; - if (groupingColumns.isEmpty()) { - // If there are no grouping columns, we can do a cross-join as the target should only have one - // row. - newDataset = inputContext.getDataset() - .crossJoin(aggregatedPath.getDataset().select(aggregatedPath.getValueColumn())); - } else { - // If there are grouping columns, we need to join on those columns. - final List aggregatedSelection = new ArrayList<>(groupingColumns); - aggregatedSelection.add(aggregatedPath.getValueColumn()); - final Dataset aggregatedDataset = aggregatedPath.getDataset().select(aggregatedSelection - .toArray(new Column[0])); - newDataset = join(inputContext.getDataset(), groupingColumns, aggregatedDataset, - groupingColumns, JoinType.LEFT_OUTER); - } - final FhirPath newInputContext = inputContext.withDataset(newDataset); - final ParserContext parserContext = new ParserContext(newInputContext, fhirContext, - sparkSession, dataSource, terminologyServiceFactory, groupingColumns, unnestBehaviour, - variables, new Nesting(), constantReplacer); - thisContext.ifPresent(parserContext::setThisContext); - return parserContext; - } - } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java index d46206c28e..36a1d97df6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java @@ -17,12 +17,10 @@ package au.csiro.pathling.fhirpath.parser; -import static au.csiro.pathling.utilities.Preconditions.check; import static java.util.Objects.requireNonNull; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirPathAndContext; -import au.csiro.pathling.fhirpath.NonLiteralPath; +import au.csiro.pathling.fhirpath.FhirPathTransformation; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ExternalConstantTermContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.InvocationTermContext; @@ -36,7 +34,7 @@ * * @author John Grimes */ -class TermVisitor extends FhirPathBaseVisitor { +class TermVisitor extends FhirPathBaseVisitor { @Nonnull private final ParserContext context; @@ -47,49 +45,44 @@ class TermVisitor extends FhirPathBaseVisitor { @Override @Nonnull - public FhirPath visitInvocationTerm(@Nullable final InvocationTermContext ctx) { + public FhirPathTransformation visitInvocationTerm(@Nullable final InvocationTermContext ctx) { return new InvocationVisitor(context).visit(requireNonNull(ctx).invocation()); } @Override @Nonnull - public FhirPath visitLiteralTerm(@Nullable final LiteralTermContext ctx) { - return new LiteralTermVisitor(context).visit(requireNonNull(ctx).literal()); + public FhirPathTransformation visitLiteralTerm(@Nullable final LiteralTermContext ctx) { + return new LiteralTermVisitor().visit(requireNonNull(ctx).literal()); } @Override @Nonnull - public FhirPath visitExternalConstantTerm(@Nullable final ExternalConstantTermContext ctx) { + public FhirPathTransformation visitExternalConstantTerm( + @Nullable final ExternalConstantTermContext ctx) { @Nullable final String term = requireNonNull(ctx).getText(); requireNonNull(term); - if (term.equals("%resource") || term.equals("%context")) { - check(context.getInputContext() instanceof NonLiteralPath); - - // The %resource and %context elements both return the input context. - final NonLiteralPath inputContext = (NonLiteralPath) context.getInputContext(); - - // In the case of %resource and %context, the new expression will be the input context with the - // expression updated to match the external constant term. - return inputContext.copy(term, inputContext.getDataset(), inputContext.getIdColumn(), - inputContext.getValueColumn(), inputContext.getOrderingColumn(), - inputContext.isSingular(), inputContext.getThisColumn()); - } else { - final String variableName = term.replaceFirst("%", ""); - final FhirPathAndContext variable = context.getVariables().get(variableName); - if (variable == null) { - throw new IllegalArgumentException("Unknown variable: " + variableName); + return input -> { + if (term.equals("%context")) { + return context.getInputContext(); + } else if (term.equals("%resource") || term.equals("%rootResource")) { + return context.getResource(); + } else { + throw new IllegalArgumentException("Unknown constant: " + term); } - return variable.getFhirPath(); - } + }; } @Override @Nonnull - public FhirPath visitParenthesizedTerm(@Nullable final ParenthesizedTermContext ctx) { - // Parentheses are ignored in the standalone term case. - final FhirPath result = new Visitor(context).visit(requireNonNull(ctx).expression()); - return result.withExpression("(" + result.getExpression() + ")"); + public FhirPathTransformation visitParenthesizedTerm( + @Nullable final ParenthesizedTermContext ctx) { + return input -> { + // Parentheses are ignored in the standalone term case. + final FhirPath result = new Visitor(context).visit( + requireNonNull(ctx).expression()).apply(input); + return result.withExpression("(" + result.getExpression() + ")"); + }; } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/UnnestBehaviour.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/UnnestBehaviour.java deleted file mode 100644 index 76de796eae..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/UnnestBehaviour.java +++ /dev/null @@ -1,18 +0,0 @@ -package au.csiro.pathling.fhirpath.parser; - -public enum UnnestBehaviour { - /** - * When a repeating element is encountered, it will be unnested. - */ - UNNEST, - - /** - * When a repeating element is encountered, an error will be thrown. - */ - ERROR, - - /** - * When a repeating element is encountered, it will be left as an array. - */ - NOOP -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java index 98ac803665..3286d09307 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java @@ -21,6 +21,7 @@ import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.FhirPathTransformation; import au.csiro.pathling.fhirpath.operator.Operator; import au.csiro.pathling.fhirpath.operator.OperatorInput; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; @@ -49,7 +50,7 @@ * * @author John Grimes */ -class Visitor extends FhirPathBaseVisitor { +class Visitor extends FhirPathBaseVisitor { @Nonnull private final ParserContext context; @@ -62,11 +63,11 @@ class Visitor extends FhirPathBaseVisitor { * A term is typically a standalone literal or function invocation. * * @param ctx The {@link TermExpressionContext} - * @return A {@link FhirPath} expression + * @return A {@link FhirPathTransformation} expression */ @Override @Nonnull - public FhirPath visitTermExpression(@Nullable final TermExpressionContext ctx) { + public FhirPathTransformation visitTermExpression(@Nullable final TermExpressionContext ctx) { return requireNonNull(ctx).term().accept(new TermVisitor(context)); } @@ -74,96 +75,84 @@ public FhirPath visitTermExpression(@Nullable final TermExpressionContext ctx) { * An invocation expression is one expression invoking another using the dot notation. * * @param ctx The {@link InvocationExpressionContext} - * @return A {@link FhirPath} expression + * @return A {@link FhirPathTransformation} expression */ @Override @Nonnull - public FhirPath visitInvocationExpression(@Nullable final InvocationExpressionContext ctx) { - final FhirPath expressionResult = new Visitor(context).visit( - requireNonNull(ctx).expression()); - // The input context is passed through to the invocation visitor as the invoker. - return ctx.invocation().accept(new InvocationVisitor(context, expressionResult)); + public FhirPathTransformation visitInvocationExpression( + @Nullable final InvocationExpressionContext ctx) { + return (input) -> { + final FhirPath expression = new Visitor(context).visit(requireNonNull(ctx).expression()) + .apply(input); + // The input context is passed through to the invocation visitor as the invoker. + return ctx.invocation().accept(new InvocationVisitor(context)).apply(expression); + }; } @Nonnull - private FhirPath visitBinaryOperator(@Nullable final ParseTree leftContext, + private FhirPathTransformation visitBinaryOperator(@Nullable final ParseTree leftContext, @Nullable final ParseTree rightContext, @Nullable final String operatorName) { requireNonNull(operatorName); + return (input) -> { + final FhirPath left = new Visitor(context).visit(leftContext).apply(input); + final FhirPath right = new Visitor(context).visit(rightContext) + .apply(input.withDataset(left.getDataset())); - // Parse the left expression. - final FhirPath left = new Visitor(context).visit(leftContext); + final Operator operator = Operator.getInstance(operatorName); - // If there are no grouping columns and the root nesting level has been erased by the parsing of - // the left expression (i.e. there has been aggregation or use of where), disaggregate the input - // context before parsing the right expression. - final boolean disaggregationRequired = context.getNesting().isRootErased() && - // This disaggregation only happens in the root context, not in the context of function - // arguments. - context.getThisContext().isEmpty() && - !(context.getGroupingColumns().size() == 1 - && context.getGroupingColumns().get(0).equals(context.getInputContext().getIdColumn())); - final ParserContext rightParserContext; - if (disaggregationRequired) { - rightParserContext = context.disaggregate(left); - context.getNesting().setRootErased(false); - } else { - rightParserContext = context.withContextDataset(left.getDataset()); - } - - // Parse the right expression, using the possibly modified context. - final FhirPath right = new Visitor(rightParserContext).visit(rightContext); - - // Retrieve an Operator instance based upon the operator string. - final Operator operator = Operator.getInstance(operatorName); - - final OperatorInput operatorInput = new OperatorInput(context, left, right); - return operator.invoke(operatorInput); + final OperatorInput operatorInput = new OperatorInput(context, left, right); + return operator.invoke(operatorInput); + }; } @Override @Nonnull - public FhirPath visitEqualityExpression(@Nullable final EqualityExpressionContext ctx) { + public FhirPathTransformation visitEqualityExpression( + @Nullable final EqualityExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); } @Override - public FhirPath visitInequalityExpression(@Nullable final InequalityExpressionContext ctx) { + public FhirPathTransformation visitInequalityExpression( + @Nullable final InequalityExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); } @Override @Nonnull - public FhirPath visitAndExpression(@Nullable final AndExpressionContext ctx) { + public FhirPathTransformation visitAndExpression(@Nullable final AndExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); } @Override @Nonnull - public FhirPath visitOrExpression(@Nullable final OrExpressionContext ctx) { + public FhirPathTransformation visitOrExpression(@Nullable final OrExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); } @Override @Nonnull - public FhirPath visitImpliesExpression(@Nullable final ImpliesExpressionContext ctx) { + public FhirPathTransformation visitImpliesExpression( + @Nullable final ImpliesExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); } @Override @Nonnull - public FhirPath visitMembershipExpression(@Nullable final MembershipExpressionContext ctx) { + public FhirPathTransformation visitMembershipExpression( + @Nullable final MembershipExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); } @Override @Nonnull - public FhirPath visitMultiplicativeExpression( + public FhirPathTransformation visitMultiplicativeExpression( @Nullable final MultiplicativeExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); @@ -171,13 +160,15 @@ public FhirPath visitMultiplicativeExpression( @Override @Nonnull - public FhirPath visitAdditiveExpression(@Nullable final AdditiveExpressionContext ctx) { + public FhirPathTransformation visitAdditiveExpression( + @Nullable final AdditiveExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); } @Override - public FhirPath visitCombineExpression(@Nullable final CombineExpressionContext ctx) { + public FhirPathTransformation visitCombineExpression( + @Nullable final CombineExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); } @@ -186,25 +177,25 @@ public FhirPath visitCombineExpression(@Nullable final CombineExpressionContext @Override @Nonnull - public FhirPath visitIndexerExpression(final IndexerExpressionContext ctx) { + public FhirPathTransformation visitIndexerExpression(final IndexerExpressionContext ctx) { throw new InvalidUserInputError("Indexer operation is not supported"); } @Override @Nonnull - public FhirPath visitPolarityExpression(final PolarityExpressionContext ctx) { + public FhirPathTransformation visitPolarityExpression(final PolarityExpressionContext ctx) { throw new InvalidUserInputError("Polarity operator is not supported"); } @Override @Nonnull - public FhirPath visitUnionExpression(final UnionExpressionContext ctx) { + public FhirPathTransformation visitUnionExpression(final UnionExpressionContext ctx) { throw new InvalidUserInputError("Union expressions are not supported"); } @Override @Nonnull - public FhirPath visitTypeExpression(final TypeExpressionContext ctx) { + public FhirPathTransformation visitTypeExpression(final TypeExpressionContext ctx) { throw new InvalidUserInputError("Type expressions are not supported"); } From bf11cd8d27e24a5e12503831b95e376e92236738 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sun, 6 Aug 2023 22:15:55 +1000 Subject: [PATCH 58/95] Update FhirViewExecutor to account for the parsing changes --- .../fhirpath/parser/ConstantReplacer.java | 11 ++- .../fhirpath/parser/ParserContext.java | 10 +++ .../pathling/views/FhirViewExecutor.java | 83 ++++++------------- 3 files changed, 44 insertions(+), 60 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ConstantReplacer.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ConstantReplacer.java index 87e5df2085..cb6b4e0f41 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ConstantReplacer.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ConstantReplacer.java @@ -21,7 +21,6 @@ import static java.util.Objects.requireNonNull; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; -import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ExternalConstantContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ExternalConstantTermContext; import java.util.Map; @@ -30,6 +29,12 @@ import javax.annotation.Nullable; import org.antlr.v4.runtime.tree.ParseTree; +/** + * A special type of parser that goes through an expression and replaces any constants with the + * corresponding values specified within a map. + * + * @author John Grimes + */ public class ConstantReplacer extends FhirPathBaseVisitor { private final Map constants; @@ -40,9 +45,7 @@ public ConstantReplacer(final Map constants) { @Nonnull public String execute(@Nonnull final String expression) { - final FhirPathParser parser = buildParser(expression); - - return visit(parser.expression()); + return visit(buildParser(expression).expression()); } @Override diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index 2717a6a696..ff63ae61dc 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -117,4 +117,14 @@ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final Resour this.constantReplacer = constantReplacer; } + /** + * @return a new {@link ParserContext} with the same properties as this one, but with a different + * input context + */ + @Nonnull + public ParserContext withInputContext(@Nonnull final FhirPath inputContext) { + return new ParserContext(inputContext, resource, fhirContext, sparkSession, dataSource, + terminologyServiceFactory, constantReplacer); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index 76738ebacd..c9e537e620 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -1,6 +1,5 @@ package au.csiro.pathling.views; -import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; @@ -63,79 +62,70 @@ public Dataset buildQuery(@Nonnull final FhirView view) { final ResourceType resourceType = ResourceType.fromCode(view.getResource()); final ResourcePath inputContext = ResourcePath.build(fhirContext, dataSource, resourceType, view.getResource(), true); - final ParserContext parserContext = new ParserContext(inputContext, fhirContext, sparkSession, - dataSource, terminologyServiceFactory, singletonList(inputContext.getIdColumn()), - constantReplacer); + final ParserContext parserContext = new ParserContext(inputContext, inputContext, fhirContext, + sparkSession, dataSource, terminologyServiceFactory, constantReplacer); - final ContextAndSelection select = parseSelect(view.getSelect(), parserContext, + final List select = parseSelect(view.getSelect(), parserContext, Collections.emptyList()); final List where = view.getWhere() == null ? Collections.emptyList() : view.getWhere().stream() .map(WhereClause::getExpression) .collect(toList()); - final Optional filterCondition = parseExpressions(where, parserContext).stream() + final Optional filterCondition = where.stream() + .map(e -> parseExpression(e, parserContext)) .map(FhirPath::getValueColumn) .reduce(Column::and); return filterCondition - .map(select.getContext().getDataset()::filter) - .orElse(select.getContext().getDataset()) - .select(select.getSelection().toArray(new Column[0])); + .map(inputContext.getDataset()::filter) + .orElse(inputContext.getDataset()) + .select(select.toArray(new Column[0])); } @Nonnull - private ContextAndSelection parseSelect(@Nonnull final List selectGroup, - @Nonnull final ParserContext context, @Nonnull final List selection) { - @Nonnull ContextAndSelection result = new ContextAndSelection( - context.getInputContext(), selection); - @Nonnull ParserContext currentContext = context; + private List parseSelect(@Nonnull final List selectGroup, + @Nonnull final ParserContext context, @Nonnull final List currentSelection) { + final List newSelection = new ArrayList<>(currentSelection); for (final SelectClause select : selectGroup) { if (select instanceof DirectSelection) { - result = directSelection(currentContext, (DirectSelection) select, result); + newSelection.addAll(directSelection(context, (DirectSelection) select, currentSelection)); } else if (select instanceof FromSelection) { - result = nestedSelection(currentContext, (FromSelection) select, result, false); + newSelection.addAll( + nestedSelection(context, (FromSelection) select, currentSelection, false)); } else if (select instanceof ForEachSelection) { - result = nestedSelection(currentContext, (ForEachSelection) select, result, true); + newSelection.addAll( + nestedSelection(context, (ForEachSelection) select, currentSelection, true)); } else { throw new IllegalStateException("Unknown select clause type: " + select.getClass()); } - currentContext = currentContext.withContextDataset(result.getContext().getDataset()); - currentContext.unsetThisContext(); } - return result; + return newSelection; } @Nonnull - private ContextAndSelection directSelection(final @Nonnull ParserContext context, - @Nonnull final DirectSelection select, @Nonnull final ContextAndSelection result) { - final FhirPath path = parseExpression(select.getExpression(), - context.withInputContext(result.getContext())); - final List newColumns = new ArrayList<>(result.getSelection()); + private List directSelection(@Nonnull final ParserContext context, + @Nonnull final DirectSelection select, @Nonnull final List currentSelection) { + final FhirPath path = parseExpression(select.getExpression(), context); + final List newColumns = new ArrayList<>(currentSelection); newColumns.add(path.getValueColumn().alias(select.getName())); - return new ContextAndSelection(context.getInputContext().withDataset(path.getDataset()), - newColumns); + return newColumns; } @Nonnull - private ContextAndSelection nestedSelection(final @Nonnull ParserContext context, - @Nonnull final NestedSelectClause select, @Nonnull final ContextAndSelection result, + private List nestedSelection(final @Nonnull ParserContext context, + @Nonnull final NestedSelectClause select, @Nonnull final List currentSelection, final boolean unnest) { - final FhirPath from = parseExpression(select.getExpression(), - context.withInputContext(result.getContext())); + final FhirPath from = parseExpression(select.getExpression(), context); final FhirPath nextInputContext = unnest ? from.unnest() : from; final ParserContext nextContext = context.withInputContext(nextInputContext); - nextContext.setThisContext(nextInputContext); - final ContextAndSelection nestedResult = parseSelect(select.getSelect(), nextContext, - result.getSelection()); - return new ContextAndSelection(context.withContextDataset(nestedResult.getContext() - .getDataset()).getInputContext(), nestedResult.getSelection()); + return parseSelect(select.getSelect(), nextContext, currentSelection); } @Nonnull @@ -145,26 +135,7 @@ private FhirPath parseExpression(@Nonnull final String expression, final String updatedExpression = context.getConstantReplacer() .map(replacer -> replacer.execute(expression)) .orElse(expression); - return parser.parse(updatedExpression); - } - - @Nonnull - private List parseExpressions(@Nonnull final List expressions, - @Nonnull final ParserContext context) { - return expressions.stream() - .reduce(new ArrayList<>(), (list, expression) -> { - final ParserContext currentContext = - list.isEmpty() - ? context - : context.withContextDataset(list.get(list.size() - 1).getDataset()); - - list.add(parseExpression(expression, currentContext)); - return list; - - }, (list1, list2) -> { - list1.addAll(list2); - return list1; - }); + return parser.parse(updatedExpression).apply(context.getInputContext()); } } From f4a6dc202971f1a5503dc2b8abc62b1c9ac5758a Mon Sep 17 00:00:00 2001 From: John Grimes Date: Tue, 22 Aug 2023 14:52:52 +1000 Subject: [PATCH 59/95] Partial refactoring to implement only view execution --- .../pathling/aggregate/AggregateExecutor.java | 14 +- .../pathling/aggregate/DrillDownBuilder.java | 21 +- .../au/csiro/pathling/fhir/FhirServer.java | 8 +- .../csiro/pathling/search/SearchExecutor.java | 23 +- .../security/ga4gh/PassportScopeEnforcer.java | 6 +- .../aggregate/DrillDownBuilderTest.java | 8 +- .../fhirpath/parser/AbstractParserTest.java | 10 +- .../parser/DateTimeArithmeticParserTest.java | 20 +- .../pathling/fhirpath/parser/ParserTest.java | 86 +++--- .../fhirpath/parser/QuantityParserTest.java | 20 +- .../security/ga4gh/ManifestConverterTest.java | 4 +- .../java/au/csiro/pathling/QueryExecutor.java | 53 ++-- .../java/au/csiro/pathling/QueryHelpers.java | 54 ++-- .../aggregate/AggregateQueryExecutor.java | 26 +- .../extract/ExtractQueryExecutor.java | 14 +- .../csiro/pathling/fhirpath/Comparable.java | 199 ++++++------ .../au/csiro/pathling/fhirpath/FhirPath.java | 138 +-------- .../pathling/fhirpath/FhirPathAndContext.java | 5 +- .../fhirpath/FhirPathTransformation.java | 12 - .../csiro/pathling/fhirpath/FhirPathType.java | 44 +++ .../pathling/fhirpath/FhirTypeMapping.java | 48 +++ .../java/au/csiro/pathling/fhirpath/Flat.java | 20 -- .../pathling/fhirpath/FunctionInput.java | 28 +- .../{FhirValue.java => Materializable.java} | 8 +- .../au/csiro/pathling/fhirpath/Nesting.java | 5 +- .../pathling/fhirpath/NonLiteralPath.java | 230 -------------- .../au/csiro/pathling/fhirpath/Numeric.java | 38 +-- .../au/csiro/pathling/fhirpath/Referrer.java | 23 +- .../csiro/pathling/fhirpath/ResourcePath.java | 243 --------------- .../pathling/fhirpath/StringCoercible.java | 6 +- .../au/csiro/pathling/fhirpath/Temporal.java | 51 ++-- .../pathling/fhirpath/TerminologyUtils.java | 30 +- .../pathling/fhirpath/TypeSpecifier.java | 50 --- .../fhirpath/UntypedResourcePath.java | 107 ------- .../collection/BooleanCollection.java | 83 +++++ .../CodingCollection.java} | 98 +++--- .../fhirpath/collection/Collection.java | 264 ++++++++++++++++ .../fhirpath/collection/DateCollection.java | 111 +++++++ .../collection/DateTimeCollection.java | 119 ++++++++ .../collection/DecimalCollection.java | 183 +++++++++++ .../collection/IntegerCollection.java | 182 +++++++++++ .../fhirpath/collection/MixedCollection.java | 63 ++++ .../QuantityCollection.java} | 180 +++++++---- .../collection/ResourceCollection.java | 158 ++++++++++ .../StringCollection.java} | 96 +++--- .../fhirpath/collection/TimeCollection.java | 81 +++++ .../comparison/CodingSqlComparator.java | 4 +- .../comparison/DateTimeSqlComparator.java | 4 +- .../comparison/QuantitySqlComparator.java | 4 +- .../definition/ChoiceElementDefinition.java | 42 ++- .../definition/ElementDefinition.java | 70 +---- .../fhirpath/definition/NodeDefinition.java | 21 ++ .../definition/ResourceDefinition.java | 39 +-- .../fhirpath/element/BooleanPath.java | 97 ------ .../fhirpath/element/ChoiceElementPath.java | 98 ------ .../pathling/fhirpath/element/DatePath.java | 135 -------- .../fhirpath/element/DateTimePath.java | 146 --------- .../fhirpath/element/DecimalPath.java | 192 ------------ .../fhirpath/element/ElementPath.java | 217 ------------- .../fhirpath/element/ExtensionPath.java | 76 ----- .../fhirpath/element/IntegerPath.java | 208 ------------- .../fhirpath/element/ReferencePath.java | 103 ------- .../pathling/fhirpath/element/TimePath.java | 106 ------- .../fhirpath/function/AggregateFunction.java | 199 ------------ .../pathling/fhirpath/function/Arguments.java | 6 +- .../function/BooleansTestFunction.java | 10 +- .../fhirpath/function/CountFunction.java | 47 +-- .../fhirpath/function/EmptyFunction.java | 31 +- .../fhirpath/function/ExistsFunction.java | 40 +-- .../fhirpath/function/ExtensionFunction.java | 46 +-- .../fhirpath/function/FirstFunction.java | 23 +- .../fhirpath/function/GetIdFunction.java | 12 +- .../fhirpath/function/IifFunction.java | 18 +- .../fhirpath/function/NamedFunction.java | 112 +------ .../fhirpath/function/NamedFunctionInput.java | 89 ------ .../fhirpath/function/NotFunction.java | 14 +- .../fhirpath/function/OfTypeFunction.java | 83 ++--- .../fhirpath/function/ResolveFunction.java | 35 +-- .../function/ReverseResolveFunction.java | 20 +- .../fhirpath/function/SumFunction.java | 9 +- .../fhirpath/function/ToStringFunction.java | 10 +- .../fhirpath/function/UntilFunction.java | 27 +- .../fhirpath/function/WhereFunction.java | 77 ++--- .../function/registry/FunctionRegistry.java | 31 ++ .../registry/InMemoryFunctionRegistry.java | 32 ++ .../registry/StaticFunctionRegistry.java | 72 +++++ .../terminology/DesignationFunction.java | 22 +- .../function/terminology/DisplayFunction.java | 19 +- .../terminology/MemberOfFunction.java | 23 +- .../terminology/PropertyFunction.java | 22 +- .../terminology/SubsumesFunction.java | 55 ++-- .../terminology/TranslateFunction.java | 18 +- .../fhirpath/literal/BooleanLiteralPath.java | 104 ------- .../fhirpath/literal/CodingLiteralPath.java | 125 -------- .../fhirpath/literal/DateLiteralPath.java | 128 -------- .../fhirpath/literal/DateTimeLiteralPath.java | 131 -------- .../fhirpath/literal/DecimalLiteralPath.java | 155 ---------- .../fhirpath/literal/IntegerLiteralPath.java | 144 --------- .../fhirpath/literal/LiteralPath.java | 240 --------------- .../fhirpath/literal/NullLiteralPath.java | 96 ------ .../fhirpath/literal/QuantityLiteralPath.java | 191 ------------ .../fhirpath/literal/StringLiteralPath.java | 124 -------- .../fhirpath/literal/TimeLiteralPath.java | 111 ------- .../BinaryOperator.java} | 26 +- ...torInput.java => BinaryOperatorInput.java} | 29 +- .../fhirpath/operator/BinaryOperatorType.java | 80 +++++ .../fhirpath/operator/BooleanOperator.java | 52 ++-- .../fhirpath/operator/CombineOperator.java | 12 +- .../fhirpath/operator/ComparisonOperator.java | 40 +-- .../operator/DateArithmeticOperator.java | 15 +- .../fhirpath/operator/MathOperator.java | 15 +- .../fhirpath/operator/MembershipOperator.java | 24 +- .../pathling/fhirpath/operator/Operator.java | 123 -------- .../fhirpath/operator/PathTraversalInput.java | 15 +- .../operator/PathTraversalOperator.java | 112 ------- .../fhirpath/parser/InvocationVisitor.java | 49 +-- .../fhirpath/parser/LiteralTermVisitor.java | 95 +++--- .../pathling/fhirpath/parser/Parser.java | 6 +- .../fhirpath/parser/ParserContext.java | 23 +- .../pathling/fhirpath/parser/TermVisitor.java | 15 +- .../pathling/fhirpath/parser/Visitor.java | 56 ++-- .../pathling/sql/misc/CodingToLiteral.java | 6 +- .../pathling/views/ContextAndSelection.java | 4 +- .../pathling/views/FhirViewExecutor.java | 24 +- .../fhirpath/CanBeCombinedWithTest.java | 80 ++--- .../function/BooleansTestFunctionTest.java | 21 +- .../fhirpath/function/CountFunctionTest.java | 32 +- .../fhirpath/function/EmptyFunctionTest.java | 19 +- .../fhirpath/function/ExistsFunctionTest.java | 35 ++- .../function/ExtensionFunctionTest.java | 46 +-- .../fhirpath/function/FirstFunctionTest.java | 36 +-- .../fhirpath/function/IifFunctionTest.java | 86 +++--- .../fhirpath/function/NotFunctionTest.java | 21 +- .../fhirpath/function/OfTypeFunctionTest.java | 27 +- .../function/ResolveFunctionTest.java | 35 +-- .../function/ReverseResolveFunctionTest.java | 41 ++- .../fhirpath/function/SumFunctionTest.java | 22 +- .../fhirpath/function/UntilFunctionTest.java | 93 +++--- .../fhirpath/function/WhereFunctionTest.java | 41 ++- .../terminology/DesignationFunctionTest.java | 44 ++- .../terminology/DisplayFunctionTest.java | 36 +-- .../terminology/MemberOfFunctionTest.java | 62 ++-- .../terminology/PropertyFunctionTest.java | 53 ++-- .../terminology/SubsumesFunctionTest.java | 94 +++--- .../terminology/TranslateFunctionTest.java | 62 ++-- .../literal/CodingLiteralPathTest.java | 29 +- .../operator/BooleanOperatorTest.java | 46 +-- .../BooleanOperatorValidationTest.java | 22 +- .../operator/CombineOperatorTest.java | 50 +-- .../operator/ComparisonOperatorDateTest.java | 44 +-- .../ComparisonOperatorDateTimeTest.java | 44 +-- .../ComparisonOperatorInstantTest.java | 44 +-- .../ComparisonOperatorQuantityTest.java | 43 +-- .../operator/ComparisonOperatorTest.java | 121 ++++---- .../operator/ComparisonOperatorTimeTest.java | 44 +-- .../ComparisonOperatorValidationTest.java | 12 +- .../fhirpath/operator/DateArithmeticTest.java | 287 +++++++++--------- .../operator/EqualityOperatorCodingTest.java | 56 ++-- .../EqualityOperatorQuantityTest.java | 91 +++--- .../operator/MathOperatorQuantityTest.java | 36 +-- .../fhirpath/operator/MathOperatorTest.java | 64 ++-- .../operator/MathOperatorValidationTest.java | 32 +- .../operator/MembershipOperatorTest.java | 60 ++-- .../operator/PathTraversalOperatorTest.java | 57 ++-- .../QuantityOperatorsPrecisionTest.java | 56 ++-- .../pathling/test/assertions/Assertions.java | 14 +- .../assertions/BaseFhirPathAssertion.java | 54 ++-- .../test/assertions/ElementPathAssertion.java | 9 +- .../test/assertions/FhirPathAssertion.java | 6 +- .../assertions/ResourcePathAssertion.java | 6 +- .../test/builders/ElementPathBuilder.java | 18 +- .../test/builders/ParserContextBuilder.java | 11 +- .../test/builders/ResourcePathBuilder.java | 10 +- .../builders/UntypedResourcePathBuilder.java | 4 +- .../test/fixtures/ExtensionFixture.java | 8 +- .../pathling/test/helpers/SparkHelpers.java | 6 +- .../pathling/test/helpers/TestHelpers.java | 4 +- 177 files changed, 3982 insertions(+), 6796 deletions(-) delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathTransformation.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathType.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirTypeMapping.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/Flat.java rename fhirpath/src/main/java/au/csiro/pathling/fhirpath/{FhirValue.java => Materializable.java} (80%) delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/TypeSpecifier.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/BooleanCollection.java rename fhirpath/src/main/java/au/csiro/pathling/fhirpath/{element/CodingPath.java => collection/CodingCollection.java} (52%) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/Collection.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateCollection.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateTimeCollection.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DecimalCollection.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/IntegerCollection.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java rename fhirpath/src/main/java/au/csiro/pathling/fhirpath/{element/QuantityPath.java => collection/QuantityCollection.java} (52%) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/ResourceCollection.java rename fhirpath/src/main/java/au/csiro/pathling/fhirpath/{element/StringPath.java => collection/StringCollection.java} (57%) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/TimeCollection.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/NodeDefinition.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/BooleanPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ChoiceElementPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ExtensionPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunctionInput.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/FunctionRegistry.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/InMemoryFunctionRegistry.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/BooleanLiteralPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/NullLiteralPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/QuantityLiteralPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java rename fhirpath/src/main/java/au/csiro/pathling/fhirpath/{definition/ReferenceExtensionDefinition.java => operator/BinaryOperator.java} (52%) rename fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/{OperatorInput.java => BinaryOperatorInput.java} (61%) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperatorType.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/Operator.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java diff --git a/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java index adcc3edae7..15c25f7c55 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java @@ -20,18 +20,16 @@ import static java.util.stream.Collectors.toList; import au.csiro.pathling.config.QueryConfiguration; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.Materializable; import au.csiro.pathling.io.Database; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.function.Function; -import java.util.stream.Collectors; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; import org.apache.spark.sql.Row; @@ -107,14 +105,14 @@ private AggregateResponse buildResponse( @Nonnull @SuppressWarnings("unchecked") private Function mapRowToGrouping( - @Nonnull final List aggregations, @Nonnull final List groupings, - @Nonnull final Collection filters) { + @Nonnull final List aggregations, @Nonnull final List groupings, + @Nonnull final java.util.Collection filters) { return row -> { final List> labels = new ArrayList<>(); final List> results = new ArrayList<>(); for (int i = 0; i < groupings.size(); i++) { - final FhirValue grouping = (FhirValue) groupings.get(i); + final Materializable grouping = (Materializable) groupings.get(i); // Delegate to the `getValueFromRow` method within each Materializable path class to extract // the Type value from the Row in the appropriate way. final Optional label = grouping.getFhirValueFromRow(row, i); @@ -123,7 +121,7 @@ private Function mapRowToGrouping( for (int i = 0; i < aggregations.size(); i++) { //noinspection rawtypes - final FhirValue aggregation = (FhirValue) aggregations.get(i); + final Materializable aggregation = (Materializable) aggregations.get(i); // Delegate to the `getValueFromRow` method within each Materializable path class to extract // the Type value from the Row in the appropriate way. final Optional result = aggregation.getFhirValueFromRow(row, i + groupings.size()); diff --git a/fhir-server/src/main/java/au/csiro/pathling/aggregate/DrillDownBuilder.java b/fhir-server/src/main/java/au/csiro/pathling/aggregate/DrillDownBuilder.java index 3df4a4a645..dbf4d8444d 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/aggregate/DrillDownBuilder.java +++ b/fhir-server/src/main/java/au/csiro/pathling/aggregate/DrillDownBuilder.java @@ -20,11 +20,10 @@ import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static au.csiro.pathling.utilities.Strings.parentheses; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.literal.LiteralPath; import au.csiro.pathling.utilities.Strings; import java.util.ArrayList; -import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; @@ -44,10 +43,10 @@ public class DrillDownBuilder { private final List> labels; @Nonnull - private final List groupings; + private final List groupings; @Nonnull - private final Collection filters; + private final java.util.Collection filters; /** * @param labels The labels from the subject grouping @@ -55,7 +54,7 @@ public class DrillDownBuilder { * @param filters The filters from the request */ public DrillDownBuilder(@Nonnull final List> labels, - @Nonnull final List groupings, @Nonnull final Collection filters) { + @Nonnull final List groupings, @Nonnull final java.util.Collection filters) { checkArgument(labels.size() == groupings.size(), "Labels should be same size as groupings"); this.labels = labels; this.groupings = groupings; @@ -71,7 +70,7 @@ public DrillDownBuilder(@Nonnull final List> labels, public Optional build() { // We use a Set here to avoid situations where we needlessly have the same condition in the // expression more than once. - final Collection fhirPaths = new LinkedHashSet<>(); + final java.util.Collection fhirPaths = new LinkedHashSet<>(); // Add each of the grouping expressions, along with either equality or contains against the // group value to convert it in to a Boolean expression. @@ -87,9 +86,9 @@ public Optional build() { : Optional.empty(); } - private void addGroupings(final Collection fhirPaths) { + private void addGroupings(final java.util.Collection fhirPaths) { for (int i = 0; i < groupings.size(); i++) { - final FhirPath grouping = groupings.get(i); + final Collection grouping = groupings.get(i); final Optional label = labels.get(i); if (label.isPresent()) { final String literal = LiteralPath @@ -109,15 +108,15 @@ private void addGroupings(final Collection fhirPaths) { } } - private void addFilters(@Nonnull final Collection fhirPaths) { + private void addFilters(@Nonnull final java.util.Collection fhirPaths) { final List filterExpressions = filters.stream() - .map(FhirPath::getExpression) + .map(Collection::getExpression) .collect(Collectors.toList()); fhirPaths.addAll(filterExpressions); } @Nonnull - private List parenthesiseExpressions(@Nonnull final Collection fhirPaths) { + private List parenthesiseExpressions(@Nonnull final java.util.Collection fhirPaths) { if (fhirPaths.size() > 1) { return fhirPaths.stream() .map(Strings::parentheses) diff --git a/fhir-server/src/main/java/au/csiro/pathling/fhir/FhirServer.java b/fhir-server/src/main/java/au/csiro/pathling/fhir/FhirServer.java index ccd39f9002..7b12ad6eaa 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/fhir/FhirServer.java +++ b/fhir-server/src/main/java/au/csiro/pathling/fhir/FhirServer.java @@ -22,12 +22,11 @@ import au.csiro.pathling.async.JobProvider; import au.csiro.pathling.caching.EntityTagInterceptor; import au.csiro.pathling.config.ServerConfiguration; -import au.csiro.pathling.encoders.EncoderBuilder; import au.csiro.pathling.errors.DiagnosticContextInterceptor; import au.csiro.pathling.errors.ErrorHandlingInterceptor; import au.csiro.pathling.errors.ErrorReportingInterceptor; import au.csiro.pathling.extract.ResultProvider; -import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.security.OidcConfiguration; import au.csiro.pathling.update.BatchProvider; import au.csiro.pathling.update.ImportProvider; @@ -43,11 +42,9 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; -import java.util.EnumSet; import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; @@ -59,7 +56,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Profile; -import scala.collection.JavaConverters; /** * A HAPI RestfulServer that provides the FHIR interface to the functionality within Pathling. @@ -329,7 +325,7 @@ public static Enumerations.ResourceType resourceTypeFromClass( */ @Nonnull public static Set supportedResourceTypes() { - return ResourcePath.supportedResourceTypes(); + return ResourceCollection.supportedResourceTypes(); } } diff --git a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java index 881193080d..1493bba3ed 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java @@ -26,8 +26,8 @@ import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.Database; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -116,9 +116,9 @@ public SearchExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull private Dataset initializeDataset() { - final ResourcePath resourcePath = ResourcePath - .build(getFhirContext(), getDataSource(), subjectResource, subjectResource.toCode(), true); - final Dataset subjectDataset = resourcePath.getDataset(); + final ResourceCollection resourceCollection = ResourceCollection + .build(getFhirContext(), getDataSource(), subjectResource, subjectResource.toCode()); + final Dataset subjectDataset = resourceCollection.getDataset(); final Dataset dataset; if (filters.isEmpty() || filters.get().getValuesAsQueryTokens().isEmpty()) { @@ -126,9 +126,10 @@ private Dataset initializeDataset() { dataset = subjectDataset; } else { - final ParserContext parserContext = new ParserContext(resourcePath, fhirContext, sparkSession, + final ParserContext parserContext = new ParserContext(resourceCollection, fhirContext, sparkSession, dataSource, terminologyServiceFactory, - Collections.singletonList(resourcePath.getIdColumn()), Optional.empty()); + functionRegistry, Collections.singletonList(resourceCollection.getIdColumn()), + Optional.empty()); Dataset currentDataset = subjectDataset; @Nullable Column filterCondition = null; @@ -143,7 +144,7 @@ private Dataset initializeDataset() { .collect(toList()); checkUserInput(filterExpressions.stream().noneMatch(String::isBlank), "Filter expression cannot be blank"); - final List filters = parseExpressions(parserContext, filterExpressions, + final List filters = parseExpressions(parserContext, filterExpressions, Optional.of(currentDataset)); validateFilters(filters); @@ -152,7 +153,7 @@ private Dataset initializeDataset() { // Combine all the columns with OR logic. final Column orColumn = filters.stream() - .map(FhirPath::getValueColumn) + .map(Collection::getValueColumn) .reduce(Column::or) .orElseThrow(); @@ -164,8 +165,8 @@ private Dataset initializeDataset() { dataset = currentDataset.filter(filterCondition); } - final Column[] resourceColumns = resourcePath.getElementsToColumns().keySet().stream() - .map(colName -> resourcePath.getElementsToColumns().get(colName).alias(colName)) + final Column[] resourceColumns = resourceCollection.getElementsToColumns().keySet().stream() + .map(colName -> resourceCollection.getElementsToColumns().get(colName).alias(colName)) .toArray(Column[]::new); // Resources are ordered by ID to ensure consistent paging. final Dataset result = dataset.select(resourceColumns); diff --git a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java index ae8f98b96d..5df8cbf150 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java +++ b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java @@ -19,7 +19,7 @@ import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.config.QueryConfiguration; -import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.io.Database; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -83,9 +83,9 @@ public Dataset enforce(@Nonnull final ResourceType subjectResource, log.debug("Enforcing scope {} on {} resources", filters, subjectResource.toCode()); // Build a new expression parser, and parse all the column expressions within the query. - final ResourcePath inputContext = ResourcePath + final ResourceCollection inputContext = ResourceCollection .build(getFhirContext(), getDataSource(), subjectResource, - subjectResource.toCode(), true); + subjectResource.toCode()); return filterDataset(inputContext, filters, dataset, dataset.col("id"), Column::or); } diff --git a/fhir-server/src/test/java/au/csiro/pathling/aggregate/DrillDownBuilderTest.java b/fhir-server/src/test/java/au/csiro/pathling/aggregate/DrillDownBuilderTest.java index ab45957013..87b3ee8fd4 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/aggregate/DrillDownBuilderTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/aggregate/DrillDownBuilderTest.java @@ -20,8 +20,8 @@ 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.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.ElementPathBuilder; import au.csiro.pathling.test.helpers.TestHelpers; @@ -148,11 +148,11 @@ static Stream parameters() { @MethodSource("parameters") void labelTypes(@Nonnull final TestParameters parameters) { final List> labels = List.of(Optional.of(parameters.getLabel())); - final ElementPath grouping = new ElementPathBuilder(spark) + final PrimitivePath grouping = new ElementPathBuilder(spark) .expression("someElement") .singular(true) .build(); - final List groupings = List.of(grouping); + final List groupings = List.of(grouping); final DrillDownBuilder builder = new DrillDownBuilder(labels, groupings, Collections.emptyList()); final Optional drillDown = builder.build(); diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java index e47ef9ecf5..688a6df2b1 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java @@ -20,7 +20,7 @@ import static org.mockito.Mockito.when; import au.csiro.pathling.encoders.FhirEncoders; -import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyService; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -74,8 +74,8 @@ void setUp() { ResourceType.DIAGNOSTICREPORT, ResourceType.ORGANIZATION, ResourceType.QUESTIONNAIRE, ResourceType.CAREPLAN); - final ResourcePath subjectResource = ResourcePath - .build(fhirContext, dataSource, ResourceType.PATIENT, ResourceType.PATIENT.toCode(), true); + final ResourceCollection subjectResource = ResourceCollection + .build(fhirContext, dataSource, ResourceType.PATIENT, ResourceType.PATIENT.toCode()); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .terminologyClientFactory(terminologyServiceFactory) @@ -97,8 +97,8 @@ void mockResource(final ResourceType... resourceTypes) { @Nonnull protected FhirPathAssertion assertThatResultOf(@Nonnull final ResourceType resourceType, @Nonnull final String expression) { - final ResourcePath subjectResource = ResourcePath - .build(fhirContext, dataSource, resourceType, resourceType.toCode(), true); + final ResourceCollection subjectResource = ResourceCollection + .build(fhirContext, dataSource, resourceType, resourceType.toCode()); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .terminologyClientFactory(terminologyServiceFactory) diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java index bda7525690..8ad68715bc 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java @@ -17,8 +17,8 @@ package au.csiro.pathling.fhirpath.parser; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.BooleanPath; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.test.builders.ParserContextBuilder; import java.util.Collections; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -28,9 +28,9 @@ public class DateTimeArithmeticParserTest extends AbstractParserTest { @Test void lengthOfEncounter() { - final ResourcePath subjectResource = ResourcePath - .build(fhirContext, dataSource, ResourceType.ENCOUNTER, ResourceType.ENCOUNTER.toCode(), - true); + final ResourceCollection subjectResource = ResourceCollection + .build(fhirContext, dataSource, ResourceType.ENCOUNTER, ResourceType.ENCOUNTER.toCode() + ); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .terminologyClientFactory(terminologyServiceFactory) .database(dataSource) @@ -40,16 +40,16 @@ void lengthOfEncounter() { parser = new Parser(parserContext); assertThatResultOf("(period.start + 20 minutes) > period.end") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/lengthOfEncounter.tsv"); } @Test void ageAtTimeOfEncounter() { - final ResourcePath subjectResource = ResourcePath - .build(fhirContext, dataSource, ResourceType.ENCOUNTER, ResourceType.ENCOUNTER.toCode(), - true); + final ResourceCollection subjectResource = ResourceCollection + .build(fhirContext, dataSource, ResourceType.ENCOUNTER, ResourceType.ENCOUNTER.toCode() + ); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .terminologyClientFactory(terminologyServiceFactory) .database(dataSource) @@ -59,7 +59,7 @@ void ageAtTimeOfEncounter() { parser = new Parser(parserContext); assertThatResultOf("period.start > (subject.resolve().ofType(Patient).birthDate + 60 years)") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/ageAtTimeOfEncounter.tsv"); } diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java index 1deac4b1a8..d05a787563 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java @@ -37,12 +37,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.BooleanPath; -import au.csiro.pathling.fhirpath.element.DatePath; -import au.csiro.pathling.fhirpath.element.DecimalPath; -import au.csiro.pathling.fhirpath.element.IntegerPath; -import au.csiro.pathling.fhirpath.element.StringPath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.DateCollection; +import au.csiro.pathling.fhirpath.collection.DecimalCollection; +import au.csiro.pathling.fhirpath.collection.IntegerCollection; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; import au.csiro.pathling.fhirpath.literal.DateLiteralPath; import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; @@ -110,13 +110,13 @@ private void setupMockPropertiesFor_195662009_444814009() { @Test void testContainsOperator() { assertThatResultOf("name.family contains 'Wuckert783'") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectOrderedResult() .hasRows(allPatientsWithValue(spark, false) .changeValue(PATIENT_ID_9360820c, true)); assertThatResultOf("name.suffix contains 'MD'") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectOrderedResult() .hasRows(allPatientsWithValue(spark, false) .changeValue(PATIENT_ID_8ee183e2, true)); @@ -125,13 +125,13 @@ void testContainsOperator() { @Test void testInOperator() { assertThatResultOf("'Wuckert783' in name.family") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectOrderedResult() .hasRows(allPatientsWithValue(spark, false) .changeValue(PATIENT_ID_9360820c, true)); assertThatResultOf("'MD' in name.suffix") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectOrderedResult() .hasRows(allPatientsWithValue(spark, false) .changeValue(PATIENT_ID_8ee183e2, true)); @@ -143,14 +143,14 @@ void testCodingOperations() { // test unversioned assertThatResultOf( "maritalStatus.coding contains http://terminology.hl7.org/CodeSystem/v3-MaritalStatus|S") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectOrderedResult() .hasRows(allPatientsWithValue(spark, false)); // test versioned assertThatResultOf( "http://terminology.hl7.org/CodeSystem/v2-0203|v2.0.3|PPN in identifier.type.coding") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectOrderedResult() .hasRows(allPatientsWithValue(spark, false)); } @@ -256,7 +256,7 @@ void testCodingLiterals() { @Test void testCountWithReverseResolve() { assertThatResultOf("reverseResolve(Condition.subject).code.coding.count()") - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .isSingular() .selectOrderedResult() .hasRows( @@ -300,13 +300,13 @@ void testSubsumesAndSubsumedBy() { // With empty concept map subsume should work as member of assertThatResultOf( "reverseResolve(Condition.subject).code.subsumes(http://snomed.info/sct|40055000)") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectOrderedResult() .hasRows(spark, "responses/ParserTest/testSubsumesAndSubsumedBy-subsumes-empty.tsv"); assertThatResultOf( "reverseResolve(Condition.subject).code.subsumedBy(http://snomed.info/sct|40055000)") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectOrderedResult() .hasRows(spark, "responses/ParserTest/testSubsumesAndSubsumedBy-subsumedBy-empty.tsv"); @@ -562,7 +562,7 @@ void testWithCodingLiteral() { @Test void testCombineOperator() { assertThatResultOf("name.family combine name.given") - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testCombineOperator.tsv"); } @@ -570,7 +570,7 @@ void testCombineOperator() { @Test void testCombineOperatorWithWhereFunction() { assertThatResultOf("where((name.family combine name.given) contains 'Gleichner915').birthDate") - .isElementPath(DatePath.class) + .isElementPath(DateCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testCombineOperatorWithWhereFunction.tsv"); } @@ -589,7 +589,7 @@ void testCombineOperatorWithDifferentlyTypedStringPaths() { assertThatResultOf( "reverseResolve(Condition.subject).code.coding.system combine " + "reverseResolve(Condition.subject).code.coding.code") - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testCombineOperatorWithDifferentlyTypedStringPaths.tsv"); @@ -598,7 +598,7 @@ void testCombineOperatorWithDifferentlyTypedStringPaths() { @Test void testCombineOperatorWithComplexTypeAndNull() { assertThatResultOf("(name combine {}).given") - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testCombineOperatorWithComplexTypeAndNull.tsv"); @@ -607,7 +607,7 @@ void testCombineOperatorWithComplexTypeAndNull() { @Test void testCombineOperatorWithTwoLiterals() { assertThatResultOf("1 combine 2") - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testCombineOperatorWithTwoLiterals.tsv"); } @@ -628,7 +628,7 @@ void testCombineOperatorWithCodingLiterals() { assertThatResultOf( "(http://snomed.info/sct|410429000||'Cardiac Arrest' combine " + "http://snomed.info/sct|230690007||'Stroke').empty()") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testCombineOperatorWithCodingLiterals.tsv"); } @@ -636,7 +636,7 @@ void testCombineOperatorWithCodingLiterals() { @Test void testBooleanOperatorWithLeftLiteral() { assertThatResultOf("@1970-11-22 = birthDate") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testBooleanOperatorWithLeftLiteral.tsv"); } @@ -655,7 +655,7 @@ void parserErrorThrows() { void testExtensionsOnResources() { assertThatResultOf( "extension.url") - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testExtensionsOnResources.tsv"); } @@ -665,7 +665,7 @@ void testExtensionFunction() { // This should be the same as: "extension.where($this.url='http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName').valueString" assertThatResultOf( "extension('http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName').valueString") - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testExtensionFunction.tsv"); } @@ -674,7 +674,7 @@ void testExtensionFunction() { void testExtensionsOnElements() { assertThatResultOf( "address.extension.url") - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testExtensionsOnElements.tsv"); } @@ -683,7 +683,7 @@ void testExtensionsOnElements() { void testNestedExtensions() { assertThatResultOf( "extension.extension.url") - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testNestedExtensions.tsv"); } @@ -692,7 +692,7 @@ void testNestedExtensions() { void testExtensionsCurrentResource() { assertThatResultOf(ResourceType.CONDITION, "subject.resolve().ofType(Patient).extension.url") - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testExtensionsCurrentResource.tsv"); } @@ -703,7 +703,7 @@ void testComplexExtensionsOnComplexPath() { "address.where($this.city = 'Boston')" + ".extension('http://hl7.org/fhir/StructureDefinition/geolocation')" + ".extension('latitude').valueDecimal") - .isElementPath(DecimalPath.class) + .isElementPath(DecimalCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testComplexExtensionsOnComplexPath.tsv"); } @@ -712,7 +712,7 @@ void testComplexExtensionsOnComplexPath() { void testExtensionFunctionInWhere() { assertThatResultOf( "address.where($this.extension('http://hl7.org/fhir/StructureDefinition/geolocation').extension('latitude').valueDecimal contains 42.391383).city") - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testExtensionFunctionInWhere.tsv"); } @@ -745,7 +745,7 @@ void testReverseResolveFollowingMonomorphicResolve() { setSubjectResource(ResourceType.ENCOUNTER); assertThatResultOf( "serviceProvider.resolve().reverseResolve(Encounter.serviceProvider).id") - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testReverseResolveFollowingMonomorphicResolve.tsv"); } @@ -756,7 +756,7 @@ void testReverseResolveFollowingPolymorphicResolve() { assertThatResultOf( "subject.resolve().ofType(Patient).reverseResolve(Encounter.subject).id " + "contains '2aff9edd-def2-487a-b435-a162e11a303c'") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testReverseResolveFollowingPolymorphicResolve.tsv"); } @@ -765,7 +765,7 @@ void testReverseResolveFollowingPolymorphicResolve() { void testReverseResolveFollowingReverseResolve() { assertThatResultOf( "reverseResolve(Encounter.subject).reverseResolve(CarePlan.encounter).id") - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testReverseResolveFollowingReverseResolve.tsv"); } @@ -773,7 +773,7 @@ void testReverseResolveFollowingReverseResolve() { @Test void testIifWithNullLiteral() { assertThatResultOf("iif(gender='male', birthDate, {})") - .isElementPath(DatePath.class) + .isElementPath(DateCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testIifWithNullLiteral.csv"); } @@ -783,16 +783,16 @@ void testUntilFunction() { setSubjectResource(ResourceType.ENCOUNTER); assertThatResultOf( "subject.resolve().ofType(Patient).birthDate.until(%resource.period.start, 'years')") - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testUntilFunction.csv"); } @SuppressWarnings("SameParameterValue") private void setSubjectResource(@Nonnull final ResourceType resourceType) { - final ResourcePath subjectResource = ResourcePath - .build(fhirContext, dataSource, resourceType, resourceType.toCode(), - true); + final ResourceCollection subjectResource = ResourceCollection + .build(fhirContext, dataSource, resourceType, resourceType.toCode() + ); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .terminologyClientFactory(terminologyServiceFactory) @@ -808,7 +808,7 @@ void testQuantityMultiplicationAndDivision() { assertThatResultOf( "((reverseResolve(Observation.subject).where(valueQuantity < 150 'cm').valueQuantity.first() * 2 '1')" + " / reverseResolve(Observation.subject).where(valueQuantity < 1.50 'm').valueQuantity.first()).value") - .isElementPath(DecimalPath.class) + .isElementPath(DecimalCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testQuantityMultiplicationAndDivision.csv"); } @@ -819,7 +819,7 @@ void testQuantityAdditionSubtractionAndEquality() { assertThatResultOf( "((reverseResolve(Observation.subject).where(valueQuantity > 1 'mmol/L').valueQuantity.first() + 33 'mmol/L')" + " - reverseResolve(Observation.subject).where(valueQuantity > 1 'mmol/L').valueQuantity.first()) = 19873051110000000000000000 'm-3'") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testQuantityAdditionSubtractionAndEquality.csv"); } @@ -829,12 +829,12 @@ void testQuantityAdditionWithOverflow() { // values for 121503c8-9564-4b48-9086-a22df717948e and a7eb2ce7-1075-426c-addd-957b861b0e55 exceed 10^26 m-3 assertThatResultOf( "(reverseResolve(Observation.subject).where(valueQuantity > 100 'mmol/L').valueQuantity.first() + 33 'mmol/L').value") - .isElementPath(DecimalPath.class) + .isElementPath(DecimalCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testQuantityAdditionWithOverflow_value.csv"); assertThatResultOf( "(reverseResolve(Observation.subject).where(valueQuantity > 100 'mmol/L').valueQuantity.first() + 33 'mmol/L').code") - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testQuantityAdditionWithOverflow_code.csv"); } @@ -853,7 +853,7 @@ void testResolutionOfExtensionReference() { assertThatResultOf( "reverseResolve(Encounter.subject).extension.where(url = 'urn:test:associated-goal')" + ".valueReference.resolve().ofType(Goal).description.text") - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testResolutionOfExtensionReference.csv"); } @@ -864,7 +864,7 @@ void testResolutionOfExtensionReferenceWithWrongType() { assertThatResultOf( "reverseResolve(Encounter.subject).extension.where(url = 'urn:test:associated-goal')" + ".valueReference.resolve().ofType(Condition).id") - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testResolutionOfExtensionReferenceWithWrongType.csv"); } diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java index 108a215406..1faf547ccf 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java @@ -17,8 +17,8 @@ package au.csiro.pathling.fhirpath.parser; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.BooleanPath; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.test.builders.ParserContextBuilder; import java.util.Collections; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -28,9 +28,9 @@ public class QuantityParserTest extends AbstractParserTest { @Test void lengthObservationComparison() { - final ResourcePath subjectResource = ResourcePath - .build(fhirContext, dataSource, ResourceType.OBSERVATION, ResourceType.OBSERVATION.toCode(), - true); + final ResourceCollection subjectResource = ResourceCollection + .build(fhirContext, dataSource, ResourceType.OBSERVATION, ResourceType.OBSERVATION.toCode() + ); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .terminologyClientFactory(terminologyServiceFactory) .database(dataSource) @@ -40,16 +40,16 @@ void lengthObservationComparison() { parser = new Parser(parserContext); assertThatResultOf("valueQuantity < 1.5 'm'") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/lengthObservationComparison.tsv"); } @Test void lengthObservationSubtraction() { - final ResourcePath subjectResource = ResourcePath - .build(fhirContext, dataSource, ResourceType.OBSERVATION, ResourceType.OBSERVATION.toCode(), - true); + final ResourceCollection subjectResource = ResourceCollection + .build(fhirContext, dataSource, ResourceType.OBSERVATION, ResourceType.OBSERVATION.toCode() + ); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .terminologyClientFactory(terminologyServiceFactory) .database(dataSource) @@ -59,7 +59,7 @@ void lengthObservationSubtraction() { parser = new Parser(parserContext); assertThatResultOf("valueQuantity > (valueQuantity - 2 'g/dL')") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/lengthObservationSubtraction.tsv"); } diff --git a/fhir-server/src/test/java/au/csiro/pathling/security/ga4gh/ManifestConverterTest.java b/fhir-server/src/test/java/au/csiro/pathling/security/ga4gh/ManifestConverterTest.java index ea79d5890a..09194bec33 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/security/ga4gh/ManifestConverterTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/security/ga4gh/ManifestConverterTest.java @@ -23,7 +23,7 @@ import au.csiro.pathling.config.ServerConfiguration; import au.csiro.pathling.encoders.FhirEncoders; -import au.csiro.pathling.fhirpath.element.BooleanPath; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; import au.csiro.pathling.fhirpath.parser.AbstractParserTest; import au.csiro.pathling.io.Database; import ca.uhn.fhir.context.FhirContext; @@ -123,7 +123,7 @@ void convertsManifest() { boolean found = false; for (final String filter : passportScope.get(resourceType)) { final Dataset dataset = assertThatResultOf(resourceType, filter) - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectResult() .apply(result -> result.filter(col(result.columns()[1]))) .getDataset(); diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index 54ffa0fd39..7c3e5ea501 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -24,9 +24,9 @@ import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.config.QueryConfiguration; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.BooleanPath; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; import au.csiro.pathling.fhirpath.parser.Parser; import au.csiro.pathling.fhirpath.parser.ParserContext; @@ -35,7 +35,6 @@ import ca.uhn.fhir.context.FhirContext; import com.google.common.collect.Streams; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -84,21 +83,23 @@ protected QueryExecutor(@Nonnull final QueryConfiguration configuration, } @Nonnull - protected List parseExpressions( - @Nonnull final ParserContext parserContext, @Nonnull final Collection expressions) { + protected List parseExpressions( + @Nonnull final ParserContext parserContext, + @Nonnull final java.util.Collection expressions) { return parseExpressions(parserContext, expressions, Optional.empty()); } @Nonnull - protected List parseExpressions( - @Nonnull final ParserContext parserContext, @Nonnull final Collection expressions, + protected List parseExpressions( + @Nonnull final ParserContext parserContext, + @Nonnull final java.util.Collection expressions, @Nonnull final Optional> contextDataset) { - final List parsed = new ArrayList<>(); + final List parsed = new ArrayList<>(); ParserContext currentContext = contextDataset.map(parserContext::withContextDataset).orElse( parserContext); for (final String expression : expressions) { if (parsed.size() > 0) { - final FhirPath lastParsed = parsed.get(parsed.size() - 1); + final Collection lastParsed = parsed.get(parsed.size() - 1); // If there are no grouping columns and the root nesting level has been erased by the // parsing of the previous expression (i.e. there has been aggregation or use of where), // disaggregate the input context before parsing the right expression. @@ -118,25 +119,25 @@ protected List parseExpressions( } @Nonnull - protected void validateFilters(@Nonnull final Collection filters) { - for (final FhirPath filter : filters) { + protected void validateFilters(@Nonnull final java.util.Collection filters) { + for (final Collection filter : filters) { // Each filter expression must evaluate to a singular Boolean value, or a user error will be // thrown. - checkUserInput(filter instanceof BooleanPath || filter instanceof BooleanLiteralPath, + checkUserInput(filter instanceof BooleanCollection || filter instanceof BooleanLiteralPath, "Filter expression must be a Boolean: " + filter.getExpression()); checkUserInput(filter.isSingular(), "Filter expression must be a singular value: " + filter.getExpression()); } } - protected Dataset filterDataset(@Nonnull final ResourcePath inputContext, - @Nonnull final Collection filters, @Nonnull final Dataset dataset, + protected Dataset filterDataset(@Nonnull final ResourceCollection inputContext, + @Nonnull final java.util.Collection filters, @Nonnull final Dataset dataset, @Nonnull final BinaryOperator operator) { return filterDataset(inputContext, filters, dataset, inputContext.getIdColumn(), operator); } - protected Dataset filterDataset(@Nonnull final ResourcePath inputContext, - @Nonnull final Collection filters, @Nonnull final Dataset dataset, + protected Dataset filterDataset(@Nonnull final ResourceCollection inputContext, + @Nonnull final java.util.Collection filters, @Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final BinaryOperator operator) { final Dataset filteredDataset; if (filters.isEmpty()) { @@ -163,31 +164,33 @@ protected static Stream labelColumns(@Nonnull final Stream colum @Nonnull private DatasetWithColumn getFilteredIds(@Nonnull final Iterable filters, - @Nonnull final ResourcePath inputContext, @Nonnull final BinaryOperator operator) { - ResourcePath currentContext = inputContext; + @Nonnull final ResourceCollection inputContext, + @Nonnull final BinaryOperator operator) { + ResourceCollection currentContext = inputContext; @Nullable Column filterColumn = null; for (final String filter : filters) { // Parse the filter expression. final ParserContext parserContext = new ParserContext(currentContext, fhirContext, sparkSession, dataSource, - terminologyServiceFactory, Collections.singletonList(currentContext.getIdColumn()), + terminologyServiceFactory, functionRegistry, + Collections.singletonList(currentContext.getIdColumn()), Optional.empty()); final Parser parser = new Parser(parserContext); - final FhirPath fhirPath = parser.parse(filter); + final Collection result = parser.parse(filter); // Check that it is a Boolean expression. - checkUserInput(fhirPath instanceof BooleanPath || fhirPath instanceof BooleanLiteralPath, - "Filter expression must be of Boolean type: " + fhirPath.getExpression()); + checkUserInput(result instanceof BooleanCollection || result instanceof BooleanLiteralPath, + "Filter expression must be of Boolean type: " + result.getExpression()); // Add the filter column to the overall filter expression using the supplied operator. - final Column filterValue = fhirPath.getValueColumn(); + final Column filterValue = result.getValueColumn(); filterColumn = filterColumn == null ? filterValue : operator.apply(filterColumn, filterValue); // Update the context to build the next expression from the same dataset. - currentContext = currentContext.copy(currentContext.getExpression(), fhirPath.getDataset(), + currentContext = currentContext.copy(currentContext.getExpression(), result.getDataset(), currentContext.getIdColumn(), currentContext.getValueColumn(), currentContext.getOrderingColumn(), currentContext.isSingular(), currentContext.getThisColumn()); diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java b/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java index 2bcb3f918a..dfab45fbbf 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java @@ -27,14 +27,13 @@ import static org.apache.spark.sql.functions.posexplode_outer; import au.csiro.pathling.encoders.FhirEncoders; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.literal.LiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.utilities.Strings; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; @@ -43,7 +42,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nonnull; import lombok.Getter; @@ -159,7 +157,7 @@ private static Dataset join(@Nonnull final Dataset left, "Left columns should be same size as right columns"); Dataset aliasedLeft = left; - final Collection joinConditions = new ArrayList<>(); + final java.util.Collection joinConditions = new ArrayList<>(); for (int i = 0; i < leftColumns.size(); i++) { // We alias the join columns on the left-hand side to disambiguate them from columns named the // same on the right-hand side. @@ -282,41 +280,41 @@ public static Dataset join(@Nonnull final Dataset left, } /** - * Joins two {@link FhirPath} expressions, using equality between their respective resource ID + * Joins two {@link Collection} expressions, using equality between their respective resource ID * columns. * * @param parserContext the current {@link ParserContext} - * @param left a {@link FhirPath} expression + * @param left a {@link Collection} expression * @param right another FhirPath expression * @param joinType a {@link JoinType} * @return a new {@link Dataset} */ @Nonnull public static Dataset join(@Nonnull final ParserContext parserContext, - @Nonnull final FhirPath left, @Nonnull final FhirPath right, + @Nonnull final Collection left, @Nonnull final Collection right, @Nonnull final JoinType joinType) { return join(parserContext, Arrays.asList(left, right), joinType); } /** - * Joins any number of {@link FhirPath} expressions, using equality between their respective + * Joins any number of {@link Collection} expressions, using equality between their respective * resource ID columns. * * @param parserContext the current {@link ParserContext} - * @param fhirPaths a list of {@link FhirPath} expressions + * @param results a list of {@link Collection} expressions * @param joinType a {@link JoinType} * @return a new {@link Dataset} */ @Nonnull public static Dataset join(@Nonnull final ParserContext parserContext, - @Nonnull final List fhirPaths, @Nonnull final JoinType joinType) { - checkArgument(fhirPaths.size() > 1, "fhirPaths must contain more than one FhirPath"); + @Nonnull final List results, @Nonnull final JoinType joinType) { + checkArgument(results.size() > 1, "fhirPaths must contain more than one FhirPath"); - final FhirPath left = fhirPaths.get(0); - final List joinTargets = fhirPaths.subList(1, fhirPaths.size()); + final Collection left = results.get(0); + final List joinTargets = results.subList(1, results.size()); // Only non-literal paths will trigger a join. - final List nonLiteralTargets = joinTargets.stream() + final List nonLiteralTargets = joinTargets.stream() .filter(t -> t instanceof NonLiteralPath) .collect(toList()); if (left instanceof NonLiteralPath && nonLiteralTargets.isEmpty()) { @@ -334,7 +332,7 @@ public static Dataset join(@Nonnull final ParserContext parserContext, final Column idColumn = parserContext.getInputContext().getIdColumn(); final List leftColumns = checkColumnsAndFallback(left.getDataset(), groupingColumns, idColumn); - for (final FhirPath right : nonLiteralTargets) { + for (final Collection right : nonLiteralTargets) { final List resolvedGroupingColumns = checkColumnsAndFallback(right.getDataset(), leftColumns, idColumn); dataset = join(dataset, resolvedGroupingColumns, right.getDataset(), resolvedGroupingColumns, @@ -344,26 +342,26 @@ public static Dataset join(@Nonnull final ParserContext parserContext, } /** - * Joins a {@link Dataset} to a {@link FhirPath}, using equality between the resource ID in the + * Joins a {@link Dataset} to a {@link Collection}, using equality between the resource ID in the * FhirPath and the supplied column. * - * @param left a {@link FhirPath} expression + * @param left a {@link Collection} expression * @param right a {@link Dataset} * @param rightColumn the {@link Column} in the right Dataset * @param joinType a {@link JoinType} * @return A new {@link Dataset} */ @Nonnull - public static Dataset join(@Nonnull final FhirPath left, @Nonnull final Dataset right, + public static Dataset join(@Nonnull final Collection left, @Nonnull final Dataset right, @Nonnull final Column rightColumn, @Nonnull final JoinType joinType) { return join(left.getDataset(), left.getIdColumn(), right, rightColumn, joinType); } /** - * Joins a {@link Dataset} to a {@link FhirPath}, using equality between the resource ID in the + * Joins a {@link Dataset} to a {@link Collection}, using equality between the resource ID in the * FhirPath and the supplied column. * - * @param left a {@link FhirPath} expression + * @param left a {@link Collection} expression * @param right a {@link Dataset} * @param rightColumn the {@link Column} in the right Dataset * @param additionalCondition an additional Column to be added to the join condition, using AND @@ -371,7 +369,7 @@ public static Dataset join(@Nonnull final FhirPath left, @Nonnull final Dat * @return A new {@link Dataset} */ @Nonnull - public static Dataset join(@Nonnull final FhirPath left, @Nonnull final Dataset right, + public static Dataset join(@Nonnull final Collection left, @Nonnull final Dataset right, @Nonnull final Column rightColumn, @Nonnull final Column additionalCondition, @Nonnull final JoinType joinType) { return join(left.getDataset(), Collections.singletonList(left.getIdColumn()), right, @@ -379,18 +377,18 @@ public static Dataset join(@Nonnull final FhirPath left, @Nonnull final Dat } /** - * Joins a {@link Dataset} to a {@link FhirPath}, using equality between the resource ID in the + * Joins a {@link Dataset} to a {@link Collection}, using equality between the resource ID in the * FhirPath and the supplied column. * * @param left a {@link Dataset} * @param leftColumn the {@link Column} in the left Dataset - * @param right a {@link FhirPath} expression + * @param right a {@link Collection} expression * @param joinType a {@link JoinType} * @return A new {@link Dataset} */ @Nonnull public static Dataset join(@Nonnull final Dataset left, - @Nonnull final Column leftColumn, @Nonnull final FhirPath right, + @Nonnull final Column leftColumn, @Nonnull final Collection right, @Nonnull final JoinType joinType) { return join(left, leftColumn, right.getDataset(), right.getIdColumn(), joinType); } @@ -422,7 +420,7 @@ private static List checkColumnsAndFallback(@Nonnull final Dataset * @return A new Dataset that is the union of all the inputs */ @Nonnull - public static Dataset union(@Nonnull final Collection> datasets) { + public static Dataset union(@Nonnull final java.util.Collection> datasets) { return datasets.stream() .reduce(Dataset::union) .orElseThrow(); @@ -430,7 +428,7 @@ public static Dataset union(@Nonnull final Collection> dataset @Nonnull private static Dataset applySelection(@Nonnull final Dataset dataset, - @Nonnull final Collection includes, @Nonnull final Collection excludes) { + @Nonnull final java.util.Collection includes, @Nonnull final java.util.Collection excludes) { return dataset.select(Stream.of(dataset.columns()) .filter(column -> includes.contains(column) || !excludes.contains(column)) .map(dataset::col) @@ -438,8 +436,8 @@ private static Dataset applySelection(@Nonnull final Dataset dataset, } @Nonnull - public static List getUnionableColumns(@Nonnull final FhirPath source, - @Nonnull final FhirPath target) { + public static List getUnionableColumns(@Nonnull final Collection source, + @Nonnull final Collection target) { // The columns will be those common to both datasets, plus the value column of the source. final Set commonColumnNames = new HashSet<>(List.of(source.getDataset().columns())); commonColumnNames.retainAll(List.of(target.getDataset().columns())); diff --git a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java index fe77e3544c..73bf9e2eac 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java @@ -20,18 +20,16 @@ import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static au.csiro.pathling.utilities.Strings.randomAlias; import static org.apache.spark.sql.functions.col; -import static org.apache.spark.sql.functions.first; import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.config.QueryConfiguration; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.Materializable; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.Database; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; -import java.util.Collection; import java.util.List; import java.util.Optional; import javax.annotation.Nonnull; @@ -160,10 +158,10 @@ public ResultWithExpressions buildQuery(@Nonnull final AggregateRequest query) { return null; } - private void validateAggregations(@Nonnull final Collection aggregations) { - for (final FhirPath aggregation : aggregations) { + private void validateAggregations(@Nonnull final java.util.Collection aggregations) { + for (final Collection aggregation : aggregations) { // An aggregation expression must be able to be extracted into a FHIR value. - checkUserInput(aggregation instanceof FhirValue, + checkUserInput(aggregation instanceof Materializable, "Aggregation expression is not of a supported type: " + aggregation.getExpression()); // An aggregation expression must be singular, relative to its input context. checkUserInput(aggregation.isSingular(), @@ -172,17 +170,17 @@ private void validateAggregations(@Nonnull final Collection aggregatio } } - private void validateGroupings(@Nonnull final Collection groupings) { - for (final FhirPath grouping : groupings) { + private void validateGroupings(@Nonnull final java.util.Collection groupings) { + for (final Collection grouping : groupings) { // A grouping expression must be able to be extracted into a FHIR value. - checkUserInput(grouping instanceof FhirValue, + checkUserInput(grouping instanceof Materializable, "Grouping expression is not of a supported type: " + grouping.getExpression()); } } @Nonnull private static Dataset applyAggregation(@Nonnull final ParserContext context, - @Nonnull final List aggregationColumns, @Nonnull final FhirPath lastAggregation, + @Nonnull final List aggregationColumns, @Nonnull final Collection lastAggregation, @Nonnull final Dataset disaggregated) { // Group the dataset by the grouping columns, and take the first row. final Column[] groupBy = context.getGroupingColumns().toArray(new Column[0]); @@ -202,13 +200,13 @@ public static class ResultWithExpressions { Dataset dataset; @Nonnull - List parsedAggregations; + List parsedAggregations; @Nonnull - List parsedGroupings; + List parsedGroupings; @Nonnull - Collection parsedFilters; + java.util.Collection parsedFilters; } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java index e1148012a7..2f1af93571 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java @@ -6,8 +6,7 @@ import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.AbstractPath; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Flat; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -116,25 +115,26 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query, return null; } - private List validateAndCoerceColumns( - @Nonnull final List columnParseResult, + private List validateAndCoerceColumns( + @Nonnull final List columnParseResult, @Nonnull final ExtractResultType resultType) { // Perform any necessary String coercion. - final List coerced = columnParseResult.stream() + final List coerced = columnParseResult.stream() .map(column -> { if (resultType == ExtractResultType.FLAT && !(column instanceof Flat) && column instanceof StringCoercible) { // If the result type is flat and the path is string-coercible, we can coerce it. final StringCoercible stringCoercible = (StringCoercible) column; - return stringCoercible.asStringPath(column.getExpression()); + return stringCoercible.asStringPath(column.getExpression(), + stringCoercible.getExpression()); } else { return column; } }).collect(toList()); // Validate the final set of paths. - for (final FhirPath column : coerced) { + for (final Collection column : coerced) { final boolean condition; if (resultType == ExtractResultType.FLAT) { // In flat mode, only flat columns are allowed. diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Comparable.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Comparable.java index ab6bf57b1e..36c231093a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Comparable.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Comparable.java @@ -17,6 +17,7 @@ package au.csiro.pathling.fhirpath; +import au.csiro.pathling.fhirpath.collection.Collection; import java.util.function.Function; import javax.annotation.Nonnull; import org.apache.commons.lang3.function.TriFunction; @@ -24,80 +25,55 @@ import org.apache.spark.sql.functions; /** - * Describes a path that can be compared with other paths, e.g. for equality. + * Describes a set of methods that can be used to compare {@link Collection} objects to other paths, + * e.g. for equality. * * @author John Grimes * @author Piotr Szul */ public interface Comparable { + ColumnComparator DEFAULT_COMPARATOR = new DefaultComparator(); + /** - * The interface that defines comparison operation on columns. The actual implementation and the - * implemented operation depend on the type of value in the column. + * Builds a comparison function for directly comparable paths using the custom comparator. + * + * @param source The path to build the comparison function for + * @param operation The {@link ComparisonOperation} type to retrieve a comparison for + * @param comparator The {@link ColumnComparator} to use + * @return A new {@link Function} */ - interface SqlComparator { - - Column equalsTo(Column left, Column right); - - default Column notEqual(final Column left, final Column right) { - return functions.not(equalsTo(left, right)); - } - - Column lessThan(Column left, Column right); - - default Column lessThanOrEqual(final Column left, final Column right) { - return lessThan(left, right).or(equalsTo(left, right)); - } + @Nonnull + static Function buildComparison(@Nonnull final Comparable source, + @Nonnull final ComparisonOperation operation, @Nonnull final ColumnComparator comparator) { - Column greaterThan(Column left, Column right); + final TriFunction compFunction = operation.compFunction; - default Column greaterThanOrEqual(final Column left, final Column right) { - return greaterThan(left, right).or(equalsTo(left, right)); - } + return target -> compFunction + .apply(comparator, source.getColumn(), target.getColumn()); } /** - * The implementation of comparator that use the standard Spark SQL operators. + * Builds a comparison function for directly comparable paths using the standard Spark SQL + * comparison operators. + * + * @param source The path to build the comparison function for + * @param operation The {@link ComparisonOperation} type to retrieve a comparison for + * @return A new {@link Function} */ - class StandardSqlComparator implements SqlComparator { - - @Override - public Column equalsTo(@Nonnull final Column left, @Nonnull final Column right) { - return left.equalTo(right); - } - - @Override - public Column notEqual(@Nonnull final Column left, @Nonnull final Column right) { - return left.notEqual(right); - } - - @Override - public Column lessThan(@Nonnull final Column left, @Nonnull final Column right) { - return left.lt(right); - } - - @Override - public Column lessThanOrEqual(@Nonnull final Column left, @Nonnull final Column right) { - return left.leq(right); - } - - @Override - public Column greaterThan(@Nonnull final Column left, @Nonnull final Column right) { - return left.gt(right); - } + static Function buildComparison(@Nonnull final Comparable source, + @Nonnull final ComparisonOperation operation) { - @Override - public Column greaterThanOrEqual(@Nonnull final Column left, @Nonnull final Column right) { - return left.geq(right); - } + return buildComparison(source, operation, DEFAULT_COMPARATOR); } - SqlComparator STD_SQL_COMPARATOR = new StandardSqlComparator(); - /** * Get a function that can take two Comparable paths and return a {@link Column} that contains a * comparison condition. The type of condition is controlled by supplying a * {@link ComparisonOperation}. + *

+ * Please use {@link #isComparableTo(Collection)} to first check whether the specified path should + * be compared to this path. * * @param operation The {@link ComparisonOperation} type to retrieve a comparison for * @return A {@link Function} that takes a Comparable as its parameter, and returns a @@ -112,45 +88,13 @@ public Column greaterThanOrEqual(@Nonnull final Column left, @Nonnull final Colu * @return A {@link Column} */ @Nonnull - Column getValueColumn(); + Column getColumn(); /** - * @param type A subtype of {@link FhirPath} + * @param type A subtype of {@link Collection} * @return {@code true} if this path can be compared to the specified class */ - boolean isComparableTo(@Nonnull Class type); - - /** - * Builds a comparison function for directly comparable paths using the custom comparator. - * - * @param source The path to build the comparison function for - * @param operation The {@link ComparisonOperation} type to retrieve a comparison for - * @param sqlComparator The {@link SqlComparator} to use - * @return A new {@link Function} - */ - @Nonnull - static Function buildComparison(@Nonnull final Comparable source, - @Nonnull final ComparisonOperation operation, @Nonnull final SqlComparator sqlComparator) { - - final TriFunction compFunction = operation.compFunction; - - return target -> compFunction - .apply(sqlComparator, source.getValueColumn(), target.getValueColumn()); - } - - /** - * Builds a comparison function for directly comparable paths using the standard Spark SQL - * comparison operators. - * - * @param source The path to build the comparison function for - * @param operation The {@link ComparisonOperation} type to retrieve a comparison for - * @return A new {@link Function} - */ - static Function buildComparison(@Nonnull final Comparable source, - @Nonnull final ComparisonOperation operation) { - - return buildComparison(source, operation, STD_SQL_COMPARATOR); - } + boolean isComparableTo(@Nonnull Collection path); /** * Represents a type of comparison operation. @@ -159,41 +103,41 @@ enum ComparisonOperation { /** * The equals operation. */ - EQUALS("=", SqlComparator::equalsTo), + EQUALS("=", ColumnComparator::equalsTo), /** * The not equals operation. */ - NOT_EQUALS("!=", SqlComparator::notEqual), + NOT_EQUALS("!=", ColumnComparator::notEqual), /** * The less than or equal to operation. */ - LESS_THAN_OR_EQUAL_TO("<=", SqlComparator::lessThanOrEqual), + LESS_THAN_OR_EQUAL_TO("<=", ColumnComparator::lessThanOrEqual), /** * The less than operation. */ - LESS_THAN("<", SqlComparator::lessThan), + LESS_THAN("<", ColumnComparator::lessThan), /** * The greater than or equal to operation. */ - GREATER_THAN_OR_EQUAL_TO(">=", SqlComparator::greaterThanOrEqual), + GREATER_THAN_OR_EQUAL_TO(">=", ColumnComparator::greaterThanOrEqual), /** * The greater than operation. */ - GREATER_THAN(">", SqlComparator::greaterThan); + GREATER_THAN(">", ColumnComparator::greaterThan); @Nonnull private final String fhirPath; @Nonnull - private final TriFunction compFunction; + private final TriFunction compFunction; ComparisonOperation(@Nonnull final String fhirPath, - @Nonnull final TriFunction compFunction) { + @Nonnull final TriFunction compFunction) { this.fhirPath = fhirPath; this.compFunction = compFunction; } @@ -203,5 +147,68 @@ enum ComparisonOperation { public String toString() { return fhirPath; } + + } + + /** + * An interface that defines comparison operation on columns. The actual implementation and the + * implemented operation depend on the type of value in the column. + */ + interface ColumnComparator { + + Column equalsTo(Column left, Column right); + + default Column notEqual(final Column left, final Column right) { + return functions.not(equalsTo(left, right)); + } + + Column lessThan(Column left, Column right); + + default Column lessThanOrEqual(final Column left, final Column right) { + return lessThan(left, right).or(equalsTo(left, right)); + } + + Column greaterThan(Column left, Column right); + + default Column greaterThanOrEqual(final Column left, final Column right) { + return greaterThan(left, right).or(equalsTo(left, right)); + } + } + + /** + * An implementation of {@link ColumnComparator} that uses the standard Spark SQL comparison + * operators. + */ + class DefaultComparator implements ColumnComparator { + + @Override + public Column equalsTo(@Nonnull final Column left, @Nonnull final Column right) { + return left.equalTo(right); + } + + @Override + public Column notEqual(@Nonnull final Column left, @Nonnull final Column right) { + return left.notEqual(right); + } + + @Override + public Column lessThan(@Nonnull final Column left, @Nonnull final Column right) { + return left.lt(right); + } + + @Override + public Column lessThanOrEqual(@Nonnull final Column left, @Nonnull final Column right) { + return left.leq(right); + } + + @Override + public Column greaterThan(@Nonnull final Column left, @Nonnull final Column right) { + return left.gt(right); + } + + @Override + public Column greaterThanOrEqual(@Nonnull final Column left, @Nonnull final Column right) { + return left.geq(right); + } } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java index 666a7ac2b2..508619a55e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java @@ -1,141 +1,15 @@ -/* - * Copyright 2023 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; -import java.util.Optional; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; +import au.csiro.pathling.fhirpath.collection.Collection; +import java.util.function.Function; /** - * Represents any FHIRPath expression - all expressions implement this interface. + * A description of how to take one {@link Collection} and transform it into another. * + * @param The input type of {@link Collection} + * @param The output type of {@link Collection} * @author John Grimes */ -public interface FhirPath { - - /** - * @return the FHIRPath expression that represents this path - */ - @Nonnull - String getExpression(); - - /** - * @return the {@link Dataset} that can be used to evaluate this path against data - */ - @Nonnull - Dataset getDataset(); - - /** - * @param nesting the nesting information to use for ordering - * @return the {@link Dataset} that can be used to evaluate this path against data, ordered - */ - @Nonnull - Dataset getOrderedDataset(@Nonnull Nesting nesting); - - /** - * @return a {@link Column} within the dataset containing the identity of the subject resource - */ - @Nonnull - Column getIdColumn(); - - /** - * @return a {@link Column} within the dataset containing the values of the nodes - */ - @Nonnull - Column getValueColumn(); - - /** - * @return a {@link Column} that can be used to order the dataset, for paths that have a defined - * order - */ - Optional getOrderingColumn(); - - /** - * @return an indicator of whether this path represents a single-valued collection - */ - boolean isSingular(); - - /** - * @param target the path to test - * @return an indicator of whether this path's values can be combined into a single collection - * with values from the supplied expression type - */ - boolean canBeCombinedWith(@Nonnull FhirPath target); - - /** - * Creates a copy of the path with a different expression. - * - * @param expression the new expression - * @return the new FhirPath - */ - @Nonnull - FhirPath withExpression(@Nonnull String expression); - - /** - * Creates a copy of the path with a different dataset. - * - * @param dataset the new dataset - * @return the new FhirPath - */ - @Nonnull - FhirPath withDataset(@Nonnull Dataset dataset); - - /** - * Trims the columns to those common with the target and sorts them, ready for a union operation. - * The value column of the path is always included as the last column of the dataset. - * - * @param target the expression that this path will be combined with - * @return a new {@link Dataset} with a subset of columns - */ - @Nonnull - Dataset getUnionableDataset(@Nonnull final FhirPath target); - - /** - * Creates a path that can be used to represent a collection which includes elements from both a - * source and a target path. - * - * @param target the path the merge the invoking path with - * @param dataset the {@link Dataset} that can be used to evaluate this path against data - * @param expression the FHIRPath expression that represents the result - * @param idColumn a {@link Column} within the dataset containing the identity of the subject - * resource - * @param valueColumn a {@link Column} within the dataset containing the values of the nodes - * @param singular an indicator of whether this path represents a single-valued collection - * @param thisColumn for paths that traverse from the {@code $this} keyword, this column refers to - * the values in the collection - * @return the resulting new {@link NonLiteralPath} - */ - @Nonnull - NonLiteralPath combineWith(@Nonnull FhirPath target, @Nonnull Dataset dataset, - @Nonnull String expression, @Nonnull Column idColumn, @Nonnull Column valueColumn, - boolean singular, @Nonnull Optional thisColumn); - - @Nonnull - FhirPath unnest(); - - /** - * Prints out to stdout all the ids and values of all the elements in this path. For debugging - * purposes only. - */ - default void show() { - getDataset().select(getIdColumn(), getValueColumn()).show(false); - } +public interface FhirPath extends Function { } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java index 0e0b073f66..028074ef67 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java @@ -1,11 +1,12 @@ package au.csiro.pathling.fhirpath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.parser.ParserContext; import javax.annotation.Nonnull; import lombok.Value; /** - * Holds the value of a {@link FhirPath} and an associated {@link ParserContext}. + * Holds the value of a {@link Collection} and an associated {@link ParserContext}. * * @author John Grimes */ @@ -13,7 +14,7 @@ public class FhirPathAndContext { @Nonnull - FhirPath fhirPath; + Collection result; @Nonnull ParserContext context; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathTransformation.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathTransformation.java deleted file mode 100644 index 2a721eb79d..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathTransformation.java +++ /dev/null @@ -1,12 +0,0 @@ -package au.csiro.pathling.fhirpath; - -import java.util.function.UnaryOperator; - -/** - * A description of how to take one {@link FhirPath} and transform it into another. - * - * @author John Grimes - */ -public interface FhirPathTransformation extends UnaryOperator { - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathType.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathType.java new file mode 100644 index 0000000000..b911529926 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathType.java @@ -0,0 +1,44 @@ +package au.csiro.pathling.fhirpath; + +import javax.annotation.Nonnull; +import lombok.Getter; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +/** + * Represents one of the types defined within the FHIRPath specification. + * + * @author John Grimes + */ +@Getter +public enum FhirPathType { + + BOOLEAN("Boolean"), + STRING("String"), + INTEGER("Integer"), + DECIMAL("Decimal"), + DATE("Date"), + DATETIME("DateTime"), + TIME("Time"), + QUANTITY("Quantity"), + CODING("Coding"), + TYPE_SPECIFIER("TypeSpecifier"); + + + @Nonnull + private final String typeSpecifier; + + FhirPathType(@Nonnull final String typeSpecifier) { + this.typeSpecifier = typeSpecifier; + } + + /** + * @param fhirType a {@link FHIRDefinedType} + * @return the corresponding {@link FhirPathType} according to the rules of automatic conversion + * within the FHIR spec + */ + @Nonnull + public static FhirPathType forFhirType(@Nonnull final FHIRDefinedType fhirType) { + return FhirTypeMapping.get(fhirType); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirTypeMapping.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirTypeMapping.java new file mode 100644 index 0000000000..2aded7ac4f --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirTypeMapping.java @@ -0,0 +1,48 @@ +package au.csiro.pathling.fhirpath; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import javax.annotation.Nonnull; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +/** + * Maps FHIR types to FHIRPath types. + * + * @author John Grimes + * @see Using FHIR types in expressions + */ +public abstract class FhirTypeMapping { + + @Nonnull + private static final Map FHIR_TO_FHIRPATH_TYPE = + new ImmutableMap.Builder() + .put(FHIRDefinedType.BOOLEAN, FhirPathType.BOOLEAN) + .put(FHIRDefinedType.STRING, FhirPathType.STRING) + .put(FHIRDefinedType.URI, FhirPathType.STRING) + .put(FHIRDefinedType.CODE, FhirPathType.STRING) + .put(FHIRDefinedType.OID, FhirPathType.STRING) + .put(FHIRDefinedType.ID, FhirPathType.STRING) + .put(FHIRDefinedType.UUID, FhirPathType.STRING) + .put(FHIRDefinedType.MARKDOWN, FhirPathType.STRING) + .put(FHIRDefinedType.BASE64BINARY, FhirPathType.STRING) + .put(FHIRDefinedType.INTEGER, FhirPathType.INTEGER) + .put(FHIRDefinedType.UNSIGNEDINT, FhirPathType.INTEGER) + .put(FHIRDefinedType.POSITIVEINT, FhirPathType.INTEGER) + .put(FHIRDefinedType.DECIMAL, FhirPathType.DECIMAL) + .put(FHIRDefinedType.DATE, FhirPathType.DATETIME) + .put(FHIRDefinedType.DATETIME, FhirPathType.DATETIME) + .put(FHIRDefinedType.INSTANT, FhirPathType.DATETIME) + .put(FHIRDefinedType.TIME, FhirPathType.TIME) + .put(FHIRDefinedType.QUANTITY, FhirPathType.QUANTITY) + .put(FHIRDefinedType.CODING, FhirPathType.CODING) + .build(); + + /** + * @return the FHIRPath type that the given FHIR type can be automatically converted to + */ + @Nonnull + public static FhirPathType get(@Nonnull final FHIRDefinedType fhirType) { + return FhirTypeMapping.FHIR_TO_FHIRPATH_TYPE.get(fhirType); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Flat.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Flat.java deleted file mode 100644 index fad2f88ccc..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Flat.java +++ /dev/null @@ -1,20 +0,0 @@ -package au.csiro.pathling.fhirpath; - -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; - -/** - * Designates an expression that is capable of being rendered into a flat, unstructured - * representation such as CSV. - * - * @author John Grimes - */ -public interface Flat { - - /** - * @return a {@link Column} within the dataset containing the values of the nodes - */ - @Nonnull - Column getValueColumn(); - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FunctionInput.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FunctionInput.java index d5c75f31e2..3d1a9ec247 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FunctionInput.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FunctionInput.java @@ -17,26 +17,40 @@ package au.csiro.pathling.fhirpath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.parser.ParserContext; +import java.util.List; import javax.annotation.Nonnull; -import lombok.Getter; +import lombok.Value; /** * Represents the inputs to a FHIRPath function. * * @author John Grimes */ -@Getter -public abstract class FunctionInput { +@Value +public class FunctionInput { /** * Context and dependencies for use in evaluating the function. */ @Nonnull - private final ParserContext context; + ParserContext context; - protected FunctionInput(@Nonnull final ParserContext context) { - this.context = context; - } + /** + * The collection that is the input to the function, i.e. the result of the evaluation of the + * expression on the left-hand side of the dot preceding the function invocation, or the left-hand + * operand in the case of an operator. + */ + @Nonnull + Collection input; + + /** + * A list of expressions representing the arguments to the function, i.e. the expressions inside + * the parentheses following the function invocation, separated by commas, or the right-hand + * operand in the case of an operator. + */ + @Nonnull + List> arguments; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirValue.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Materializable.java similarity index 80% rename from fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirValue.java rename to fhirpath/src/main/java/au/csiro/pathling/fhirpath/Materializable.java index 2a3a3fa297..d097939ba6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirValue.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Materializable.java @@ -23,13 +23,13 @@ import org.hl7.fhir.r4.model.Type; /** - * Designates an expression that is capable of being extracted into a FHIR value. + * Designates an expression for which we can extract a FHIR value for inclusion in responses. * - * @param the {@link Type} of FHIR value that can be extracted + * @param the {@link Type} of FHIR value that the expression can have * @author John Grimes - * @see Data Types + * @see Using FHIR types in expressions */ -public interface FhirValue { +public interface Materializable { /** * Extracts a FHIR value from a {@link Row} within a {@link org.apache.spark.sql.Dataset}. diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java index f58c1122fe..075da5ef11 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java @@ -2,6 +2,7 @@ import static java.util.stream.Collectors.toList; +import au.csiro.pathling.fhirpath.collection.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -72,7 +73,7 @@ public boolean isOrderable() { @Nonnull public List getColumns() { return nesting.values().stream() - .map(FhirPath::getValueColumn) + .map(Collection::getValueColumn) .collect(toList()); } @@ -82,7 +83,7 @@ public List getColumns() { @Nonnull public List getOrderingColumns() { return nesting.values().stream() - .map(FhirPath::getOrderingColumn) + .map(Collection::getOrderingColumn) .filter(Optional::isPresent) .map(Optional::get) .collect(toList()); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java deleted file mode 100644 index 5ed1cf7a35..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/NonLiteralPath.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright 2023 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; - -import static au.csiro.pathling.QueryHelpers.createColumn; -import static au.csiro.pathling.QueryHelpers.flattenValue; -import static au.csiro.pathling.QueryHelpers.getUnionableColumns; -import static au.csiro.pathling.utilities.Preconditions.checkArgument; -import static au.csiro.pathling.utilities.Preconditions.checkPresent; -import static org.apache.spark.sql.functions.explode_outer; - -import au.csiro.pathling.QueryHelpers; -import au.csiro.pathling.QueryHelpers.DatasetWithColumn; -import au.csiro.pathling.fhirpath.definition.BasicElementDefinition; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.literal.NullLiteralPath; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import lombok.Getter; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; - -/** - * Represents any FHIRPath expression which is not a literal. - * - * @author John Grimes - */ -@Getter -public abstract class NonLiteralPath implements FhirPath { - - @Nonnull - protected final String expression; - - @Nonnull - protected final Dataset dataset; - - @Nonnull - protected final Column idColumn; - - @Nonnull - protected final Column valueColumn; - - protected final boolean singular; - - /** - * Returns an expression representing the most current resource that has been navigated to within - * this path. This is used in {@code reverseResolve} for joining between the subject resource and - * a reference. - */ - @Nonnull - protected Optional currentResource; - - /** - * For paths that traverse from the {@code $this} keyword, this column refers to the values in the - * collection. This is so that functions that operate over collections can construct a result that - * is based on the original input using the argument alone, without having to join from the input - * to the argument (which has problems relating to the generation of duplicate rows). - */ - @Nonnull - protected Optional thisColumn; - - protected NonLiteralPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, final boolean singular, - @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn) { - - final List datasetColumns = Arrays.asList(dataset.columns()); - checkArgument(datasetColumns.contains(idColumn.toString()), - "ID column name not present in dataset"); - checkArgument(datasetColumns.contains(valueColumn.toString()), - "Value column name not present in dataset"); - thisColumn.ifPresent(col -> checkArgument(datasetColumns.contains(col.toString()), - "$this column name not present in dataset")); - - this.expression = expression; - this.dataset = dataset; - this.idColumn = idColumn; - this.valueColumn = valueColumn; - this.singular = singular; - this.currentResource = currentResource; - this.thisColumn = thisColumn; - } - - /** - * Get an ordering {@link Column} from any of the inputs, if there is one. - * - * @param inputs a collection of objects - * @return a {@link Column}, if one was found - */ - @Nonnull - public static Optional findOrderingColumn(@Nonnull final Object... inputs) { - return Stream.of(inputs) - .filter(input -> input instanceof NonLiteralPath) - .map(path -> (NonLiteralPath) path) - .filter(path -> path.getOrderingColumn().isPresent()) - .findFirst() - .flatMap(NonLiteralPath::getOrderingColumn); - } - - /** - * Gets a this {@link Column} from any of the inputs, if there is one. - * - * @param inputs a collection of objects - * @return a {@link Column}, if one was found - */ - @Nonnull - public static Optional findThisColumn(@Nonnull final Object... inputs) { - return Stream.of(inputs) - .filter(input -> input instanceof NonLiteralPath) - .map(path -> (NonLiteralPath) path) - .filter(path -> path.getThisColumn().isPresent()) - .findFirst() - .flatMap(NonLiteralPath::getThisColumn); - } - - @Nonnull - @Override - public Dataset getOrderedDataset(@Nonnull final Nesting nesting) { - final Column[] sortColumns = nesting.getOrderingColumns().toArray(new Column[0]); - return getDataset().orderBy(sortColumns); - } - - /** - * Returns the column with the extension container (the _fid to extension values map). - * - * @return the column with the extension container. - */ - @Nonnull - public Column getExtensionContainerColumn() { - final ResourcePath rootResource = checkPresent(getCurrentResource(), - "Current resource missing in traversed path. This is a bug in current resource propagation"); - return rootResource.getExtensionContainerColumn(); - } - - /** - * Returns the specified child of this path, if there is one. - * - * @param name The name of the child element - * @return an {@link BasicElementDefinition} object - */ - @Nonnull - public abstract Optional getChildElement(@Nonnull final String name); - - /** - * Creates a copy of this NonLiteralPath with an updated {@link Dataset}, ID and value - * {@link Column}s. - * - * @param expression an updated expression to describe the new NonLiteralPath - * @param dataset the new Dataset that can be used to evaluate this NonLiteralPath against data - * @param idColumn the new resource identity column - * @param valueColumn the new expression value column - * @param orderingColumn the new ordering column - * @param singular the new singular value - * @param thisColumn a list of columns containing the collection being iterated, for cases where a - * path is being created to represent the {@code $this} keyword - * @return a new instance of NonLiteralPath - */ - @Nonnull - public abstract NonLiteralPath copy(@Nonnull String expression, @Nonnull Dataset dataset, - @Nonnull Column idColumn, @Nonnull Column valueColumn, - @Nonnull Optional orderingColumn, boolean singular, - @Nonnull Optional thisColumn); - - @Nonnull - @Override - public FhirPath withExpression(@Nonnull final String expression) { - return copy(expression, dataset, idColumn, valueColumn, getOrderingColumn(), singular, - thisColumn); - } - - @Nonnull - @Override - public FhirPath withDataset(@Nonnull final Dataset dataset) { - return copy(expression, dataset, idColumn, valueColumn, getOrderingColumn(), singular, - thisColumn); - } - - /** - * Construct a $this path based upon this path. - * - * @return a new NonLiteralPath - */ - @Nonnull - public NonLiteralPath toThisPath() { - return copy(NamedFunction.THIS, this.getDataset(), this.getIdColumn(), this.getValueColumn(), - this.getOrderingColumn(), true, Optional.of(this.getValueColumn())); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return getClass().equals(target.getClass()) || target instanceof NullLiteralPath; - } - - @Nonnull - @Override - public Dataset getUnionableDataset(@Nonnull final FhirPath target) { - return getDataset().select(getUnionableColumns(this, target).toArray(new Column[]{})); - } - - @Nonnull - @Override - public FhirPath unnest() { - final Column flattenedValue = flattenValue(dataset, getValueColumn()); - final DatasetWithColumn datasetWithColumn = createColumn(getDataset(), - explode_outer(flattenedValue)); - return copy(getExpression(), datasetWithColumn.getDataset(), datasetWithColumn.getColumn(), - datasetWithColumn.getColumn(), getOrderingColumn(), isSingular(), getThisColumn()); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Numeric.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Numeric.java index c454369302..a2832b4c2b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Numeric.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Numeric.java @@ -17,14 +17,13 @@ package au.csiro.pathling.fhirpath; +import au.csiro.pathling.fhirpath.collection.Collection; +import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; import lombok.Getter; import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** * Describes a path that represents a numeric value, and can be the subject of math operations. @@ -34,37 +33,28 @@ public interface Numeric { /** - * Get a function that can take two Numeric paths and return a {@link NonLiteralPath} that - * contains the result of a math operation. The type of operation is controlled by supplying a + * Get a function that can take two Numeric paths and return a {@link Collection} that contains + * the result of a math operation. The type of operation is controlled by supplying a * {@link MathOperation}. * * @param operation The {@link MathOperation} type to retrieve a result for - * @param expression The FHIRPath expression to use within the result - * @param dataset The {@link Dataset} to use within the result * @return A {@link Function} that takes a Numeric as its parameter, and returns a - * {@link NonLiteralPath}. + * {@link Collection}. */ @Nonnull - Function getMathOperation(@Nonnull MathOperation operation, - @Nonnull String expression, @Nonnull Dataset dataset); - - /** - * @return a {@link Column} within the dataset containing the identity of the subject resource - */ - @Nonnull - Column getIdColumn(); + Function getMathOperation(@Nonnull MathOperation operation); /** * @return a {@link Column} within the dataset containing the values of the nodes */ @Nonnull - Column getValueColumn(); + Column getColumn(); /** * @return a {@link Column} that provides a value that can me used in math operations */ @Nonnull - Column getNumericValueColumn(); + Optional getNumericValueColumn(); /** * Provides a {@link Column} that provides additional context that informs the way that math @@ -74,19 +64,13 @@ Function getMathOperation(@Nonnull MathOperation operat * @return a {@link Column} that provides additional context for math operations */ @Nonnull - Column getNumericContextColumn(); + Optional getNumericContextColumn(); /** - * The FHIR data type of the element being represented by this expression. - *

- * Note that there can be multiple valid FHIR types for a given FHIRPath type, e.g. {@code uri} - * and {@code code} both map to the {@code String} FHIRPath type. - * - * @return the FHIR data type of the expression - * @see Using FHIR types in expressions + * The type of the result of evaluating this expression, if known. */ @Nonnull - FHIRDefinedType getFhirType(); + Optional getType(); /** * Represents a type of math operator. diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java index 7ecc8795d7..3fd02b8e8e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java @@ -22,6 +22,7 @@ import static org.apache.spark.sql.functions.lit; import static org.apache.spark.sql.functions.split; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -56,19 +57,20 @@ static Column referenceIdColumnFor(@Nonnull final Column referenceColumn) { } /** - * Constructs an equality column for matching a resource reference to a {@link ResourcePath}. + * Constructs an equality column for matching a resource reference to a + * {@link ResourceCollection}. * * @param referrer the Referrer that is the subject of the operation - * @param resourcePath the target ResourcePath + * @param resourceCollection the target ResourcePath * @return a {@link Column} representing the matching condition */ @Nonnull static Column resourceEqualityFor(@Nonnull final Referrer referrer, - @Nonnull final ResourcePath resourcePath) { - final Column targetId = resourcePath.getCurrentResource() - .map(ResourcePath::getIdColumn) - .orElse(resourcePath.getIdColumn()); - final Column targetCode = lit(resourcePath.getResourceType().toCode()); + @Nonnull final ResourceCollection resourceCollection) { + final Column targetId = resourceCollection.getCurrentResource() + .map(ResourceCollection::getIdColumn) + .orElse(resourceCollection.getIdColumn()); + final Column targetCode = lit(resourceCollection.getResourceType().toCode()); return Referrer.resourceEqualityFor(referrer, targetCode, targetId); } @@ -109,13 +111,14 @@ static Column resourceEqualityFor(@Nonnull final Referrer referrer, Column getReferenceIdColumn(); /** - * Constructs an equality column for matching a resource reference to a {@link ResourcePath}. + * Constructs an equality column for matching a resource reference to a + * {@link ResourceCollection}. * - * @param resourcePath the target ResourcePath + * @param resourceCollection the target ResourcePath * @return a {@link Column} representing the matching condition */ @Nonnull - Column getResourceEquality(@Nonnull ResourcePath resourcePath); + Column getResourceEquality(@Nonnull ResourceCollection resourceCollection); /** * Constructs an equality column for matching a resource reference to a dataset with a target diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java deleted file mode 100644 index 039a9b9a45..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ResourcePath.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2023 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; - -import static au.csiro.pathling.QueryHelpers.aliasAllColumns; -import static au.csiro.pathling.utilities.Preconditions.checkPresent; -import static org.apache.spark.sql.functions.col; - -import au.csiro.pathling.QueryHelpers.DatasetWithColumnMap; -import au.csiro.pathling.encoders.EncoderBuilder; -import au.csiro.pathling.encoders.ExtensionSupport; -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.definition.ResourceDefinition; -import au.csiro.pathling.io.source.DataSource; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import java.util.EnumSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import lombok.Getter; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.functions; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; -import scala.collection.JavaConverters; - -/** - * Represents any FHIRPath expression which refers to a resource type. - * - * @author John Grimes - */ -public class ResourcePath extends NonLiteralPath { - - @Nonnull - @Getter - private final ResourceDefinition definition; - - @Nonnull - @Getter - private final Map elementsToColumns; - - protected ResourcePath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, final boolean singular, - @Nonnull final Optional thisColumn, @Nonnull final ResourceDefinition definition, - @Nonnull final Map elementsToColumns) { - super(expression, dataset, idColumn, valueColumn, singular, Optional.empty(), - thisColumn); - this.definition = definition; - this.elementsToColumns = elementsToColumns; - this.setCurrentResource(this); - } - - /** - * Build a new ResourcePath using the supplied {@link FhirContext} and {@link DataSource}. - * - * @param fhirContext the {@link FhirContext} to use for sourcing the resource definition - * @param dataSource the {@link DataSource} to use for retrieving the Dataset - * @param resourceType the type of the resource - * @param expression the expression to use in the resulting path - * @param singular whether the resulting path should be flagged as a single item collection - * @return A shiny new ResourcePath - */ - @Nonnull - public static ResourcePath build(@Nonnull final FhirContext fhirContext, - @Nonnull final DataSource dataSource, @Nonnull final ResourceType resourceType, - @Nonnull final String expression, final boolean singular) { - return build(fhirContext, dataSource, resourceType, expression, singular, false); - } - - /** - * Build a new ResourcePath using the supplied {@link FhirContext} and {@link DataSource}. - * - * @param fhirContext the {@link FhirContext} to use for sourcing the resource definition - * @param dataSource the {@link DataSource} to use for retrieving the Dataset - * @param resourceType the type of the resource - * @param expression the expression to use in the resulting path - * @param singular whether the resulting path should be flagged as a single item collection - * @param skipAliasing set to true to skip column aliasing - * @return A shiny new ResourcePath - */ - @Nonnull - public static ResourcePath build(@Nonnull final FhirContext fhirContext, - @Nonnull final DataSource dataSource, @Nonnull final ResourceType resourceType, - @Nonnull final String expression, final boolean singular, final boolean skipAliasing) { - - // Get the resource definition from HAPI. - final String resourceCode = resourceType.toCode(); - final RuntimeResourceDefinition hapiDefinition = fhirContext - .getResourceDefinition(resourceCode); - final ResourceDefinition definition = new ResourceDefinition(resourceType, hapiDefinition, - Optional.empty()); - - // Retrieve the dataset for the resource type using the supplied resource reader. - final Dataset dataset = dataSource.read(resourceType); - - final Column idColumn = col("id"); - final Column finalIdColumn; - final Dataset finalDataset; - final Map elementsToColumns; - - if (skipAliasing) { - // If aliasing is disabled, the dataset will contain columns with the original element names. - // This is used for contexts where we need the original column names for encoding (e.g. - // search). - finalDataset = dataset; - finalIdColumn = idColumn; - //noinspection ReturnOfNull - elementsToColumns = Stream.of(dataset.columns()) - .collect(Collectors.toMap(Function.identity(), functions::col, (a, b) -> null)); - } else { - // If aliasing is enabled, all columns in the dataset will be aliased, and the original - // columns will be dropped. This is to avoid column name clashes when doing joins. - final DatasetWithColumnMap datasetWithColumnMap = aliasAllColumns(dataset); - finalDataset = datasetWithColumnMap.getDataset(); - final Map columnMap = datasetWithColumnMap.getColumnMap(); - //noinspection ReturnOfNull - elementsToColumns = columnMap.keySet().stream() - .collect(Collectors.toMap(Column::toString, columnMap::get, (a, b) -> null)); - finalIdColumn = elementsToColumns.get(idColumn.toString()); - } - - // We use the ID column as the value column for a ResourcePath. - return new ResourcePath(expression, finalDataset, finalIdColumn, - finalIdColumn, singular, Optional.empty(), definition, elementsToColumns); - } - - /** - * @param elementName the name of the element - * @return the {@link Column} within the dataset pertaining to this element - */ - @Nonnull - public Optional getElementColumn(@Nonnull final String elementName) { - return Optional.ofNullable(elementsToColumns.get(elementName)); - } - - @Nonnull - @Override - public Column getExtensionContainerColumn() { - final Optional maybeExtensionColumn = Optional - .ofNullable(elementsToColumns.get(ExtensionSupport.EXTENSIONS_FIELD_NAME())); - return checkPresent(maybeExtensionColumn, - "Extension container column '_extension' not present in the resource." - + " Check if extension support was enabled when data were imported!"); - } - - public ResourceType getResourceType() { - return definition.getResourceType(); - } - - @Override - @Nonnull - public Optional getChildElement(@Nonnull final String name) { - return definition.getChildElement(name); - } - - public void setCurrentResource(@Nonnull final ResourcePath currentResource) { - this.currentResource = Optional.of(currentResource); - } - - @Nonnull - @Override - public ResourcePath copy(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional thisColumn) { - return new ResourcePath(expression, dataset, idColumn, valueColumn, singular, thisColumn, - definition, elementsToColumns); - } - - @Nonnull - @Override - public Optional getOrderingColumn() { - return Optional.empty(); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - // A ResourcePath can be combined with another ResourcePath of the same type. - return super.canBeCombinedWith(target) || - (target instanceof ResourcePath && - ((ResourcePath) target).getResourceType().equals(getResourceType())); - } - - @Override - @Nonnull - public NonLiteralPath combineWith(@Nonnull final FhirPath target, - @Nonnull final Dataset dataset, @Nonnull final String expression, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, final boolean singular, - @Nonnull final Optional thisColumn) { - if (target instanceof ResourcePath && definition - .equals(((ResourcePath) target).getDefinition())) { - // Two ResourcePaths can be merged together if they have the same definition. - return copy(expression, dataset, idColumn, valueColumn, getOrderingColumn(), singular, - thisColumn); - } - // Anything else is invalid. - throw new InvalidUserInputError( - "Paths cannot be merged into a collection together: " + getExpression() + ", " + target - .getExpression()); - } - - /** - * @return The set of resource types currently supported by this implementation. - */ - @Nonnull - public static Set supportedResourceTypes() { - final Set availableResourceTypes = EnumSet.allOf( - ResourceType.class); - final Set unsupportedResourceTypes = - JavaConverters.setAsJavaSet(EncoderBuilder.UNSUPPORTED_RESOURCES()).stream() - .map(ResourceType::fromCode) - .collect(Collectors.toSet()); - availableResourceTypes.removeAll(unsupportedResourceTypes); - availableResourceTypes.remove(ResourceType.RESOURCE); - availableResourceTypes.remove(ResourceType.DOMAINRESOURCE); - availableResourceTypes.remove(ResourceType.NULL); - availableResourceTypes.remove(ResourceType.OPERATIONDEFINITION); - return availableResourceTypes; - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/StringCoercible.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/StringCoercible.java index 39c32181e1..ccc39ab4df 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/StringCoercible.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/StringCoercible.java @@ -1,5 +1,6 @@ package au.csiro.pathling.fhirpath; +import au.csiro.pathling.fhirpath.collection.Collection; import javax.annotation.Nonnull; /** @@ -11,10 +12,9 @@ public interface StringCoercible { /** - * @param expression the expression for the new path - * @return a new {@link FhirPath} representing the String representation of this path + * @return a new {@link Collection} representing the String representation of this path */ @Nonnull - FhirPath asStringPath(@Nonnull String expression); + Collection asStringPath(); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java index fcaab04d4c..e82f82d20f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java @@ -18,16 +18,14 @@ package au.csiro.pathling.fhirpath; import au.csiro.pathling.fhirpath.Numeric.MathOperation; -import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.DateTimeCollection; +import au.csiro.pathling.fhirpath.collection.QuantityCollection; import java.util.Optional; import java.util.function.Function; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; import org.apache.spark.sql.functions; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** * Describes a path that represents a temporal value such as DateTime or Date, and can be the @@ -38,46 +36,40 @@ public interface Temporal { /** - * Gets a function that can take the {@link QuantityLiteralPath} representing a time duration and - * return a {@link FhirPath} that contains the result of date arithmetic operation for this path + * Gets a function that can take the {@link QuantityCollection} representing a time duration and + * return a {@link Collection} that contains the result of date arithmetic operation for this path * and the provided duration. The type of operation is controlled by supplying a * {@link MathOperation}. * * @param operation The {@link MathOperation} type to retrieve a result for - * @param dataset The {@link Dataset} to use within the result - * @param expression The FHIRPath expression to use within the result - * @return A {@link Function} that takes a {@link QuantityLiteralPath} as its parameter, and - * returns a {@link FhirPath}. + * @return A {@link Function} that takes a {@link QuantityCollection} as its parameter, and + * returns a {@link Collection}. */ @Nonnull - Function getDateArithmeticOperation( - @Nonnull MathOperation operation, @Nonnull Dataset dataset, - @Nonnull String expression); + Function getDateArithmeticOperation( + @Nonnull MathOperation operation); /** - * Gets a function that can take the {@link QuantityLiteralPath} representing a time duration and - * return a {@link FhirPath} that contains the result of applying the date arithmetic operation + * Gets a function that can take the {@link QuantityCollection} representing a time duration and + * return a {@link Collection} that contains the result of applying the date arithmetic operation * for to the source path and the provided duration. The type of operation is controlled by * supplying a {@link MathOperation}. * - * @param source the {@link FhirPath} to which the operation should be applied to. Should be a + * @param source the {@link Collection} to which the operation should be applied to. Should be a * {@link Temporal} path. * @param operation The {@link MathOperation} type to retrieve a result for - * @param dataset The {@link Dataset} to use within the result - * @param expression the FHIRPath expression to use within the result * @param additionFunctionName the name of the UDF to use for additions. * @param subtractionFunctionName the name of the UDF to use for subtractions. - * @return A {@link Function} that takes a {@link QuantityLiteralPath} as its parameter, and - * returns a {@link FhirPath}. + * @return A {@link Function} that takes a {@link QuantityCollection} as its parameter, and + * returns a {@link Collection}. */ @Nonnull - static Function buildDateArithmeticOperation( - @Nonnull final FhirPath source, final @Nonnull MathOperation operation, - final @Nonnull Dataset dataset, final @Nonnull String expression, - final String additionFunctionName, final String subtractionFunctionName) { + static Function buildDateArithmeticOperation( + @Nonnull final Collection source, final @Nonnull MathOperation operation, + final String additionFunctionName, + final String subtractionFunctionName) { return target -> { final String functionName; - final Optional thisColumn = NonLiteralPath.findThisColumn(source, target); switch (operation) { case ADDITION: @@ -90,10 +82,9 @@ static Function buildDateArithmeticOperation( throw new AssertionError("Unsupported date arithmetic operation: " + operation); } - final Column valueColumn = functions.callUDF(functionName, source.getValueColumn(), - target.getValueColumn()); - return ElementPath.build(expression, dataset, source.getIdColumn(), valueColumn, - Optional.empty(), true, Optional.empty(), thisColumn, FHIRDefinedType.DATETIME); + final Column valueColumn = functions.callUDF(functionName, source.getColumn(), + target.getColumn()); + return DateTimeCollection.build(valueColumn, expression, Optional.empty()); }; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TerminologyUtils.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TerminologyUtils.java index efcd4f86ce..7e1c920a33 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TerminologyUtils.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TerminologyUtils.java @@ -19,10 +19,10 @@ import static au.csiro.pathling.utilities.Preconditions.check; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; import javax.annotation.Nonnull; -import au.csiro.pathling.utilities.Preconditions; import org.apache.spark.sql.Column; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; @@ -34,25 +34,25 @@ public interface TerminologyUtils { /** * Checks if a path is a codeable concept element. * - * @param fhirPath a path to check + * @param result a path to check * @return true if the path is a codeable concept */ - static boolean isCodeableConcept(@Nonnull final FhirPath fhirPath) { - return (fhirPath instanceof ElementPath && - FHIRDefinedType.CODEABLECONCEPT.equals(((ElementPath) fhirPath).getFhirType())); + static boolean isCodeableConcept(@Nonnull final Collection result) { + return (result instanceof PrimitivePath && + FHIRDefinedType.CODEABLECONCEPT.equals(((PrimitivePath) result).getFhirType())); } /** * Checks is a path is a coding or codeable concept path. * - * @param fhirPath a path to check + * @param result a path to check * @return true if the path is coding or codeable concept */ - static boolean isCodingOrCodeableConcept(@Nonnull final FhirPath fhirPath) { - if (fhirPath instanceof CodingLiteralPath) { + static boolean isCodingOrCodeableConcept(@Nonnull final Collection result) { + if (result instanceof CodingLiteralPath) { return true; - } else if (fhirPath instanceof ElementPath) { - final FHIRDefinedType elementFhirType = ((ElementPath) fhirPath).getFhirType(); + } else if (result instanceof PrimitivePath) { + final FHIRDefinedType elementFhirType = ((PrimitivePath) result).getFhirType(); return FHIRDefinedType.CODING.equals(elementFhirType) || FHIRDefinedType.CODEABLECONCEPT.equals(elementFhirType); } else { @@ -61,11 +61,11 @@ static boolean isCodingOrCodeableConcept(@Nonnull final FhirPath fhirPath) { } @Nonnull - static Column getCodingColumn(@Nonnull final FhirPath fhirPath) { - check(isCodingOrCodeableConcept(fhirPath), + static Column getCodingColumn(@Nonnull final Collection result) { + check(isCodingOrCodeableConcept(result), "Coding or CodeableConcept path expected"); - final Column conceptColumn = fhirPath.getValueColumn(); - return isCodeableConcept(fhirPath) + final Column conceptColumn = result.getValueColumn(); + return isCodeableConcept(result) ? conceptColumn.getField("coding") : conceptColumn; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TypeSpecifier.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TypeSpecifier.java deleted file mode 100644 index ebd05300af..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TypeSpecifier.java +++ /dev/null @@ -1,50 +0,0 @@ -package au.csiro.pathling.fhirpath; - -import static org.apache.spark.sql.functions.lit; - -import au.csiro.pathling.fhirpath.literal.LiteralPath; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.StringType; - -public class TypeSpecifier extends LiteralPath implements AbstractPath { - - @Nonnull - private final FHIRDefinedType type; - - protected TypeSpecifier(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final FHIRDefinedType type) { - super(dataset, idColumn, new StringType(type.toCode()), type.toCode()); - this.type = type; - this.valueColumn = lit(type.toCode()); - } - - @Nonnull - public static TypeSpecifier build(@Nonnull final FhirPath context, - @Nonnull final FHIRDefinedType type) { - return new TypeSpecifier(context.getDataset(), context.getIdColumn(), type); - } - - - @Nonnull - @Override - public FhirPath withDataset(@Nonnull final Dataset dataset) { - return new TypeSpecifier(dataset, idColumn, type); - } - - @Nonnull - @Override - public String getExpression() { - return type.toCode(); - } - - @Nonnull - @Override - public Column buildValueColumn() { - return lit(expression.orElse("[type specifier]")); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java deleted file mode 100644 index 59b2f5d47a..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/UntypedResourcePath.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2023 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; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.element.ReferencePath; -import java.util.Optional; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; - -/** - * Represents a path that is a collection of resources of more than one type. - * - * @author John Grimes - */ -public class UntypedResourcePath extends ReferencePath implements AbstractPath { - - public UntypedResourcePath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, - thisColumn, fhirType); - } - - /** - * @param referencePath a {@link ReferencePath} to base the new UntypedResourcePath on - * @param expression the FHIRPath representation of this path - * @return a shiny new UntypedResourcePath - */ - @Nonnull - public static UntypedResourcePath build(@Nonnull final ReferencePath referencePath, - @Nonnull final String expression) { - final UntypedResourcePath result = new UntypedResourcePath(expression, - referencePath.getDataset(), - referencePath.getIdColumn(), referencePath.getValueColumn(), - referencePath.getOrderingColumn(), referencePath.isSingular(), - referencePath.getCurrentResource(), referencePath.getThisColumn(), - referencePath.getFhirType()); - result.definition = referencePath.getDefinition(); - return result; - } - - @Nonnull - public Column getReferenceColumn() { - return valueColumn.getField(Referrer.REFERENCE_FIELD_NAME); - } - - @Nonnull - @Override - public Column getReferenceIdColumn() { - return Referrer.referenceIdColumnFor(getReferenceColumn()); - } - - @Nonnull - @Override - public Optional getChildElement(@Nonnull final String name) { - return Optional.empty(); - } - - @Nonnull - @Override - public UntypedResourcePath copy(@Nonnull final String expression, - @Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final Column valueColumn, @Nonnull final Optional orderingColumn, - final boolean singular, @Nonnull final Optional thisColumn) { - return new UntypedResourcePath(expression, dataset, idColumn, valueColumn, orderingColumn, - singular, currentResource, thisColumn, getFhirType()); - } - - @Override - @Nonnull - public NonLiteralPath combineWith(@Nonnull final FhirPath target, - @Nonnull final Dataset dataset, @Nonnull final String expression, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, final boolean singular, - @Nonnull final Optional thisColumn) { - if (target instanceof UntypedResourcePath) { - return copy(expression, dataset, idColumn, valueColumn, getOrderingColumn(), singular, - thisColumn); - } - // Anything else is invalid. - throw new InvalidUserInputError( - "Paths cannot be merged into a collection together: " + getExpression() + ", " + target - .getExpression()); - } - -} 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 new file mode 100644 index 0000000000..325e784382 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/BooleanCollection.java @@ -0,0 +1,83 @@ +/* + * Copyright 2023 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.collection; + +import static org.apache.spark.sql.functions.lit; + +import au.csiro.pathling.fhirpath.Comparable; +import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.Materializable; +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.Column; +import org.apache.spark.sql.Row; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +/** + * Represents a collection of Boolean-typed elements. + * + * @author John Grimes + */ +public class BooleanCollection extends Collection implements Materializable, + Comparable { + + private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet + .of(BooleanCollection.class); + + public BooleanCollection(@Nonnull final Column column, + @Nonnull final Optional definition) { + super(column, Optional.of(FhirPathType.BOOLEAN), Optional.of(FHIRDefinedType.BOOLEAN), + definition); + } + + /** + * Returns a new instance, parsed from a FHIRPath literal. + * + * @param literal The FHIRPath representation of the literal + * @return A new instance of {@link BooleanCollection} + */ + @Nonnull + public static BooleanCollection fromLiteral(@Nonnull final String literal) { + final boolean value = literal.equals("true"); + return new BooleanCollection(lit(value), Optional.empty()); + } + + @Nonnull + public static BooleanCollection build(@Nonnull final Column column, + @Nonnull final Optional definition) { + return new BooleanCollection(column, definition); + } + + @Nonnull + @Override + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { + if (row.isNullAt(columnNumber)) { + return Optional.empty(); + } + return Optional.of(new BooleanType(row.getBoolean(columnNumber))); + } + + @Override + public boolean isComparableTo(@Nonnull final Collection path) { + return COMPARABLE_TYPES.contains(path.getClass()); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/CodingPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/CodingCollection.java similarity index 52% rename from fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/CodingPath.java rename to fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/CodingCollection.java index 9448264181..9cb96986da 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/CodingPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/CodingCollection.java @@ -15,64 +15,80 @@ * limitations under the License. */ -package au.csiro.pathling.fhirpath.element; +package au.csiro.pathling.fhirpath.collection; import static org.apache.spark.sql.functions.callUDF; +import static org.apache.spark.sql.functions.lit; +import static org.apache.spark.sql.functions.struct; import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.Materializable; import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.fhirpath.comparison.CodingSqlComparator; -import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; -import au.csiro.pathling.fhirpath.literal.NullLiteralPath; +import au.csiro.pathling.fhirpath.definition.NodeDefinition; +import au.csiro.pathling.fhirpath.literal.CodingLiteral; import au.csiro.pathling.sql.misc.CodingToLiteral; -import com.google.common.collect.ImmutableSet; import java.util.Optional; import java.util.function.Function; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** - * Represents a FHIRPath expression which refers to an element of type Coding. + * Represents a collection of Coding-typed elements. * * @author John Grimes */ -public class CodingPath extends ElementPath implements FhirValue, Comparable, - StringCoercible { - - private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet - .of(CodingPath.class, CodingLiteralPath.class, NullLiteralPath.class); - - protected CodingPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, - thisColumn, fhirType); +public class CodingCollection extends Collection implements Materializable, + Comparable, StringCoercible { + + public CodingCollection(@Nonnull final Column column, + @Nonnull final Optional definition) { + super(column, Optional.of(FhirPathType.CODING), Optional.of(FHIRDefinedType.CODING), + definition); } @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { - return valueFromRow(row, columnNumber); + public static CodingCollection build(@Nonnull final Column column, + @Nonnull final Optional definition) { + return new CodingCollection(column, definition); } /** - * Gets a value from a row for a Coding or Coding literal. + * Returns a new instance, parsed from a FHIRPath literal. * - * @param row The {@link Row} from which to extract the value - * @param columnNumber The column number to extract the value from - * @return A {@link Coding}, or the absence of a value + * @param fhirPath The FHIRPath representation of the literal + * @return A new instance of {@link CodingCollection} + * @throws IllegalArgumentException if the literal is malformed */ @Nonnull - public static Optional valueFromRow(@Nonnull final Row row, final int columnNumber) { + public static CodingCollection fromLiteral(@Nonnull final String fhirPath) + throws IllegalArgumentException { + final Coding coding = CodingLiteral.fromString(fhirPath); + final Column column = buildColumn(coding); + return CodingCollection.build(column, Optional.empty()); + } + + @Nonnull + private static Column buildColumn(@Nonnull final Coding coding) { + return struct( + lit(coding.getId()).as("id"), + lit(coding.getSystem()).as("system"), + lit(coding.getVersion()).as("version"), + lit(coding.getCode()).as("code"), + lit(coding.getDisplay()).as("display"), + lit(coding.hasUserSelected() + ? coding.getUserSelected() + : null).as("userSelected"), + lit(null).as("_fid")); + } + + @Nonnull + @Override + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { if (row.isNullAt(columnNumber)) { return Optional.empty(); } @@ -96,33 +112,17 @@ public static Optional valueFromRow(@Nonnull final Row row, final int co return Optional.of(coding); } - @Nonnull - public static ImmutableSet> getComparableTypes() { - return COMPARABLE_TYPES; - } - @Override @Nonnull public Function getComparison(@Nonnull final ComparisonOperation operation) { return CodingSqlComparator.buildComparison(this, operation); } - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return COMPARABLE_TYPES.contains(type); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof CodingLiteralPath; - } - @Nonnull @Override - public FhirPath asStringPath(@Nonnull final String expression) { - final Column valueColumn = callUDF(CodingToLiteral.FUNCTION_NAME, this.valueColumn); - return ElementPath.build(expression, getDataset(), getIdColumn(), valueColumn, - getOrderingColumn(), isSingular(), getCurrentResource(), getThisColumn(), FHIRDefinedType.STRING); + public Collection asStringPath() { + final Column valueColumn = callUDF(CodingToLiteral.FUNCTION_NAME, getColumn()); + return CodingCollection.build(valueColumn, Optional.empty()); } } 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 new file mode 100644 index 0000000000..7bdcb2c5f3 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/Collection.java @@ -0,0 +1,264 @@ +/* + * Copyright 2023 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.collection; + +import au.csiro.pathling.fhirpath.Comparable; +import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.Numeric; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.NodeDefinition; +import com.google.common.collect.ImmutableMap; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import javax.annotation.Nonnull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.SparkSession; +import org.apache.spark.sql.types.ArrayType; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +/** + * Represents a collection of nodes that are the result of evaluating a FHIRPath expression. + * + * @author John Grimes + */ +@Getter +@AllArgsConstructor +public class Collection implements Comparable, Numeric { + + // See https://hl7.org/fhir/fhirpath.html#types. + @Nonnull + private static final Map> FHIR_TYPE_TO_ELEMENT_PATH_CLASS = + new ImmutableMap.Builder>() + .put(FHIRDefinedType.BOOLEAN, BooleanCollection.class) + .put(FHIRDefinedType.STRING, StringCollection.class) + .put(FHIRDefinedType.URI, StringCollection.class) + .put(FHIRDefinedType.URL, StringCollection.class) + .put(FHIRDefinedType.CANONICAL, StringCollection.class) + .put(FHIRDefinedType.CODE, StringCollection.class) + .put(FHIRDefinedType.OID, StringCollection.class) + .put(FHIRDefinedType.ID, StringCollection.class) + .put(FHIRDefinedType.UUID, StringCollection.class) + .put(FHIRDefinedType.MARKDOWN, StringCollection.class) + .put(FHIRDefinedType.BASE64BINARY, StringCollection.class) + .put(FHIRDefinedType.INTEGER, IntegerCollection.class) + .put(FHIRDefinedType.UNSIGNEDINT, IntegerCollection.class) + .put(FHIRDefinedType.POSITIVEINT, IntegerCollection.class) + .put(FHIRDefinedType.DECIMAL, DecimalCollection.class) + .put(FHIRDefinedType.DATE, DateCollection.class) + .put(FHIRDefinedType.DATETIME, DateTimeCollection.class) + .put(FHIRDefinedType.INSTANT, DateTimeCollection.class) + .put(FHIRDefinedType.TIME, TimeCollection.class) + .put(FHIRDefinedType.CODING, CodingCollection.class) + .put(FHIRDefinedType.QUANTITY, QuantityCollection.class) + .put(FHIRDefinedType.SIMPLEQUANTITY, QuantityCollection.class) + .build(); + /** + * A {@link Column} representing the result of evaluating this expression. + */ + @Nonnull + private Column column; + + /** + * The type of the result of evaluating this expression, if known. + */ + @Nonnull + private Optional type; + + /** + * The FHIR type of the result of evaluating this expression, if there is one. + */ + @Nonnull + private Optional fhirType; + + /** + * The FHIR definition that describes this path, if there is one. + */ + @Nonnull + private Optional definition; + + /** + * Builds the appropriate subtype of {@link Collection} based upon the supplied + * {@link ElementDefinition}. + *

+ * Use this builder when the path is the child of another path, and will need to be traversable. + * + * @param column a {@link Column} containing the result of the expression + * @param definition the {@link ElementDefinition} that this path should be based upon + * @return a new {@link Collection} + */ + @Nonnull + public static Collection build(@Nonnull final Column column, + @Nonnull final ElementDefinition definition) { + final Optional optionalFhirType = definition.getFhirType(); + if (optionalFhirType.isPresent()) { + return getInstance(column, Optional.empty(), Optional.of(definition)); + } else { + throw new IllegalArgumentException( + "Attempted to build an ElementPath with an ElementDefinition with no fhirType"); + } + } + + /** + * Builds the appropriate subtype of {@link Collection} based upon the supplied + * {@link FHIRDefinedType}. + *

+ * Use this builder when the path is derived, e.g. the result of a function. + * + * @param column a {@link Column} containing the result of the expression + * @param fhirType the {@link FHIRDefinedType} that this path should be based upon + * @return a new {@link Collection} + */ + @Nonnull + public static Collection build(@Nonnull final Column column, + @Nonnull final FHIRDefinedType fhirType) { + return getInstance(column, Optional.of(fhirType), Optional.empty()); + } + + @Nonnull + private static Collection getInstance(@Nonnull final Column column, + @Nonnull final Optional fhirType, + @Nonnull final Optional definition) { + // Look up the class that represents an element with the specified FHIR type. + 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 FhirPathType fhirPathType = FhirPathType.forFhirType(resolvedType); + + try { + // Call its constructor and return. + final Constructor constructor = elementPathClass + .getDeclaredConstructor(Column.class, Optional.class, Optional.class, Optional.class); + return constructor + .newInstance(column, Optional.of(fhirPathType), Optional.of(fhirType), definition); + } catch (final NoSuchMethodException | InstantiationException | IllegalAccessException | + InvocationTargetException e) { + throw new RuntimeException("Problem building an FhirPath object", e); + } + } + + /** + * @param fhirType A {@link FHIRDefinedType} + * @return The subtype of {@link Collection} that represents this type + */ + @Nonnull + public static Optional> classForType( + @Nonnull final FHIRDefinedType fhirType) { + return Optional.ofNullable(FHIR_TYPE_TO_ELEMENT_PATH_CLASS.get(fhirType)); + } + + /** + * @param spark a {@link SparkSession} to help with determining the schema of the result + * @return whether the result of evaluating this path is a non-singular collection + */ + public boolean isSingular(@Nonnull final SparkSession spark) { + return spark.emptyDataFrame().select(column).schema() + .fields()[0].dataType() instanceof ArrayType; + } + + /** + * Return the child {@link Collection} that results from traversing to the given expression. + * + * @param expression the name of the child element + * @return a new {@link Collection} representing the child element + */ + @Nonnull + public Optional traverse(@Nonnull final String expression) { + // It is only possible to traverse to a child with an element definition. + return definition.filter(def -> def instanceof ElementDefinition) + .map(def -> (ElementDefinition) def) + // Get the named child definition. + .flatMap(def -> def.getChildElement(expression)) + .map(def -> { + // Build a new FhirPath object, with a column that uses `getField` to extract the + // appropriate child. + return Collection.build(column.getField(expression), def); + }); + } + + // @Nonnull + // private Optional traverseExtension(@Nonnull final SparkSession spark, + // @Nonnull final String name) { + // // This code introspects the type of the extension container column to determine the valid child + // // element names. + // final String extensionElementName = ExtensionSupport.EXTENSION_ELEMENT_NAME(); + // final MapType mapType = (MapType) spark.emptyDataFrame().select(extensionElementName) + // .schema() + // .fields()[0].dataType(); + // final ArrayType arrayType = (ArrayType) mapType.valueType(); + // final StructType structType = (StructType) arrayType.elementType(); + // final List fieldNames = Arrays.stream(structType.fields()).map(StructField::name) + // .collect(Collectors.toList()); + // // Add the extension field name, so that we can support traversal to nested extensions. + // fieldNames.add(extensionElementName); + // + // // If the field is part of the encoded extension container, pass control to the generic code to + // // determine the correct element definition. + // if (fieldNames.contains(name)) { + // // Create a new expression that looks like a path traversal. + // final String resultExpression = this.expression + "." + expression; + // // Build a new FhirPath object, with a column that uses `getField` to extract the + // // appropriate child. + // return Result.build(column.getField(expression), resultExpression, def); + // } + // return Optional.empty(); + // } + + /** + * @return whether the order of the collection returned by this expression has any meaning + */ + public boolean isOrderable() { + return true; + } + + @Nonnull + @Override + public Function getComparison(@Nonnull final ComparisonOperation operation) { + return Comparable.buildComparison(this, operation); + } + + @Override + public boolean isComparableTo(@Nonnull final Collection path) { + return path.getClass().equals(this.getClass()) || path.getClass().equals(Collection.class); + } + + @Nonnull + @Override + public Function getMathOperation(@Nonnull final MathOperation operation) { + return input -> this; + } + + @Nonnull + @Override + public Optional getNumericValueColumn() { + return Optional.empty(); + } + + @Nonnull + @Override + public Optional getNumericContextColumn() { + return Optional.empty(); + } + +} 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 new file mode 100644 index 0000000000..c1c683ccf5 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateCollection.java @@ -0,0 +1,111 @@ +/* + * Copyright 2023 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.collection; + +import static au.csiro.pathling.fhirpath.Temporal.buildDateArithmeticOperation; +import static org.apache.spark.sql.functions.lit; + +import au.csiro.pathling.fhirpath.Comparable; +import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.StringCoercible; +import au.csiro.pathling.fhirpath.Temporal; +import au.csiro.pathling.fhirpath.comparison.DateTimeSqlComparator; +import au.csiro.pathling.fhirpath.definition.NodeDefinition; +import au.csiro.pathling.sql.dates.date.DateAddDurationFunction; +import au.csiro.pathling.sql.dates.date.DateSubtractDurationFunction; +import java.text.ParseException; +import java.util.Optional; +import java.util.function.Function; +import javax.annotation.Nonnull; +import lombok.extern.slf4j.Slf4j; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.types.DataTypes; +import org.hl7.fhir.r4.model.DateType; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +/** + * Represents a collection of Date-typed elements. + * + * @author John Grimes + */ +@Slf4j +public class DateCollection extends Collection implements Materializable, + Comparable, Temporal, StringCoercible { + + public DateCollection(@Nonnull final Column column, + @Nonnull final Optional definition) { + super(column, Optional.of(FhirPathType.DATE), Optional.of(FHIRDefinedType.DATE), definition); + } + + @Nonnull + public static DateCollection build(@Nonnull final Column column, + @Nonnull final Optional definition) { + return new DateCollection(column, definition); + } + + /** + * Returns a new instance, parsed from a FHIRPath literal. + * + * @param fhirPath The FHIRPath representation of the literal + * @return A new instance of {@link DateCollection} + * @throws ParseException if the literal is malformed + */ + @Nonnull + public static DateCollection fromLiteral(@Nonnull final String fhirPath) throws ParseException { + final String dateString = fhirPath.replaceFirst("^@", ""); + return DateCollection.build(lit(dateString), Optional.empty()); + } + + @Nonnull + @Override + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { + if (row.isNullAt(columnNumber)) { + return Optional.empty(); + } + final String dateString = row.getString(columnNumber); + return Optional.of(new DateType(dateString)); + } + + @Override + @Nonnull + public Function getComparison(@Nonnull final ComparisonOperation operation) { + return DateTimeSqlComparator.buildComparison(this, operation); + } + + @Override + public boolean isComparableTo(@Nonnull final Collection path) { + return DateTimeCollection.getComparableTypes().contains(path.getClass()); + } + + @Nonnull + @Override + public Function getDateArithmeticOperation( + @Nonnull final MathOperation operation) { + return buildDateArithmeticOperation(this, operation, + DateAddDurationFunction.FUNCTION_NAME, DateSubtractDurationFunction.FUNCTION_NAME); + } + + @Nonnull + @Override + public Collection asStringPath() { + return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty()); + } + +} 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 new file mode 100644 index 0000000000..e1c76a6d7f --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateTimeCollection.java @@ -0,0 +1,119 @@ +/* + * Copyright 2023 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.collection; + +import static au.csiro.pathling.fhirpath.Temporal.buildDateArithmeticOperation; +import static org.apache.spark.sql.functions.date_format; + +import au.csiro.pathling.fhirpath.Comparable; +import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.StringCoercible; +import au.csiro.pathling.fhirpath.Temporal; +import au.csiro.pathling.fhirpath.comparison.DateTimeSqlComparator; +import au.csiro.pathling.fhirpath.definition.NodeDefinition; +import au.csiro.pathling.sql.dates.datetime.DateTimeAddDurationFunction; +import au.csiro.pathling.sql.dates.datetime.DateTimeSubtractDurationFunction; +import com.google.common.collect.ImmutableSet; +import java.util.Optional; +import java.util.function.Function; +import javax.annotation.Nonnull; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.Row; +import org.hl7.fhir.r4.model.BaseDateTimeType; +import org.hl7.fhir.r4.model.DateTimeType; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; +import org.hl7.fhir.r4.model.InstantType; + +/** + * Represents a collection of DateTime-typed elements. + * + * @author John Grimes + */ +public class DateTimeCollection extends Collection implements + Materializable, Comparable, Temporal, StringCoercible { + + public static final String SPARK_FHIRPATH_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; + private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet + .of(DateCollection.class, DateTimeCollection.class); + + public DateTimeCollection(@Nonnull final Column column, + @Nonnull final Optional definition) { + super(column, Optional.of(FhirPathType.DATETIME), Optional.of(FHIRDefinedType.DATETIME), + definition); + } + + @Nonnull + public static DateTimeCollection build(@Nonnull final Column column, + @Nonnull final Optional definition) { + return new DateTimeCollection(column, definition); + } + + @Nonnull + public static ImmutableSet> getComparableTypes() { + return COMPARABLE_TYPES; + } + + @Nonnull + @Override + public Optional getFhirValueFromRow(@Nonnull final Row row, + final int columnNumber) { + if (row.isNullAt(columnNumber)) { + return Optional.empty(); + } + if (getFhirType().isPresent() && getFhirType().get() == FHIRDefinedType.INSTANT) { + final InstantType value = new InstantType(row.getTimestamp(columnNumber)); + return Optional.of(value); + } else { + final DateTimeType value = new DateTimeType(row.getString(columnNumber)); + return Optional.of(value); + } + } + + @Override + @Nonnull + public Function getComparison(@Nonnull final ComparisonOperation operation) { + return DateTimeSqlComparator.buildComparison(this, operation); + } + + @Override + public boolean isComparableTo(@Nonnull final Collection path) { + return COMPARABLE_TYPES.contains(path.getClass()); + } + + @Nonnull + @Override + public Function getDateArithmeticOperation( + @Nonnull final MathOperation operation) { + return buildDateArithmeticOperation(this, operation, + DateTimeAddDurationFunction.FUNCTION_NAME, DateTimeSubtractDurationFunction.FUNCTION_NAME); + } + + @Nonnull + @Override + public Collection asStringPath() { + final Column valueColumn; + if (getFhirType().isPresent() && getFhirType().get() == FHIRDefinedType.INSTANT) { + valueColumn = date_format(getColumn(), SPARK_FHIRPATH_DATETIME_FORMAT); + } else { + valueColumn = getColumn(); + } + return StringCollection.build(valueColumn, Optional.empty()); + } + +} 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 new file mode 100644 index 0000000000..3a062287fc --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DecimalCollection.java @@ -0,0 +1,183 @@ +/* + * Copyright 2023 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.collection; + +import static au.csiro.pathling.utilities.Preconditions.checkPresent; +import static org.apache.spark.sql.functions.lit; + +import au.csiro.pathling.encoders.datatypes.DecimalCustomCoder; +import au.csiro.pathling.errors.InvalidUserInputError; +import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.Numeric; +import au.csiro.pathling.fhirpath.StringCoercible; +import au.csiro.pathling.fhirpath.definition.NodeDefinition; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Optional; +import java.util.function.Function; +import javax.annotation.Nonnull; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.types.DataTypes; +import org.apache.spark.sql.types.LongType; +import org.hl7.fhir.r4.model.DecimalType; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +/** + * Represents a FHIRPath decimal literal. + * + * @author John Grimes + */ +public class DecimalCollection extends Collection implements Materializable, + StringCoercible { + + private static final org.apache.spark.sql.types.DecimalType DECIMAL_TYPE = DataTypes + .createDecimalType(DecimalCustomCoder.precision(), DecimalCustomCoder.scale()); + + public DecimalCollection(@Nonnull final Column column, + @Nonnull final Optional fhirPathType, + @Nonnull final Optional fhirType, + @Nonnull final Optional definition) { + super(column, fhirPathType, fhirType, definition); + } + + @Nonnull + public static DecimalCollection build(@Nonnull final Column column, + @Nonnull final Optional definition) { + return new DecimalCollection(column, Optional.of(FhirPathType.DECIMAL), + Optional.of(FHIRDefinedType.DECIMAL), definition); + } + + /** + * Returns a new instance, parsed from a FHIRPath literal. + * + * @param literal the FHIRPath representation of the literal + * @return a new instance of {@link DecimalCollection} + * @throws NumberFormatException if the literal is malformed + */ + @Nonnull + public static DecimalCollection fromLiteral(@Nonnull final String literal) + throws NumberFormatException { + final BigDecimal value = parseLiteral(literal); + return DecimalCollection.build(lit(value), Optional.empty()); + } + + @Nonnull + public static BigDecimal parseLiteral(final @Nonnull String literal) { + final BigDecimal value = new BigDecimal(literal); + + if (value.precision() > DecimalCollection.getDecimalType().precision()) { + throw new InvalidUserInputError( + "Decimal literal exceeded maximum precision supported (" + + DecimalCollection.getDecimalType() + .precision() + "): " + literal); + } + if (value.scale() > DecimalCollection.getDecimalType().scale()) { + throw new InvalidUserInputError( + "Decimal literal exceeded maximum scale supported (" + DecimalCollection.getDecimalType() + .scale() + "): " + literal); + } + return value; + } + + /** + * @return the {@link org.apache.spark.sql.types.DataType} used for representing decimal values in + * Spark + */ + public static org.apache.spark.sql.types.DecimalType getDecimalType() { + return DECIMAL_TYPE; + } + + @Override + public boolean isComparableTo(@Nonnull final Collection path) { + return IntegerCollection.getComparableTypes().contains(path.getClass()); + } + + @Nonnull + @Override + public Function getMathOperation(@Nonnull final MathOperation operation) { + return target -> { + final Column sourceNumeric = checkPresent(((Numeric) this).getNumericValueColumn()); + final Column targetNumeric = checkPresent(target.getNumericValueColumn()); + Column result = operation.getSparkFunction().apply(sourceNumeric, targetNumeric); + + switch (operation) { + case ADDITION: + case SUBTRACTION: + case MULTIPLICATION: + case DIVISION: + result = result.cast(getDecimalType()); + return DecimalCollection.build(result, Optional.empty()); + case MODULUS: + result = result.cast(DataTypes.LongType); + return IntegerCollection.build(result, Optional.empty()); + default: + throw new AssertionError("Unsupported math operation encountered: " + operation); + } + }; + } + + @Nonnull + @Override + public Optional getNumericValueColumn() { + return Optional.of(getColumn()); + } + + @Nonnull + @Override + public Optional getNumericContextColumn() { + return getNumericValueColumn(); + } + + @Nonnull + @Override + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { + if (row.isNullAt(columnNumber)) { + return Optional.empty(); + } + // We support the extraction of Decimal values from columns with the long type. This will be + // used in the future to support things like casting large numbers to Decimal to work around the + // maximum Integer limit. + if (row.schema().fields()[columnNumber].dataType() instanceof LongType) { + final long longValue = row.getLong(columnNumber); + return Optional.of(new DecimalType(longValue)); + } else { + final BigDecimal decimal = row.getDecimal(columnNumber); + + if (decimal.precision() > getDecimalType().precision()) { + throw new InvalidUserInputError( + "Attempt to return a Decimal value with greater than supported precision"); + } + if (decimal.scale() > getDecimalType().scale()) { + return Optional.of( + new DecimalType(decimal.setScale(getDecimalType().scale(), RoundingMode.HALF_UP))); + } + + return Optional.of(new DecimalType(decimal)); + } + } + + @Override + @Nonnull + public Collection asStringPath() { + return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty()); + } + + +} 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 new file mode 100644 index 0000000000..71a5a99182 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/IntegerCollection.java @@ -0,0 +1,182 @@ +/* + * Copyright 2023 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.collection; + +import static au.csiro.pathling.utilities.Preconditions.checkPresent; +import static org.apache.spark.sql.functions.lit; + +import au.csiro.pathling.errors.InvalidUserInputError; +import au.csiro.pathling.fhirpath.Comparable; +import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.Numeric; +import au.csiro.pathling.fhirpath.StringCoercible; +import au.csiro.pathling.fhirpath.definition.NodeDefinition; +import com.google.common.collect.ImmutableSet; +import java.util.Optional; +import java.util.function.Function; +import javax.annotation.Nonnull; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.types.DataTypes; +import org.apache.spark.sql.types.LongType; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.PositiveIntType; +import org.hl7.fhir.r4.model.PrimitiveType; +import org.hl7.fhir.r4.model.UnsignedIntType; + +/** + * Represents a FHIRPath expression which refers to an integer typed element. + * + * @author John Grimes + */ +public class IntegerCollection extends Collection implements + Materializable, Comparable, Numeric, StringCoercible { + + private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet + .of(IntegerCollection.class, DecimalCollection.class); + + public IntegerCollection(@Nonnull final Column column, + @Nonnull final Optional definition) { + super(column, Optional.of(FhirPathType.INTEGER), Optional.of(FHIRDefinedType.INTEGER), + definition); + } + + @Nonnull + public static IntegerCollection build(@Nonnull final Column column, + @Nonnull final Optional definition) { + return new IntegerCollection(column, definition); + } + + /** + * Returns a new instance, parsed from a FHIRPath literal. + * + * @param fhirPath The FHIRPath representation of the literal + * @return A new instance of {@link IntegerCollection} + * @throws NumberFormatException if the literal is malformed + */ + public static IntegerCollection fromLiteral(@Nonnull final String fhirPath) + throws NumberFormatException { + final int value = Integer.parseInt(fhirPath); + return new IntegerCollection(lit(value), Optional.empty()); + } + + @Nonnull + @Override + public Optional getFhirValueFromRow(@Nonnull final Row row, + final int columnNumber) { + if (row.isNullAt(columnNumber)) { + return Optional.empty(); + } + final int value; + if (row.schema().fields()[columnNumber].dataType() instanceof LongType) { + try { + // Currently, some functions such as count currently return an Integer type, even though + // their return values can theoretically exceed the maximum value permitted for an integer. + // This guard allows us to handle this situation in a safe way. In the future, we will + // implement the "as" operator to allow the user to explicitly use a Decimal where large + // values are possible. + value = Math.toIntExact(row.getLong(columnNumber)); + } catch (final ArithmeticException e) { + throw new InvalidUserInputError( + "Attempt to return an Integer value greater than the maximum value permitted for this type"); + } + } else { + value = row.getInt(columnNumber); + } + switch (getFhirType().orElse(FHIRDefinedType.NULL)) { + case UNSIGNEDINT: + return Optional.of(new UnsignedIntType(value)); + case POSITIVEINT: + return Optional.of(new PositiveIntType(value)); + default: + return Optional.of(new IntegerType(value)); + } + } + + @Nonnull + public static ImmutableSet> getComparableTypes() { + return COMPARABLE_TYPES; + } + + @Override + public boolean isComparableTo(@Nonnull final Collection path) { + return COMPARABLE_TYPES.contains(path.getClass()); + } + + @Nonnull + @Override + public Function getMathOperation(@Nonnull final MathOperation operation) { + return buildMathOperation(this, operation); + } + + @Nonnull + @Override + public Optional getNumericValueColumn() { + return Optional.ofNullable(getColumn().cast(DataTypes.LongType)); + } + + @Nonnull + @Override + public Optional getNumericContextColumn() { + return getNumericValueColumn(); + } + + /** + * Builds a math operation result for a collection of Integers. + * + * @param source The left operand for the operation + * @param operation The type of {@link au.csiro.pathling.fhirpath.Numeric.MathOperation} + * @return A {@link Function} that takes a {@link Numeric} as a parameter, and returns a + * {@link Collection} + */ + @Nonnull + public static Function buildMathOperation(@Nonnull final Numeric source, + @Nonnull final MathOperation operation) { + return target -> { + final Column sourceNumeric = checkPresent(source.getNumericValueColumn()); + final Column targetNumeric = checkPresent(target.getNumericValueColumn()); + Column valueColumn = operation.getSparkFunction().apply(sourceNumeric, targetNumeric); + + switch (operation) { + case ADDITION: + case SUBTRACTION: + case MULTIPLICATION: + case MODULUS: + if (target instanceof DecimalCollection) { + valueColumn = valueColumn.cast(DataTypes.LongType); + } + return IntegerCollection.build(valueColumn, Optional.empty()); + case DIVISION: + final Column numerator = source.getColumn().cast(DecimalCollection.getDecimalType()); + valueColumn = operation.getSparkFunction().apply(numerator, targetNumeric); + return DecimalCollection.build(valueColumn, Optional.empty()); + default: + throw new AssertionError("Unsupported math operation encountered: " + operation); + } + }; + } + + @Override + @Nonnull + public Collection asStringPath() { + return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty()); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java new file mode 100644 index 0000000000..d5b872adc5 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java @@ -0,0 +1,63 @@ +package au.csiro.pathling.fhirpath.collection; + +import static au.csiro.pathling.utilities.Preconditions.checkPresent; +import static au.csiro.pathling.utilities.Preconditions.checkUserInput; + +import au.csiro.pathling.fhirpath.definition.ChoiceElementDefinition; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import java.util.Optional; +import javax.annotation.Nonnull; +import org.apache.spark.sql.Column; + +/** + * Represents a choice element, which can be resolved to any one of a number of data types. + * + * @author John Grimes + */ +public class MixedCollection extends Collection { + + @Nonnull + private final Collection from; + + @Nonnull + private final ChoiceElementDefinition choiceDefinition; + + public MixedCollection(@Nonnull final Column column, + @Nonnull final Optional definition, @Nonnull final Collection from) { + super(column, Optional.empty(), Optional.empty(), definition); + this.from = from; + this.choiceDefinition = checkPresent(definition); + } + + @Nonnull + public static MixedCollection build(@Nonnull final Column column, + @Nonnull final Optional definition, @Nonnull final Collection from) { + return new MixedCollection(column, definition, from); + } + + @Nonnull + @Override + public Optional traverse(@Nonnull final String expression) { + return Optional.empty(); + } + + @Nonnull + private Optional resolveChoiceDefinition(@Nonnull final String type) { + return choiceDefinition.getChildByType(type); + } + + @Nonnull + public Optional resolveChoice(@Nonnull final String type) { + final String elementName = choiceDefinition.getElementName(); + final String columnName = ChoiceElementDefinition.getColumnName(elementName, type); + if (from instanceof ResourceCollection) { + final Optional elementColumn = ((ResourceCollection) from).getElementColumn(columnName); + final Optional definition = resolveChoiceDefinition(type); + checkUserInput(elementColumn.isPresent() && definition.isPresent(), + "No such child: " + columnName); + return elementColumn.map(column -> Collection.build(column, definition.get())); + } + return from.traverse(columnName); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/QuantityPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/QuantityCollection.java similarity index 52% rename from fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/QuantityPath.java rename to fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/QuantityCollection.java index a78b7c3bd8..e252713328 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/QuantityPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/QuantityCollection.java @@ -15,83 +15,108 @@ * limitations under the License. */ -package au.csiro.pathling.fhirpath.element; +package au.csiro.pathling.fhirpath.collection; +import static au.csiro.pathling.fhirpath.CalendarDurationUtils.parseCalendarDuration; +import static au.csiro.pathling.fhirpath.collection.DecimalCollection.parseLiteral; +import static au.csiro.pathling.fhirpath.collection.StringCollection.parseStringLiteral; +import static au.csiro.pathling.utilities.Preconditions.checkPresent; import static org.apache.spark.sql.functions.lit; import static org.apache.spark.sql.functions.when; import au.csiro.pathling.encoders.terminology.ucum.Ucum; +import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.NonLiteralPath; +import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.Numeric; -import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.fhirpath.comparison.QuantitySqlComparator; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.NodeDefinition; import au.csiro.pathling.fhirpath.encoding.QuantityEncoding; -import au.csiro.pathling.fhirpath.literal.NullLiteralPath; -import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; import au.csiro.pathling.sql.types.FlexiDecimal; -import com.google.common.collect.ImmutableSet; +import java.math.BigDecimal; import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; +import org.fhir.ucum.UcumService; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; +import org.hl7.fhir.r4.model.Quantity; /** * Represents a FHIRPath expression which refers to an element of type Quantity. * * @author John Grimes */ -public class QuantityPath extends ElementPath implements Comparable, Numeric { - - public static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet - .of(QuantityPath.class, QuantityLiteralPath.class, NullLiteralPath.class); - - protected QuantityPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, - thisColumn, fhirType); - } +public class QuantityCollection extends Collection implements Comparable, Numeric { - @Nonnull - @Override - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return QuantitySqlComparator.buildComparison(this, operation); - } + private static final Column NO_UNIT_LITERAL = lit(Ucum.NO_UNIT_CODE); + private static final Pattern UCUM_PATTERN = Pattern.compile("([0-9.]+) ('[^']+')"); - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return COMPARABLE_TYPES.contains(type); + public QuantityCollection(@Nonnull final Column column, + @Nonnull final Optional definition) { + super(column, Optional.of(FhirPathType.QUANTITY), Optional.of(FHIRDefinedType.QUANTITY), + definition); } @Nonnull - @Override - public Column getNumericValueColumn() { - return getValueColumn().getField(QuantityEncoding.CANONICALIZED_VALUE_COLUMN); + public static QuantityCollection build(@Nonnull final Column column, + @Nonnull final Optional definition) { + return new QuantityCollection(column, definition); } + /** + * Returns a new instance, parsed from a FHIRPath literal. + * + * @param fhirPath the FHIRPath representation of the literal + * @param ucumService a UCUM service for validating the unit within the literal + * @return A new instance of {@link QuantityCollection} + * @throws IllegalArgumentException if the literal is malformed + */ @Nonnull - @Override - public Column getNumericContextColumn() { - return getValueColumn(); + public static QuantityCollection fromUcumString(@Nonnull final String fhirPath, + @Nonnull final UcumService ucumService) { + final Matcher matcher = UCUM_PATTERN.matcher(fhirPath); + if (!matcher.matches()) { + throw new IllegalArgumentException("UCUM Quantity literal has invalid format: " + fhirPath); + } + final String fullPath = matcher.group(0); + final String value = matcher.group(1); + final String rawUnit = matcher.group(2); + final String unit = parseStringLiteral(rawUnit); + + @Nullable final String validationResult = ucumService.validate(unit); + if (validationResult != null) { + throw new InvalidUserInputError( + "Invalid UCUM unit provided within Quantity literal (" + fullPath + "): " + + validationResult); + } + + final BigDecimal decimalValue = getQuantityValue(value); + @Nullable final String display = ucumService.getCommonDisplay(unit); + + return buildLiteralPath(decimalValue, unit, Optional.ofNullable(display)); } + /** + * Returns a new instance, parsed from a FHIRPath literal representing a calendar duration. + * + * @param fhirPath the FHIRPath representation of the literal + * @return A new instance of {@link QuantityCollection} + * @see Time-valued quantities + */ @Nonnull - @Override - public Function getMathOperation(@Nonnull final MathOperation operation, - @Nonnull final String expression, @Nonnull final Dataset dataset) { - return buildMathOperation(this, operation, expression, dataset, getDefinition()); + public static QuantityCollection fromCalendarDurationString(@Nonnull final String fhirPath) { + + final Column column = QuantityEncoding.encodeLiteral(parseCalendarDuration(fhirPath)); + return QuantityCollection.build(column, Optional.empty()); } @Nonnull - private static BiFunction getMathOperation( + private static BiFunction getMathColumnOperation( @Nonnull final MathOperation operation) { switch (operation) { case ADDITION: @@ -107,8 +132,6 @@ private static BiFunction getMathOperation( } } - private static final Column NO_UNIT_LITERAL = lit(Ucum.NO_UNIT_CODE); - @Nonnull private static Column getResultUnit( @Nonnull final MathOperation operation, @Nonnull final Column leftUnit, @@ -154,17 +177,56 @@ private static Column getValidResult( } @Nonnull - public static Function buildMathOperation(@Nonnull final Numeric source, - @Nonnull final MathOperation operation, @Nonnull final String expression, - @Nonnull final Dataset dataset, - @Nonnull final Optional elementDefinition) { + private static BigDecimal getQuantityValue(@Nonnull final String value) { + final BigDecimal decimalValue; + try { + decimalValue = parseLiteral(value); + } catch (final NumberFormatException e) { + throw new IllegalArgumentException("Quantity literal has invalid value: " + value); + } + return decimalValue; + } + + @Nonnull + private static QuantityCollection buildLiteralPath(@Nonnull final BigDecimal decimalValue, + @Nonnull final String unit, @Nonnull final Optional display) { + final Quantity quantity = new Quantity(); + quantity.setValue(decimalValue); + quantity.setSystem(Ucum.SYSTEM_URI); + quantity.setCode(unit); + display.ifPresent(quantity::setUnit); + + return QuantityCollection.build(QuantityEncoding.encodeLiteral(quantity), Optional.empty()); + } + + @Nonnull + @Override + public Function getComparison(@Nonnull final ComparisonOperation operation) { + return QuantitySqlComparator.buildComparison(this, operation); + } + + @Nonnull + @Override + public Optional getNumericValueColumn() { + return Optional.ofNullable(getColumn().getField(QuantityEncoding.CANONICALIZED_VALUE_COLUMN)); + } + + @Nonnull + @Override + public Optional getNumericContextColumn() { + return Optional.of(getColumn()); + } + + @Nonnull + @Override + public Function getMathOperation(@Nonnull final MathOperation operation) { return target -> { - final BiFunction mathOperation = getMathOperation(operation); - final Column sourceComparable = source.getNumericValueColumn(); - final Column sourceContext = source.getNumericContextColumn(); - final Column targetContext = target.getNumericContextColumn(); - final Column resultColumn = mathOperation - .apply(sourceComparable, target.getNumericValueColumn()); + final BiFunction mathOperation = getMathColumnOperation(operation); + final Column sourceComparable = checkPresent(((Numeric) this).getNumericValueColumn()); + final Column targetComparable = checkPresent(target.getNumericValueColumn()); + final Column sourceContext = checkPresent(((Numeric) this).getNumericContextColumn()); + final Column targetContext = checkPresent(target.getNumericContextColumn()); + final Column resultColumn = mathOperation.apply(sourceComparable, targetComparable); final Column sourceCanonicalizedCode = sourceContext.getField( QuantityEncoding.CANONICALIZED_CODE_COLUMN); final Column targetCanonicalizedCode = targetContext.getField( @@ -192,17 +254,7 @@ public static Function buildMathOperation(@Nonnull fina final Column resultQuantityColumn = when(sourceContext.isNull().or(targetContext.isNull()), null).otherwise(validResult); - final Column idColumn = source.getIdColumn(); - final Optional orderingColumn = findOrderingColumn(source, target); - final Optional thisColumn = findThisColumn(source, target); - return - elementDefinition.map(definition -> ElementPath - .build(expression, dataset, idColumn, resultQuantityColumn, orderingColumn, true, - Optional.empty(), thisColumn, definition)) - .orElseGet(() -> ElementPath - .build(expression, dataset, idColumn, resultQuantityColumn, orderingColumn, true, - Optional.empty(), thisColumn, FHIRDefinedType.QUANTITY)); - + return QuantityCollection.build(resultQuantityColumn, Optional.empty()); }; } 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 new file mode 100644 index 0000000000..f21429530a --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/ResourceCollection.java @@ -0,0 +1,158 @@ +/* + * Copyright 2023 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.collection; + +import static au.csiro.pathling.utilities.Preconditions.checkPresent; + +import au.csiro.pathling.encoders.EncoderBuilder; +import au.csiro.pathling.encoders.ExtensionSupport; +import au.csiro.pathling.fhirpath.definition.ResourceDefinition; +import au.csiro.pathling.io.source.DataSource; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import java.util.EnumSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.functions; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; +import org.hl7.fhir.r4.model.Enumerations.ResourceType; +import scala.collection.JavaConverters; + +/** + * Represents any FHIRPath expression which refers to a resource type. + * + * @author John Grimes + */ +public class ResourceCollection extends Collection { + + /** + * A mapping between the names of elements in the resource and the corresponding {@link Column}. + */ + @Nonnull + private final Map elementsToColumns; + + @Nonnull + private final ResourceDefinition resourceDefinition; + + public ResourceCollection(@Nonnull final Column column, @Nonnull final ResourceDefinition definition, + @Nonnull final Map elementsToColumns) { + super(column, Optional.empty(), getFhirType(definition.getResourceType()), + Optional.of(definition)); + this.elementsToColumns = elementsToColumns; + this.resourceDefinition = definition; + } + + @Nonnull + private static Optional getFhirType(@Nonnull final ResourceType resourceType) { + try { + return Optional.ofNullable(FHIRDefinedType.fromCode(resourceType.toCode())); + } catch (final FHIRException e) { + return Optional.empty(); + } + } + + /** + * Build a new ResourcePath using the supplied {@link FhirContext} and {@link DataSource}. + * + * @param fhirContext the {@link FhirContext} to use for sourcing the resource definition + * @param dataSource the {@link DataSource} to use for retrieving the Dataset + * @param resourceType the type of the resource + * @return A shiny new ResourcePath + */ + @Nonnull + public static ResourceCollection build(@Nonnull final FhirContext fhirContext, + @Nonnull final DataSource dataSource, @Nonnull final ResourceType resourceType) { + + // Get the resource definition from HAPI. + final String resourceCode = resourceType.toCode(); + final RuntimeResourceDefinition hapiDefinition = fhirContext.getResourceDefinition( + resourceCode); + final ResourceDefinition definition = new ResourceDefinition(resourceType, hapiDefinition); + + // Retrieve the dataset for the resource type using the supplied resource reader. + final Dataset dataset = dataSource.read(resourceType); + + //noinspection ReturnOfNull + final Map elementsToColumns = Stream.of(dataset.columns()) + .collect(Collectors.toMap(Function.identity(), functions::col, (a, b) -> null)); + + // We use the ID column as the value column for a ResourcePath. + return new ResourceCollection(functions.col("id"), definition, elementsToColumns); + } + + /** + * @return The set of resource types currently supported by this implementation. + */ + @Nonnull + public static Set supportedResourceTypes() { + final Set availableResourceTypes = EnumSet.allOf( + ResourceType.class); + final Set unsupportedResourceTypes = + JavaConverters.setAsJavaSet(EncoderBuilder.UNSUPPORTED_RESOURCES()).stream() + .map(ResourceType::fromCode) + .collect(Collectors.toSet()); + availableResourceTypes.removeAll(unsupportedResourceTypes); + availableResourceTypes.remove(ResourceType.RESOURCE); + availableResourceTypes.remove(ResourceType.DOMAINRESOURCE); + availableResourceTypes.remove(ResourceType.NULL); + availableResourceTypes.remove(ResourceType.OPERATIONDEFINITION); + return availableResourceTypes; + } + + /** + * @param elementName the name of the element + * @return the {@link Column} within the dataset pertaining to this element + */ + @Nonnull + public Optional getElementColumn(@Nonnull final String elementName) { + return Optional.ofNullable(elementsToColumns.get(elementName)); + } + + @Nonnull + public Column getExtensionContainerColumn() { + final Optional maybeExtensionColumn = Optional + .ofNullable(elementsToColumns.get(ExtensionSupport.EXTENSIONS_FIELD_NAME())); + return checkPresent(maybeExtensionColumn, + "Extension container column '_extension' not present in the resource." + + " Check if extension support was enabled when data were imported!"); + } + + public ResourceType getResourceType() { + return resourceDefinition.getResourceType(); + } + + @Nonnull + @Override + public Optional traverse(@Nonnull final String expression) { + // Get the child column from the map of elements to columns. + return getElementColumn(expression).flatMap(value -> + // Get the child element definition from the resource definition. + resourceDefinition.getChildElement(expression).map(definition -> + Collection.build(value, definition))); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/StringPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/StringCollection.java similarity index 57% rename from fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/StringPath.java rename to fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/StringCollection.java index 113321f289..ba56c84198 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/StringPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/StringCollection.java @@ -15,22 +15,18 @@ * limitations under the License. */ -package au.csiro.pathling.fhirpath.element; +package au.csiro.pathling.fhirpath.collection; -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.Flat; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.fhirpath.literal.NullLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import com.google.common.collect.ImmutableSet; +import static au.csiro.pathling.fhirpath.literal.StringLiteral.unescapeFhirPathString; +import static au.csiro.pathling.utilities.Strings.unSingleQuote; +import static org.apache.spark.sql.functions.lit; + +import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.definition.NodeDefinition; import java.util.Optional; -import java.util.function.Function; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.hl7.fhir.r4.model.Base64BinaryType; import org.hl7.fhir.r4.model.CodeType; @@ -48,26 +44,39 @@ * * @author John Grimes */ -public class StringPath extends ElementPath implements FhirValue, Flat, Comparable, - StringCoercible { +public class StringCollection extends Collection implements Materializable { - private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet - .of(StringPath.class, StringLiteralPath.class, NullLiteralPath.class); + public StringCollection(@Nonnull final Column column, + @Nonnull final Optional definition) { + super(column, Optional.of(FhirPathType.STRING), Optional.of(FHIRDefinedType.STRING), + definition); + } - protected StringPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, - thisColumn, fhirType); + @Nonnull + public static StringCollection build(@Nonnull final Column column, + @Nonnull final Optional definition) { + return new StringCollection(column, definition); } + /** + * Returns a new instance, parsed from a FHIRPath literal. + * + * @param fhirPath The FHIRPath representation of the literal + * @return A new instance of {@link StringCollection} + */ @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, - final int columnNumber) { - return valueFromRow(row, columnNumber, getFhirType()); + public static StringCollection fromLiteral(@Nonnull final String fhirPath) { + final String value = parseStringLiteral(fhirPath); + return StringCollection.build(lit(value), Optional.empty()); + } + + @Nonnull + public static String parseStringLiteral(final @Nonnull String fhirPath) { + // Remove the surrounding single quotes and unescape the string according to the rules within + // the FHIRPath specification. + String value = unSingleQuote(fhirPath); + value = unescapeFhirPathString(value); + return value; } /** @@ -80,11 +89,14 @@ public Optional getFhirValueFromRow(@Nonnull final Row row, */ @Nonnull public static Optional valueFromRow(@Nonnull final Row row, final int columnNumber, - final FHIRDefinedType fhirType) { + @Nonnull final Optional fhirType) { if (row.isNullAt(columnNumber)) { return Optional.empty(); } - switch (fhirType) { + if (fhirType.isEmpty()) { + return Optional.of(new StringType(row.getString(columnNumber))); + } + switch (fhirType.get()) { case URI: return Optional.of(new UriType(row.getString(columnNumber))); case CODE: @@ -105,30 +117,10 @@ public static Optional valueFromRow(@Nonnull final Row row, final } @Nonnull - public static ImmutableSet> getComparableTypes() { - return COMPARABLE_TYPES; - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return Comparable.buildComparison(this, operation); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return COMPARABLE_TYPES.contains(type); - } - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof StringLiteralPath; - } - - @Nonnull - @Override - public FhirPath asStringPath(@Nonnull final String expression) { - return this; + public Optional getFhirValueFromRow(@Nonnull final Row row, + final int columnNumber) { + return valueFromRow(row, columnNumber, getFhirType()); } } 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 new file mode 100644 index 0000000000..b6020ff70f --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/TimeCollection.java @@ -0,0 +1,81 @@ +/* + * Copyright 2023 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.collection; + +import static org.apache.spark.sql.functions.lit; + +import au.csiro.pathling.fhirpath.Comparable; +import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.StringCoercible; +import au.csiro.pathling.fhirpath.definition.NodeDefinition; +import java.util.Optional; +import javax.annotation.Nonnull; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.types.DataTypes; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; +import org.hl7.fhir.r4.model.TimeType; + +/** + * Represents a FHIRPath expression which refers to a time typed element. + * + * @author John Grimes + */ +public class TimeCollection extends Collection implements Materializable, + Comparable, StringCoercible { + + public TimeCollection(@Nonnull final Column column, + @Nonnull final Optional definition) { + super(column, Optional.of(FhirPathType.TIME), Optional.of(FHIRDefinedType.TIME), definition); + } + + @Nonnull + public static TimeCollection build(@Nonnull final Column column, + @Nonnull final Optional definition) { + return new TimeCollection(column, definition); + } + + /** + * Returns a new instance, parsed from a FHIRPath literal. + * + * @param literal The FHIRPath representation of the literal + * @return A new instance of {@link TimeCollection} + */ + @Nonnull + public static TimeCollection fromLiteral(@Nonnull final String literal) { + final String timeString = literal.replaceFirst("^@T", ""); + return TimeCollection.build(lit(timeString), Optional.empty()); + } + + @Nonnull + @Override + public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { + if (row.isNullAt(columnNumber)) { + return Optional.empty(); + } + return Optional.of(new TimeType(row.getString(columnNumber))); + } + + @Nonnull + @Override + public Collection asStringPath() { + return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty()); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/CodingSqlComparator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/CodingSqlComparator.java index 45c65a9634..84352acf85 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/CodingSqlComparator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/CodingSqlComparator.java @@ -21,8 +21,8 @@ import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.Comparable; +import au.csiro.pathling.fhirpath.Comparable.ColumnComparator; import au.csiro.pathling.fhirpath.Comparable.ComparisonOperation; -import au.csiro.pathling.fhirpath.Comparable.SqlComparator; import java.util.Arrays; import java.util.List; import java.util.function.Function; @@ -35,7 +35,7 @@ * * @author Piotr Szul */ -public class CodingSqlComparator implements SqlComparator { +public class CodingSqlComparator implements ColumnComparator { private static final List EQUALITY_COLUMNS = Arrays .asList("system", "code", "version", "display", "userSelected"); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/DateTimeSqlComparator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/DateTimeSqlComparator.java index da374584a9..546dbf3313 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/DateTimeSqlComparator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/DateTimeSqlComparator.java @@ -20,8 +20,8 @@ import static org.apache.spark.sql.functions.callUDF; import au.csiro.pathling.fhirpath.Comparable; +import au.csiro.pathling.fhirpath.Comparable.ColumnComparator; import au.csiro.pathling.fhirpath.Comparable.ComparisonOperation; -import au.csiro.pathling.fhirpath.Comparable.SqlComparator; import au.csiro.pathling.sql.dates.datetime.DateTimeEqualsFunction; import au.csiro.pathling.sql.dates.datetime.DateTimeGreaterThanFunction; import au.csiro.pathling.sql.dates.datetime.DateTimeGreaterThanOrEqualToFunction; @@ -36,7 +36,7 @@ * * @author Piotr Szul */ -public class DateTimeSqlComparator implements SqlComparator { +public class DateTimeSqlComparator implements ColumnComparator { private static final DateTimeSqlComparator INSTANCE = new DateTimeSqlComparator(); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/QuantitySqlComparator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/QuantitySqlComparator.java index 50eb1b4a97..e2ec5830b4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/QuantitySqlComparator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/QuantitySqlComparator.java @@ -20,8 +20,8 @@ import static org.apache.spark.sql.functions.when; import au.csiro.pathling.fhirpath.Comparable; +import au.csiro.pathling.fhirpath.Comparable.ColumnComparator; import au.csiro.pathling.fhirpath.Comparable.ComparisonOperation; -import au.csiro.pathling.fhirpath.Comparable.SqlComparator; import au.csiro.pathling.fhirpath.encoding.QuantityEncoding; import au.csiro.pathling.sql.types.FlexiDecimal; import java.util.function.BiFunction; @@ -35,7 +35,7 @@ * * @author Piotr Szul */ -public class QuantitySqlComparator implements SqlComparator { +public class QuantitySqlComparator implements ColumnComparator { private final static QuantitySqlComparator INSTANCE = new QuantitySqlComparator(); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChoiceElementDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChoiceElementDefinition.java index 903f402c1f..6bb45cf3aa 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChoiceElementDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChoiceElementDefinition.java @@ -9,11 +9,19 @@ import org.apache.commons.lang.WordUtils; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; +/** + * Represents the definition of an element that can be represented by multiple different data + * types. + * + * @author John Grimes + * @see Polymorphism in FHIR + */ public class ChoiceElementDefinition implements ElementDefinition { @Nonnull private final RuntimeChildChoiceDefinition childDefinition; + @Nonnull private final Map> elementNameToDefinition; protected ChoiceElementDefinition(@Nonnull final RuntimeChildChoiceDefinition childDefinition) { @@ -23,6 +31,19 @@ protected ChoiceElementDefinition(@Nonnull final RuntimeChildChoiceDefinition ch .forEach(name -> elementNameToDefinition.put(name, childDefinition.getChildByName(name))); } + /** + * Returns the column name for a given type. + * + * @param elementName the name of the parent element + * @param type the type of the child element + * @return the column name + */ + @Nonnull + public static String getColumnName(@Nonnull final String elementName, + @Nonnull final String type) { + return elementName + WordUtils.capitalize(type); + } + @Nonnull @Override public String getElementName() { @@ -46,17 +67,28 @@ public Optional getFhirType() { return Optional.empty(); } + /** + * Returns the child element definition for the given type, if it exists. + * + * @param type the type of the child element + * @return the child element definition, if it exists + */ @Nonnull public Optional getChildByType(@Nonnull final String type) { final String key = ChoiceElementDefinition.getColumnName(getElementName(), type); - return Optional.ofNullable(elementNameToDefinition.get(key)) - .map(def -> new ResolvedChoiceDefinition(def, this)); + return getChildByElementName(key); } + /** + * Returns the child element definition for the given element name, if it exists. + * + * @param name the name of the child element + * @return the child element definition, if it exists + */ @Nonnull - public static String getColumnName(@Nonnull final String elementName, - @Nonnull final String type) { - return elementName + WordUtils.capitalize(type); + public Optional getChildByElementName(final String name) { + return Optional.ofNullable(elementNameToDefinition.get(name)) + .map(def -> new ResolvedChoiceDefinition(def, this)); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java index 48b335ef1b..2cdf12b6e6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java @@ -1,62 +1,17 @@ package au.csiro.pathling.fhirpath.definition; -import au.csiro.pathling.fhirpath.element.BooleanPath; -import au.csiro.pathling.fhirpath.element.CodingPath; -import au.csiro.pathling.fhirpath.element.DatePath; -import au.csiro.pathling.fhirpath.element.DateTimePath; -import au.csiro.pathling.fhirpath.element.DecimalPath; -import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.element.ExtensionPath; -import au.csiro.pathling.fhirpath.element.IntegerPath; -import au.csiro.pathling.fhirpath.element.QuantityPath; -import au.csiro.pathling.fhirpath.element.ReferencePath; -import au.csiro.pathling.fhirpath.element.StringPath; -import au.csiro.pathling.fhirpath.element.TimePath; import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.RuntimeChildAny; import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; import ca.uhn.fhir.context.RuntimeChildResourceDefinition; -import com.google.common.collect.ImmutableMap; -import java.util.Map; import java.util.Optional; import javax.annotation.Nonnull; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.r4.model.BackboneElement; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -public interface ElementDefinition { - - // See https://hl7.org/fhir/fhirpath.html#types. - @Nonnull - Map> FHIR_TYPE_TO_ELEMENT_PATH_CLASS = - new ImmutableMap.Builder>() - .put(FHIRDefinedType.BOOLEAN, BooleanPath.class) - .put(FHIRDefinedType.STRING, StringPath.class) - .put(FHIRDefinedType.URI, StringPath.class) - .put(FHIRDefinedType.URL, StringPath.class) - .put(FHIRDefinedType.CANONICAL, StringPath.class) - .put(FHIRDefinedType.CODE, StringPath.class) - .put(FHIRDefinedType.OID, StringPath.class) - .put(FHIRDefinedType.ID, StringPath.class) - .put(FHIRDefinedType.UUID, StringPath.class) - .put(FHIRDefinedType.MARKDOWN, StringPath.class) - .put(FHIRDefinedType.BASE64BINARY, StringPath.class) - .put(FHIRDefinedType.INTEGER, IntegerPath.class) - .put(FHIRDefinedType.UNSIGNEDINT, IntegerPath.class) - .put(FHIRDefinedType.POSITIVEINT, IntegerPath.class) - .put(FHIRDefinedType.DECIMAL, DecimalPath.class) - .put(FHIRDefinedType.DATE, DatePath.class) - .put(FHIRDefinedType.DATETIME, DateTimePath.class) - .put(FHIRDefinedType.INSTANT, DateTimePath.class) - .put(FHIRDefinedType.TIME, TimePath.class) - .put(FHIRDefinedType.CODING, CodingPath.class) - .put(FHIRDefinedType.QUANTITY, QuantityPath.class) - .put(FHIRDefinedType.SIMPLEQUANTITY, QuantityPath.class) - .put(FHIRDefinedType.REFERENCE, ReferencePath.class) - .put(FHIRDefinedType.EXTENSION, ExtensionPath.class) - .build(); +public interface ElementDefinition extends NodeDefinition { /** * @param childDefinition A HAPI {@link BaseRuntimeChildDefinition} that describes this element @@ -76,17 +31,6 @@ static ElementDefinition build(@Nonnull final BaseRuntimeChildDefinition childDe } } - /** - * @param fhirType A {@link FHIRDefinedType} - * @return The subtype of {@link ElementPath} that represents this type - */ - @Nonnull - static Optional> elementClassForType( - @Nonnull final FHIRDefinedType fhirType) { - return Optional.ofNullable( - BasicElementDefinition.FHIR_TYPE_TO_ELEMENT_PATH_CLASS.get(fhirType)); - } - @Nonnull static Optional getFhirTypeFromElementDefinition( @Nonnull final BaseRuntimeElementDefinition elementDefinition) { @@ -94,7 +38,8 @@ static Optional getFhirTypeFromElementDefinition( .flatMap(ElementDefinition::getFhirTypeFromObject); } - static Optional getFhirTypeFromObject(final IBase hapiObject) { + @Nonnull + static Optional getFhirTypeFromObject(@Nonnull final IBase hapiObject) { // BackboneElements do not seem to correctly report their FHIR type. if (hapiObject.getClass().getSuperclass() == BackboneElement.class) { return Optional.of(FHIRDefinedType.BACKBONEELEMENT); @@ -110,15 +55,6 @@ static Optional getFhirTypeFromObject(final IBase hapiObject) { @Nonnull String getElementName(); - /** - * Returns the child element of this element with the specified name. - * - * @param name The name of the child element - * @return A new ElementDefinition describing the child - */ - @Nonnull - Optional getChildElement(@Nonnull String name); - /** * @return The maximum cardinality for this element */ diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/NodeDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/NodeDefinition.java new file mode 100644 index 0000000000..5883c4ed68 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/NodeDefinition.java @@ -0,0 +1,21 @@ +package au.csiro.pathling.fhirpath.definition; + +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * + * @param + */ +public interface NodeDefinition { + + /** + * Returns the child element of this definition with the specified name. + * + * @param name the name of the child element + * @return a new {@link NodeDefinition} describing the child + */ + @Nonnull + Optional getChildElement(@Nonnull String name); + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResourceDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResourceDefinition.java index 04d4872a81..0ea563e310 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResourceDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResourceDefinition.java @@ -17,9 +17,7 @@ package au.csiro.pathling.fhirpath.definition; -import au.csiro.pathling.fhirpath.NestingKey; import ca.uhn.fhir.context.RuntimeResourceDefinition; -import java.util.Objects; import java.util.Optional; import javax.annotation.Nonnull; import lombok.Getter; @@ -32,36 +30,26 @@ * * @author John Grimes */ -public class ResourceDefinition implements NestingKey { +@Getter +public class ResourceDefinition implements NodeDefinition { /** * The HAPI FHIR resource type. */ @Nonnull - @Getter private final ResourceType resourceType; @Nonnull private final RuntimeResourceDefinition definition; - /** - * The parent definition of this resource. This may be empty, in the case of the root resource. It - * may also be populated with the element definition that linked to this resource, for example a - * reference element. - */ - @Nonnull - private final Optional parent; - /** * @param resourceType The {@link ResourceType} that describes this resource * @param definition The HAPI {@link RuntimeResourceDefinition} for this resource */ public ResourceDefinition(@Nonnull final ResourceType resourceType, - @Nonnull final RuntimeResourceDefinition definition, - @Nonnull final Optional parent) { + @Nonnull final RuntimeResourceDefinition definition) { this.resourceType = resourceType; this.definition = definition; - this.parent = parent; } /** @@ -71,6 +59,7 @@ public ResourceDefinition(@Nonnull final ResourceType resourceType, * @return A new ElementDefinition describing the child */ @Nonnull + @Override public Optional getChildElement(@Nonnull final String name) { return Optional.ofNullable(definition.getChildByName(name)) .or(() -> Optional.ofNullable(definition.getChildByName(name + "[x]"))) @@ -95,24 +84,4 @@ public static ResourceType getResourceTypeFromClass( return ResourceType.fromCode(resourceName); } - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final ResourceDefinition that = (ResourceDefinition) o; - return resourceType == that.resourceType - // Recursively compare parent definitions. - && Objects.equals(parent, that.parent); - } - - @Override - public int hashCode() { - // Recursively hash parent definitions. - return Objects.hash(resourceType, parent); - } - } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/BooleanPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/BooleanPath.java deleted file mode 100644 index 6e73bb52ce..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/BooleanPath.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2023 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.element; - -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; -import au.csiro.pathling.fhirpath.literal.NullLiteralPath; -import com.google.common.collect.ImmutableSet; -import java.util.Optional; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.BooleanType; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; - -/** - * Represents a FHIRPath expression which refers to a boolean typed element. - * - * @author John Grimes - */ -public class BooleanPath extends ElementPath implements FhirValue, Comparable { - - private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet - .of(BooleanPath.class, BooleanLiteralPath.class, NullLiteralPath.class); - - protected BooleanPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, - thisColumn, fhirType); - } - - @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { - return valueFromRow(row, columnNumber); - } - - /** - * Gets a value from a row for a Boolean or Boolean literal. - * - * @param row The {@link Row} from which to extract the value - * @param columnNumber The column number to extract the value from - * @return A {@link BooleanType}, or the absence of a value - */ - @Nonnull - public static Optional valueFromRow(@Nonnull final Row row, final int columnNumber) { - if (row.isNullAt(columnNumber)) { - return Optional.empty(); - } - return Optional.of(new BooleanType(row.getBoolean(columnNumber))); - } - - @Nonnull - public static ImmutableSet> getComparableTypes() { - return COMPARABLE_TYPES; - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return Comparable.buildComparison(this, operation); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return COMPARABLE_TYPES.contains(type); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof BooleanLiteralPath; - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ChoiceElementPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ChoiceElementPath.java deleted file mode 100644 index 932f181f38..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ChoiceElementPath.java +++ /dev/null @@ -1,98 +0,0 @@ -package au.csiro.pathling.fhirpath.element; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.AbstractPath; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.definition.ChoiceElementDefinition; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.operator.PathTraversalOperator; -import java.util.Optional; -import javax.annotation.Nonnull; -import lombok.AccessLevel; -import lombok.Getter; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; - -public class ChoiceElementPath extends NonLiteralPath implements AbstractPath { - - @Getter(AccessLevel.PUBLIC) - @Nonnull - protected final ChoiceElementDefinition definition; - - @Getter - @Nonnull - private final Optional orderingColumn; - - @Nonnull - private final NonLiteralPath from; - - protected ChoiceElementPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, - @Nonnull final ChoiceElementDefinition definition, @Nonnull final NonLiteralPath from) { - super(expression, dataset, idColumn, valueColumn, singular, currentResource, thisColumn); - this.orderingColumn = orderingColumn; - this.definition = definition; - this.from = from; - } - - @Nonnull - public static ChoiceElementPath build(@Nonnull final String expression, - @Nonnull final NonLiteralPath from, @Nonnull final Dataset dataset, - @Nonnull final Column valueColumn, @Nonnull final Optional orderingColumn, - final boolean singular, final ChoiceElementDefinition definition) { - return new ChoiceElementPath(expression, dataset, from.getIdColumn(), valueColumn, - orderingColumn, singular, from.getCurrentResource(), from.getThisColumn(), definition, - from); - } - - @Nonnull - @Override - public NonLiteralPath combineWith(@Nonnull final FhirPath target, - @Nonnull final Dataset dataset, @Nonnull final String expression, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, final boolean singular, - @Nonnull final Optional thisColumn) { - throw new InvalidUserInputError( - "Paths cannot be merged into a collection together: " + getExpression() + ", " + target - .getExpression()); - } - - @Nonnull - @Override - public Optional getChildElement(@Nonnull final String name) { - return Optional.empty(); - } - - @Nonnull - public Optional resolveChoice(@Nonnull final String type) { - return definition.getChildByType(type); - } - - @Nonnull - public Optional resolveChoiceColumn(@Nonnull final String type) { - if (from instanceof ResourcePath) { - return ((ResourcePath) from).getElementColumn( - ChoiceElementDefinition.getColumnName(definition.getElementName(), type)); - } else if (from instanceof ElementPath) { - return Optional.of(PathTraversalOperator.buildTraversalColumn(from, type)); - } else { - throw new IllegalStateException("Cannot resolve choice column from " + from); - } - } - - @Nonnull - @Override - public NonLiteralPath copy(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional thisColumn) { - return new ChoiceElementPath(expression, dataset, idColumn, valueColumn, orderingColumn, - singular, currentResource, thisColumn, definition, from); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java deleted file mode 100644 index f2cd209b29..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DatePath.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2023 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.element; - -import static au.csiro.pathling.fhirpath.Temporal.buildDateArithmeticOperation; -import static org.apache.spark.sql.functions.to_timestamp; - -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.Numeric.MathOperation; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.fhirpath.Temporal; -import au.csiro.pathling.fhirpath.comparison.DateTimeSqlComparator; -import au.csiro.pathling.fhirpath.literal.DateLiteralPath; -import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; -import au.csiro.pathling.sql.dates.date.DateAddDurationFunction; -import au.csiro.pathling.sql.dates.date.DateSubtractDurationFunction; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Function; -import javax.annotation.Nonnull; -import lombok.extern.slf4j.Slf4j; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.DateType; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; - -/** - * Represents a FHIRPath expression which refers to a date typed element. - * - * @author John Grimes - */ -@Slf4j -public class DatePath extends ElementPath implements FhirValue, Comparable, - Temporal, StringCoercible { - - protected DatePath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, - thisColumn, fhirType); - } - - /** - * Builds a comparison function for date and date/time like paths. - * - * @param source The path to build the comparison function for - * @param sparkFunction The Spark column function to use - * @return A new {@link Function} - */ - @Nonnull - public static Function buildComparison(@Nonnull final Comparable source, - @Nonnull final BiFunction sparkFunction) { - // The value columns are converted to native Spark timestamps before comparison. - return target -> sparkFunction - .apply(org.apache.spark.sql.functions.to_date(source.getValueColumn()), - to_timestamp(target.getValueColumn())); - } - - @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { - return valueFromRow(row, columnNumber); - } - - /** - * Gets a value from a row for a Date or Date literal. - * - * @param row The {@link Row} from which to extract the value - * @param columnNumber The column number to extract the value from - * @return A {@link DateType}, or the absence of a value - */ - @Nonnull - public static Optional valueFromRow(@Nonnull final Row row, final int columnNumber) { - if (row.isNullAt(columnNumber)) { - return Optional.empty(); - } - final String dateString = row.getString(columnNumber); - return Optional.of(new DateType(dateString)); - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return DateTimeSqlComparator.buildComparison(this, operation); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return DateTimePath.getComparableTypes().contains(type); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof DateLiteralPath; - } - - @Nonnull - @Override - public Function getDateArithmeticOperation( - @Nonnull final MathOperation operation, @Nonnull final Dataset dataset, - @Nonnull final String expression) { - return buildDateArithmeticOperation(this, operation, dataset, expression, - DateAddDurationFunction.FUNCTION_NAME, DateSubtractDurationFunction.FUNCTION_NAME); - } - - @Nonnull - @Override - public FhirPath asStringPath(@Nonnull final String expression) { - return ElementPath.build(expression, getDataset(), getIdColumn(), getValueColumn(), - getOrderingColumn(), isSingular(), getCurrentResource(), getThisColumn(), - FHIRDefinedType.STRING); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java deleted file mode 100644 index 00727167a8..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DateTimePath.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2023 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.element; - -import static au.csiro.pathling.fhirpath.Temporal.buildDateArithmeticOperation; -import static org.apache.spark.sql.functions.date_format; - -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.Numeric.MathOperation; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.fhirpath.Temporal; -import au.csiro.pathling.fhirpath.comparison.DateTimeSqlComparator; -import au.csiro.pathling.fhirpath.literal.DateLiteralPath; -import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; -import au.csiro.pathling.fhirpath.literal.NullLiteralPath; -import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; -import au.csiro.pathling.sql.dates.datetime.DateTimeAddDurationFunction; -import au.csiro.pathling.sql.dates.datetime.DateTimeSubtractDurationFunction; -import com.google.common.collect.ImmutableSet; -import java.util.Optional; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.BaseDateTimeType; -import org.hl7.fhir.r4.model.DateTimeType; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.InstantType; - -/** - * Represents a FHIRPath expression which refers to a datetime typed element. - * - * @author John Grimes - */ -public class DateTimePath extends ElementPath implements FhirValue, - Comparable, Temporal, StringCoercible { - - private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet - .of(DatePath.class, DateTimePath.class, DateLiteralPath.class, DateTimeLiteralPath.class, - NullLiteralPath.class); - - public static final String SPARK_FHIRPATH_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; - - protected DateTimePath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, - thisColumn, fhirType); - } - - @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, - final int columnNumber) { - return valueFromRow(row, columnNumber, getFhirType()); - } - - /** - * Gets a value from a row for a DateTime or DateTime literal. - * - * @param row The {@link Row} from which to extract the value - * @param columnNumber The column number to extract the value from - * @param fhirType The FHIR type to assume when extracting the value - * @return A {@link BaseDateTimeType}, or the absence of a value - */ - @Nonnull - public static Optional valueFromRow(@Nonnull final Row row, - final int columnNumber, final FHIRDefinedType fhirType) { - if (row.isNullAt(columnNumber)) { - return Optional.empty(); - } - if (fhirType == FHIRDefinedType.INSTANT) { - final InstantType value = new InstantType(row.getTimestamp(columnNumber)); - return Optional.of(value); - } else { - final DateTimeType value = new DateTimeType(row.getString(columnNumber)); - return Optional.of(value); - } - } - - @Nonnull - public static ImmutableSet> getComparableTypes() { - return COMPARABLE_TYPES; - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return DateTimeSqlComparator.buildComparison(this, operation); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return COMPARABLE_TYPES.contains(type); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof DateTimeLiteralPath; - } - - @Nonnull - @Override - public Function getDateArithmeticOperation( - @Nonnull final MathOperation operation, @Nonnull final Dataset dataset, - @Nonnull final String expression) { - return buildDateArithmeticOperation(this, operation, dataset, expression, - DateTimeAddDurationFunction.FUNCTION_NAME, DateTimeSubtractDurationFunction.FUNCTION_NAME); - } - - @Nonnull - @Override - public FhirPath asStringPath(@Nonnull final String expression) { - final Column valueColumn; - if (getFhirType() == FHIRDefinedType.INSTANT) { - valueColumn = date_format(getValueColumn(), SPARK_FHIRPATH_DATETIME_FORMAT); - } else { - valueColumn = getValueColumn(); - } - return ElementPath.build(expression, getDataset(), getIdColumn(), valueColumn, - getOrderingColumn(), isSingular(), getCurrentResource(), getThisColumn(), - FHIRDefinedType.STRING); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java deleted file mode 100644 index c406e8723e..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/DecimalPath.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2023 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.element; - -import au.csiro.pathling.encoders.datatypes.DecimalCustomCoder; -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.Numeric; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.fhirpath.literal.DecimalLiteralPath; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.Optional; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.types.DataTypes; -import org.apache.spark.sql.types.LongType; -import org.hl7.fhir.r4.model.DecimalType; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; - -/** - * Represents a FHIRPath expression which refers to a decimal typed element. - * - * @author John Grimes - */ -public class DecimalPath extends ElementPath implements FhirValue, Comparable, - Numeric, StringCoercible { - - private static final org.apache.spark.sql.types.DecimalType DECIMAL_TYPE = DataTypes - .createDecimalType(DecimalCustomCoder.precision(), DecimalCustomCoder.scale()); - - protected DecimalPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, - thisColumn, fhirType); - } - - @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { - return valueFromRow(row, columnNumber); - } - - /** - * Gets a value from a row for a Decimal or Decimal literal. - * - * @param row The {@link Row} from which to extract the value - * @param columnNumber The column number to extract the value from - * @return A {@link DecimalType}, or the absence of a value - */ - @Nonnull - public static Optional valueFromRow(@Nonnull final Row row, final int columnNumber) { - if (row.isNullAt(columnNumber)) { - return Optional.empty(); - } - // We support the extraction of Decimal values from columns with the long type. This will be - // used in the future to support things like casting large numbers to Decimal to work around the - // maximum Integer limit. - if (row.schema().fields()[columnNumber].dataType() instanceof LongType) { - final long longValue = row.getLong(columnNumber); - return Optional.of(new DecimalType(longValue)); - } else { - final BigDecimal decimal = row.getDecimal(columnNumber); - - if (decimal.precision() > getDecimalType().precision()) { - throw new InvalidUserInputError( - "Attempt to return a Decimal value with greater than supported precision"); - } - if (decimal.scale() > getDecimalType().scale()) { - return Optional.of( - new DecimalType(decimal.setScale(getDecimalType().scale(), RoundingMode.HALF_UP))); - } - - return Optional.of(new DecimalType(decimal)); - } - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return Comparable.buildComparison(this, operation); - } - - public static org.apache.spark.sql.types.DecimalType getDecimalType() { - return DECIMAL_TYPE; - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return IntegerPath.getComparableTypes().contains(type); - } - - @Nonnull - @Override - public Function getMathOperation(@Nonnull final MathOperation operation, - @Nonnull final String expression, @Nonnull final Dataset dataset) { - return buildMathOperation(this, operation, expression, dataset); - } - - @Nonnull - @Override - public Column getNumericValueColumn() { - return getValueColumn(); - } - - @Nonnull - @Override - public Column getNumericContextColumn() { - return getNumericValueColumn(); - } - - /** - * Builds a math operation result for a Decimal-like path. - * - * @param source the left operand for the operation - * @param operation the type of {@link au.csiro.pathling.fhirpath.Numeric.MathOperation} - * @param expression the FHIRPath expression to use in the result - * @param dataset the {@link Dataset} to use in the result - * @return A {@link Function} that takes a {@link Numeric} as a parameter, and returns a - * {@link NonLiteralPath} - */ - @Nonnull - public static Function buildMathOperation(@Nonnull final Numeric source, - @Nonnull final MathOperation operation, @Nonnull final String expression, - @Nonnull final Dataset dataset) { - return target -> { - Column valueColumn = operation.getSparkFunction() - .apply(source.getNumericValueColumn(), target.getNumericValueColumn()); - final Column idColumn = source.getIdColumn(); - final Optional orderingColumn = findOrderingColumn(source, target); - final Optional thisColumn = findThisColumn(source, target); - - switch (operation) { - case ADDITION: - case SUBTRACTION: - case MULTIPLICATION: - case DIVISION: - valueColumn = valueColumn.cast(getDecimalType()); - return ElementPath - .build(expression, dataset, idColumn, valueColumn, orderingColumn, true, - Optional.empty(), thisColumn, source.getFhirType()); - case MODULUS: - valueColumn = valueColumn.cast(DataTypes.LongType); - return ElementPath - .build(expression, dataset, idColumn, valueColumn, orderingColumn, true, - Optional.empty(), thisColumn, FHIRDefinedType.INTEGER); - default: - throw new AssertionError("Unsupported math operation encountered: " + operation); - } - }; - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof DecimalLiteralPath; - } - - @Override - @Nonnull - public FhirPath asStringPath(@Nonnull final String expression) { - final Column valueColumn = getValueColumn().cast(DataTypes.StringType); - return ElementPath.build(expression, getDataset(), getIdColumn(), valueColumn, - getOrderingColumn(), isSingular(), getCurrentResource(), getThisColumn(), - FHIRDefinedType.STRING); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java deleted file mode 100644 index 63281935ad..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ElementPath.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2023 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.element; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.definition.BasicElementDefinition; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.Optional; -import javax.annotation.Nonnull; -import lombok.AccessLevel; -import lombok.Getter; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; - -/** - * Represents any FHIRPath expression which refers to an element within a resource. - * - * @author John Grimes - */ -public class ElementPath extends NonLiteralPath { - - /** - * The FHIR data type of the element being represented by this expression. - *

- * Note that there can be multiple valid FHIR types for a given FHIRPath type, e.g. {@code uri} - * and {@code code} both map to the {@code String} FHIRPath type. - * - * @see Using FHIR types in expressions - */ - @Getter - @Nonnull - private final FHIRDefinedType fhirType; - - @Getter(AccessLevel.PUBLIC) - @Nonnull - protected Optional definition = Optional.empty(); - - @Getter - @Nonnull - private final Optional orderingColumn; - - protected ElementPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, valueColumn, singular, currentResource, - thisColumn); - this.fhirType = fhirType; - this.orderingColumn = orderingColumn; - } - - /** - * Builds the appropriate subtype of ElementPath based upon the supplied - * {@link BasicElementDefinition}. - *

- * Use this builder when the path is the child of another path, and will need to be traversable. - * - * @param expression the FHIRPath representation of this path - * @param dataset a {@link Dataset} that can be used to evaluate this path against data - * @param idColumn a {@link Column} within the dataset containing the identity of the subject - * resource - * @param valueColumn a {@link Column} within the dataset containing the values of the nodes - * @param orderingColumn a {@link Column} within the dataset containing the ordering of the - * elements - * @param singular an indicator of whether this path represents a single-valued collection - * @param currentResource the current resource within this path - * @param thisColumn collection values where this path originated from {@code $this} - * @param definition the HAPI element definition that this path should be based upon - * @return a new ElementPath - */ - @Nonnull - public static ElementPath build(@Nonnull final String expression, - @Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final Column valueColumn, @Nonnull final Optional orderingColumn, - final boolean singular, @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, @Nonnull final ElementDefinition definition) { - final Optional optionalFhirType = definition.getFhirType(); - if (optionalFhirType.isPresent()) { - final FHIRDefinedType fhirType = optionalFhirType.get(); - final ElementPath path = ElementPath - .build(expression, dataset, idColumn, valueColumn, orderingColumn, singular, - currentResource, thisColumn, fhirType); - path.definition = Optional.of(definition); - return path; - } else { - throw new IllegalArgumentException( - "Attempted to build an ElementPath with an ElementDefinition with no fhirType"); - } - } - - /** - * Builds the appropriate subtype of ElementPath based upon the supplied {@link FHIRDefinedType}. - *

- * Use this builder when the path is derived, e.g. the result of a function. - * - * @param expression the FHIRPath representation of this path - * @param dataset a {@link Dataset} that can be used to evaluate this path against data - * @param idColumn a {@link Column} within the dataset containing the identity of the subject - * resource - * @param valueColumn a {@link Column} within the dataset containing the values of the nodes - * @param orderingColumn a {@link Column} within the dataset containing the ordering of the - * elements - * @param singular an indicator of whether this path represents a single-valued collection - * @param currentResource the current resource within this path - * @param thisColumn collection values where this path originated from {@code $this} - * @param fhirType the FHIR type that this path should be based upon - * @return a new ElementPath - */ - @Nonnull - public static ElementPath build(@Nonnull final String expression, - @Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final Column valueColumn, @Nonnull final Optional orderingColumn, - final boolean singular, @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - return getInstance(expression, dataset, idColumn, valueColumn, orderingColumn, singular, - currentResource, thisColumn, fhirType); - } - - @Nonnull - private static ElementPath getInstance(@Nonnull final String expression, - @Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final Column valueColumn, @Nonnull final Optional orderingColumn, - final boolean singular, @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - // Look up the class that represents an element with the specified FHIR type. - final Class elementPathClass = ElementDefinition - .elementClassForType(fhirType).orElse(ElementPath.class); - - try { - // Call its constructor and return. - final Constructor constructor = elementPathClass - .getDeclaredConstructor(String.class, Dataset.class, Column.class, Column.class, - Optional.class, boolean.class, Optional.class, Optional.class, FHIRDefinedType.class); - return constructor - .newInstance(expression, dataset, idColumn, valueColumn, orderingColumn, singular, - currentResource, thisColumn, fhirType); - } catch (final NoSuchMethodException | InstantiationException | IllegalAccessException | - InvocationTargetException e) { - throw new RuntimeException("Problem building an ElementPath class", e); - } - } - - @Nonnull - @Override - public Optional getChildElement(@Nonnull final String name) { - return definition.flatMap(elementDefinition -> elementDefinition.getChildElement(name)); - } - - @Nonnull - @Override - public ElementPath copy(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional thisColumn) { - return definition - .map(elementDefinition -> ElementPath - .build(expression, dataset, idColumn, valueColumn, orderingColumn, singular, - currentResource, thisColumn, elementDefinition)) - .orElseGet( - () -> ElementPath - .build(expression, dataset, idColumn, valueColumn, orderingColumn, singular, - currentResource, thisColumn, fhirType)); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - // If either of the paths are an unclassified ElementPath (not a subclass), we need to check - // their FHIRDefinedType to make sure that their values can co-exist in the same column. - if (getClass().equals(ElementPath.class) && target.getClass().equals(ElementPath.class)) { - return super.canBeCombinedWith(target) && - getFhirType().equals(((ElementPath) target).getFhirType()) && - !getFhirType().equals(FHIRDefinedType.BACKBONEELEMENT); - } else { - return super.canBeCombinedWith(target); - } - } - - @Override - @Nonnull - public NonLiteralPath combineWith(@Nonnull final FhirPath target, - @Nonnull final Dataset dataset, @Nonnull final String expression, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, final boolean singular, - @Nonnull final Optional thisColumn) { - if (canBeCombinedWith(target)) { - return copy(expression, dataset, idColumn, valueColumn, Optional.empty(), singular, - thisColumn); - } - // Anything else is invalid. - throw new InvalidUserInputError( - "Paths cannot be merged into a collection together: " + getExpression() + ", " + target - .getExpression()); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ExtensionPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ExtensionPath.java deleted file mode 100644 index 91838d5a3d..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ExtensionPath.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2023 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.element; - -import au.csiro.pathling.encoders.ExtensionSupport; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.types.ArrayType; -import org.apache.spark.sql.types.MapType; -import org.apache.spark.sql.types.StructField; -import org.apache.spark.sql.types.StructType; -import org.hl7.fhir.r4.model.Enumerations; - -/** - * Represents a FHIRPath expression that is an extension container. - * - * @author John Grimes - * @see Extensibility - */ -public class ExtensionPath extends ElementPath { - - protected ExtensionPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, - @Nonnull final Enumerations.FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, - thisColumn, fhirType); - } - - @Nonnull - @Override - public Optional getChildElement(@Nonnull final String name) { - // This code introspects the type of the extension container column to determine the valid child - // element names. - final MapType mapType = (MapType) dataset.select(getExtensionContainerColumn()).schema() - .fields()[0].dataType(); - final ArrayType arrayType = (ArrayType) mapType.valueType(); - final StructType structType = (StructType) arrayType.elementType(); - final List fieldNames = Arrays.stream(structType.fields()).map(StructField::name) - .collect(Collectors.toList()); - // Add the extension field name, so that we can support traversal to nested extensions. - fieldNames.add(ExtensionSupport.EXTENSION_ELEMENT_NAME()); - - // If the field is part of the encoded extension container, pass control to the generic code to - // determine the correct element definition. - return fieldNames.contains(name) - ? super.getChildElement(name) - : Optional.empty(); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java deleted file mode 100644 index f2c3a30d90..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/IntegerPath.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2023 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.element; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.Numeric; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.fhirpath.literal.DecimalLiteralPath; -import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; -import au.csiro.pathling.fhirpath.literal.NullLiteralPath; -import com.google.common.collect.ImmutableSet; -import java.util.Optional; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.types.DataTypes; -import org.apache.spark.sql.types.LongType; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.IntegerType; -import org.hl7.fhir.r4.model.PositiveIntType; -import org.hl7.fhir.r4.model.PrimitiveType; -import org.hl7.fhir.r4.model.UnsignedIntType; - -/** - * Represents a FHIRPath expression which refers to an integer typed element. - * - * @author John Grimes - */ -public class IntegerPath extends ElementPath implements FhirValue, Comparable, - Numeric, StringCoercible { - - private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet - .of(IntegerPath.class, IntegerLiteralPath.class, DecimalPath.class, DecimalLiteralPath.class, - NullLiteralPath.class); - - protected IntegerPath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, - thisColumn, fhirType); - } - - @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, - final int columnNumber) { - return valueFromRow(row, columnNumber, getFhirType()); - } - - /** - * Gets a value from a row for an Integer or Integer literal. - * - * @param row The {@link Row} from which to extract the value - * @param columnNumber The column number to extract the value from - * @param fhirType The FHIR type to assume when extracting the value - * @return A {@link PrimitiveType}, or the absence of a value - */ - @Nonnull - public static Optional valueFromRow(@Nonnull final Row row, final int columnNumber, - @Nonnull final FHIRDefinedType fhirType) { - if (row.isNullAt(columnNumber)) { - return Optional.empty(); - } - final int value; - if (row.schema().fields()[columnNumber].dataType() instanceof LongType) { - try { - // Currently, some functions such as count currently return an Integer type, even though - // their return values can theoretically exceed the maximum value permitted for an integer. - // This guard allows us to handle this situation in a safe way. In the future, we will - // implement the "as" operator to allow the user to explicitly use a Decimal where large - // values are possible. - value = Math.toIntExact(row.getLong(columnNumber)); - } catch (final ArithmeticException e) { - throw new InvalidUserInputError( - "Attempt to return an Integer value greater than the maximum value permitted for this type"); - } - } else { - value = row.getInt(columnNumber); - } - switch (fhirType) { - case UNSIGNEDINT: - return Optional.of(new UnsignedIntType(value)); - case POSITIVEINT: - return Optional.of(new PositiveIntType(value)); - default: - return Optional.of(new IntegerType(value)); - } - } - - @Nonnull - public static ImmutableSet> getComparableTypes() { - return COMPARABLE_TYPES; - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return Comparable.buildComparison(this, operation); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return COMPARABLE_TYPES.contains(type); - } - - @Nonnull - @Override - public Function getMathOperation(@Nonnull final MathOperation operation, - @Nonnull final String expression, @Nonnull final Dataset dataset) { - return buildMathOperation(this, operation, expression, dataset); - } - - @Nonnull - @Override - public Column getNumericValueColumn() { - return getValueColumn().cast(DataTypes.LongType); - } - - @Nonnull - @Override - public Column getNumericContextColumn() { - return getNumericValueColumn(); - } - - /** - * Builds a math operation result for an Integer-like path. - * - * @param source The left operand for the operation - * @param operation The type of {@link au.csiro.pathling.fhirpath.Numeric.MathOperation} - * @param expression The FHIRPath expression to use in the result - * @param dataset The {@link Dataset} to use in the result - * @return A {@link Function} that takes a {@link Numeric} as a parameter, and returns a - * {@link NonLiteralPath} - */ - @Nonnull - public static Function buildMathOperation(@Nonnull final Numeric source, - @Nonnull final MathOperation operation, @Nonnull final String expression, - @Nonnull final Dataset dataset) { - return target -> { - final Column targetValueColumn = target.getNumericValueColumn(); - Column valueColumn = operation.getSparkFunction() - .apply(source.getNumericValueColumn(), targetValueColumn); - final Column idColumn = source.getIdColumn(); - final Optional orderingColumn = findOrderingColumn(source, target); - final Optional thisColumn = findThisColumn(source, target); - - switch (operation) { - case ADDITION: - case SUBTRACTION: - case MULTIPLICATION: - case MODULUS: - if (target instanceof DecimalPath || target instanceof DecimalLiteralPath) { - valueColumn = valueColumn.cast(DataTypes.LongType); - } - return ElementPath - .build(expression, dataset, idColumn, valueColumn, orderingColumn, true, - Optional.empty(), thisColumn, source.getFhirType()); - case DIVISION: - final Column numerator = source.getValueColumn().cast(DecimalPath.getDecimalType()); - valueColumn = operation.getSparkFunction().apply(numerator, targetValueColumn); - return ElementPath - .build(expression, dataset, idColumn, valueColumn, orderingColumn, true, - Optional.empty(), thisColumn, FHIRDefinedType.DECIMAL); - default: - throw new AssertionError("Unsupported math operation encountered: " + operation); - } - }; - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof IntegerLiteralPath; - } - - @Override - @Nonnull - public FhirPath asStringPath(@Nonnull final String expression) { - final Column valueColumn = getValueColumn().cast(DataTypes.StringType); - return ElementPath.build(expression, getDataset(), getIdColumn(), valueColumn, - getOrderingColumn(), isSingular(), getCurrentResource(), getThisColumn(), - FHIRDefinedType.STRING); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java deleted file mode 100644 index 4780ef1105..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/ReferencePath.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2023 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.element; - -import static au.csiro.pathling.utilities.Preconditions.check; - -import au.csiro.pathling.fhirpath.Referrer; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.definition.ReferenceDefinition; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; - -/** - * Represents a FHIRPath expression which is a resource reference. - * - * @author John Grimes - */ -public class ReferencePath extends ElementPath implements Referrer { - - protected ReferencePath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, - thisColumn, fhirType); - } - - @Nonnull - @Override - public Optional getDefinition() { - final Optional definition = super.getDefinition(); - check(definition.isEmpty() || definition.get() instanceof ReferenceDefinition); - //noinspection unchecked - return (Optional) definition; - } - - @Nonnull - public Set getResourceTypes() { - if (getDefinition().isPresent()) { - return getDefinition().get().getReferenceTypes(); - } else { - return Collections.emptySet(); - } - } - - @Nonnull - public Column getReferenceColumn() { - return Referrer.referenceColumnFor(this); - } - - @Nonnull - @Override - public Column getReferenceIdColumn() { - return Referrer.referenceIdColumnFor(Referrer.referenceColumnFor(this)); - } - - @Nonnull - public Column getResourceEquality(@Nonnull final ResourcePath resourcePath) { - return Referrer.resourceEqualityFor(this, resourcePath); - } - - @Nonnull - public Column getResourceEquality(@Nonnull final Column targetId, - @Nonnull final Column targetCode) { - return Referrer.resourceEqualityFor(this, targetCode, targetId); - } - - @Nonnull - @Override - public Optional getChildElement(@Nonnull final String name) { - // We only encode the reference and display elements of the Reference type. - if (name.equals("reference") || name.equals("display")) { - return super.getChildElement(name); - } else { - return Optional.empty(); - } - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java deleted file mode 100644 index 3005410a1b..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/element/TimePath.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2023 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.element; - -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.fhirpath.literal.NullLiteralPath; -import au.csiro.pathling.fhirpath.literal.TimeLiteralPath; -import com.google.common.collect.ImmutableSet; -import java.util.Optional; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.TimeType; - -/** - * Represents a FHIRPath expression which refers to a time typed element. - * - * @author John Grimes - */ -public class TimePath extends ElementPath implements FhirValue, Comparable, - StringCoercible { - - private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet - .of(TimePath.class, TimeLiteralPath.class, NullLiteralPath.class); - - protected TimePath(@Nonnull final String expression, @Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, - @Nonnull final Optional orderingColumn, final boolean singular, - @Nonnull final Optional currentResource, - @Nonnull final Optional thisColumn, @Nonnull final FHIRDefinedType fhirType) { - super(expression, dataset, idColumn, valueColumn, orderingColumn, singular, currentResource, - thisColumn, fhirType); - } - - @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { - return valueFromRow(row, columnNumber); - } - - /** - * Gets a value from a row for a Time or Time literal. - * - * @param row The {@link Row} from which to extract the value - * @param columnNumber The column number to extract the value from - * @return A {@link TimeType}, or the absence of a value - */ - @Nonnull - public static Optional valueFromRow(@Nonnull final Row row, final int columnNumber) { - if (row.isNullAt(columnNumber)) { - return Optional.empty(); - } - return Optional.of(new TimeType(row.getString(columnNumber))); - } - - @Nonnull - public static ImmutableSet> getComparableTypes() { - return COMPARABLE_TYPES; - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return Comparable.buildComparison(this, operation); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return COMPARABLE_TYPES.contains(type); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof TimeLiteralPath; - } - - @Nonnull - @Override - public FhirPath asStringPath(@Nonnull final String expression) { - return ElementPath.build(expression, getDataset(), getIdColumn(), getValueColumn(), - getOrderingColumn(), isSingular(), getCurrentResource(), getThisColumn(), - FHIRDefinedType.STRING); - } -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java deleted file mode 100644 index eb7fc892e3..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/AggregateFunction.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 2023 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.function; - -import static au.csiro.pathling.utilities.Preconditions.checkArgument; -import static au.csiro.pathling.utilities.Preconditions.checkPresent; -import static au.csiro.pathling.utilities.Strings.randomAlias; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; -import static org.apache.spark.sql.functions.col; -import static org.apache.spark.sql.functions.first; - -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.function.UnaryOperator; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.functions; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; - -/** - * Represents a function intended to reduce a set of values to a single value. - * - * @author John Grimes - */ -public abstract class AggregateFunction { - - /** - * Builds a result for an aggregation operation, with a single {@link FhirPath} object as input - * that will be copied and used as a template for the new result. - * - * @param dataset the {@link Dataset} that will be used in the result - * @param parserContext the current {@link ParserContext} - * @param input the {@link FhirPath} objects being aggregated - * @param aggregateColumn a {@link Column} describing the resulting value - * @param expression the FHIRPath expression for the result - * @return a new {@link ElementPath} representing the result - */ - @Nonnull - protected NonLiteralPath buildAggregateResult(@Nonnull final Dataset dataset, - @Nonnull final ParserContext parserContext, @Nonnull final NonLiteralPath input, - @Nonnull final Column aggregateColumn, @Nonnull final String expression) { - - return buildAggregateResult(dataset, parserContext, Collections.singletonList(input), - aggregateColumn, expression, input::copy); - } - - /** - * Builds a result for an aggregation operation, with a single {@link FhirPath} object as input. - * - * @param dataset the {@link Dataset} that will be used in the result - * @param parserContext the current {@link ParserContext} - * @param input the {@link FhirPath} objects being aggregated - * @param aggregateColumn a {@link Column} describing the resulting value - * @param expression the FHIRPath expression for the result - * @param fhirType the {@link FHIRDefinedType} of the result - * @return a new {@link ElementPath} representing the result - */ - @SuppressWarnings("SameParameterValue") - @Nonnull - protected ElementPath buildAggregateResult(@Nonnull final Dataset dataset, - @Nonnull final ParserContext parserContext, @Nonnull final FhirPath input, - @Nonnull final Column aggregateColumn, @Nonnull final String expression, - @Nonnull final FHIRDefinedType fhirType) { - - return buildAggregateResult(dataset, parserContext, Collections.singletonList(input), - aggregateColumn, expression, fhirType); - } - - /** - * Builds a result for an aggregation operation, with possibly multiple {@link FhirPath} objects - * as input (e.g. in the case of a binary operator that performs aggregation). - * - * @param dataset the {@link Dataset} that will be used in the result - * @param parserContext the current {@link ParserContext} - * @param inputs the {@link FhirPath} objects being aggregated - * @param aggregateColumn a {@link Column} describing the aggregation operation - * the final value - * @param expression the FHIRPath expression for the result - * @param fhirType the {@link FHIRDefinedType} of the result - * @return a new {@link ElementPath} representing the result - */ - @Nonnull - protected ElementPath buildAggregateResult(@Nonnull final Dataset dataset, - @Nonnull final ParserContext parserContext, @Nonnull final Collection inputs, - @Nonnull final Column aggregateColumn, @Nonnull final String expression, - @Nonnull final FHIRDefinedType fhirType) { - - return buildAggregateResult(dataset, parserContext, inputs, aggregateColumn, expression, - // Create the result as an ElementPath of the given FHIR type. - (exp, ds, id, value, ordering, singular, thisColumn) -> ElementPath.build(exp, ds, id, - value, ordering, true, Optional.empty(), thisColumn, fhirType)); - } - - @Nonnull - private T buildAggregateResult(@Nonnull final Dataset dataset, - @Nonnull final ParserContext parserContext, @Nonnull final Collection inputs, - @Nonnull final Column aggregateColumn, @Nonnull final String expression, - @Nonnull final ResultPathFactory resultPathFactory) { - - checkArgument(!inputs.isEmpty(), "Collection of inputs cannot be empty"); - - // Use an ID column from any of the inputs. - final Column idColumn = inputs.stream().findFirst().get().getIdColumn(); - - // Get the grouping columns from the parser context. - final List groupingColumns = parserContext.getGroupingColumns(); - final Column[] groupBy = groupingColumns.toArray(new Column[0]); - - // Clear the nesting present within the context. - parserContext.getNesting().clear(); - - // Perform the aggregation, while also preserving all columns that are not being aggregated - // using the "first()" function. - // First, collect the grouping and non-grouping columns into separate collections so that we can - // feed them to the "agg" method. - final Set nonGroupingColumns = Stream.of(dataset.columns()) - .map(functions::col) - .collect(toSet()); - groupingColumns.forEach(nonGroupingColumns::remove); - - // Wrap the non-grouping columns in a "first" aggregation. - final List selection = nonGroupingColumns.stream() - .map(column -> first(column, true).alias(column.toString())) - .collect(toList()); - final String valueColumnName = randomAlias(); - selection.add(aggregateColumn.alias(valueColumnName)); - - // Separate the first column from the rest, so that we can pass all the columns to the "agg" - // method. - final Column firstSelection = checkPresent(selection.stream().limit(1).findFirst()); - final Column[] remainingSelection = selection.stream().skip(1).toArray(Column[]::new); - - // Perform the final aggregation, the first row of each non-grouping column grouped by the - // grouping columns. - final Dataset result = dataset - .groupBy(groupBy) - .agg(firstSelection, remainingSelection); - final Column valueColumn = col(valueColumnName); - - // Get any "this" columns that may be present in the inputs. - final Optional thisColumn = NonLiteralPath - .findThisColumn((Object[]) inputs.toArray(new FhirPath[0])); - - return resultPathFactory.create(expression, result, idColumn, valueColumn, Optional.empty(), - true, thisColumn); - } - - /** - * A factory that encapsulates creation of the aggregation result path. - * - * @param subtype of FhirPath to create - */ - private interface ResultPathFactory { - - /** - * Creates a subtype T of FhirPath - * - * @param expression an updated expression to describe the new FhirPath - * @param dataset the new Dataset that can be used to evaluate this FhirPath against data - * @param idColumn the new resource identity column - * @param valueColumn the new expression value column - * @param orderingColumn the new ordering column - * @param singular the new singular value - * @param thisColumn a column containing the collection being iterated, for cases where a path - * is being created to represent the {@code $this} keyword - * @return a new instance of T - */ - T create(@Nonnull String expression, @Nonnull Dataset dataset, @Nonnull Column idColumn, - @Nonnull Column valueColumn, @Nonnull Optional orderingColumn, boolean singular, - @Nonnull Optional thisColumn); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/Arguments.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/Arguments.java index 28d624e4b9..571575681e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/Arguments.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/Arguments.java @@ -19,7 +19,7 @@ import static java.util.Objects.requireNonNull; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.literal.LiteralPath; import java.util.List; import java.util.Optional; @@ -33,9 +33,9 @@ public class Arguments { @Nonnull - private final List arguments; + private final List arguments; - private Arguments(@Nonnull final List arguments) { + private Arguments(@Nonnull final List arguments) { this.arguments = arguments; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java index 8b2c16b63d..2ef080dc75 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java @@ -25,9 +25,9 @@ import static org.apache.spark.sql.functions.max; import static org.apache.spark.sql.functions.min; -import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.element.BooleanPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; import java.util.function.UnaryOperator; import javax.annotation.Nonnull; import lombok.AllArgsConstructor; @@ -51,13 +51,13 @@ public BooleansTestFunction(@Nonnull final BooleansTestType type) { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + public Collection invoke(@Nonnull final NamedFunctionInput input) { checkNoArguments(type.getFunctionName(), input); final NonLiteralPath inputPath = input.getInput(); - checkUserInput(inputPath instanceof BooleanPath, + checkUserInput(inputPath instanceof BooleanCollection, "Input to " + type + " function must be Boolean"); final Column inputColumn = inputPath.getValueColumn(); - final String expression = expressionFromInput(input, type.getFunctionName()); + final String expression = expressionFromInput(input, type.getFunctionName(), input.getInput()); final Column valueColumn = type.getEquality().apply(inputColumn); return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, valueColumn, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java index d1b236d66e..cd7083c902 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java @@ -19,15 +19,14 @@ import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static org.apache.spark.sql.functions.when; +import static org.apache.spark.sql.functions.size; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import java.util.function.Function; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.FunctionInput; +import au.csiro.pathling.fhirpath.collection.IntegerCollection; +import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; -import org.apache.spark.sql.functions; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** * A function for aggregating data based on counting the number of rows within the result. @@ -35,36 +34,22 @@ * @author John Grimes * @see count */ -public class CountFunction extends AggregateFunction implements NamedFunction { +public class CountFunction implements NamedFunction { - private static final String NAME = "count"; - - protected CountFunction() { + @Nonnull + @Override + public String getName() { + return "count"; } @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { - checkNoArguments("count", input); - final NonLiteralPath inputPath = input.getInput(); - final String expression = expressionFromInput(input, NAME); - final Column subjectColumn = inputPath.getValueColumn(); - - // When we are counting resources from the input context, we use the distinct count to account - // for the fact that there may be duplicate IDs in the dataset. - // When we are counting anything else, we use a non-distinct count, to account for the fact that - // it is valid to have multiple of the same value. - final Function countFunction = inputPath == input.getContext().getInputContext() - ? functions::countDistinct - : functions::count; - - // According to the FHIRPath specification, the count function must return 0 when invoked on an - // empty collection. - final Column valueColumn = when(countFunction.apply(subjectColumn).isNull(), 0L) - .otherwise(countFunction.apply(subjectColumn)); - - return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, valueColumn, - expression, FHIRDefinedType.UNSIGNEDINT); + public Collection invoke(@Nonnull final FunctionInput input) { + checkNoArguments(getName(), input); + final Collection inputPath = input.getInput(); + final Column valueColumn = size(inputPath.getColumn()); + final String expression = expressionFromInput(input, getName(), input.getInput()); + return IntegerCollection.build(valueColumn, expression, Optional.empty()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java index 4d710a73a2..2de42ccb68 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java @@ -20,15 +20,12 @@ import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.FunctionInput; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** * This function returns true if the input collection is empty. @@ -42,19 +39,21 @@ public class EmptyFunction implements NamedFunction { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { - checkNoArguments(NAME, input); - final NonLiteralPath inputPath = input.getInput(); - final String expression = expressionFromInput(input, NAME); + public String getName() { + return NAME; + } + + @Nonnull + @Override + public Collection invoke(@Nonnull final FunctionInput input) { + checkNoArguments(getName(), input); // We use the count function to determine whether there are zero items in the input collection. - final FhirPath countResult = new CountFunction().invoke(input); - final Dataset dataset = countResult.getDataset(); - final Column valueColumn = countResult.getValueColumn().equalTo(0); + final Collection countResult = new CountFunction().invoke(input); + final Column valueColumn = countResult.getColumn().equalTo(0); + final String expression = expressionFromInput(input, getName(), input.getInput()); - return ElementPath.build(expression, dataset, inputPath.getIdColumn(), valueColumn, - Optional.empty(), true, Optional.empty(), inputPath.getThisColumn(), - FHIRDefinedType.BOOLEAN); + return BooleanCollection.build(valueColumn, expression, Optional.empty()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java index de8698cd24..2c51299648 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java @@ -21,10 +21,11 @@ import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.not; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.element.BooleanPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.FunctionInput; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; import java.util.Collections; +import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -41,35 +42,34 @@ public class ExistsFunction implements NamedFunction { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + public String getName() { + return NAME; + } + + @Nonnull + @Override + public Collection invoke(@Nonnull final FunctionInput input) { final int numberOfArguments = input.getArguments().size(); checkUserInput(numberOfArguments <= 1, "exists function accepts at most one argument"); - final String expression = expressionFromInput(input, NAME); + final String expression = expressionFromInput(input, getName(), input.getInput()); + final Column column; if (numberOfArguments == 0) { // If there are no arguments, invoke the `empty` function and use the inverse of the result. - final BooleanPath emptyResult = (BooleanPath) new EmptyFunction().invoke(input); - final Column valueColumn = not(emptyResult.getValueColumn()); - return emptyResult.copy(expression, emptyResult.getDataset(), emptyResult.getIdColumn(), - valueColumn, emptyResult.getOrderingColumn(), emptyResult.isSingular(), - emptyResult.getThisColumn()); + final Collection emptyResult = new EmptyFunction().invoke(input); + column = not(emptyResult.getColumn()); } else { - final FhirPath argument = input.getArguments().get(0); - checkUserInput(argument.isSingular() && argument instanceof BooleanPath, - "Argument to exists function must be a singular Boolean: " + argument.getExpression()); - // If there is an argument, first invoke the `where` function. - final NonLiteralPath whereResult = (NonLiteralPath) new WhereFunction().invoke(input); + final Collection whereResult = new WhereFunction().invoke(input); // Then invoke the `exists` function on the result, with no arguments. - final NamedFunctionInput existsInput = new NamedFunctionInput(input.getContext(), + final FunctionInput existsInput = new FunctionInput(input.getContext(), whereResult, Collections.emptyList()); - final BooleanPath existsResult = (BooleanPath) invoke(existsInput); - return existsResult.copy(expression, existsResult.getDataset(), existsResult.getIdColumn(), - existsResult.getValueColumn(), existsResult.getOrderingColumn(), - existsResult.isSingular(), existsResult.getThisColumn()); + final Collection existsResult = invoke(existsInput); + column = existsResult.getColumn(); } + return BooleanCollection.build(column, expression, Optional.empty()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java index 09a63285ca..dceaf5eab5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java @@ -17,23 +17,23 @@ package au.csiro.pathling.fhirpath.function; +import static au.csiro.pathling.utilities.Preconditions.checkPresent; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import au.csiro.pathling.encoders.ExtensionSupport; import au.csiro.pathling.fhirpath.Comparable.ComparisonOperation; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.NonLiteralPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.FunctionInput; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.operator.ComparisonOperator; -import au.csiro.pathling.fhirpath.operator.OperatorInput; -import au.csiro.pathling.fhirpath.operator.PathTraversalInput; import au.csiro.pathling.fhirpath.operator.PathTraversalOperator; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.collection.StringCollection; import java.util.Collections; +import java.util.List; import javax.annotation.Nonnull; /** - * A function that returns the extensions of the current element that match a given url. + * A function that returns the extensions of the current element that match a given URL. * * @author Piotr Szul * @see extension @@ -44,31 +44,33 @@ public class ExtensionFunction implements NamedFunction { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { - final String expression = NamedFunction.expressionFromInput(input, NAME); + public String getName() { + return NAME; + } + @Nonnull + @Override + public Collection invoke(@Nonnull final FunctionInput input) { + final String expression = NamedFunction.expressionFromInput(input, getName(), + input.getInput()); checkUserInput(input.getArguments().size() == 1, "extension function must have one argument: " + expression); - final FhirPath urlArgument = input.getArguments().get(0); - checkUserInput(urlArgument instanceof StringLiteralPath, - "extension function must have argument of type String literal: " + expression); - final NonLiteralPath inputPath = input.getInput(); - final NonLiteralPath extensionPath = new PathTraversalOperator() - .invoke(new PathTraversalInput(input.getContext(), inputPath, - ExtensionSupport.EXTENSION_ELEMENT_NAME())); + final Collection inputPath = input.getInput(); + final Collection urlArgument = input.getArguments().get(0).apply(inputPath); + checkUserInput(urlArgument instanceof StringCollection, + "extension function must have argument of type String: " + expression); + final Collection extensionPath = checkPresent( + inputPath.traverse(ExtensionSupport.EXTENSION_ELEMENT_NAME())); - // Now we need to create a correct argument context for the `where` call. - final ParserContext argumentContext = input.getContext(); - final FhirPath extensionUrlPath = new PathTraversalOperator() - .invoke(new PathTraversalInput(argumentContext, extensionPath.toThisPath(), "url")); - final FhirPath extensionUrCondition = new ComparisonOperator(ComparisonOperation.EQUALS) - .invoke(new OperatorInput(argumentContext, extensionUrlPath, urlArgument)); + final Collection extensionUrlPath = checkPresent(extensionPath.traverse("url")); + final Collection extensionUrCondition = new ComparisonOperator(ComparisonOperation.EQUALS) + .invoke(new FunctionInput(input.getContext(), extensionUrlPath, List.of(i -> urlArgument))); // Override the expression in the function input. return new WhereFunction() .invoke(new NamedFunctionInput(input.getContext(), extensionPath, - Collections.singletonList(extensionUrCondition), expression)); + Collections.singletonList(extensionUrCondition))); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java index e99fdc56e0..b8ff361612 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java @@ -19,11 +19,10 @@ import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.first; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Nesting; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.FunctionInput; import au.csiro.pathling.fhirpath.NonLiteralPath; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -37,26 +36,24 @@ * @author Piotr Szul * @see first */ -public class FirstFunction extends AggregateFunction implements NamedFunction { +public class FirstFunction implements NamedFunction { private static final String NAME = "first"; - protected FirstFunction() { + @Nonnull + @Override + public String getName() { + return NAME; } @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { - checkNoArguments("first", input); - - final Nesting nesting = input.getContext().getNesting(); - checkUserInput(nesting.isOrderable(), - "The input path to the first function is not orderable: " + input.getInput() - .getExpression()); + public Collection invoke(@Nonnull final FunctionInput input) { + checkNoArguments(getName(), input); final NonLiteralPath inputPath = input.getInput(); final Dataset dataset = inputPath.getOrderedDataset(nesting); - final String expression = expressionFromInput(input, NAME); + final String expression = expressionFromInput(input, NAME, input.getInput()); final Column aggregateColumn = first(inputPath.getValueColumn(), true); return buildAggregateResult(dataset, input.getContext(), inputPath, aggregateColumn, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java index be628e1a8c..3c751acc3d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java @@ -6,9 +6,9 @@ import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.element.ReferencePath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.ReferencePath; import javax.annotation.Nonnull; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; @@ -18,15 +18,15 @@ public class GetIdFunction implements NamedFunction { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + public Collection invoke(@Nonnull final NamedFunctionInput input) { checkNoArguments(NAME, input); checkUserInput(input.getInput() instanceof ReferencePath, "Input to getId must be a Reference"); final ReferencePath referencePath = (ReferencePath) input.getInput(); final DatasetWithColumn datasetWithColumn = createColumn(referencePath.getDataset(), referencePath.getReferenceIdColumn()); - final String expression = expressionFromInput(input, NAME); + final String expression = expressionFromInput(input, NAME, input.getInput()); - return ElementPath.build(expression, datasetWithColumn.getDataset(), + return PrimitivePath.build(expression, datasetWithColumn.getDataset(), referencePath.getIdColumn(), datasetWithColumn.getColumn(), referencePath.getOrderingColumn(), referencePath.isSingular(), referencePath.getCurrentResource(), referencePath.getThisColumn(), FHIRDefinedType.STRING); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java index b5e45361dc..0e74270114 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java @@ -23,9 +23,9 @@ import static org.apache.spark.sql.functions.when; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.element.BooleanPath; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -42,23 +42,23 @@ public class IifFunction implements NamedFunction { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + public Collection invoke(@Nonnull final NamedFunctionInput input) { final NonLiteralPath inputPath = input.getInput(); checkUserInput(input.getArguments().size() == 3, "3 arguments must be supplied to iif function"); - final FhirPath condition = input.getArguments().get(0); - checkUserInput(condition instanceof BooleanPath, + final Collection condition = input.getArguments().get(0); + checkUserInput(condition instanceof BooleanCollection, "Condition argument to iif must be Boolean: " + condition.getExpression()); checkUserInput(condition.isSingular(), "Condition argument to iif must be singular: " + condition.getExpression()); - final BooleanPath conditionBoolean = (BooleanPath) condition; + final BooleanCollection conditionBoolean = (BooleanCollection) condition; checkUserInput(conditionBoolean.getThisColumn().isPresent(), "Condition argument to iif function must be navigable from collection item (use $this): " + conditionBoolean.getExpression()); // Join the three datasets together and create a value column. - final FhirPath ifTrue = input.getArguments().get(1); - final FhirPath otherwise = input.getArguments().get(2); + final Collection ifTrue = input.getArguments().get(1); + final Collection otherwise = input.getArguments().get(2); final Column valueColumn = when(conditionBoolean.getValueColumn().equalTo(true), ifTrue.getValueColumn()) .otherwise(otherwise.getValueColumn()); @@ -66,7 +66,7 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { // Build a new ElementPath based on the type of the literal `ifTrue` and `otherwise` arguments, // and populate it with the dataset and calculated value column. - final String expression = expressionFromInput(input, NAME); + final String expression = expressionFromInput(input, NAME, input.getInput()); return ifTrue.combineWith(otherwise, datasetWithColumn.getDataset(), expression, inputPath.getIdColumn(), datasetWithColumn.getColumn(), inputPath.isSingular(), inputPath.getThisColumn()); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java index 84827f1b8e..b28c2bfb06 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java @@ -17,129 +17,45 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.fhirpath.function.BooleansTestFunction.BooleansTestType.ALL_FALSE; -import static au.csiro.pathling.fhirpath.function.BooleansTestFunction.BooleansTestType.ALL_TRUE; -import static au.csiro.pathling.fhirpath.function.BooleansTestFunction.BooleansTestType.ANY_FALSE; -import static au.csiro.pathling.fhirpath.function.BooleansTestFunction.BooleansTestType.ANY_TRUE; +import static au.csiro.pathling.utilities.Preconditions.check; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.function.terminology.DesignationFunction; -import au.csiro.pathling.fhirpath.function.terminology.DisplayFunction; -import au.csiro.pathling.fhirpath.function.terminology.MemberOfFunction; -import au.csiro.pathling.fhirpath.function.terminology.PropertyFunction; -import au.csiro.pathling.fhirpath.function.terminology.SubsumesFunction; -import au.csiro.pathling.fhirpath.function.terminology.TranslateFunction; -import com.google.common.collect.ImmutableMap; -import java.util.Map; -import java.util.stream.Collectors; +import au.csiro.pathling.fhirpath.FunctionInput; +import au.csiro.pathling.fhirpath.collection.Collection; import javax.annotation.Nonnull; /** - * Represents a named function in FHIRPath. + * Represents a function in FHIRPath. * * @author John Grimes */ -public interface NamedFunction { +public interface NamedFunction { /** - * Mapping of function names to the instances of those functions. - */ - Map NAME_TO_INSTANCE = new ImmutableMap.Builder() - .put("count", new CountFunction()) - .put("resolve", new ResolveFunction()) - .put("ofType", new OfTypeFunction()) - .put("reverseResolve", new ReverseResolveFunction()) - .put("memberOf", new MemberOfFunction()) - .put("where", new WhereFunction()) - .put("subsumes", new SubsumesFunction()) - .put("subsumedBy", new SubsumesFunction(true)) - .put("empty", new EmptyFunction()) - .put("first", new FirstFunction()) - .put("not", new NotFunction()) - .put("iif", new IifFunction()) - .put("translate", new TranslateFunction()) - .put("sum", new SumFunction()) - .put("anyTrue", new BooleansTestFunction(ANY_TRUE)) - .put("anyFalse", new BooleansTestFunction(ANY_FALSE)) - .put("allTrue", new BooleansTestFunction(ALL_TRUE)) - .put("allFalse", new BooleansTestFunction(ALL_FALSE)) - .put("extension", new ExtensionFunction()) - .put("until", new UntilFunction()) - .put("exists", new ExistsFunction()) - .put("display", new DisplayFunction()) - .put("property", new PropertyFunction()) - .put("designation", new DesignationFunction()) - .put("toString", new ToStringFunction()) - .put("getId", new GetIdFunction()) - .build(); - - /** - * The FHIRPath expression for the $this keyword, used to access the current item in the - * collection in functions such as {@code where}. - * - * @see Functions - */ - String THIS = "$this"; - - /** - * Invokes this function with the specified inputs. - * - * @param input A NamedFunctionInput object - * @return A FhirPath object representing the resulting expression + * @return the name of this function */ @Nonnull - FhirPath invoke(@Nonnull NamedFunctionInput input); + String getName(); /** - * Retrieves an instance of the function with the specified name in the specified context. + * Invokes this function with the specified inputs. * - * @param name The name of the function - * @return An instance of a NamedFunction + * @param input a {@link FunctionInput} object + * @return a {@link Collection} object representing the resulting expression */ @Nonnull - static NamedFunction getInstance(@Nonnull final String name) { - final NamedFunction function = NAME_TO_INSTANCE.get(name); - checkUserInput(function != null, "Unsupported function: " + name); - return function; - } + O invoke(@Nonnull FunctionInput input); /** - * Check that no arguments have been passed within the supplied {@link NamedFunctionInput}. + * Check that no arguments have been passed within the supplied {@link FunctionInput}. * * @param functionName The name of the function, used for error reporting purposes - * @param input The {@link NamedFunctionInput} to check for arguments + * @param input The {@link FunctionInput} to check for arguments */ static void checkNoArguments(@Nonnull final String functionName, - @Nonnull final NamedFunctionInput input) { + @Nonnull final FunctionInput input) { checkUserInput(input.getArguments().isEmpty(), "Arguments can not be passed to " + functionName + " function"); } - /** - * @param input A {@link NamedFunctionInput} - * @param functionName The name of the function - * @return A FHIRPath expression for use in the output of the function - */ - @Nonnull - static String expressionFromInput(@Nonnull final NamedFunctionInput input, - @Nonnull final String functionName) { - - return input.getOverrideExpression().orElseGet(() -> { - - final String inputExpression = input.getInput().getExpression(); - final String argumentsExpression = input.getArguments().stream() - .map(FhirPath::getExpression) - .collect(Collectors.joining(", ")); - final String functionExpression = functionName + "(" + argumentsExpression + ")"; - - // If the input expression is the same as the input context, the child will be the start of - // the expression. This is to account for where we omit the expression that represents the - // input expression, e.g. "gender" instead of "Patient.gender". - final String inputContextExpression = input.getContext().getInputContext().getExpression(); - return inputExpression.equals(inputContextExpression) - ? functionExpression - : inputExpression + "." + functionExpression; - }); - } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunctionInput.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunctionInput.java deleted file mode 100644 index 9a2ffaf31d..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunctionInput.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2023 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.function; - -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FunctionInput; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import java.util.List; -import java.util.Optional; -import javax.annotation.Nonnull; -import lombok.Getter; - -/** - * Represents the inputs to a named function in FHIRPath. - * - * @author John Grimes - */ -@Getter -public class NamedFunctionInput extends FunctionInput { - - /** - * An expression representing the input to the function, i.e. the expression on the left-hand side - * of the dot preceding the function invocation. - */ - @Nonnull - private final FhirPath input; - - /** - * A list of expressions representing the arguments to the function, i.e. the expressions inside - * the parentheses following the function invocation, separated by commas. - */ - @Nonnull - private final List arguments; - - /** - * Override expression to use instead of the default one. - */ - @Nonnull - private final Optional overrideExpression; - - /** - * @param context The {@link ParserContext} that the function should be executed within - * @param input The {@link NonLiteralPath} representing the expression on the left-hand side of - * the function invocation - * @param arguments A list of {@link FhirPath} objects representing the arguments passed to the - * function within the parentheses - * @param overrideExpression Override expression to use instead of the default one. - */ - public NamedFunctionInput(@Nonnull final ParserContext context, - @Nonnull final FhirPath input, @Nonnull final List arguments, - @Nonnull final String overrideExpression) { - super(context); - this.input = input; - this.arguments = arguments; - this.overrideExpression = Optional.of(overrideExpression); - } - - /** - * @param context The {@link ParserContext} that the function should be executed within - * @param input The {@link NonLiteralPath} representing the expression on the left-hand side of - * the function invocation - * @param arguments A list of {@link FhirPath} objects representing the arguments passed to the - * function within the parentheses - */ - public NamedFunctionInput(@Nonnull final ParserContext context, - @Nonnull final FhirPath input, @Nonnull final List arguments) { - super(context); - this.input = input; - this.arguments = arguments; - this.overrideExpression = Optional.empty(); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NotFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NotFunction.java index 84b13e4dbd..96338916e0 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NotFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NotFunction.java @@ -22,10 +22,10 @@ import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.not; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.element.BooleanPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -44,17 +44,17 @@ public class NotFunction implements NamedFunction { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + public Collection invoke(@Nonnull final NamedFunctionInput input) { checkNoArguments(NAME, input); final NonLiteralPath inputPath = input.getInput(); - checkUserInput(inputPath instanceof BooleanPath, + checkUserInput(inputPath instanceof BooleanCollection, "Input to not function must be Boolean: " + inputPath.getExpression()); - final String expression = expressionFromInput(input, NAME); + final String expression = expressionFromInput(input, NAME, input.getInput()); // The not function is just a thin wrapper over the Spark not function. final Column valueColumn = not(inputPath.getValueColumn()); - return ElementPath + return PrimitivePath .build(expression, inputPath.getDataset(), inputPath.getIdColumn(), valueColumn, inputPath.getOrderingColumn(), inputPath.isSingular(), Optional.empty(), inputPath.getThisColumn(), FHIRDefinedType.BOOLEAN); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/OfTypeFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/OfTypeFunction.java index 97069c223e..ba1c835570 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/OfTypeFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/OfTypeFunction.java @@ -22,13 +22,11 @@ import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.TypeSpecifier; -import au.csiro.pathling.fhirpath.UntypedResourcePath; -import au.csiro.pathling.fhirpath.element.ChoiceElementPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.FunctionInput; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.MixedCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.definition.ElementDefinition; import java.util.Optional; import javax.annotation.Nonnull; @@ -46,62 +44,43 @@ public class OfTypeFunction implements NamedFunction { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { - final String expression = NamedFunction.expressionFromInput(input, NAME); - final NonLiteralPath inputPath = input.getInput(); - final boolean untypedResource = inputPath instanceof UntypedResourcePath; - final boolean choiceElement = inputPath instanceof ChoiceElementPath; - checkUserInput(untypedResource || choiceElement, - "Input to ofType function must be a polymorphic resource type or choice element path: " - + inputPath.getExpression()); - checkUserInput(input.getArguments().size() == 1, - "ofType function must have one argument: " + expression); - - return untypedResource - ? resolveUntypedResource(input, expression) - : resolveChoiceElement(input, expression); + public String getName() { + return NAME; } @Nonnull - private static FhirPath resolveUntypedResource(final @Nonnull NamedFunctionInput input, - final String expression) { - final UntypedResourcePath inputPath = (UntypedResourcePath) input.getInput(); - final FhirPath argumentPath = input.getArguments().get(0); - - // If the input is a polymorphic resource reference, check that the argument is a resource - // type. - checkUserInput(argumentPath instanceof ResourcePath, - "Argument to ofType function must be a resource type: " + argumentPath.getExpression()); - final ResourcePath resourcePath = (ResourcePath) argumentPath; - - return ResolveFunction.resolveMonomorphicReference(inputPath, - input.getContext().getDataSource(), input.getContext().getFhirContext(), - resourcePath.getResourceType(), expression, input.getContext()); - } + @Override + public Collection invoke(@Nonnull final FunctionInput input) { + final Collection inputCollection = input.getInput(); + final boolean choiceElement = inputCollection instanceof MixedCollection; + checkUserInput(choiceElement, + "Input to ofType function must be a mixed collection"); + checkUserInput(input.getArguments().size() == 1, + "ofType function must have one argument"); - @Nonnull - private FhirPath resolveChoiceElement(@Nonnull final NamedFunctionInput input, - @Nonnull final String expression) { - final ChoiceElementPath inputPath = (ChoiceElementPath) input.getInput(); - final FhirPath argumentPath = input.getArguments().get(0); + final MixedCollection mixedCollection = (MixedCollection) inputCollection; + final Collection argument = input.getArguments().get(0).apply(inputCollection); // If the input is a choice element, check that the argument is a type specifier. - checkUserInput(argumentPath instanceof TypeSpecifier, - "Argument to ofType function must be a type specifier: " + argumentPath.getExpression()); - final TypeSpecifier typeSpecifier = (TypeSpecifier) argumentPath; - final String type = typeSpecifier.getExpression(); - final Optional maybeDefinition = inputPath.resolveChoice(type); + checkUserInput(argument.getType().isPresent() && FhirPathType.TYPE_SPECIFIER.equals( + argument.getType().get()), + "Argument to ofType function must be a type specifier"); + final Optional maybeDefinition = mixedCollection.resolveChoiceDefinition( + type); checkUserInput(maybeDefinition.isPresent(), "Choice element does not have a child element with name " + type + ": " - + inputPath.getExpression()); + + mixedCollection.getExpression()); final ElementDefinition definition = maybeDefinition.get(); - final Column valueColumn = checkPresent(inputPath.resolveChoiceColumn(type)); - final DatasetWithColumn datasetWithColumn = createColumn(inputPath.getDataset(), + final Column valueColumn = checkPresent( + mixedCollection.resolveChoice(type, mixedCollection.getExpression())); + final DatasetWithColumn datasetWithColumn = createColumn(mixedCollection.getDataset(), valueColumn); - return ElementPath.build(expression, datasetWithColumn.getDataset(), inputPath.getIdColumn(), - datasetWithColumn.getColumn(), inputPath.getOrderingColumn(), inputPath.isSingular(), - inputPath.getCurrentResource(), inputPath.getThisColumn(), definition); + return PrimitivePath.build(expression, datasetWithColumn.getDataset(), + mixedCollection.getIdColumn(), + datasetWithColumn.getColumn(), mixedCollection.getOrderingColumn(), + mixedCollection.isSingular(), + mixedCollection.getCurrentResource(), mixedCollection.getThisColumn(), definition); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java index 778c0efcee..4cbd33a593 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java @@ -17,7 +17,6 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.QueryHelpers.join; import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import static au.csiro.pathling.utilities.Preconditions.check; @@ -25,12 +24,12 @@ import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import au.csiro.pathling.QueryHelpers.JoinType; -import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.ReferenceNestingKey; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.UntypedResourcePath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.ReferencePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.collection.UntypedResourcePath; import au.csiro.pathling.fhirpath.definition.BasicElementDefinition; -import au.csiro.pathling.fhirpath.element.ReferencePath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; import ca.uhn.fhir.context.FhirContext; @@ -53,12 +52,12 @@ public class ResolveFunction implements NamedFunction { private static final String NAME = "resolve"; - protected ResolveFunction() { + public ResolveFunction() { } @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + public Collection invoke(@Nonnull final NamedFunctionInput input) { checkUserInput(input.getInput() instanceof ReferencePath, "Input to " + NAME + " function must be a Reference: " + input.getInput().getExpression()); checkNoArguments(NAME, input); @@ -70,12 +69,12 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { Set referenceTypes = inputPath.getResourceTypes(); // If the type is Resource, all resource types need to be looked at. if (referenceTypes.contains(ResourceType.RESOURCE)) { - referenceTypes = ResourcePath.supportedResourceTypes(); + referenceTypes = ResourceCollection.supportedResourceTypes(); } check(referenceTypes.size() > 0); final boolean isPolymorphic = referenceTypes.size() > 1; - final String expression = expressionFromInput(input, NAME); + final String expression = expressionFromInput(input, NAME, input.getInput()); if (isPolymorphic) { final ReferencePath referencePath = (ReferencePath) input.getInput(); @@ -89,32 +88,34 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { } @Nonnull - public static FhirPath resolveMonomorphicReference(@Nonnull final ReferencePath referencePath, + public static Collection resolveMonomorphicReference(@Nonnull final ReferencePath referencePath, @Nonnull final DataSource dataSource, @Nonnull final FhirContext fhirContext, @Nonnull final ResourceType resourceType, @Nonnull final String expression, @Nonnull final ParserContext context) { // If this is a monomorphic reference, we just need to retrieve the appropriate table and // create a dataset with the full resources. - final ResourcePath resourcePath = ResourcePath.build(fhirContext, dataSource, resourceType, + final ResourceCollection resourceCollection = ResourceCollection.build(fhirContext, dataSource, + resourceType, expression, referencePath.isSingular()); final BasicElementDefinition referenceDefinition = checkPresent(referencePath.getDefinition()); final ReferenceNestingKey referenceNestingKey = new ReferenceNestingKey(referenceDefinition, - resourcePath.getDefinition()); + resourceCollection.getDefinition()); return context.getNesting() .updateOrRetrieve(referenceNestingKey, expression, referencePath.getDataset(), referencePath.isSingular(), referencePath.getThisColumn(), key -> { // Join the resource dataset to the reference dataset. - final Column joinCondition = referencePath.getResourceEquality(resourcePath); + final Column joinCondition = referencePath.getResourceEquality(resourceCollection); final Dataset dataset = join(referencePath.getDataset(), - resourcePath.getDataset(), + resourceCollection.getDataset(), joinCondition, JoinType.LEFT_OUTER); final Column inputId = referencePath.getIdColumn(); - final ResourcePath result = resourcePath.copy(expression, dataset, inputId, - resourcePath.getValueColumn(), resourcePath.getOrderingColumn(), + final ResourceCollection result = resourceCollection.copy(expression, dataset, + inputId, + resourceCollection.getValueColumn(), resourceCollection.getOrderingColumn(), referencePath.isSingular(), referencePath.getThisColumn()); - result.setCurrentResource(resourcePath); + result.setCurrentResource(resourceCollection); return result; }); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java index c3babf3204..aac9f85611 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java @@ -22,12 +22,12 @@ import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import au.csiro.pathling.QueryHelpers.JoinType; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.ReferenceNestingKey; -import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.definition.BasicElementDefinition; -import au.csiro.pathling.fhirpath.element.ReferencePath; +import au.csiro.pathling.fhirpath.collection.ReferencePath; import java.util.Set; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -49,14 +49,14 @@ public class ReverseResolveFunction implements NamedFunction { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { - checkUserInput(input.getInput() instanceof ResourcePath, + public Collection invoke(@Nonnull final NamedFunctionInput input) { + checkUserInput(input.getInput() instanceof ResourceCollection, "Input to " + NAME + " function must be a resource: " + input.getInput().getExpression()); - final ResourcePath inputPath = (ResourcePath) input.getInput(); - final String expression = NamedFunction.expressionFromInput(input, NAME); + final ResourceCollection inputPath = (ResourceCollection) input.getInput(); + final String expression = NamedFunction.expressionFromInput(input, NAME, input.getInput()); checkUserInput(input.getArguments().size() == 1, "reverseResolve function accepts a single argument: " + expression); - final FhirPath argument = input.getArguments().get(0); + final Collection argument = input.getArguments().get(0); checkUserInput(argument instanceof ReferencePath, "Argument to reverseResolve function must be a Reference: " + argument.getExpression()); @@ -66,7 +66,7 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { checkUserInput(nonLiteralArgument.getCurrentResource().isPresent(), "Argument to reverseResolve must be an element that is navigable from a " + "target resource type: " + expression); - final ResourcePath currentResource = nonLiteralArgument.getCurrentResource().get(); + final ResourceCollection currentResource = nonLiteralArgument.getCurrentResource().get(); final ReferencePath referencePath = (ReferencePath) argument; final BasicElementDefinition referenceDefinition = checkPresent(referencePath.getDefinition()); @@ -89,7 +89,7 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Dataset dataset = join(inputPath.getDataset(), referencePath.getDataset(), joinCondition, JoinType.LEFT_OUTER); - final ResourcePath result = currentResource.copy(expression, dataset, + final ResourceCollection result = currentResource.copy(expression, dataset, inputPath.getIdColumn(), currentResource.getValueColumn(), currentResource.getOrderingColumn(), false, inputPath.getThisColumn()); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java index fb1e82a63c..fa5aa5bbc8 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java @@ -20,9 +20,8 @@ import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static org.apache.spark.sql.functions.sum; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.NonLiteralPath; import au.csiro.pathling.fhirpath.Numeric; import javax.annotation.Nonnull; @@ -40,19 +39,19 @@ public class SumFunction extends AggregateFunction implements NamedFunction { private static final String NAME = "sum"; - protected SumFunction() { + public SumFunction() { } @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + public Collection invoke(@Nonnull final NamedFunctionInput input) { checkNoArguments("sum", input); checkUserInput(input.getInput() instanceof Numeric, "Input to sum function must be numeric: " + input.getInput().getExpression()); final NonLiteralPath inputPath = input.getInput(); final Dataset dataset = inputPath.getDataset(); - final String expression = expressionFromInput(input, NAME); + final String expression = expressionFromInput(input, NAME, input.getInput()); final Column aggregateColumn = sum(inputPath.getValueColumn()); return buildAggregateResult(dataset, input.getContext(), inputPath, aggregateColumn, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ToStringFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ToStringFunction.java index fed8a00b84..fcf1cb901e 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ToStringFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ToStringFunction.java @@ -4,7 +4,7 @@ import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.StringCoercible; import javax.annotation.Nonnull; @@ -19,10 +19,10 @@ public class ToStringFunction implements NamedFunction { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + public Collection invoke(@Nonnull final NamedFunctionInput input) { // Check that the function has no arguments. checkNoArguments(NAME, input); - final FhirPath inputPath = input.getInput(); + final Collection inputPath = input.getInput(); // Check that the input is coercible to a String. checkUserInput(inputPath instanceof StringCoercible, @@ -30,10 +30,10 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final StringCoercible stringCoercible = (StringCoercible) inputPath; // Create an expression for the new path. - final String expression = expressionFromInput(input, NAME); + final String expression = expressionFromInput(input, NAME, input.getInput()); // Coerce the input to a String. - return stringCoercible.asStringPath(expression); + return stringCoercible.asStringPath(expression, stringCoercible.getExpression()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/UntilFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/UntilFunction.java index 32d76be0ee..8a322ad050 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/UntilFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/UntilFunction.java @@ -17,20 +17,19 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.QueryHelpers.join; import static au.csiro.pathling.fhirpath.NonLiteralPath.findThisColumn; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.callUDF; import au.csiro.pathling.QueryHelpers.JoinType; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.element.DatePath; -import au.csiro.pathling.fhirpath.element.DateTimePath; -import au.csiro.pathling.fhirpath.element.ElementPath; import au.csiro.pathling.fhirpath.literal.DateLiteralPath; import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; +import au.csiro.pathling.fhirpath.collection.DateCollection; +import au.csiro.pathling.fhirpath.collection.DateTimeCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.sql.misc.TemporalDifferenceFunction; import java.util.Optional; import javax.annotation.Nonnull; @@ -52,17 +51,19 @@ public class UntilFunction implements NamedFunction { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + public Collection invoke(@Nonnull final NamedFunctionInput input) { checkUserInput(input.getArguments().size() == 2, "until function must have two arguments"); final NonLiteralPath fromArgument = input.getInput(); - final FhirPath toArgument = input.getArguments().get(0); - final FhirPath calendarDurationArgument = input.getArguments().get(1); + final Collection toArgument = input.getArguments().get(0); + final Collection calendarDurationArgument = input.getArguments().get(1); - checkUserInput(fromArgument instanceof DateTimePath || fromArgument instanceof DatePath, + checkUserInput( + fromArgument instanceof DateTimeCollection || fromArgument instanceof DateCollection, "until function must be invoked on a DateTime or Date"); - checkUserInput(toArgument instanceof DateTimePath || toArgument instanceof DateTimeLiteralPath - || toArgument instanceof DatePath || toArgument instanceof DateLiteralPath, + checkUserInput( + toArgument instanceof DateTimeCollection || toArgument instanceof DateTimeLiteralPath + || toArgument instanceof DateCollection || toArgument instanceof DateLiteralPath, "until function must have a DateTime or Date as the first argument"); checkUserInput(fromArgument.isSingular(), @@ -82,10 +83,10 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Column valueColumn = callUDF(TemporalDifferenceFunction.FUNCTION_NAME, fromArgument.getValueColumn(), toArgument.getValueColumn(), calendarDurationArgument.getValueColumn()); - final String expression = NamedFunction.expressionFromInput(input, NAME); + final String expression = NamedFunction.expressionFromInput(input, NAME, input.getInput()); final Optional thisColumn = findThisColumn(fromArgument, toArgument); - return ElementPath.build(expression, dataset, fromArgument.getIdColumn(), valueColumn, + return PrimitivePath.build(expression, dataset, fromArgument.getIdColumn(), valueColumn, fromArgument.getOrderingColumn(), true, fromArgument.getCurrentResource(), thisColumn, FHIRDefinedType.INTEGER); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java index b80b5b838d..7a748637dd 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java @@ -17,24 +17,18 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.QueryHelpers.createColumn; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import static au.csiro.pathling.utilities.Preconditions.checkPresent; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static org.apache.spark.sql.functions.lit; -import static org.apache.spark.sql.functions.row_number; -import static org.apache.spark.sql.functions.when; +import static org.apache.spark.sql.functions.filter; -import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.element.BooleanPath; +import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.FunctionInput; +import au.csiro.pathling.fhirpath.collection.Collection; +import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.expressions.Window; -import org.apache.spark.sql.expressions.WindowSpec; +import org.apache.spark.sql.SparkSession; /** * Describes a function which can scope down the previous invocation within a FHIRPath expression, @@ -44,51 +38,36 @@ * @author John Grimes * @see where */ -public class WhereFunction implements NamedFunction { +public class WhereFunction implements NamedFunction { private static final String NAME = "where"; @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { - checkUserInput(input.getArguments().size() == 1, - "where function accepts one argument"); - final NonLiteralPath inputPath = input.getInput(); - checkUserInput(input.getArguments().get(0) instanceof NonLiteralPath, - "Argument to where function cannot be a literal: " + input.getArguments().get(0) - .getExpression()); - final NonLiteralPath argumentPath = (NonLiteralPath) input.getArguments().get(0); - - checkUserInput(argumentPath instanceof BooleanPath && argumentPath.isSingular(), - "Argument to where function must be a singular Boolean: " + argumentPath.getExpression()); - - checkUserInput(argumentPath.getThisColumn().isPresent(), - "Argument to where function must be navigable from collection item (use $this): " - + argumentPath.getExpression()); - final Column argumentValue = argumentPath.getValueColumn(); - - // The result is the input value if it is equal to true, or null otherwise (signifying the - // absence of a value). - final Column idColumn = argumentPath.getIdColumn(); - final Column thisValue = checkPresent(argumentPath.getThisColumn()); - final Column valueColumn = when(argumentValue.equalTo(true), thisValue).otherwise(lit(null)); - final DatasetWithColumn datasetWithColumn = createColumn(argumentPath.getDataset(), - valueColumn); + public String getName() { + return NAME; + } - // We use a windowing function to remove all null values except one. A single null value against - // a resource signifies the absence of a value for a particular element. - final WindowSpec window = Window.partitionBy(idColumn).orderBy(valueColumn.asc_nulls_last()); - final DatasetWithColumn datasetWithRowNumber = createColumn(datasetWithColumn.getDataset(), - row_number().over(window)); - final Dataset dataset = datasetWithRowNumber.getDataset() - .filter(valueColumn.isNotNull().or(datasetWithRowNumber.getColumn().equalTo(1))); + @Nonnull + @Override + public Collection invoke(@Nonnull final FunctionInput input) { + checkUserInput(input.getArguments().size() == 1, + getName() + " function accepts one argument"); + final Collection previous = input.getInput(); + final FhirPath argument = input.getArguments().get(0); - // Clear the nesting present within the context. - input.getContext().getNesting().clear(); + final Column column = filter(previous.getColumn(), element -> { + final Collection result = argument.apply( + new Collection(element, previous.getType(), previous.getFhirType(), + Optional.empty())); + final SparkSession spark = input.getContext().getSparkSession(); + final FhirPathType type = checkPresent(result.getType()); + checkUserInput(type.equals(FhirPathType.BOOLEAN) && result.isSingular(spark), + "Argument to " + getName() + " function must be a singular Boolean"); + return result.getColumn(); + }); - final String expression = expressionFromInput(input, NAME); - return inputPath.copy(expression, dataset, idColumn, datasetWithColumn.getColumn(), - inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getThisColumn()); + return new Collection(column, previous.getType(), previous.getFhirType(), Optional.empty()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/FunctionRegistry.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/FunctionRegistry.java new file mode 100644 index 0000000000..fed3b12ca9 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/FunctionRegistry.java @@ -0,0 +1,31 @@ +package au.csiro.pathling.fhirpath.function.registry; + +import au.csiro.pathling.fhirpath.function.NamedFunction; +import javax.annotation.Nonnull; + +/** + * A registry of FHIRPath functions. + * + * @author John Grimes + */ +public interface FunctionRegistry { + + /** + * Retrieves an instance of the function with the specified name in the specified context. + * + * @param name The name of the function + * @return An instance of a NamedFunction + */ + @Nonnull + NamedFunction getInstance(@Nonnull final String name) throws NoSuchFunctionException; + + class NoSuchFunctionException extends Exception { + + private static final long serialVersionUID = 1L; + + public NoSuchFunctionException(final String message) { + super(message); + } + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/InMemoryFunctionRegistry.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/InMemoryFunctionRegistry.java new file mode 100644 index 0000000000..7cc2c02223 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/InMemoryFunctionRegistry.java @@ -0,0 +1,32 @@ +package au.csiro.pathling.fhirpath.function.registry; + +import java.util.Map; +import javax.annotation.Nonnull; + +/** + * An implementation of {@link FunctionRegistry} that stores function instances in an in-memory map + * structure. + * + * @param The type of function to be stored + * @author John Grimes + */ +public class InMemoryFunctionRegistry implements FunctionRegistry { + + @Nonnull + private final Map functions; + + public InMemoryFunctionRegistry(@Nonnull final Map functions) { + this.functions = functions; + } + + @Nonnull + @Override + public T getInstance(@Nonnull final String name) throws NoSuchFunctionException { + final T function = functions.get(name); + if (function == null) { + throw new NoSuchFunctionException("Unsupported function: " + name); + } + return function; + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java new file mode 100644 index 0000000000..066d7df59d --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java @@ -0,0 +1,72 @@ +package au.csiro.pathling.fhirpath.function.registry; + +import static au.csiro.pathling.fhirpath.function.BooleansTestFunction.BooleansTestType.ALL_FALSE; +import static au.csiro.pathling.fhirpath.function.BooleansTestFunction.BooleansTestType.ALL_TRUE; +import static au.csiro.pathling.fhirpath.function.BooleansTestFunction.BooleansTestType.ANY_FALSE; +import static au.csiro.pathling.fhirpath.function.BooleansTestFunction.BooleansTestType.ANY_TRUE; + +import au.csiro.pathling.fhirpath.function.BooleansTestFunction; +import au.csiro.pathling.fhirpath.function.CountFunction; +import au.csiro.pathling.fhirpath.function.EmptyFunction; +import au.csiro.pathling.fhirpath.function.ExistsFunction; +import au.csiro.pathling.fhirpath.function.ExtensionFunction; +import au.csiro.pathling.fhirpath.function.NamedFunction; +import au.csiro.pathling.fhirpath.function.FirstFunction; +import au.csiro.pathling.fhirpath.function.GetIdFunction; +import au.csiro.pathling.fhirpath.function.IifFunction; +import au.csiro.pathling.fhirpath.function.NotFunction; +import au.csiro.pathling.fhirpath.function.OfTypeFunction; +import au.csiro.pathling.fhirpath.function.ResolveFunction; +import au.csiro.pathling.fhirpath.function.ReverseResolveFunction; +import au.csiro.pathling.fhirpath.function.SumFunction; +import au.csiro.pathling.fhirpath.function.ToStringFunction; +import au.csiro.pathling.fhirpath.function.UntilFunction; +import au.csiro.pathling.fhirpath.function.WhereFunction; +import au.csiro.pathling.fhirpath.function.terminology.DesignationFunction; +import au.csiro.pathling.fhirpath.function.terminology.DisplayFunction; +import au.csiro.pathling.fhirpath.function.terminology.MemberOfFunction; +import au.csiro.pathling.fhirpath.function.terminology.PropertyFunction; +import au.csiro.pathling.fhirpath.function.terminology.SubsumesFunction; +import au.csiro.pathling.fhirpath.function.terminology.TranslateFunction; +import com.google.common.collect.ImmutableMap; + +/** + * A static registry of FHIRPath function implementations, for use in environments where dependency + * injection is not available. + * + * @author John Grimes + */ +public class StaticFunctionRegistry extends InMemoryFunctionRegistry { + + public StaticFunctionRegistry() { + super(new ImmutableMap.Builder() + .put("count", new CountFunction()) + .put("resolve", new ResolveFunction()) + .put("ofType", new OfTypeFunction()) + .put("reverseResolve", new ReverseResolveFunction()) + .put("memberOf", new MemberOfFunction()) + .put("where", new WhereFunction()) + .put("subsumes", new SubsumesFunction()) + .put("subsumedBy", new SubsumesFunction(true)) + .put("empty", new EmptyFunction()) + .put("first", new FirstFunction()) + .put("not", new NotFunction()) + .put("iif", new IifFunction()) + .put("translate", new TranslateFunction()) + .put("sum", new SumFunction()) + .put("anyTrue", new BooleansTestFunction(ANY_TRUE)) + .put("anyFalse", new BooleansTestFunction(ANY_FALSE)) + .put("allTrue", new BooleansTestFunction(ALL_TRUE)) + .put("allFalse", new BooleansTestFunction(ALL_FALSE)) + .put("extension", new ExtensionFunction()) + .put("until", new UntilFunction()) + .put("exists", new ExistsFunction()) + .put("display", new DisplayFunction()) + .put("property", new PropertyFunction()) + .put("designation", new DesignationFunction()) + .put("toString", new ToStringFunction()) + .put("getId", new GetIdFunction()) + .build()); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunction.java index 0b8f91139b..ce7129a392 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunction.java @@ -18,15 +18,13 @@ package au.csiro.pathling.fhirpath.function.terminology; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.sql.Terminology.designation; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.explode_outer; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.function.Arguments; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; @@ -53,11 +51,11 @@ public class DesignationFunction implements NamedFunction { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + public Collection invoke(@Nonnull final NamedFunctionInput input) { validateInput(input); - final ElementPath inputPath = (ElementPath) input.getInput(); - final String expression = expressionFromInput(input, NAME); + final PrimitivePath inputPath = (PrimitivePath) input.getInput(); + final String expression = expressionFromInput(input, NAME, input.getInput()); final Arguments arguments = Arguments.of(input); @Nullable final Coding use = arguments.getOptionalValue(0, Coding.class).orElse(null); @@ -71,7 +69,7 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { // The result is an array of designations per each input element, which we now need to explode // in the same way as for path traversal, creating unique element ids. final Column explodedDesignations = explode_outer(designations); - return ElementPath.build(expression, dataset, inputPath.getIdColumn(), explodedDesignations, + return PrimitivePath.build(expression, dataset, inputPath.getIdColumn(), explodedDesignations, Optional.empty(), inputPath.isSingular(), inputPath.getCurrentResource(), inputPath.getThisColumn(), FHIRDefinedType.STRING); } @@ -82,12 +80,12 @@ private void validateInput(@Nonnull final NamedFunctionInput input) { .isPresent(), "Attempt to call terminology function " + NAME + " when terminology service has not been configured"); - final FhirPath inputPath = input.getInput(); - checkUserInput(inputPath instanceof ElementPath - && (((ElementPath) inputPath).getFhirType().equals(FHIRDefinedType.CODING)), + final Collection inputPath = input.getInput(); + checkUserInput(inputPath instanceof PrimitivePath + && (((PrimitivePath) inputPath).getFhirType().equals(FHIRDefinedType.CODING)), "Input to " + NAME + " function must be Coding but is: " + inputPath.getExpression()); - final List arguments = input.getArguments(); + final List arguments = input.getArguments(); checkUserInput(arguments.size() <= 2, NAME + " function accepts two optional arguments"); checkUserInput(arguments.isEmpty() || arguments.get(0) instanceof CodingLiteralPath, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunction.java index 46091f439e..8dc93f2ac3 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunction.java @@ -21,11 +21,10 @@ import static au.csiro.pathling.sql.Terminology.display; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.function.Arguments; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import java.util.Optional; @@ -48,11 +47,11 @@ public class DisplayFunction implements NamedFunction { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + public Collection invoke(@Nonnull final NamedFunctionInput input) { validateInput(input); - final ElementPath inputPath = (ElementPath) input.getInput(); - final String expression = expressionFromInput(input, NAME); + final PrimitivePath inputPath = (PrimitivePath) input.getInput(); + final String expression = expressionFromInput(input, NAME, input.getInput()); final Arguments arguments = Arguments.of(input); final Optional acceptLanguage = arguments.getOptionalValue(0, StringType.class); @@ -60,7 +59,7 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Dataset dataset = inputPath.getDataset(); final Column resultColumn = display(inputPath.getValueColumn(), acceptLanguage.map(StringType::getValue).orElse(null)); - return ElementPath.build(expression, dataset, inputPath.getIdColumn(), resultColumn, + return PrimitivePath.build(expression, dataset, inputPath.getIdColumn(), resultColumn, inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getCurrentResource(), inputPath.getThisColumn(), FHIRDefinedType.STRING); } @@ -79,9 +78,9 @@ private void validateInput(@Nonnull final NamedFunctionInput input) { .isPresent(), "Attempt to call terminology function " + NAME + " when terminology service has not been configured"); - final FhirPath inputPath = input.getInput(); - checkUserInput(inputPath instanceof ElementPath - && (((ElementPath) inputPath).getFhirType().equals(FHIRDefinedType.CODING)), + final Collection inputPath = input.getInput(); + checkUserInput(inputPath instanceof PrimitivePath + && (((PrimitivePath) inputPath).getFhirType().equals(FHIRDefinedType.CODING)), "Input to display function must be Coding but is: " + inputPath.getExpression()); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunction.java index 8fdf410ed0..ee092a72ee 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunction.java @@ -22,10 +22,9 @@ import static au.csiro.pathling.sql.Terminology.member_of; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import javax.annotation.Nonnull; @@ -54,17 +53,17 @@ public MemberOfFunction() { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + public Collection invoke(@Nonnull final NamedFunctionInput input) { validateInput(input); - final ElementPath inputPath = (ElementPath) input.getInput(); + final PrimitivePath inputPath = (PrimitivePath) input.getInput(); final StringLiteralPath argument = (StringLiteralPath) input.getArguments().get(0); final String valueSetUrl = argument.getValue().asStringValue(); final Column resultColumn = member_of(getCodingColumn(inputPath), valueSetUrl); // Construct a new result expression. - final String expression = expressionFromInput(input, NAME); + final String expression = expressionFromInput(input, NAME, input.getInput()); - return ElementPath + return PrimitivePath .build(expression, inputPath.getDataset(), inputPath.getIdColumn(), resultColumn, inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getCurrentResource(), inputPath.getThisColumn(), FHIRDefinedType.BOOLEAN); @@ -76,14 +75,14 @@ private void validateInput(@Nonnull final NamedFunctionInput input) { .isPresent(), "Attempt to call terminology function " + NAME + " when terminology service has not been configured"); - final FhirPath inputPath = input.getInput(); - checkUserInput(inputPath instanceof ElementPath - && (((ElementPath) inputPath).getFhirType().equals(FHIRDefinedType.CODING) - || ((ElementPath) inputPath).getFhirType().equals(FHIRDefinedType.CODEABLECONCEPT)), + final Collection inputPath = input.getInput(); + checkUserInput(inputPath instanceof PrimitivePath + && (((PrimitivePath) inputPath).getFhirType().equals(FHIRDefinedType.CODING) + || ((PrimitivePath) inputPath).getFhirType().equals(FHIRDefinedType.CODEABLECONCEPT)), "Input to memberOf function is of unsupported type: " + inputPath.getExpression()); checkUserInput(input.getArguments().size() == 1, "memberOf function accepts one argument of type String"); - final FhirPath argument = input.getArguments().get(0); + final Collection argument = input.getArguments().get(0); checkUserInput(argument instanceof StringLiteralPath, "memberOf function accepts one argument of type String literal"); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunction.java index 42912471f2..7b4335e680 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunction.java @@ -19,15 +19,13 @@ import static au.csiro.pathling.QueryHelpers.explodeArray; import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.sql.Terminology.property_of; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static au.csiro.pathling.utilities.Preconditions.wrapInUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.function.Arguments; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.sql.udf.PropertyUdf; @@ -53,11 +51,11 @@ public class PropertyFunction implements NamedFunction { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + public Collection invoke(@Nonnull final NamedFunctionInput input) { validateInput(input); - final ElementPath inputPath = (ElementPath) input.getInput(); - final String expression = expressionFromInput(input, NAME); + final PrimitivePath inputPath = (PrimitivePath) input.getInput(); + final String expression = expressionFromInput(input, NAME, input.getInput()); final Arguments arguments = Arguments.of(input); final String propertyCode = arguments.getValue(0, StringType.class).asStringValue(); @@ -85,7 +83,7 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { valueAndOrderingColumns.getLeft(), Optional.of(valueAndOrderingColumns.getRight()), inputPath.isSingular(), inputPath.getThisColumn()); } else { - return ElementPath.build(expression, resultDataset, inputPath.getIdColumn(), + return PrimitivePath.build(expression, resultDataset, inputPath.getIdColumn(), valueAndOrderingColumns.getLeft(), Optional.of(valueAndOrderingColumns.getRight()), inputPath.isSingular(), inputPath.getCurrentResource(), inputPath.getThisColumn(), propertyType); @@ -98,12 +96,12 @@ private void validateInput(@Nonnull final NamedFunctionInput input) { .isPresent(), "Attempt to call terminology function " + NAME + " when terminology service has not been configured"); - final FhirPath inputPath = input.getInput(); - checkUserInput(inputPath instanceof ElementPath - && (((ElementPath) inputPath).getFhirType().equals(FHIRDefinedType.CODING)), + final Collection inputPath = input.getInput(); + checkUserInput(inputPath instanceof PrimitivePath + && (((PrimitivePath) inputPath).getFhirType().equals(FHIRDefinedType.CODING)), "Input to property function must be Coding but is: " + inputPath.getExpression()); - final List arguments = input.getArguments(); + final List arguments = input.getArguments(); checkUserInput(arguments.size() >= 1 && arguments.size() <= 3, NAME + " function accepts one required and one optional arguments"); checkUserInput(arguments.get(0) instanceof StringLiteralPath, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunction.java index 0d9324f962..eef2720ec1 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunction.java @@ -29,11 +29,10 @@ import static org.apache.spark.sql.functions.explode_outer; import static org.apache.spark.sql.functions.when; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.parser.ParserContext; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; @@ -94,7 +93,7 @@ public SubsumesFunction(final boolean inverted) { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + public Collection invoke(@Nonnull final NamedFunctionInput input) { validateInput(input); final NonLiteralPath inputPath = input.getInput(); @@ -107,8 +106,8 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { : subsumes(leftCodings, rightCodings); // Construct a new result expression. - final String expression = expressionFromInput(input, functionName); - return ElementPath.build(expression, idAndCodingSet, inputPath.getIdColumn(), resultColumn, + final String expression = expressionFromInput(input, functionName, input.getInput()); + return PrimitivePath.build(expression, idAndCodingSet, inputPath.getIdColumn(), resultColumn, inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getCurrentResource(), inputPath.getThisColumn(), FHIRDefinedType.BOOLEAN); } @@ -117,15 +116,15 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { * Creates a dataset that preserves previous columns and adds three new ones: resource ID, input * codings and argument codings. * - * @see #toInputDataset(FhirPath) - * @see #toArgDataset(FhirPath) + * @see #toInputDataset(Collection) + * @see #toArgDataset(Collection) */ @Nonnull - private Dataset createJoinedDataset(@Nonnull final FhirPath inputFhirPath, - @Nonnull final FhirPath argFhirPath) { + private Dataset createJoinedDataset(@Nonnull final Collection inputResult, + @Nonnull final Collection argResult) { - final Dataset inputCodingSet = toInputDataset(inputFhirPath); - final Dataset argCodingSet = toArgDataset(argFhirPath); + final Dataset inputCodingSet = toInputDataset(inputResult); + final Dataset argCodingSet = toArgDataset(argResult); return inputCodingSet.join(argCodingSet, col(COL_ID).equalTo(col(COL_ARG_ID)), "left_outer"); } @@ -137,18 +136,18 @@ private Dataset createJoinedDataset(@Nonnull final FhirPath inputFhirPath, *

* Null Codings and CodeableConcepts are represented as null. * - * @param fhirPath the {@link FhirPath} object to convert + * @param result the {@link Collection} object to convert * @return the resulting Dataset */ @Nonnull - private Dataset toInputDataset(@Nonnull final FhirPath fhirPath) { - final Column valueColumn = fhirPath.getValueColumn(); + private Dataset toInputDataset(@Nonnull final Collection result) { + final Column valueColumn = result.getValueColumn(); - assert (isCodingOrCodeableConcept(fhirPath)); + assert (isCodingOrCodeableConcept(result)); - final Dataset expressionDataset = fhirPath.getDataset() - .withColumn(COL_ID, fhirPath.getIdColumn()); - final Column codingArrayCol = (isCodeableConcept(fhirPath)) + final Dataset expressionDataset = result.getDataset() + .withColumn(COL_ID, result.getIdColumn()); + final Column codingArrayCol = (isCodeableConcept(result)) ? valueColumn.getField(FIELD_CODING) : array(valueColumn); @@ -157,7 +156,7 @@ private Dataset toInputDataset(@Nonnull final FhirPath fhirPath) { } /** - * Converts the argument {@link FhirPath} to a Dataset with the schema: STRING id, ARRAY(struct + * Converts the argument {@link Collection} to a Dataset with the schema: STRING id, ARRAY(struct * CODING) codingSet. *

* All codings are collected in a single `array` per resource. @@ -165,22 +164,22 @@ private Dataset toInputDataset(@Nonnull final FhirPath fhirPath) { * Null Codings and CodeableConcepts are ignored. In the case where the resource does not have any * non-null elements, an empty array will be created. * - * @param fhirPath to convert + * @param result to convert * @return input dataset */ @Nonnull - private Dataset toArgDataset(@Nonnull final FhirPath fhirPath) { - final Column valueColumn = fhirPath.getValueColumn(); + private Dataset toArgDataset(@Nonnull final Collection result) { + final Column valueColumn = result.getValueColumn(); - assert (isCodingOrCodeableConcept(fhirPath)); + assert (isCodingOrCodeableConcept(result)); - final Dataset expressionDataset = fhirPath.getDataset(); - final Column codingCol = (isCodeableConcept(fhirPath)) + final Dataset expressionDataset = result.getDataset(); + final Column codingCol = (isCodeableConcept(result)) ? explode_outer(valueColumn.getField(FIELD_CODING)) : valueColumn; final Dataset systemAndCodeDataset = expressionDataset.select( - fhirPath.getIdColumn().alias(COL_ARG_ID), codingCol.alias(COL_CODING)); + result.getIdColumn().alias(COL_ARG_ID), codingCol.alias(COL_CODING)); return systemAndCodeDataset.groupBy(systemAndCodeDataset.col(COL_ARG_ID)) .agg(collect_set(systemAndCodeDataset.col(COL_CODING)).alias(COL_ARG_CODINGS)); @@ -200,7 +199,7 @@ private void validateInput(@Nonnull final NamedFunctionInput input) { validateExpressionType(input.getArguments().get(0), "argument"); } - private void validateExpressionType(@Nonnull final FhirPath inputPath, + private void validateExpressionType(@Nonnull final Collection inputPath, @Nonnull final String pathRole) { checkUserInput(isCodingOrCodeableConcept(inputPath), functionName + " function accepts " + pathRole + " of type Coding or CodeableConcept"); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java index 44f6061979..9c6eb4a50a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java @@ -25,13 +25,12 @@ import static au.csiro.pathling.sql.TerminologySupport.parseCsvEquivalences; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.TerminologyUtils; -import au.csiro.pathling.fhirpath.element.ElementPath; import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.function.Arguments; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; @@ -73,10 +72,10 @@ public class TranslateFunction implements NamedFunction { @Nonnull @Override - public FhirPath invoke(@Nonnull final NamedFunctionInput input) { + public Collection invoke(@Nonnull final NamedFunctionInput input) { validateInput(input); - final ElementPath inputPath = (ElementPath) input.getInput(); + final PrimitivePath inputPath = (PrimitivePath) input.getInput(); final Column idColumn = inputPath.getIdColumn(); final boolean isCodeableConcept = isCodeableConcept(inputPath); @@ -106,9 +105,10 @@ public FhirPath invoke(@Nonnull final NamedFunctionInput input) { final Dataset resultDataset = explodeArray(dataset, translatedCodings, valueAndOrderingColumns); - final String expression = expressionFromInput(input, NAME); + final String expression = expressionFromInput(input, NAME, input.getInput()); - return ElementPath.build(expression, resultDataset, idColumn, valueAndOrderingColumns.getLeft(), + return PrimitivePath.build(expression, resultDataset, idColumn, + valueAndOrderingColumns.getLeft(), Optional.of(valueAndOrderingColumns.getRight()), false, inputPath.getCurrentResource(), inputPath.getThisColumn(), resultDefinition); } @@ -119,11 +119,11 @@ private void validateInput(@Nonnull final NamedFunctionInput input) { "Attempt to call terminology function " + NAME + " when terminology service has not been configured"); - final FhirPath inputPath = input.getInput(); + final Collection inputPath = input.getInput(); checkUserInput(TerminologyUtils.isCodingOrCodeableConcept(inputPath), String.format("Input to %s function is of unsupported type: %s", NAME, inputPath.getExpression())); - final List arguments = input.getArguments(); + final List arguments = input.getArguments(); checkUserInput(arguments.size() >= 1 && arguments.size() <= 3, NAME + " function accepts one required and two optional arguments"); checkUserInput(arguments.get(0) instanceof StringLiteralPath, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/BooleanLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/BooleanLiteralPath.java deleted file mode 100644 index 660d4e1709..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/BooleanLiteralPath.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2023 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.literal; - -import static org.apache.spark.sql.functions.lit; - -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.element.BooleanPath; -import java.util.Optional; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.BooleanType; - -/** - * Represents a FHIRPath boolean literal. - * - * @author John Grimes - */ -public class BooleanLiteralPath extends LiteralPath implements - FhirValue, Comparable { - - @SuppressWarnings("WeakerAccess") - protected BooleanLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final BooleanType literalValue) { - super(dataset, idColumn, literalValue); - } - - /** - * Returns a new instance, parsed from a FHIRPath literal. - * - * @param fhirPath The FHIRPath representation of the literal - * @param context An input context that can be used to build a {@link Dataset} to represent the - * literal - * @return A new instance of {@link LiteralPath} - */ - @Nonnull - public static BooleanLiteralPath fromString(@Nonnull final String fhirPath, - @Nonnull final FhirPath context) { - final boolean value = fhirPath.equals("true"); - return new BooleanLiteralPath(context.getDataset(), context.getIdColumn(), - new BooleanType(value)); - } - - @Nonnull - @Override - public String getExpression() { - return getValue().asStringValue(); - } - - @Nonnull - @Override - public Column buildValueColumn() { - return lit(getValue().booleanValue()); - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return Comparable.buildComparison(this, operation); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return BooleanPath.getComparableTypes().contains(type); - } - - @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { - return BooleanPath.valueFromRow(row, columnNumber); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof BooleanPath; - } - - @Nonnull - @Override - public FhirPath withDataset(@Nonnull final Dataset dataset) { - return new BooleanLiteralPath(dataset, getIdColumn(), getValue()); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java deleted file mode 100644 index d40cca85ad..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPath.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2023 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.literal; - -import static org.apache.spark.sql.functions.lit; -import static org.apache.spark.sql.functions.struct; - -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.fhirpath.comparison.CodingSqlComparator; -import au.csiro.pathling.fhirpath.element.CodingPath; -import java.util.Optional; -import java.util.function.Function; -import javax.annotation.Nonnull; -import lombok.Getter; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Coding; - -/** - * Represents a FHIRPath Coding literal. - * - * @author John Grimes - */ -@Getter -public class CodingLiteralPath extends LiteralPath implements FhirValue, - Comparable, StringCoercible { - - protected CodingLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final Coding literalValue) { - super(dataset, idColumn, literalValue); - } - - /** - * Returns a new instance, parsed from a FHIRPath literal. - * - * @param fhirPath The FHIRPath representation of the literal - * @param context An input context that can be used to build a {@link Dataset} to represent the - * literal - * @return A new instance of {@link LiteralPath} - * @throws IllegalArgumentException if the literal is malformed - */ - @Nonnull - public static CodingLiteralPath fromString(@Nonnull final String fhirPath, - @Nonnull final FhirPath context) throws IllegalArgumentException { - return new CodingLiteralPath(context.getDataset(), context.getIdColumn(), - CodingLiteral.fromString(fhirPath)); - } - - @Nonnull - @Override - public String getExpression() { - return CodingLiteral.toLiteral(getValue()); - } - - @Nonnull - @Override - public Column buildValueColumn() { - final Coding value = getValue(); - return struct( - lit(value.getId()).as("id"), - lit(value.getSystem()).as("system"), - lit(value.getVersion()).as("version"), - lit(value.getCode()).as("code"), - lit(value.getDisplay()).as("display"), - lit(value.hasUserSelected() - ? value.getUserSelected() - : null).as("userSelected"), - lit(null).as("_fid")); - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return CodingSqlComparator.buildComparison(this, operation); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return CodingPath.getComparableTypes().contains(type); - } - - @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { - return CodingPath.valueFromRow(row, columnNumber); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof CodingPath; - } - - @Nonnull - @Override - public FhirPath asStringPath(@Nonnull final String expression) { - final String fhirPath = StringLiteral.stringToFhirPath(CodingLiteral.toLiteral(getValue())); - return StringLiteralPath.fromString(fhirPath, this); - } - - @Nonnull - @Override - public FhirPath withDataset(@Nonnull final Dataset dataset) { - return new CodingLiteralPath(dataset, idColumn, getValue()); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java deleted file mode 100644 index 80e2b89c0f..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateLiteralPath.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2023 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.literal; - -import static au.csiro.pathling.fhirpath.Temporal.buildDateArithmeticOperation; -import static org.apache.spark.sql.functions.lit; - -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.Numeric.MathOperation; -import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.fhirpath.Temporal; -import au.csiro.pathling.fhirpath.comparison.DateTimeSqlComparator; -import au.csiro.pathling.fhirpath.element.DatePath; -import au.csiro.pathling.fhirpath.element.DateTimePath; -import au.csiro.pathling.sql.dates.date.DateAddDurationFunction; -import au.csiro.pathling.sql.dates.date.DateSubtractDurationFunction; -import java.text.ParseException; -import java.util.Optional; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.DateType; - -/** - * Represents a FHIRPath date literal. - * - * @author John Grimes - */ -public class DateLiteralPath extends LiteralPath implements FhirValue, - Comparable, Temporal, StringCoercible { - - protected DateLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final DateType literalValue) { - super(dataset, idColumn, literalValue); - } - - /** - * Returns a new instance, parsed from a FHIRPath literal. - * - * @param fhirPath The FHIRPath representation of the literal - * @param context An input context that can be used to build a {@link Dataset} to represent the - * literal - * @return A new instance of {@link LiteralPath} - * @throws ParseException if the literal is malformed - */ - public static DateLiteralPath fromString(@Nonnull final String fhirPath, - @Nonnull final FhirPath context) throws ParseException { - final String dateString = fhirPath.replaceFirst("^@", ""); - final DateType dateType = new DateType(dateString); - return new DateLiteralPath(context.getDataset(), context.getIdColumn(), dateType); - } - - @Nonnull - @Override - public String getExpression() { - return "@" + getValue().asStringValue(); - } - - @Nonnull - @Override - public Column buildValueColumn() { - return lit(getValue().asStringValue()); - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return DateTimeSqlComparator.buildComparison(this, operation); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return DateTimePath.getComparableTypes().contains(type); - } - - @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { - return DatePath.valueFromRow(row, columnNumber); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof DatePath; - } - - @Nonnull - @Override - public Function getDateArithmeticOperation( - @Nonnull final MathOperation operation, @Nonnull final Dataset dataset, - @Nonnull final String expression) { - return buildDateArithmeticOperation(this, operation, dataset, expression, - DateAddDurationFunction.FUNCTION_NAME, DateSubtractDurationFunction.FUNCTION_NAME); - } - - @Nonnull - @Override - public FhirPath asStringPath(@Nonnull final String expression) { - final String fhirPath = StringLiteral.stringToFhirPath(getValue().asStringValue()); - return StringLiteralPath.fromString(fhirPath, this); - } - - @Nonnull - @Override - public FhirPath withDataset(@Nonnull final Dataset dataset) { - return new DateLiteralPath(dataset, getIdColumn(), getValue()); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java deleted file mode 100644 index e4f1f7afc1..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DateTimeLiteralPath.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2023 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.literal; - -import static au.csiro.pathling.fhirpath.Temporal.buildDateArithmeticOperation; -import static org.apache.spark.sql.functions.lit; - -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.Numeric.MathOperation; -import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.fhirpath.Temporal; -import au.csiro.pathling.fhirpath.comparison.DateTimeSqlComparator; -import au.csiro.pathling.fhirpath.element.DateTimePath; -import au.csiro.pathling.sql.dates.datetime.DateTimeAddDurationFunction; -import au.csiro.pathling.sql.dates.datetime.DateTimeSubtractDurationFunction; -import java.text.ParseException; -import java.util.Optional; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.BaseDateTimeType; -import org.hl7.fhir.r4.model.DateTimeType; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; - -/** - * Represents a FHIRPath date literal. - * - * @author John Grimes - */ -public class DateTimeLiteralPath extends LiteralPath implements - FhirValue, Comparable, Temporal, StringCoercible { - - protected DateTimeLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final BaseDateTimeType literalValue) { - super(dataset, idColumn, literalValue); - } - - /** - * Returns a new instance, parsed from a FHIRPath literal. - * - * @param fhirPath The FHIRPath representation of the literal - * @param context An input context that can be used to build a {@link Dataset} to represent the - * literal - * @return A new instance of {@link LiteralPath} - * @throws ParseException if the literal is malformed - */ - @Nonnull - public static DateTimeLiteralPath fromString(@Nonnull final String fhirPath, - @Nonnull final FhirPath context) throws ParseException { - final String dateTimeString = fhirPath.replaceFirst("^@", ""); - final DateTimeType dateTimeType = new DateTimeType(dateTimeString); - return new DateTimeLiteralPath(context.getDataset(), context.getIdColumn(), dateTimeType); - } - - @Nonnull - @Override - public String getExpression() { - return "@" + getValue().getValueAsString(); - } - - @Nonnull - @Override - public Column buildValueColumn() { - return lit(getValue().asStringValue()); - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return DateTimeSqlComparator.buildComparison(this, operation); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return DateTimePath.getComparableTypes().contains(type); - } - - @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, - final int columnNumber) { - return DateTimePath.valueFromRow(row, columnNumber, FHIRDefinedType.DATETIME); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof DateTimePath; - } - - @Nonnull - @Override - public Function getDateArithmeticOperation( - @Nonnull final MathOperation operation, @Nonnull final Dataset dataset, - @Nonnull final String expression) { - return buildDateArithmeticOperation(this, operation, dataset, expression, - DateTimeAddDurationFunction.FUNCTION_NAME, DateTimeSubtractDurationFunction.FUNCTION_NAME); - } - - @Nonnull - @Override - public FhirPath asStringPath(@Nonnull final String expression) { - final String fhirPath = StringLiteral.stringToFhirPath(getValue().asStringValue()); - return StringLiteralPath.fromString(fhirPath, this); - } - - @Nonnull - @Override - public FhirPath withDataset(@Nonnull final Dataset dataset) { - return new DateTimeLiteralPath(dataset, getIdColumn(), getValue()); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java deleted file mode 100644 index faa8585677..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/DecimalLiteralPath.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2023 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.literal; - -import static org.apache.spark.sql.functions.lit; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.Numeric; -import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.fhirpath.element.DecimalPath; -import au.csiro.pathling.fhirpath.element.IntegerPath; -import java.math.BigDecimal; -import java.util.Optional; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.DecimalType; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; - -/** - * Represents a FHIRPath decimal literal. - * - * @author John Grimes - */ -public class DecimalLiteralPath extends LiteralPath implements - FhirValue, Comparable, Numeric, StringCoercible { - - protected DecimalLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final DecimalType literalValue) { - super(dataset, idColumn, literalValue); - } - - /** - * Returns a new instance, parsed from a FHIRPath literal. - * - * @param fhirPath The FHIRPath representation of the literal - * @param context An input context that can be used to build a {@link Dataset} to represent the - * literal - * @return A new instance of {@link LiteralPath} - * @throws NumberFormatException if the literal is malformed - */ - public static DecimalLiteralPath fromString(@Nonnull final String fhirPath, - @Nonnull final FhirPath context) throws NumberFormatException { - final BigDecimal value = new BigDecimal(fhirPath); - - if (value.precision() > DecimalPath.getDecimalType().precision()) { - throw new InvalidUserInputError( - "Decimal literal exceeded maximum precision supported (" + DecimalPath.getDecimalType() - .precision() + "): " + fhirPath); - } - if (value.scale() > DecimalPath.getDecimalType().scale()) { - throw new InvalidUserInputError( - "Decimal literal exceeded maximum scale supported (" + DecimalPath.getDecimalType() - .scale() + "): " + fhirPath); - } - - return new DecimalLiteralPath(context.getDataset(), context.getIdColumn(), - new DecimalType(value)); - } - - @Nonnull - @Override - public String getExpression() { - return getValue().getValue().toPlainString(); - } - - @Nonnull - @Override - public Column buildValueColumn() { - return lit(getValue().getValue()); - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return Comparable.buildComparison(this, operation); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return IntegerPath.getComparableTypes().contains(type); - } - - @Nonnull - @Override - public Function getMathOperation(@Nonnull final MathOperation operation, - @Nonnull final String expression, @Nonnull final Dataset dataset) { - return DecimalPath - .buildMathOperation(this, operation, expression, dataset); - } - - @Nonnull - @Override - public Column getNumericValueColumn() { - return getValueColumn(); - } - - @Nonnull - @Override - public Column getNumericContextColumn() { - return getNumericValueColumn(); - } - - @Nonnull - @Override - public FHIRDefinedType getFhirType() { - return FHIRDefinedType.DECIMAL; - } - - @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { - return DecimalPath.valueFromRow(row, columnNumber); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof DecimalPath; - } - - @Override - @Nonnull - public FhirPath asStringPath(@Nonnull final String expression) { - final String fhirPath = StringLiteral.stringToFhirPath(getValue().asStringValue()); - return StringLiteralPath.fromString(fhirPath, this); - } - - @Nonnull - @Override - public FhirPath withDataset(@Nonnull final Dataset dataset) { - return new DecimalLiteralPath(dataset, getIdColumn(), getValue()); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java deleted file mode 100644 index 98c05f74ee..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/IntegerLiteralPath.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2023 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.literal; - -import static org.apache.spark.sql.functions.lit; - -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.Numeric; -import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.fhirpath.element.IntegerPath; -import java.util.Optional; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.types.DataTypes; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.IntegerType; -import org.hl7.fhir.r4.model.PrimitiveType; - -/** - * Represents a FHIRPath integer literal. - * - * @author John Grimes - */ -public class IntegerLiteralPath extends LiteralPath implements - FhirValue, Comparable, Numeric, StringCoercible { - - @SuppressWarnings("WeakerAccess") - protected IntegerLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final PrimitiveType literalValue) { - super(dataset, idColumn, literalValue); - } - - /** - * Returns a new instance, parsed from a FHIRPath literal. - * - * @param fhirPath The FHIRPath representation of the literal - * @param context An input context that can be used to build a {@link Dataset} to represent the - * literal - * @return A new instance of {@link LiteralPath} - * @throws NumberFormatException if the literal is malformed - */ - public static IntegerLiteralPath fromString(@Nonnull final String fhirPath, - @Nonnull final FhirPath context) throws NumberFormatException { - final int value = Integer.parseInt(fhirPath); - return new IntegerLiteralPath(context.getDataset(), context.getIdColumn(), - new IntegerType(value)); - } - - @Nonnull - @Override - public String getExpression() { - return getValue().getValueAsString(); - } - - @Nonnull - @Override - public Column buildValueColumn() { - return lit(getValue().getValue()); - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return Comparable.buildComparison(this, operation); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return IntegerPath.getComparableTypes().contains(type); - } - - @Nonnull - @Override - public Function getMathOperation(@Nonnull final MathOperation operation, - @Nonnull final String expression, @Nonnull final Dataset dataset) { - return IntegerPath - .buildMathOperation(this, operation, expression, dataset); - } - - @Nonnull - @Override - public Column getNumericValueColumn() { - return getValueColumn().cast(DataTypes.LongType); - } - - @Nonnull - @Override - public Column getNumericContextColumn() { - return getNumericValueColumn(); - } - - @Nonnull - @Override - public FHIRDefinedType getFhirType() { - return FHIRDefinedType.INTEGER; - } - - @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, - final int columnNumber) { - return IntegerPath.valueFromRow(row, columnNumber, FHIRDefinedType.INTEGER); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof IntegerPath; - } - - @Override - @Nonnull - public FhirPath asStringPath(@Nonnull final String expression) { - final String fhirPath = StringLiteral.stringToFhirPath(getValue().asStringValue()); - return StringLiteralPath.fromString(fhirPath, this); - } - - @Nonnull - @Override - public FhirPath withDataset(@Nonnull final Dataset dataset) { - return new IntegerLiteralPath(dataset, getIdColumn(), getValue()); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java deleted file mode 100644 index 43dd04eb8e..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/LiteralPath.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright 2023 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.literal; - -import static au.csiro.pathling.QueryHelpers.getUnionableColumns; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Nesting; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.element.ElementPath; -import com.google.common.collect.ImmutableMap; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.Arrays; -import java.util.Map; -import java.util.Optional; -import javax.annotation.Nonnull; -import lombok.Getter; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.Type; - -/** - * Represents any type of literal expression. - * - * @author John Grimes - */ -public abstract class LiteralPath implements FhirPath { - - // See https://hl7.org/fhir/fhirpath.html#types. - private static final Map> FHIR_TYPE_TO_FHIRPATH_TYPE = - new ImmutableMap.Builder>() - .put(FHIRDefinedType.BOOLEAN, BooleanLiteralPath.class) - .put(FHIRDefinedType.STRING, StringLiteralPath.class) - .put(FHIRDefinedType.URI, StringLiteralPath.class) - .put(FHIRDefinedType.URL, StringLiteralPath.class) - .put(FHIRDefinedType.CANONICAL, StringLiteralPath.class) - .put(FHIRDefinedType.CODE, StringLiteralPath.class) - .put(FHIRDefinedType.OID, StringLiteralPath.class) - .put(FHIRDefinedType.ID, StringLiteralPath.class) - .put(FHIRDefinedType.UUID, StringLiteralPath.class) - .put(FHIRDefinedType.MARKDOWN, StringLiteralPath.class) - .put(FHIRDefinedType.BASE64BINARY, StringLiteralPath.class) - .put(FHIRDefinedType.INTEGER, IntegerLiteralPath.class) - .put(FHIRDefinedType.UNSIGNEDINT, IntegerLiteralPath.class) - .put(FHIRDefinedType.POSITIVEINT, IntegerLiteralPath.class) - .put(FHIRDefinedType.DECIMAL, DecimalLiteralPath.class) - .put(FHIRDefinedType.DATE, DateLiteralPath.class) - .put(FHIRDefinedType.DATETIME, DateTimeLiteralPath.class) - .put(FHIRDefinedType.INSTANT, DateTimeLiteralPath.class) - .put(FHIRDefinedType.TIME, TimeLiteralPath.class) - .put(FHIRDefinedType.CODING, CodingLiteralPath.class) - .put(FHIRDefinedType.QUANTITY, QuantityLiteralPath.class) - .build(); - - private static final Map, FHIRDefinedType> FHIRPATH_TYPE_TO_FHIR_TYPE = - new ImmutableMap.Builder, FHIRDefinedType>() - .put(BooleanLiteralPath.class, FHIRDefinedType.BOOLEAN) - .put(StringLiteralPath.class, FHIRDefinedType.STRING) - .put(IntegerLiteralPath.class, FHIRDefinedType.INTEGER) - .put(DecimalLiteralPath.class, FHIRDefinedType.DECIMAL) - .put(DateLiteralPath.class, FHIRDefinedType.DATE) - .put(DateTimeLiteralPath.class, FHIRDefinedType.DATETIME) - .put(TimeLiteralPath.class, FHIRDefinedType.TIME) - .put(CodingLiteralPath.class, FHIRDefinedType.CODING) - .put(QuantityLiteralPath.class, FHIRDefinedType.QUANTITY) - .build(); - - @Getter - @Nonnull - protected Dataset dataset; - - @Getter - @Nonnull - protected Column idColumn; - - @Getter - @Nonnull - protected Column valueColumn; - - /** - * The HAPI object that represents the value of this literal. - */ - @Getter - protected ValueType value; - - @Nonnull - protected final Optional expression; - - private LiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final ValueType value, @Nonnull final Optional expression) { - this.idColumn = idColumn; - this.value = value; - this.dataset = dataset; - this.expression = expression; - this.valueColumn = buildValueColumn(); - } - - protected LiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final ValueType value) { - this(dataset, idColumn, value, Optional.empty()); - } - - protected LiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final ValueType value, @Nonnull final String expression) { - this(dataset, idColumn, value, Optional.of(expression)); - } - - /** - * Builds a FHIRPath literal expression for the provided FHIR object. - * - * @param dataset The context dataset to use in building this expression - * @param idColumn The identity column from the dataset - * @param literalValue A HAPI FHIR object - * @return A String representation of the literal in FHIRPath - */ - @Nonnull - public static String expressionFor(@Nonnull final Dataset dataset, - @Nonnull final Column idColumn, @Nonnull final Type literalValue) { - final Class literalPathClass = FHIR_TYPE_TO_FHIRPATH_TYPE - .get(FHIRDefinedType.fromCode(literalValue.fhirType())); - try { - @SuppressWarnings("unchecked") - final Constructor constructor = (Constructor) Arrays.stream( - literalPathClass.getDeclaredConstructors()) - .filter(c -> c.getParameterCount() == 3) - .filter(c -> c.getParameterTypes()[0] == Dataset.class) - .filter(c -> c.getParameterTypes()[1] == Column.class) - .filter(c -> Type.class.isAssignableFrom(c.getParameterTypes()[2])) - .findFirst() - .orElseThrow(() -> new AssertionError( - "No suitable constructor found for " + literalPathClass)); - final LiteralPath literalPath = constructor.newInstance(dataset, idColumn, literalValue); - return literalPath.getExpression(); - } catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException("Problem building a LiteralPath class", e); - } - } - - @Override - @Nonnull - public abstract String getExpression(); - - @Override - public boolean isSingular() { - return true; - } - - /** - * @return A column representing the value for this literal. - */ - @Nonnull - public abstract Column buildValueColumn(); - - /** - * @param fhirPathClass a subclass of LiteralPath - * @return a {@link FHIRDefinedType} - */ - @Nonnull - private static FHIRDefinedType fhirPathToFhirType( - @Nonnull final Class fhirPathClass) { - return FHIRPATH_TYPE_TO_FHIR_TYPE.get(fhirPathClass); - } - - @Nonnull - @Override - public FhirPath withExpression(@Nonnull final String expression) { - return this; - } - - @Override - @Nonnull - public NonLiteralPath combineWith(@Nonnull final FhirPath target, - @Nonnull final Dataset dataset, @Nonnull final String expression, - @Nonnull final Column idColumn, @Nonnull final Column valueColumn, final boolean singular, - @Nonnull final Optional thisColumn) { - if (target instanceof LiteralPath && getClass().equals(target.getClass())) { - // If the target is another LiteralPath, we can merge it if they have the same FHIR type, as - // decided by our mapping of literal FHIRPath types to FHIR types. - final FHIRDefinedType fhirType = fhirPathToFhirType(getClass()); - return ElementPath.build(expression, dataset, idColumn, valueColumn, Optional.empty(), - singular, Optional.empty(), thisColumn, fhirType); - } else if (target instanceof ElementPath) { - // If the target is an ElementPath, we delegate off to the ElementPath to do the merging. - return target.combineWith(this, dataset, expression, idColumn, valueColumn, singular, - thisColumn); - } - // Anything else is invalid. - throw new InvalidUserInputError( - "Paths cannot be merged into a collection together: " + getExpression() + ", " + target - .getExpression()); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return getClass().equals(target.getClass()) || target instanceof NullLiteralPath; - } - - @Nonnull - @Override - public Dataset getUnionableDataset(@Nonnull final FhirPath target) { - return getDataset().select(getUnionableColumns(this, target).toArray(new Column[]{})); - } - - @Override - public Optional getOrderingColumn() { - return Optional.empty(); - } - - @Nonnull - @Override - public Dataset getOrderedDataset(@Nonnull final Nesting nesting) { - return dataset; - } - - @Nonnull - @Override - public FhirPath unnest() { - return this; - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/NullLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/NullLiteralPath.java deleted file mode 100644 index 482927601c..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/NullLiteralPath.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2023 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.literal; - -import static org.apache.spark.sql.functions.lit; - -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.StringType; - -/** - * Represents the null literal ({@code {}}, an empty collection) in FHIRPath. - * - * @author John Grimes - */ -public class NullLiteralPath extends LiteralPath implements Comparable { - - private static final String EXPRESSION = "{}"; - - protected NullLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn) { - // We put a dummy String value in here as a placeholder so that we can satisfy the nullability - // constraints within LiteralValue. It is never accessed. - super(dataset, idColumn, new StringType(EXPRESSION)); - // We also need to override the value column to be a null literal rather than the placeholder - // value literal. - this.valueColumn = lit(null); - } - - /** - * Get a new instance of this class. - * - * @param context The input context to use to build a {@link Dataset} to represent this element - * @return A shiny new NullLiteralPath - */ - @Nonnull - public static NullLiteralPath build(@Nonnull final FhirPath context) { - return new NullLiteralPath(context.getDataset(), context.getIdColumn()); - } - - @Nonnull - @Override - public String getExpression() { - return EXPRESSION; - } - - @Nonnull - @Override - public Column buildValueColumn() { - return lit(null); - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - // Comparing an empty collection with anything always results in an empty collection. - return (target) -> lit(null); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return true; - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - // A null literal can be combined with any other path. - return true; - } - - @Nonnull - @Override - public FhirPath withDataset(@Nonnull final Dataset dataset) { - return new NullLiteralPath(dataset, idColumn); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/QuantityLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/QuantityLiteralPath.java deleted file mode 100644 index 4ddc4abed5..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/QuantityLiteralPath.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2023 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.literal; - -import au.csiro.pathling.encoders.terminology.ucum.Ucum; -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.CalendarDurationUtils; -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.Numeric; -import au.csiro.pathling.fhirpath.comparison.QuantitySqlComparator; -import au.csiro.pathling.fhirpath.element.QuantityPath; -import au.csiro.pathling.fhirpath.encoding.QuantityEncoding; -import java.math.BigDecimal; -import java.util.Optional; -import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import lombok.Getter; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.fhir.ucum.UcumService; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.Quantity; - -/** - * Represents a FHIRPath Quantity literal. - * - * @author John Grimes - */ -@Getter -public class QuantityLiteralPath extends LiteralPath implements Comparable, Numeric { - - private static final Pattern UCUM_PATTERN = Pattern.compile("([0-9.]+) ('[^']+')"); - - protected QuantityLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final Quantity literalValue) { - super(dataset, idColumn, literalValue); - } - - /** - * Returns a new instance, parsed from a FHIRPath literal. - * - * @param fhirPath the FHIRPath representation of the literal - * @param context an input context that can be used to build a {@link Dataset} to represent the - * literal - * @param ucumService a UCUM service for validating the unit within the literal - * @return A new instance of {@link LiteralPath} - * @throws IllegalArgumentException if the literal is malformed - */ - @Nonnull - public static QuantityLiteralPath fromUcumString(@Nonnull final String fhirPath, - @Nonnull final FhirPath context, @Nonnull final UcumService ucumService) { - final Matcher matcher = UCUM_PATTERN.matcher(fhirPath); - if (!matcher.matches()) { - throw new IllegalArgumentException("UCUM Quantity literal has invalid format: " + fhirPath); - } - final String fullPath = matcher.group(0); - final String value = matcher.group(1); - final String rawUnit = matcher.group(2); - final String unit = StringLiteralPath.fromString(rawUnit, context).getValue() - .getValueAsString(); - - @Nullable final String validationResult = ucumService.validate(unit); - if (validationResult != null) { - throw new InvalidUserInputError( - "Invalid UCUM unit provided within Quantity literal (" + fullPath + "): " - + validationResult); - } - - final BigDecimal decimalValue = getQuantityValue(value, context); - @Nullable final String display = ucumService.getCommonDisplay(unit); - - return buildLiteralPath(decimalValue, unit, Optional.ofNullable(display), context); - } - - /** - * Returns a new instance, parsed from a FHIRPath literal representing a calendar duration. - * - * @param fhirPath the FHIRPath representation of the literal - * @param context an input context that can be used to build a {@link Dataset} to represent the - * literal - * @return A new instance of {@link QuantityLiteralPath} - * @see Time-valued quantities - */ - @Nonnull - public static QuantityLiteralPath fromCalendarDurationString(@Nonnull final String fhirPath, - @Nonnull final FhirPath context) { - - return new QuantityLiteralPath(context.getDataset(), context.getIdColumn(), - CalendarDurationUtils.parseCalendarDuration(fhirPath)); - } - - private static BigDecimal getQuantityValue(final String value, final @Nonnull FhirPath context) { - final BigDecimal decimalValue; - try { - decimalValue = DecimalLiteralPath.fromString(value, context).getValue().getValue(); - } catch (final NumberFormatException e) { - throw new IllegalArgumentException("Quantity literal has invalid value: " + value); - } - return decimalValue; - } - - @Nonnull - private static QuantityLiteralPath buildLiteralPath(@Nonnull final BigDecimal decimalValue, - @Nonnull final String unit, @Nonnull final Optional display, - @Nonnull final FhirPath context) { - final Quantity quantity = new Quantity(); - quantity.setValue(decimalValue); - quantity.setSystem(Ucum.SYSTEM_URI); - quantity.setCode(unit); - display.ifPresent(quantity::setUnit); - - return new QuantityLiteralPath(context.getDataset(), context.getIdColumn(), quantity); - } - - @Nonnull - @Override - public String getExpression() { - return getValue().getValue().toPlainString() + " '" + getValue().getUnit() + "'"; - } - - @Nonnull - @Override - public Column buildValueColumn() { - return QuantityEncoding.encodeLiteral(getValue()); - } - - @Nonnull - @Override - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return QuantitySqlComparator.buildComparison(this, operation); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return QuantityPath.COMPARABLE_TYPES.contains(type); - } - - @Nonnull - @Override - public Column getNumericValueColumn() { - return getValueColumn().getField(QuantityEncoding.CANONICALIZED_VALUE_COLUMN); - } - - @Nonnull - @Override - public Column getNumericContextColumn() { - return getValueColumn(); - } - - @Nonnull - @Override - public FHIRDefinedType getFhirType() { - return FHIRDefinedType.QUANTITY; - } - - @Nonnull - @Override - public Function getMathOperation(@Nonnull final MathOperation operation, - @Nonnull final String expression, @Nonnull final Dataset dataset) { - return QuantityPath.buildMathOperation(this, operation, expression, dataset, - Optional.empty()); - } - - @Nonnull - @Override - public FhirPath withDataset(@Nonnull final Dataset dataset) { - return new QuantityLiteralPath(dataset, getIdColumn(), getValue()); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java deleted file mode 100644 index d3ee51a6be..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/StringLiteralPath.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2023 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.literal; - -import static au.csiro.pathling.fhirpath.literal.StringLiteral.stringToFhirPath; -import static au.csiro.pathling.fhirpath.literal.StringLiteral.unescapeFhirPathString; -import static au.csiro.pathling.utilities.Strings.unSingleQuote; -import static org.apache.spark.sql.functions.lit; - -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.Flat; -import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.fhirpath.element.StringPath; -import java.util.Optional; -import java.util.function.Function; -import javax.annotation.Nonnull; -import lombok.Getter; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.PrimitiveType; -import org.hl7.fhir.r4.model.StringType; - -/** - * Represents a FHIRPath string literal. - * - * @author John Grimes - */ -@SuppressWarnings("rawtypes") -@Getter -public class StringLiteralPath extends LiteralPath implements - FhirValue, Flat, Comparable, StringCoercible { - - protected StringLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final PrimitiveType literalValue) { - super(dataset, idColumn, literalValue); - } - - /** - * Returns a new instance, parsed from a FHIRPath literal. - * - * @param fhirPath The FHIRPath representation of the literal - * @param context An input context that can be used to build a {@link Dataset} to represent the - * literal - * @return A new instance of {@link LiteralPath} - */ - @Nonnull - public static StringLiteralPath fromString(@Nonnull final String fhirPath, - @Nonnull final FhirPath context) { - // Remove the surrounding single quotes and unescape the string according to the rules within - // the FHIRPath specification. - String value = unSingleQuote(fhirPath); - value = unescapeFhirPathString(value); - - return new StringLiteralPath(context.getDataset(), context.getIdColumn(), - new StringType(value)); - } - - @Nonnull - @Override - public String getExpression() { - return stringToFhirPath(getValue().getValueAsString()); - } - - @Nonnull - @Override - public Column buildValueColumn() { - return lit(getValue().getValueAsString()); - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return Comparable.buildComparison(this, operation); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return StringPath.getComparableTypes().contains(type); - } - - @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, - final int columnNumber) { - return StringPath.valueFromRow(row, columnNumber, FHIRDefinedType.STRING); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof StringPath; - } - - @Nonnull - @Override - public FhirPath asStringPath(@Nonnull final String expression) { - return this; - } - - @Nonnull - @Override - public FhirPath withDataset(@Nonnull final Dataset dataset) { - return new StringLiteralPath(dataset, getIdColumn(), getValue()); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java deleted file mode 100644 index f617b79405..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/literal/TimeLiteralPath.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2023 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.literal; - -import static org.apache.spark.sql.functions.lit; - -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirValue; -import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.fhirpath.element.TimePath; -import java.util.Optional; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.TimeType; - -/** - * Represents a FHIRPath time literal. - * - * @author John Grimes - */ -public class TimeLiteralPath extends LiteralPath implements FhirValue, - Comparable, StringCoercible { - - protected TimeLiteralPath(@Nonnull final Dataset dataset, @Nonnull final Column idColumn, - @Nonnull final TimeType literalValue) { - super(dataset, idColumn, literalValue); - } - - /** - * Returns a new instance, parsed from a FHIRPath literal. - * - * @param fhirPath The FHIRPath representation of the literal - * @param context An input context that can be used to build a {@link Dataset} to represent the - * literal - * @return A new instance of {@link LiteralPath} - */ - @Nonnull - public static TimeLiteralPath fromString(@Nonnull final String fhirPath, - @Nonnull final FhirPath context) { - final String timeString = fhirPath.replaceFirst("^@T", ""); - return new TimeLiteralPath(context.getDataset(), context.getIdColumn(), - new TimeType(timeString)); - } - - @Nonnull - @Override - public String getExpression() { - return "@T" + getValue().getValue(); - } - - @Nonnull - @Override - public Column buildValueColumn() { - return lit(getValue().asStringValue()); - } - - @Override - @Nonnull - public Function getComparison(@Nonnull final ComparisonOperation operation) { - return Comparable.buildComparison(this, operation); - } - - @Override - public boolean isComparableTo(@Nonnull final Class type) { - return TimePath.getComparableTypes().contains(type); - } - - @Nonnull - @Override - public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { - return TimePath.valueFromRow(row, columnNumber); - } - - @Override - public boolean canBeCombinedWith(@Nonnull final FhirPath target) { - return super.canBeCombinedWith(target) || target instanceof TimePath; - } - - @Nonnull - @Override - public FhirPath asStringPath(@Nonnull final String expression) { - final String fhirPath = StringLiteral.stringToFhirPath(getValue().asStringValue()); - return StringLiteralPath.fromString(fhirPath, this); - } - - @Nonnull - @Override - public FhirPath withDataset(@Nonnull final Dataset dataset) { - return new TimeLiteralPath(dataset, getIdColumn(), getValue()); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ReferenceExtensionDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperator.java similarity index 52% rename from fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ReferenceExtensionDefinition.java rename to fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperator.java index c3b0dc1131..28ac09b5ec 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ReferenceExtensionDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperator.java @@ -15,29 +15,25 @@ * limitations under the License. */ -package au.csiro.pathling.fhirpath.definition; +package au.csiro.pathling.fhirpath.operator; -import ca.uhn.fhir.context.RuntimeChildAny; -import java.util.Set; +import au.csiro.pathling.fhirpath.collection.Collection; import javax.annotation.Nonnull; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; /** - * Encapsulates the FHIR definitions for a Reference-typed extension. + * Represents a binary operator in FHIRPath. * * @author John Grimes */ -public class ReferenceExtensionDefinition extends BasicElementDefinition { - - protected ReferenceExtensionDefinition(@Nonnull final RuntimeChildAny childDefinition) { - super(childDefinition); - } +public interface BinaryOperator { + /** + * Invokes this operator with the specified inputs. + * + * @param input An {@link BinaryOperatorInput} object + * @return A {@link Collection} object representing the resulting expression + */ @Nonnull - public Set getReferenceTypes() { - // We always treat a reference extension as a reference to any resource, as we don't have enough - // information to constrain it further. - return Set.of(ResourceType.RESOURCE); - } + Collection invoke(@Nonnull BinaryOperatorInput input); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/OperatorInput.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperatorInput.java similarity index 61% rename from fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/OperatorInput.java rename to fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperatorInput.java index 26bf0ed2ad..6076875c04 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/OperatorInput.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperatorInput.java @@ -17,42 +17,35 @@ package au.csiro.pathling.fhirpath.operator; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FunctionInput; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.parser.ParserContext; import javax.annotation.Nonnull; -import lombok.Getter; +import lombok.Value; /** * Represents the inputs to a binary operator in FHIRPath. * * @author John Grimes */ -@Getter -public class OperatorInput extends FunctionInput { +@Value +public class BinaryOperatorInput { /** - * An expression representing the left operand. + * Context and dependencies for use in evaluating the function. */ @Nonnull - private final FhirPath left; + ParserContext context; /** - * An expression representing the right operand. + * An expression representing the left operand. */ @Nonnull - private final FhirPath right; + Collection left; /** - * @param context The {@link ParserContext} that the operator should be executed within - * @param left The {@link FhirPath} representing the left operand - * @param right The {@link FhirPath} representing the right operand + * An expression representing the right operand. */ - public OperatorInput(@Nonnull final ParserContext context, @Nonnull final FhirPath left, - @Nonnull final FhirPath right) { - super(context); - this.left = left; - this.right = right; - } + @Nonnull + Collection right; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperatorType.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperatorType.java new file mode 100644 index 0000000000..5f11303025 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperatorType.java @@ -0,0 +1,80 @@ +package au.csiro.pathling.fhirpath.operator; + +import au.csiro.pathling.fhirpath.Comparable.ComparisonOperation; +import au.csiro.pathling.fhirpath.Numeric.MathOperation; +import au.csiro.pathling.fhirpath.operator.BooleanOperator.BooleanOperatorType; +import au.csiro.pathling.fhirpath.operator.MembershipOperator.MembershipOperatorType; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import java.util.Map; +import javax.annotation.Nonnull; +import lombok.Getter; + +/** + * All supported operators in FHIRPath. + * + * @author John Grimes + */ +@Getter +public enum BinaryOperatorType { + + AND("and", new BooleanOperator(BooleanOperatorType.AND)), + OR("or", new BooleanOperator(BooleanOperatorType.OR)), + XOR("xor", new BooleanOperator(BooleanOperatorType.XOR)), + IMPLIES("implies", new BooleanOperator(BooleanOperatorType.IMPLIES)), + EQUALS("=", new ComparisonOperator(ComparisonOperation.EQUALS)), + NOT_EQUALS("!=", new ComparisonOperator(ComparisonOperation.NOT_EQUALS)), + LESS_THAN_OR_EQUAL_TO("<=", new ComparisonOperator(ComparisonOperation.LESS_THAN_OR_EQUAL_TO)), + LESS_THAN("<", new ComparisonOperator(ComparisonOperation.LESS_THAN)), + GREATER_THAN_OR_EQUAL_TO(">=", + new ComparisonOperator(ComparisonOperation.GREATER_THAN_OR_EQUAL_TO)), + GREATER_THAN(">", new ComparisonOperator(ComparisonOperation.GREATER_THAN)), + IN("in", new MembershipOperator(MembershipOperatorType.IN)), + CONTAINS("contains", new MembershipOperator(MembershipOperatorType.CONTAINS)), + ADDITION("+", new MathOperator(MathOperation.ADDITION)), + SUBTRACTION("-", new MathOperator(MathOperation.SUBTRACTION)), + MULTIPLICATION("*", new MathOperator(MathOperation.MULTIPLICATION)), + DIVISION("/", new MathOperator(MathOperation.DIVISION)), + MODULUS("mod", new MathOperator(MathOperation.MODULUS)), + COMBINE("combine", new CombineOperator()); + + private final String symbol; + + private final BinaryOperator instance; + + /** + * Mapping of operator symbols to instances of those operators. + */ + private static final Map SYMBOL_TO_TYPE; + + static { + final Builder builder = new ImmutableMap.Builder<>(); + for (final BinaryOperatorType type : BinaryOperatorType.values()) { + builder.put(type.getSymbol(), type); + } + SYMBOL_TO_TYPE = builder.build(); + } + + BinaryOperatorType(@Nonnull final String symbol, @Nonnull final BinaryOperator instance) { + this.symbol = symbol; + this.instance = instance; + } + + /** + * Retrieves an instance of the operator with the specified symbol. + * + * @param symbol The symbol of the operator + * @return The {@link BinaryOperatorType} corresponding to the specified symbol + * @throws IllegalArgumentException if no operator with the specified symbol is supported + */ + @Nonnull + public static BinaryOperatorType fromSymbol(@Nonnull final String symbol) + throws IllegalArgumentException { + final BinaryOperatorType operatorType = SYMBOL_TO_TYPE.get(symbol); + if (operatorType == null) { + throw new IllegalArgumentException("Unsupported operator: " + symbol); + } + return operatorType; + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java index 855497983a..ab832d1ed1 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java @@ -17,20 +17,15 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.fhirpath.NonLiteralPath.findThisColumn; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.when; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.BooleanPath; -import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; +import org.apache.spark.sql.SparkSession; /** * Provides the functionality of the family of boolean operators within FHIRPath, i.e. and, or, xor @@ -40,7 +35,7 @@ * @see Boolean * logic */ -public class BooleanOperator implements Operator { +public class BooleanOperator implements BinaryOperator { private final BooleanOperatorType type; @@ -53,21 +48,22 @@ public BooleanOperator(final BooleanOperatorType type) { @Nonnull @Override - public FhirPath invoke(@Nonnull final OperatorInput input) { - final FhirPath left = input.getLeft(); - final FhirPath right = input.getRight(); - - checkUserInput(left instanceof BooleanPath || left instanceof BooleanLiteralPath, - "Left operand to " + type + " operator must be Boolean: " + left.getExpression()); - checkUserInput(left.isSingular(), - "Left operand to " + type + " operator must be singular: " + left.getExpression()); - checkUserInput(right instanceof BooleanPath || right instanceof BooleanLiteralPath, - "Right operand to " + type + " operator must be Boolean: " + right.getExpression()); - checkUserInput(right.isSingular(), - "Right operand to " + type + " operator must be singular: " + right.getExpression()); - - final Column leftValue = left.getValueColumn(); - final Column rightValue = right.getValueColumn(); + public Collection invoke(@Nonnull final BinaryOperatorInput input) { + final Collection left = input.getLeft(); + final Collection right = input.getRight(); + final SparkSession spark = input.getContext().getSparkSession(); + + checkUserInput(left instanceof BooleanCollection, + "Left operand to " + type + " operator must be Boolean"); + checkUserInput(left.isSingular(spark), + "Left operand to " + type + " operator must be singular"); + checkUserInput(right instanceof BooleanCollection, + "Right operand to " + type + " operator must be Boolean"); + checkUserInput(right.isSingular(spark), + "Right operand to " + type + " operator must be singular"); + + final Column leftValue = left.getColumn(); + final Column rightValue = right.getColumn(); // Based on the type of operator, create the correct column expression. final Column valueColumn; @@ -101,13 +97,7 @@ public FhirPath invoke(@Nonnull final OperatorInput input) { throw new AssertionError("Unsupported boolean operator encountered: " + type); } - final String expression = left.getExpression() + " " + type + " " + right.getExpression(); - final Column idColumn = left.getIdColumn(); - final Optional thisColumn = findThisColumn(left, right); - - return ElementPath - .build(expression, right.getDataset(), idColumn, valueColumn, Optional.empty(), true, - Optional.empty(), thisColumn, FHIRDefinedType.BOOLEAN); + return BooleanCollection.build(valueColumn, Optional.empty()); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java index d02fac42fc..595c393bef 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java @@ -24,7 +24,7 @@ import static org.apache.spark.sql.functions.lit; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.NonLiteralPath; import java.util.Optional; import javax.annotation.Nonnull; @@ -36,16 +36,16 @@ * @author John Grimes * @see combine */ -public class CombineOperator implements Operator { +public class CombineOperator implements BinaryOperator { private static final String NAME = "combine"; @Nonnull @Override - public FhirPath invoke(@Nonnull final OperatorInput input) { - final String expression = Operator.buildExpression(input, NAME); - final FhirPath left = input.getLeft(); - final FhirPath right = input.getRight(); + public Collection invoke(@Nonnull final BinaryOperatorInput input) { + final String expression = BinaryOperator.buildExpression(input, NAME); + final Collection left = input.getLeft(); + final Collection right = input.getRight(); // Create an array of the two operands, excluding nulls, then explode it into rows. final Column valueColumn = explode_outer( diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java index 992535eec5..d3591abfb6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java @@ -17,22 +17,17 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.QueryHelpers.createColumn; -import static au.csiro.pathling.fhirpath.NonLiteralPath.findThisColumn; -import static au.csiro.pathling.fhirpath.operator.Operator.buildExpression; -import static au.csiro.pathling.fhirpath.operator.Operator.checkArgumentsAreComparable; +import static au.csiro.pathling.fhirpath.operator.BinaryOperator.buildExpression; +import static au.csiro.pathling.fhirpath.operator.BinaryOperator.checkArgumentsAreComparable; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import au.csiro.pathling.QueryHelpers.DatasetWithColumn; -import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.Comparable.ComparisonOperation; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; +import org.apache.spark.sql.SparkSession; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** @@ -43,7 +38,7 @@ * @see Equality * @see Comparison */ -public class ComparisonOperator implements Operator { +public class ComparisonOperator implements BinaryOperator { @Nonnull private final ComparisonOperation type; @@ -57,25 +52,22 @@ public ComparisonOperator(@Nonnull final ComparisonOperation type) { @Nonnull @Override - public FhirPath invoke(@Nonnull final OperatorInput input) { - final FhirPath left = input.getLeft(); - final FhirPath right = input.getRight(); - checkUserInput(left.isSingular(), "Left operand must be singular: " + left.getExpression()); - checkUserInput(right.isSingular(), + public Collection invoke(@Nonnull final BinaryOperatorInput input) { + final Collection left = input.getLeft(); + final Collection right = input.getRight(); + final SparkSession spark = input.getContext().getSparkSession(); + checkUserInput(left.isSingular(spark), + "Left operand must be singular: " + left.getExpression()); + + checkUserInput(right.isSingular(spark), "Right operand must be singular: " + right.getExpression()); checkArgumentsAreComparable(input, type.toString()); final String expression = buildExpression(input, type.toString()); - final Dataset dataset = right.getDataset(); - final Comparable leftComparable = (Comparable) left; - final Comparable rightComparable = (Comparable) right; - final Column valueColumn = leftComparable.getComparison(type).apply(rightComparable); - final Column idColumn = left.getIdColumn(); - final Optional thisColumn = findThisColumn(left, right); - final DatasetWithColumn datasetWithColumn = createColumn(dataset, valueColumn); + final Column valueColumn = left.getComparison(type).apply(right); - return ElementPath.build(expression, datasetWithColumn.getDataset(), idColumn, + return PrimitivePath.build(expression, datasetWithColumn.getDataset(), idColumn, datasetWithColumn.getColumn(), Optional.empty(), true, Optional.empty(), thisColumn, FHIRDefinedType.BOOLEAN); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/DateArithmeticOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/DateArithmeticOperator.java index 1f91fc47b5..a192751106 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/DateArithmeticOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/DateArithmeticOperator.java @@ -18,18 +18,15 @@ package au.csiro.pathling.fhirpath.operator; import static au.csiro.pathling.QueryHelpers.join; -import static au.csiro.pathling.fhirpath.operator.Operator.buildExpression; +import static au.csiro.pathling.fhirpath.operator.BinaryOperator.buildExpression; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.fhirpath.CalendarDurationUtils; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.Numeric.MathOperation; import au.csiro.pathling.fhirpath.Temporal; import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; import javax.annotation.Nonnull; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; /** * Provides the functionality of the family of math operators within FHIRPath, i.e. +, -, *, / and @@ -38,7 +35,7 @@ * @author John Grimes * @see Math */ -public class DateArithmeticOperator implements Operator { +public class DateArithmeticOperator implements BinaryOperator { @Nonnull private final MathOperation type; @@ -52,9 +49,9 @@ public DateArithmeticOperator(@Nonnull final MathOperation type) { @Nonnull @Override - public FhirPath invoke(@Nonnull final OperatorInput input) { - final FhirPath left = input.getLeft(); - final FhirPath right = input.getRight(); + public Collection invoke(@Nonnull final BinaryOperatorInput input) { + final Collection left = input.getLeft(); + final Collection right = input.getRight(); checkUserInput(left instanceof Temporal, type + " operator does not support left operand: " + left.getExpression()); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MathOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MathOperator.java index 4dcdf57dec..872166576d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MathOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MathOperator.java @@ -18,19 +18,16 @@ package au.csiro.pathling.fhirpath.operator; import static au.csiro.pathling.QueryHelpers.join; -import static au.csiro.pathling.fhirpath.operator.Operator.buildExpression; +import static au.csiro.pathling.fhirpath.operator.BinaryOperator.buildExpression; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.Numeric; import au.csiro.pathling.fhirpath.Numeric.MathOperation; import au.csiro.pathling.fhirpath.Temporal; import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; import javax.annotation.Nonnull; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; /** * Provides the functionality of the family of math operators within FHIRPath, i.e. +, -, *, / and @@ -39,7 +36,7 @@ * @author John Grimes * @see Math */ -public class MathOperator implements Operator { +public class MathOperator implements BinaryOperator { @Nonnull private final MathOperation type; @@ -53,9 +50,9 @@ public MathOperator(@Nonnull final MathOperation type) { @Nonnull @Override - public FhirPath invoke(@Nonnull final OperatorInput input) { - final FhirPath left = input.getLeft(); - final FhirPath right = input.getRight(); + public Collection invoke(@Nonnull final BinaryOperatorInput input) { + final Collection left = input.getLeft(); + final Collection right = input.getRight(); // Check whether this needs to be delegated off to the DateArithmeticOperator. if (left instanceof Temporal && right instanceof QuantityLiteralPath) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java index 0c0009cc8b..704bcaa198 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java @@ -17,7 +17,7 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.fhirpath.operator.Operator.checkArgumentsAreComparable; +import static au.csiro.pathling.fhirpath.operator.BinaryOperator.checkArgumentsAreComparable; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.lit; import static org.apache.spark.sql.functions.max; @@ -25,7 +25,7 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.Comparable.ComparisonOperation; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.function.AggregateFunction; import java.util.Arrays; import javax.annotation.Nonnull; @@ -38,7 +38,7 @@ * @author John Grimes * @see Membership */ -public class MembershipOperator extends AggregateFunction implements Operator { +public class MembershipOperator extends AggregateFunction implements BinaryOperator { private final MembershipOperatorType type; @@ -51,15 +51,15 @@ public MembershipOperator(final MembershipOperatorType type) { @Nonnull @Override - public FhirPath invoke(@Nonnull final OperatorInput input) { - final FhirPath left = input.getLeft(); - final FhirPath right = input.getRight(); - final FhirPath element = type.equals(MembershipOperatorType.IN) - ? left - : right; - final FhirPath collection = type.equals(MembershipOperatorType.IN) - ? right - : left; + public Collection invoke(@Nonnull final BinaryOperatorInput input) { + final Collection left = input.getLeft(); + final Collection right = input.getRight(); + final Collection element = type.equals(MembershipOperatorType.IN) + ? left + : right; + final Collection collection = type.equals(MembershipOperatorType.IN) + ? right + : left; checkUserInput(element.isSingular(), "Element operand used with " + type + " operator is not singular: " + element diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/Operator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/Operator.java deleted file mode 100644 index 72e90b010e..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/Operator.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2023 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.utilities.Preconditions.checkUserInput; - -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.Comparable.ComparisonOperation; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.Numeric.MathOperation; -import au.csiro.pathling.fhirpath.operator.BooleanOperator.BooleanOperatorType; -import au.csiro.pathling.fhirpath.operator.MembershipOperator.MembershipOperatorType; -import com.google.common.collect.ImmutableMap; -import java.util.Map; -import javax.annotation.Nonnull; - -/** - * Represents a binary operator in FHIRPath. - * - * @author John Grimes - */ -public interface Operator { - - /** - * Mapping of operator names to instances of those operators. - */ - Map NAME_TO_INSTANCE = new ImmutableMap.Builder() - .put("and", new BooleanOperator(BooleanOperatorType.AND)) - .put("or", new BooleanOperator(BooleanOperatorType.OR)) - .put("xor", new BooleanOperator(BooleanOperatorType.XOR)) - .put("implies", new BooleanOperator(BooleanOperatorType.IMPLIES)) - .put("=", new ComparisonOperator(ComparisonOperation.EQUALS)) - .put("!=", new ComparisonOperator(ComparisonOperation.NOT_EQUALS)) - .put("<=", new ComparisonOperator(ComparisonOperation.LESS_THAN_OR_EQUAL_TO)) - .put("<", new ComparisonOperator(ComparisonOperation.LESS_THAN)) - .put(">=", new ComparisonOperator(ComparisonOperation.GREATER_THAN_OR_EQUAL_TO)) - .put(">", new ComparisonOperator(ComparisonOperation.GREATER_THAN)) - .put("in", new MembershipOperator(MembershipOperatorType.IN)) - .put("contains", new MembershipOperator(MembershipOperatorType.CONTAINS)) - .put("+", new MathOperator(MathOperation.ADDITION)) - .put("-", new MathOperator(MathOperation.SUBTRACTION)) - .put("*", new MathOperator(MathOperation.MULTIPLICATION)) - .put("/", new MathOperator(MathOperation.DIVISION)) - .put("mod", new MathOperator(MathOperation.MODULUS)) - .put("combine", new CombineOperator()) - .build(); - - /** - * Invokes this operator with the specified inputs. - * - * @param input An {@link OperatorInput} object - * @return A {@link FhirPath} object representing the resulting expression - */ - @Nonnull - FhirPath invoke(@Nonnull OperatorInput input); - - /** - * Retrieves an instance of the specified operator. - * - * @param name The operator string - * @return An instance of an Operator - */ - @Nonnull - static Operator getInstance(@Nonnull final String name) { - final Operator operator = NAME_TO_INSTANCE.get(name); - checkUserInput(operator != null, "Unsupported operator: " + name); - return operator; - } - - /** - * @param input The inputs to the operator - * @param operatorName The FHIRPath representation of the operator - * @return A FHIRPath expression describing the invocation of the operator - */ - @Nonnull - static String buildExpression(@Nonnull final OperatorInput input, - @Nonnull final String operatorName) { - final FhirPath left = input.getLeft(); - final FhirPath right = input.getRight(); - return left.getExpression() + " " + operatorName + " " + right.getExpression(); - } - - /** - * Performs a set of validation checks on inputs that are intended to be used within a comparison - * operation. - * - * @param input The inputs to the operator - * @param operatorName The FHIRPath representation of the operator - */ - static void checkArgumentsAreComparable(@Nonnull final OperatorInput input, - @Nonnull final String operatorName) { - final FhirPath left = input.getLeft(); - final FhirPath right = input.getRight(); - - checkUserInput(left instanceof Comparable, - operatorName + " operator does not support left operand: " + left.getExpression()); - checkUserInput(right instanceof Comparable, - operatorName + " operator does not support right operand: " + left.getExpression()); - - final Comparable leftComparable = (Comparable) left; - final Comparable rightComparable = (Comparable) right; - final String expression = buildExpression(input, operatorName); - checkUserInput(leftComparable.isComparableTo(rightComparable.getClass()), - "Left operand to " + operatorName + " operator is not comparable to right operand: " - + expression); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalInput.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalInput.java index 08fae4a311..5eb75585e5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalInput.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalInput.java @@ -17,41 +17,40 @@ package au.csiro.pathling.fhirpath.operator; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.FunctionInput; import au.csiro.pathling.fhirpath.parser.ParserContext; import javax.annotation.Nonnull; import lombok.Getter; /** - * Represents the inputs to a binary operator in FHIRPath. + * Represents the inputs to the path traversal operator in FHIRPath. * * @author John Grimes */ +@Getter public class PathTraversalInput extends FunctionInput { /** * An expression representing the left operand. */ @Nonnull - @Getter - private final FhirPath left; + private final Collection left; /** * An expression representing the right operand. */ @Nonnull - @Getter private final String right; /** * @param context the {@link ParserContext} that the operator should be executed within - * @param left the {@link FhirPath} representing the left operand + * @param left the {@link Collection} representing the left operand * @param right the FHIRPath expression on the right-hand side of the operator */ - public PathTraversalInput(@Nonnull final ParserContext context, @Nonnull final FhirPath left, + public PathTraversalInput(@Nonnull final ParserContext context, @Nonnull final Collection left, @Nonnull final String right) { - super(context); + super(context, input, arguments); this.left = left; this.right = right; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java deleted file mode 100644 index 43fb9a3108..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperator.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2023 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.QueryHelpers.createColumn; -import static au.csiro.pathling.QueryHelpers.flattenValue; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; - -import au.csiro.pathling.QueryHelpers.DatasetWithColumn; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.definition.ChoiceElementDefinition; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.element.ChoiceElementPath; -import au.csiro.pathling.fhirpath.element.ElementPath; -import java.util.Optional; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; - -/** - * Provides the ability to move from one element to its child element, using the path selection - * notation ".". - * - * @author John Grimes - * @see Path selection - */ -public class PathTraversalOperator { - - /** - * Invokes this operator with the specified inputs. - * - * @param input A {@link PathTraversalInput} object - * @return A {@link FhirPath} object representing the resulting expression - */ - @Nonnull - public NonLiteralPath invoke(@Nonnull final PathTraversalInput input) { - checkUserInput(input.getLeft() instanceof NonLiteralPath, - "Path traversal operator cannot be invoked on a literal value: " + input.getLeft() - .getExpression()); - final NonLiteralPath left = (NonLiteralPath) input.getLeft(); - final String right = input.getRight(); - final String expression = buildExpression(input, left, right); - - final Optional optionalChild = left.getChildElement(right); - checkUserInput(optionalChild.isPresent(), "No such child: " + expression); - final ElementDefinition childDefinition = optionalChild.get(); - - final boolean maxCardinalityOfOne = childDefinition.getMaxCardinality().isPresent() - && childDefinition.getMaxCardinality().get() == 1; - final boolean resultSingular = left.isSingular() && maxCardinalityOfOne; - - final Dataset leftDataset = left.getDataset(); - final Dataset result; - final Column valueColumn; - if (left instanceof ResourcePath) { - result = leftDataset; - valueColumn = ((ResourcePath) left).getElementColumn(right).orElse(left.getIdColumn()); - } else { - final DatasetWithColumn datasetAndColumn = createColumn(leftDataset, - buildTraversalColumn(left, right)); - result = datasetAndColumn.getDataset(); - valueColumn = datasetAndColumn.getColumn(); - } - - if (childDefinition instanceof ChoiceElementDefinition) { - return ChoiceElementPath.build(expression, left, result, valueColumn, Optional.empty(), - resultSingular, (ChoiceElementDefinition) childDefinition); - } else { - return ElementPath.build(expression, result, left.getIdColumn(), valueColumn, - Optional.empty(), resultSingular, left.getCurrentResource(), left.getThisColumn(), - childDefinition); - } - } - - @Nonnull - private static String buildExpression(final @Nonnull PathTraversalInput input, - @Nonnull final NonLiteralPath left, @Nonnull final String right) { - // If the input expression is the same as the input context, the child will be the start of the - // expression. This is to account for where we omit the expression that represents the input - // expression, e.g. "gender" instead of "Patient.gender". - final String inputContextExpression = input.getContext().getInputContext().getExpression(); - return left.getExpression().equals(inputContextExpression) - ? right - : left.getExpression() + "." + right; - } - - @Nonnull - public static Column buildTraversalColumn(@Nonnull final NonLiteralPath left, - @Nonnull final String right) { - final Column flattenedValue = flattenValue(left.getDataset(), left.getValueColumn()); - return flattenedValue.getField(right); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index d488694fbf..4e361aa2bd 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -17,17 +17,17 @@ package au.csiro.pathling.fhirpath.parser; +import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirPathTransformation; -import au.csiro.pathling.fhirpath.TypeSpecifier; +import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.FunctionInput; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.function.NamedFunctionInput; -import au.csiro.pathling.fhirpath.operator.PathTraversalInput; -import au.csiro.pathling.fhirpath.operator.PathTraversalOperator; +import au.csiro.pathling.fhirpath.function.registry.FunctionRegistry.NoSuchFunctionException; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.FunctionInvocationContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.IndexInvocationContext; @@ -40,6 +40,7 @@ import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.apache.spark.sql.functions; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; @@ -49,7 +50,7 @@ * * @author John Grimes */ -class InvocationVisitor extends FhirPathBaseVisitor { +class InvocationVisitor extends FhirPathBaseVisitor> { @Nonnull private final ParserContext context; @@ -70,26 +71,28 @@ class InvocationVisitor extends FhirPathBaseVisitor { * or when an identifier is referred to as a term (e.g. "Encounter" or "type"). * * @param ctx The {@link MemberInvocationContext} - * @return A {@link FhirPathTransformation} expression + * @return A {@link FhirPath} expression */ @Override @Nonnull - public FhirPathTransformation visitMemberInvocation(@Nullable final MemberInvocationContext ctx) { + public FhirPath visitMemberInvocation( + @Nullable final MemberInvocationContext ctx) { @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); return input -> { try { // Attempt path traversal. - final PathTraversalInput pathTraversalInput = new PathTraversalInput(context, input, - fhirPath); - return new PathTraversalOperator().invoke(pathTraversalInput); + final Optional result = input.traverse(fhirPath); + checkUserInput(result.isPresent(), "No such child: " + fhirPath); + return result.get(); } catch (final InvalidUserInputError e) { try { // If it is not a valid path traversal, see if it is a valid type specifier. final FHIRDefinedType fhirType = FHIRDefinedType.fromCode(fhirPath); - return TypeSpecifier.build(context.getInputContext(), fhirType); + return new Collection(functions.lit(null), Optional.of(FhirPathType.TYPE_SPECIFIER), + Optional.of(fhirType), Optional.empty()); } catch (final FHIRException e2) { throw new InvalidUserInputError( @@ -104,47 +107,51 @@ public FhirPathTransformation visitMemberInvocation(@Nullable final MemberInvoca * expression. * * @param ctx The {@link FunctionInvocationContext} - * @return A {@link FhirPathTransformation} expression + * @return A {@link FhirPath} expression */ @Override @Nonnull - public FhirPathTransformation visitFunctionInvocation( + public FhirPath visitFunctionInvocation( @Nullable final FunctionInvocationContext ctx) { final String functionIdentifier = requireNonNull(ctx).function().identifier().getText(); @Nullable final ParamListContext paramList = ctx.function().paramList(); return input -> { - final NamedFunction function = NamedFunction.getInstance(functionIdentifier); + final NamedFunction function; + try { + function = context.getFunctionRegistry().getInstance(functionIdentifier); + } catch (final NoSuchFunctionException e) { + throw new InvalidUserInputError(e.getMessage()); + } final Visitor paramListVisitor = new Visitor(context); - final List arguments = Optional.ofNullable(paramList) + final List> arguments = Optional.ofNullable(paramList) .map(ParamListContext::expression) .map(p -> p.stream() .map(paramListVisitor::visit) - .map(fpt -> fpt.apply(input)) .collect(toList()) ).orElse(new ArrayList<>()); - final NamedFunctionInput functionInput = new NamedFunctionInput(context, input, arguments); + final FunctionInput functionInput = new FunctionInput(context, input, arguments); return function.invoke(functionInput); }; } @Override @Nonnull - public FhirPathTransformation visitThisInvocation(@Nullable final ThisInvocationContext ctx) { + public FhirPath visitThisInvocation(@Nullable final ThisInvocationContext ctx) { return input -> context.getInputContext(); } @Override @Nonnull - public FhirPathTransformation visitIndexInvocation(@Nullable final IndexInvocationContext ctx) { + public FhirPath visitIndexInvocation(@Nullable final IndexInvocationContext ctx) { throw new InvalidUserInputError("$index is not supported"); } @Override @Nonnull - public FhirPathTransformation visitTotalInvocation(@Nullable final TotalInvocationContext ctx) { + public FhirPath visitTotalInvocation(@Nullable final TotalInvocationContext ctx) { throw new InvalidUserInputError("$total is not supported"); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java index 7d0bf3bd3b..b342e11351 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java @@ -21,17 +21,18 @@ import au.csiro.pathling.encoders.terminology.ucum.Ucum; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPathTransformation; -import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; -import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; -import au.csiro.pathling.fhirpath.literal.DateLiteralPath; +import au.csiro.pathling.fhirpath.collection.Collection; +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.DateCollection; +import au.csiro.pathling.fhirpath.collection.DecimalCollection; +import au.csiro.pathling.fhirpath.collection.IntegerCollection; +import au.csiro.pathling.fhirpath.collection.NullPath; +import au.csiro.pathling.fhirpath.collection.QuantityCollection; +import au.csiro.pathling.fhirpath.collection.StringCollection; +import au.csiro.pathling.fhirpath.collection.TimeCollection; import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; -import au.csiro.pathling.fhirpath.literal.DecimalLiteralPath; -import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; -import au.csiro.pathling.fhirpath.literal.NullLiteralPath; -import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.literal.TimeLiteralPath; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.BooleanLiteralContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.CodingLiteralContext; @@ -53,40 +54,26 @@ * * @author John Grimes */ -class LiteralTermVisitor extends FhirPathBaseVisitor { +class LiteralTermVisitor extends FhirPathBaseVisitor> { @Override @Nonnull - public FhirPathTransformation visitCodingLiteral(@Nullable final CodingLiteralContext ctx) { + public FhirPath visitStringLiteral( + @Nullable final StringLiteralContext ctx) { @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return input -> { - try { - return CodingLiteralPath.fromString(fhirPath, input); - } catch (final IllegalArgumentException e) { - throw new InvalidUserInputError(e.getMessage(), e); - } - }; - } - - @Override - @Nonnull - public FhirPathTransformation visitStringLiteral(@Nullable final StringLiteralContext ctx) { - @Nullable final String fhirPath = requireNonNull(ctx).getText(); - requireNonNull(fhirPath); - - return input -> StringLiteralPath.fromString(fhirPath, input); + return input -> StringCollection.fromLiteral(fhirPath); } @Override - public FhirPathTransformation visitDateLiteral(@Nullable final DateLiteralContext ctx) { + public FhirPath visitDateLiteral(@Nullable final DateLiteralContext ctx) { @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); return input -> { try { - return DateLiteralPath.fromString(fhirPath, input); + return DateCollection.fromLiteral(fhirPath); } catch (final ParseException e) { throw new InvalidUserInputError("Unable to parse date format: " + fhirPath); } @@ -95,13 +82,14 @@ public FhirPathTransformation visitDateLiteral(@Nullable final DateLiteralContex @Override @Nonnull - public FhirPathTransformation visitDateTimeLiteral(@Nullable final DateTimeLiteralContext ctx) { + public FhirPath visitDateTimeLiteral( + @Nullable final DateTimeLiteralContext ctx) { @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); return input -> { try { - return DateTimeLiteralPath.fromString(fhirPath, input); + return DateTimeLiteralPath.fromString(fhirPath); } catch (final ParseException e) { throw new InvalidUserInputError("Unable to parse date/time format: " + fhirPath); } @@ -110,16 +98,17 @@ public FhirPathTransformation visitDateTimeLiteral(@Nullable final DateTimeLiter @Override @Nonnull - public FhirPathTransformation visitTimeLiteral(@Nullable final TimeLiteralContext ctx) { + public FhirPath visitTimeLiteral(@Nullable final TimeLiteralContext ctx) { @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return input -> TimeLiteralPath.fromString(fhirPath, input); + return input -> TimeCollection.fromLiteral(fhirPath); } @Override @Nonnull - public FhirPathTransformation visitNumberLiteral(@Nullable final NumberLiteralContext ctx) { + public FhirPath visitNumberLiteral( + @Nullable final NumberLiteralContext ctx) { @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); @@ -127,10 +116,10 @@ public FhirPathTransformation visitNumberLiteral(@Nullable final NumberLiteralCo // The FHIRPath grammar lumps these two types together, so we tease them apart by trying to // parse them. try { - return IntegerLiteralPath.fromString(fhirPath, input); + return IntegerCollection.fromLiteral(fhirPath); } catch (final NumberFormatException e) { try { - return DecimalLiteralPath.fromString(fhirPath, input); + return DecimalCollection.fromLiteral(fhirPath); } catch (final NumberFormatException ex) { throw new InvalidUserInputError("Invalid date format: " + fhirPath); } @@ -140,23 +129,25 @@ public FhirPathTransformation visitNumberLiteral(@Nullable final NumberLiteralCo @Override @Nonnull - public FhirPathTransformation visitBooleanLiteral(@Nullable final BooleanLiteralContext ctx) { + public FhirPath visitBooleanLiteral( + @Nullable final BooleanLiteralContext ctx) { requireNonNull(ctx); @Nullable final String fhirPath = ctx.getText(); requireNonNull(fhirPath); - return input -> BooleanLiteralPath.fromString(fhirPath, input); + return input -> BooleanCollection.fromLiteral(fhirPath); } @Override @Nonnull - public FhirPathTransformation visitNullLiteral(@Nullable final NullLiteralContext ctx) { - return NullLiteralPath::build; + public FhirPath visitNullLiteral(@Nullable final NullLiteralContext ctx) { + return input -> NullPath.build(); } @Override @Nonnull - public FhirPathTransformation visitQuantityLiteral(@Nullable final QuantityLiteralContext ctx) { + public FhirPath visitQuantityLiteral( + @Nullable final QuantityLiteralContext ctx) { requireNonNull(ctx); @Nullable final String number = ctx.quantity().NUMBER().getText(); requireNonNull(number); @@ -166,12 +157,12 @@ public FhirPathTransformation visitQuantityLiteral(@Nullable final QuantityLiter if (ucumUnit == null) { // Create a calendar duration literal. final String fhirPath = String.format("%s %s", number, ctx.quantity().unit().getText()); - return QuantityLiteralPath.fromCalendarDurationString(fhirPath, input); + return QuantityCollection.fromCalendarDurationString(fhirPath); } else { // Create a UCUM Quantity literal. final String fhirPath = String.format("%s %s", number, ucumUnit.getText()); try { - return QuantityLiteralPath.fromUcumString(fhirPath, input, Ucum.service()); + return QuantityCollection.fromUcumString(fhirPath, Ucum.service()); } catch (final UcumException e) { throw new RuntimeException(e); } @@ -179,4 +170,20 @@ public FhirPathTransformation visitQuantityLiteral(@Nullable final QuantityLiter }; } + @Override + @Nonnull + public FhirPath visitCodingLiteral( + @Nullable final CodingLiteralContext ctx) { + @Nullable final String fhirPath = requireNonNull(ctx).getText(); + requireNonNull(fhirPath); + + return input -> { + try { + return CodingCollection.fromLiteral(fhirPath); + } catch (final IllegalArgumentException e) { + throw new InvalidUserInputError(e.getMessage(), e); + } + }; + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java index 8f306424de..1c7751136d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java @@ -18,7 +18,7 @@ package au.csiro.pathling.fhirpath.parser; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirPathTransformation; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.parser.generated.FhirPathLexer; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser; import javax.annotation.Nonnull; @@ -50,10 +50,10 @@ public Parser(@Nonnull final ParserContext context) { * Parses a FHIRPath expression. * * @param expression The String representation of the FHIRPath expression - * @return a new {@link FhirPath} object + * @return a new {@link Collection} object */ @Nonnull - public FhirPathTransformation parse(@Nonnull final String expression) { + public FhirPath parse(@Nonnull final String expression) { final FhirPathParser parser = buildParser(expression); final Visitor visitor = new Visitor(context); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index ff63ae61dc..51c3b2fde1 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -17,8 +17,9 @@ package au.csiro.pathling.fhirpath.parser; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.function.registry.FunctionRegistry; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyService; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -46,7 +47,7 @@ public class ParserContext { * @see FHIR Specific Variables */ @Nonnull - private final FhirPath inputContext; + private final Collection inputContext; /** * The resource that contains the input context, referred to through {@code %resource} or @@ -58,7 +59,7 @@ public class ParserContext { * @see FHIR Specific Variables */ @Nonnull - private final ResourcePath resource; + private final ResourceCollection resource; /** * A FHIR context that can be used to do FHIR stuff. @@ -78,6 +79,12 @@ public class ParserContext { @Nonnull private final DataSource dataSource; + /** + * A registry of FHIRPath function implementations. + */ + @Nonnull + private final FunctionRegistry functionRegistry; + /** * A factory for creating new {@link TerminologyService} objects, which is needed within blocks of * code that are run in parallel. Will only be present if a terminology service has been @@ -103,9 +110,10 @@ public class ParserContext { * @param constantReplacer a list of constants and the expressions that they should be replaced * with */ - public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final ResourcePath resource, + public ParserContext(@Nonnull final Collection inputContext, @Nonnull final ResourceCollection resource, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataSource, + @Nonnull final FunctionRegistry functionRegistry, @Nonnull final Optional terminologyServiceFactory, @Nonnull final Optional constantReplacer) { this.inputContext = inputContext; @@ -113,6 +121,7 @@ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final Resour this.fhirContext = fhirContext; this.sparkSession = sparkSession; this.dataSource = dataSource; + this.functionRegistry = functionRegistry; this.terminologyServiceFactory = terminologyServiceFactory; this.constantReplacer = constantReplacer; } @@ -122,9 +131,9 @@ public ParserContext(@Nonnull final FhirPath inputContext, @Nonnull final Resour * input context */ @Nonnull - public ParserContext withInputContext(@Nonnull final FhirPath inputContext) { + public ParserContext withInputContext(@Nonnull final Collection inputContext) { return new ParserContext(inputContext, resource, fhirContext, sparkSession, dataSource, - terminologyServiceFactory, constantReplacer); + functionRegistry, terminologyServiceFactory, constantReplacer); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java index 36a1d97df6..baeaf77d34 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java @@ -20,7 +20,7 @@ import static java.util.Objects.requireNonNull; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirPathTransformation; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ExternalConstantTermContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.InvocationTermContext; @@ -34,7 +34,7 @@ * * @author John Grimes */ -class TermVisitor extends FhirPathBaseVisitor { +class TermVisitor extends FhirPathBaseVisitor> { @Nonnull private final ParserContext context; @@ -45,19 +45,19 @@ class TermVisitor extends FhirPathBaseVisitor { @Override @Nonnull - public FhirPathTransformation visitInvocationTerm(@Nullable final InvocationTermContext ctx) { + public FhirPath visitInvocationTerm(@Nullable final InvocationTermContext ctx) { return new InvocationVisitor(context).visit(requireNonNull(ctx).invocation()); } @Override @Nonnull - public FhirPathTransformation visitLiteralTerm(@Nullable final LiteralTermContext ctx) { + public FhirPath visitLiteralTerm(@Nullable final LiteralTermContext ctx) { return new LiteralTermVisitor().visit(requireNonNull(ctx).literal()); } @Override @Nonnull - public FhirPathTransformation visitExternalConstantTerm( + public FhirPath visitExternalConstantTerm( @Nullable final ExternalConstantTermContext ctx) { @Nullable final String term = requireNonNull(ctx).getText(); requireNonNull(term); @@ -75,13 +75,12 @@ public FhirPathTransformation visitExternalConstantTerm( @Override @Nonnull - public FhirPathTransformation visitParenthesizedTerm( + public FhirPath visitParenthesizedTerm( @Nullable final ParenthesizedTermContext ctx) { return input -> { // Parentheses are ignored in the standalone term case. - final FhirPath result = new Visitor(context).visit( + return new Visitor(context).visit( requireNonNull(ctx).expression()).apply(input); - return result.withExpression("(" + result.getExpression() + ")"); }; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java index 3286d09307..5be533be8f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java @@ -21,9 +21,10 @@ import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.FhirPathTransformation; -import au.csiro.pathling.fhirpath.operator.Operator; -import au.csiro.pathling.fhirpath.operator.OperatorInput; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.operator.BinaryOperator; +import au.csiro.pathling.fhirpath.operator.BinaryOperatorInput; +import au.csiro.pathling.fhirpath.operator.BinaryOperatorType; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.AdditiveExpressionContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.AndExpressionContext; @@ -50,7 +51,7 @@ * * @author John Grimes */ -class Visitor extends FhirPathBaseVisitor { +class Visitor extends FhirPathBaseVisitor> { @Nonnull private final ParserContext context; @@ -63,11 +64,11 @@ class Visitor extends FhirPathBaseVisitor { * A term is typically a standalone literal or function invocation. * * @param ctx The {@link TermExpressionContext} - * @return A {@link FhirPathTransformation} expression + * @return A {@link FhirPath} expression */ @Override @Nonnull - public FhirPathTransformation visitTermExpression(@Nullable final TermExpressionContext ctx) { + public FhirPath visitTermExpression(@Nullable final TermExpressionContext ctx) { return requireNonNull(ctx).term().accept(new TermVisitor(context)); } @@ -75,14 +76,14 @@ public FhirPathTransformation visitTermExpression(@Nullable final TermExpression * An invocation expression is one expression invoking another using the dot notation. * * @param ctx The {@link InvocationExpressionContext} - * @return A {@link FhirPathTransformation} expression + * @return A {@link FhirPath} expression */ @Override @Nonnull - public FhirPathTransformation visitInvocationExpression( + public FhirPath visitInvocationExpression( @Nullable final InvocationExpressionContext ctx) { return (input) -> { - final FhirPath expression = new Visitor(context).visit(requireNonNull(ctx).expression()) + final Collection expression = new Visitor(context).visit(requireNonNull(ctx).expression()) .apply(input); // The input context is passed through to the invocation visitor as the invoker. return ctx.invocation().accept(new InvocationVisitor(context)).apply(expression); @@ -90,31 +91,30 @@ public FhirPathTransformation visitInvocationExpression( } @Nonnull - private FhirPathTransformation visitBinaryOperator(@Nullable final ParseTree leftContext, + private FhirPath visitBinaryOperator(@Nullable final ParseTree leftContext, @Nullable final ParseTree rightContext, @Nullable final String operatorName) { requireNonNull(operatorName); return (input) -> { - final FhirPath left = new Visitor(context).visit(leftContext).apply(input); - final FhirPath right = new Visitor(context).visit(rightContext) - .apply(input.withDataset(left.getDataset())); + final Collection left = new Visitor(context).visit(leftContext).apply(input); + final Collection right = new Visitor(context).visit(rightContext).apply(input); - final Operator operator = Operator.getInstance(operatorName); + final BinaryOperator operator = BinaryOperatorType.fromSymbol(operatorName).getInstance(); - final OperatorInput operatorInput = new OperatorInput(context, left, right); + final BinaryOperatorInput operatorInput = new BinaryOperatorInput(context, left, right); return operator.invoke(operatorInput); }; } @Override @Nonnull - public FhirPathTransformation visitEqualityExpression( + public FhirPath visitEqualityExpression( @Nullable final EqualityExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); } @Override - public FhirPathTransformation visitInequalityExpression( + public FhirPath visitInequalityExpression( @Nullable final InequalityExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); @@ -122,21 +122,21 @@ public FhirPathTransformation visitInequalityExpression( @Override @Nonnull - public FhirPathTransformation visitAndExpression(@Nullable final AndExpressionContext ctx) { + public FhirPath visitAndExpression(@Nullable final AndExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); } @Override @Nonnull - public FhirPathTransformation visitOrExpression(@Nullable final OrExpressionContext ctx) { + public FhirPath visitOrExpression(@Nullable final OrExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); } @Override @Nonnull - public FhirPathTransformation visitImpliesExpression( + public FhirPath visitImpliesExpression( @Nullable final ImpliesExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); @@ -144,7 +144,7 @@ public FhirPathTransformation visitImpliesExpression( @Override @Nonnull - public FhirPathTransformation visitMembershipExpression( + public FhirPath visitMembershipExpression( @Nullable final MembershipExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); @@ -152,7 +152,7 @@ public FhirPathTransformation visitMembershipExpression( @Override @Nonnull - public FhirPathTransformation visitMultiplicativeExpression( + public FhirPath visitMultiplicativeExpression( @Nullable final MultiplicativeExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); @@ -160,14 +160,14 @@ public FhirPathTransformation visitMultiplicativeExpression( @Override @Nonnull - public FhirPathTransformation visitAdditiveExpression( + public FhirPath visitAdditiveExpression( @Nullable final AdditiveExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); } @Override - public FhirPathTransformation visitCombineExpression( + public FhirPath visitCombineExpression( @Nullable final CombineExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); @@ -177,25 +177,25 @@ public FhirPathTransformation visitCombineExpression( @Override @Nonnull - public FhirPathTransformation visitIndexerExpression(final IndexerExpressionContext ctx) { + public FhirPath visitIndexerExpression(final IndexerExpressionContext ctx) { throw new InvalidUserInputError("Indexer operation is not supported"); } @Override @Nonnull - public FhirPathTransformation visitPolarityExpression(final PolarityExpressionContext ctx) { + public FhirPath visitPolarityExpression(final PolarityExpressionContext ctx) { throw new InvalidUserInputError("Polarity operator is not supported"); } @Override @Nonnull - public FhirPathTransformation visitUnionExpression(final UnionExpressionContext ctx) { + public FhirPath visitUnionExpression(final UnionExpressionContext ctx) { throw new InvalidUserInputError("Union expressions are not supported"); } @Override @Nonnull - public FhirPathTransformation visitTypeExpression(final TypeExpressionContext ctx) { + public FhirPath visitTypeExpression(final TypeExpressionContext ctx) { throw new InvalidUserInputError("Type expressions are not supported"); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/sql/misc/CodingToLiteral.java b/fhirpath/src/main/java/au/csiro/pathling/sql/misc/CodingToLiteral.java index c0dfcc8de4..5f7d9a049c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/sql/misc/CodingToLiteral.java +++ b/fhirpath/src/main/java/au/csiro/pathling/sql/misc/CodingToLiteral.java @@ -17,6 +17,8 @@ package au.csiro.pathling.sql.misc; +import static java.util.Objects.requireNonNull; + import au.csiro.pathling.fhirpath.encoding.CodingEncoding; import au.csiro.pathling.fhirpath.literal.CodingLiteral; import au.csiro.pathling.sql.udf.SqlFunction1; @@ -38,7 +40,7 @@ public class CodingToLiteral implements SqlFunction1 { */ public static final String FUNCTION_NAME = "coding_to_literal"; - private static final long serialVersionUID = -6274263255779613070L; + private static final long serialVersionUID = 1L; @Override public String getName() { @@ -56,7 +58,7 @@ public String call(@Nullable final Row row) { if (row == null) { return null; } - final Coding coding = CodingEncoding.decode(row); + final Coding coding = requireNonNull(CodingEncoding.decode(row)); return CodingLiteral.toLiteral(coding); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ContextAndSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/ContextAndSelection.java index 2b5429540a..3fe8ea5e2c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/ContextAndSelection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ContextAndSelection.java @@ -1,6 +1,6 @@ package au.csiro.pathling.views; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import java.util.List; import javax.annotation.Nonnull; import lombok.Value; @@ -10,7 +10,7 @@ public class ContextAndSelection { @Nonnull - FhirPath context; + Collection context; @Nonnull List selection; diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index c9e537e620..78e0a088ba 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -3,8 +3,8 @@ import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.parser.ConstantReplacer; import au.csiro.pathling.fhirpath.parser.Parser; import au.csiro.pathling.fhirpath.parser.ParserContext; @@ -60,10 +60,10 @@ public Dataset buildQuery(@Nonnull final FhirView view) { // Build a new expression parser, and parse all the column expressions within the query. final ResourceType resourceType = ResourceType.fromCode(view.getResource()); - final ResourcePath inputContext = ResourcePath.build(fhirContext, dataSource, resourceType, - view.getResource(), true); + final ResourceCollection inputContext = ResourceCollection.build(fhirContext, dataSource, resourceType, + view.getResource()); final ParserContext parserContext = new ParserContext(inputContext, inputContext, fhirContext, - sparkSession, dataSource, terminologyServiceFactory, constantReplacer); + sparkSession, dataSource, functionRegistry, terminologyServiceFactory, constantReplacer); final List select = parseSelect(view.getSelect(), parserContext, Collections.emptyList()); @@ -74,7 +74,7 @@ public Dataset buildQuery(@Nonnull final FhirView view) { .collect(toList()); final Optional filterCondition = where.stream() .map(e -> parseExpression(e, parserContext)) - .map(FhirPath::getValueColumn) + .map(Collection::getValueColumn) .reduce(Column::and); return filterCondition .map(inputContext.getDataset()::filter) @@ -110,7 +110,7 @@ private List parseSelect(@Nonnull final List selectGroup, @Nonnull private List directSelection(@Nonnull final ParserContext context, @Nonnull final DirectSelection select, @Nonnull final List currentSelection) { - final FhirPath path = parseExpression(select.getExpression(), context); + final Collection path = parseExpression(select.getExpression(), context); final List newColumns = new ArrayList<>(currentSelection); newColumns.add(path.getValueColumn().alias(select.getName())); return newColumns; @@ -120,16 +120,16 @@ private List directSelection(@Nonnull final ParserContext context, private List nestedSelection(final @Nonnull ParserContext context, @Nonnull final NestedSelectClause select, @Nonnull final List currentSelection, final boolean unnest) { - final FhirPath from = parseExpression(select.getExpression(), context); - final FhirPath nextInputContext = unnest - ? from.unnest() - : from; + final Collection from = parseExpression(select.getExpression(), context); + final Collection nextInputContext = unnest + ? from.unnest() + : from; final ParserContext nextContext = context.withInputContext(nextInputContext); return parseSelect(select.getSelect(), nextContext, currentSelection); } @Nonnull - private FhirPath parseExpression(@Nonnull final String expression, + private Collection parseExpression(@Nonnull final String expression, @Nonnull final ParserContext context) { final Parser parser = new Parser(context); final String updatedExpression = context.getConstantReplacer() diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/CanBeCombinedWithTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/CanBeCombinedWithTest.java index d06f78120b..b272279bba 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/CanBeCombinedWithTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/CanBeCombinedWithTest.java @@ -19,15 +19,22 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.element.ReferencePath; +import au.csiro.pathling.fhirpath.collection.CodingCollection; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.DateCollection; +import au.csiro.pathling.fhirpath.collection.DecimalCollection; +import au.csiro.pathling.fhirpath.collection.NullPath; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.ReferencePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.collection.StringCollection; +import au.csiro.pathling.fhirpath.collection.TimeCollection; +import au.csiro.pathling.fhirpath.collection.UntypedResourcePath; import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; import au.csiro.pathling.fhirpath.literal.DateLiteralPath; import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; -import au.csiro.pathling.fhirpath.literal.DecimalLiteralPath; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; -import au.csiro.pathling.fhirpath.literal.NullLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.literal.TimeLiteralPath; import au.csiro.pathling.test.SpringBootUnitTest; @@ -35,7 +42,6 @@ import au.csiro.pathling.test.builders.ResourcePathBuilder; import java.text.ParseException; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -58,34 +64,34 @@ @TestInstance(Lifecycle.PER_CLASS) class CanBeCombinedWithTest { - final ResourcePath resourcePath; + final ResourceCollection resourceCollection; final UntypedResourcePath untypedResourcePath; - final ElementPath booleanPath; - final ElementPath codingPath; - final ElementPath datePath; - final ElementPath dateTimePath; - final ElementPath decimalPath; - final ElementPath integerPath; + final PrimitivePath booleanPath; + final PrimitivePath codingPath; + final PrimitivePath datePath; + final PrimitivePath dateTimePath; + final PrimitivePath decimalPath; + final PrimitivePath integerPath; final ReferencePath referencePath; - final ElementPath stringPath; - final ElementPath timePath; + final PrimitivePath stringPath; + final PrimitivePath timePath; final BooleanLiteralPath booleanLiteralPath; final CodingLiteralPath codingLiteralPath; final DateLiteralPath dateLiteralPath; final DateTimeLiteralPath dateTimeLiteralPath; - final DecimalLiteralPath decimalLiteralPath; + final DecimalCollection decimalLiteralPath; final IntegerLiteralPath integerLiteralPath; - final NullLiteralPath nullLiteralPath; + final NullPath nullPath; final StringLiteralPath stringLiteralPath; final TimeLiteralPath timeLiteralPath; - final Set paths; + final Set paths; @Autowired CanBeCombinedWithTest(@Nonnull final SparkSession spark) throws ParseException { - resourcePath = new ResourcePathBuilder(spark) + resourceCollection = new ResourcePathBuilder(spark) .build(); booleanPath = new ElementPathBuilder(spark) @@ -117,20 +123,20 @@ class CanBeCombinedWithTest { .fhirType(FHIRDefinedType.TIME) .build(); - booleanLiteralPath = BooleanLiteralPath.fromString("true", resourcePath); - codingLiteralPath = CodingLiteralPath - .fromString("http://snomed.info/sct|27699000", resourcePath); - dateLiteralPath = DateLiteralPath.fromString("@1983-06-21", resourcePath); + booleanLiteralPath = BooleanLiteralPath.fromString("true", resourceCollection); + codingLiteralPath = CodingCollection + .fromLiteral("http://snomed.info/sct|27699000", resourceCollection); + dateLiteralPath = DateCollection.fromLiteral("@1983-06-21", resourceCollection); dateTimeLiteralPath = DateTimeLiteralPath - .fromString("@2015-02-08T13:28:17-05:00", resourcePath); - decimalLiteralPath = DecimalLiteralPath.fromString("9.5", resourcePath); - integerLiteralPath = IntegerLiteralPath.fromString("14", resourcePath); - nullLiteralPath = NullLiteralPath.build(resourcePath); - stringLiteralPath = StringLiteralPath.fromString("'foo'", resourcePath); - timeLiteralPath = TimeLiteralPath.fromString("T12:30", resourcePath); + .fromString("@2015-02-08T13:28:17-05:00", resourceCollection); + decimalLiteralPath = DecimalCollection.fromLiteral("9.5", resourceCollection); + integerLiteralPath = IntegerLiteralPath.fromString("14", resourceCollection); + nullPath = NullPath.build(resourceCollection); + stringLiteralPath = StringCollection.fromLiteral("'foo'", resourceCollection); + timeLiteralPath = TimeCollection.fromLiteral("T12:30", resourceCollection); paths = new HashSet<>(Arrays.asList( - resourcePath, + resourceCollection, untypedResourcePath, booleanPath, codingPath, @@ -147,7 +153,7 @@ class CanBeCombinedWithTest { dateTimeLiteralPath, decimalLiteralPath, integerLiteralPath, - nullLiteralPath, + nullPath, stringLiteralPath, timeLiteralPath )); @@ -158,10 +164,10 @@ class CanBeCombinedWithTest { static class TestParameters { @Nonnull - FhirPath source; + Collection source; @Nonnull - FhirPath target; + Collection target; boolean expectation; @@ -175,7 +181,7 @@ public String toString() { Stream parameters() { return Stream.of( - buildParameters(resourcePath, Collections.singletonList(resourcePath)), + buildParameters(resourceCollection, Collections.singletonList(resourceCollection)), buildParameters(untypedResourcePath, Collections.singletonList(untypedResourcePath)), buildParameters(booleanPath, Arrays.asList(booleanPath, booleanLiteralPath)), buildParameters(codingPath, Arrays.asList(codingPath, codingLiteralPath)), @@ -194,14 +200,14 @@ Stream parameters() { buildParameters(integerLiteralPath, Arrays.asList(integerLiteralPath, integerPath)), buildParameters(stringLiteralPath, Arrays.asList(stringLiteralPath, stringPath)), buildParameters(timeLiteralPath, Arrays.asList(timeLiteralPath, timePath)), - buildParameters(nullLiteralPath, paths) + buildParameters(nullPath, paths) ).flatMap(x -> x).distinct(); } - Stream buildParameters(@Nonnull final FhirPath source, - @Nonnull final Collection canBeCombined) { + Stream buildParameters(@Nonnull final Collection source, + @Nonnull final java.util.Collection canBeCombined) { return paths.stream().map(path -> new TestParameters(source, path, - canBeCombined.contains(path) || path == nullLiteralPath)); + canBeCombined.contains(path) || path == nullPath)); } @ParameterizedTest diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/BooleansTestFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/BooleansTestFunctionTest.java index 39a2d5df28..ef1692fe01 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/BooleansTestFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/BooleansTestFunctionTest.java @@ -22,9 +22,10 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.BooleanPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; @@ -151,7 +152,7 @@ void returnsCorrectResults(@Nonnull final TestParameters parameters) { .withRow("observation-6", null) .withRow("observation-7", null) .build(); - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BOOLEAN) .dataset(dataset) .idAndValueColumns() @@ -164,12 +165,12 @@ void returnsCorrectResults(@Nonnull final TestParameters parameters) { final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, Collections.emptyList()); final NamedFunction function = NamedFunction.getInstance(parameters.getFunctionName()); - final FhirPath result = function.invoke(functionInput); + final Collection result = function.invoke(functionInput); assertThat(result) .hasExpression("valueBoolean." + parameters.getFunctionName() + "()") .isSingular() - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectOrderedResult() .hasRows(parameters.getExpectedResult()); } @@ -177,9 +178,9 @@ void returnsCorrectResults(@Nonnull final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void inputMustNotContainArguments(@Nonnull final TestParameters parameters) { - final ElementPath input = new ElementPathBuilder(spark).build(); - final StringLiteralPath argument = StringLiteralPath - .fromString("'some argument'", input); + final PrimitivePath input = new ElementPathBuilder(spark).build(); + final StringLiteralPath argument = StringCollection + .fromLiteral("'some argument'", input); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, @@ -196,7 +197,7 @@ void inputMustNotContainArguments(@Nonnull final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void inputMustBeBoolean(@Nonnull final TestParameters parameters) { - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .expression("valueString") .fhirType(FHIRDefinedType.STRING) .build(); diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/CountFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/CountFunctionTest.java index 3fea54055d..cb8e78e2d1 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/CountFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/CountFunctionTest.java @@ -23,10 +23,10 @@ import static org.mockito.Mockito.when; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.element.IntegerPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.IntegerCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.test.SpringBootUnitTest; @@ -75,8 +75,8 @@ void countsByResourceIdentity() { .build(); when(dataSource.read(ResourceType.PATIENT)) .thenReturn(patientDataset); - final ResourcePath inputPath = ResourcePath - .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient", false); + final ResourceCollection inputPath = ResourceCollection + .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient"); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .idColumn(inputPath.getIdColumn()) @@ -86,7 +86,7 @@ void countsByResourceIdentity() { final NamedFunctionInput countInput = new NamedFunctionInput(parserContext, inputPath, Collections.emptyList()); final NamedFunction count = NamedFunction.getInstance("count"); - final FhirPath result = count.invoke(countInput); + final Collection result = count.invoke(countInput); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn() @@ -99,7 +99,7 @@ void countsByResourceIdentity() { assertThat(result) .hasExpression("count()") .isSingular() - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .hasFhirType(FHIRDefinedType.UNSIGNEDINT) .selectOrderedResult() .hasRows(expectedDataset); @@ -116,7 +116,7 @@ void countsByGrouping() { .withRow("patient-2", "male", true) .build(); when(dataSource.read(ResourceType.PATIENT)).thenReturn(inputDataset); - final ResourcePath inputPath = new ResourcePathBuilder(spark) + final ResourceCollection inputPath = new ResourcePathBuilder(spark) .database(dataSource) .resourceType(ResourceType.PATIENT) .expression("Patient") @@ -130,7 +130,7 @@ void countsByGrouping() { final NamedFunctionInput countInput = new NamedFunctionInput(parserContext, inputPath, Collections.emptyList()); final NamedFunction count = NamedFunction.getInstance("count"); - final FhirPath result = count.invoke(countInput); + final Collection result = count.invoke(countInput); final Dataset expectedDataset = new DatasetBuilder(spark) .withColumn(DataTypes.StringType) @@ -142,7 +142,7 @@ void countsByGrouping() { assertThat(result) .hasExpression("count()") .isSingular() - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .hasFhirType(FHIRDefinedType.UNSIGNEDINT) .selectGroupingResult(Collections.singletonList(groupingColumn)) .hasRows(expectedDataset); @@ -158,7 +158,7 @@ void doesNotCountNullElements() { .withRow("patient-2", null) .withRow("patient-3", "male") .build(); - final ElementPath inputPath = new ElementPathBuilder(spark) + final PrimitivePath inputPath = new ElementPathBuilder(spark) .expression("gender") .fhirType(FHIRDefinedType.CODE) .dataset(dataset) @@ -172,7 +172,7 @@ void doesNotCountNullElements() { final NamedFunctionInput countInput = new NamedFunctionInput(parserContext, inputPath, Collections.emptyList()); final NamedFunction count = NamedFunction.getInstance("count"); - final FhirPath result = count.invoke(countInput); + final Collection result = count.invoke(countInput); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn() @@ -183,7 +183,7 @@ void doesNotCountNullElements() { assertThat(result) .hasExpression("gender.count()") .isSingular() - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .hasFhirType(FHIRDefinedType.UNSIGNEDINT) .selectOrderedResult() .hasRows(expectedDataset); @@ -191,8 +191,8 @@ void doesNotCountNullElements() { @Test void inputMustNotContainArguments() { - final ElementPath inputPath = new ElementPathBuilder(spark).build(); - final ElementPath argumentPath = new ElementPathBuilder(spark).build(); + final PrimitivePath inputPath = new ElementPathBuilder(spark).build(); + final PrimitivePath argumentPath = new ElementPathBuilder(spark).build(); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); final NamedFunctionInput countInput = new NamedFunctionInput(parserContext, inputPath, diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/EmptyFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/EmptyFunctionTest.java index aa1afdbf49..10a8313a6a 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/EmptyFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/EmptyFunctionTest.java @@ -24,9 +24,10 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.BooleanPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; @@ -77,7 +78,7 @@ void returnsCorrectResults() { .withRow("observation-5", rowFromCodeableConcept(concept1)) .withRow("observation-5", rowFromCodeableConcept(concept2)) .build(); - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODEABLECONCEPT) .dataset(dataset) .idAndValueColumns() @@ -93,7 +94,7 @@ void returnsCorrectResults() { // Invoke the function. final NamedFunction emptyFunction = NamedFunction.getInstance("empty"); - final FhirPath result = emptyFunction.invoke(emptyInput); + final Collection result = emptyFunction.invoke(emptyInput); // Check the result. final Dataset expectedDataset = new DatasetBuilder(spark) @@ -108,16 +109,16 @@ void returnsCorrectResults() { assertThat(result) .hasExpression("code.empty()") .isSingular() - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectOrderedResult() .hasRows(expectedDataset); } @Test void inputMustNotContainArguments() { - final ElementPath input = new ElementPathBuilder(spark).build(); - final StringLiteralPath argument = StringLiteralPath - .fromString("'some argument'", input); + final PrimitivePath input = new ElementPathBuilder(spark).build(); + final StringLiteralPath argument = StringCollection + .fromLiteral("'some argument'", input); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); final NamedFunctionInput emptyInput = new NamedFunctionInput(parserContext, input, diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExistsFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExistsFunctionTest.java index 29852c83ad..17f2d5d96d 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExistsFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExistsFunctionTest.java @@ -25,14 +25,13 @@ import static org.apache.spark.sql.functions.not; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.BooleanPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.DatasetBuilder; @@ -81,7 +80,7 @@ void returnsOppositeResultsToEmpty() { .withRow("observation-5", rowFromCodeableConcept(concept1)) .withRow("observation-5", rowFromCodeableConcept(concept2)) .build(); - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODEABLECONCEPT) .dataset(dataset) .idAndValueColumns() @@ -97,7 +96,7 @@ void returnsOppositeResultsToEmpty() { // Invoke the empty function. final NamedFunction emptyFunction = NamedFunction.getInstance("empty"); - final FhirPath emptyResult = emptyFunction.invoke(emptyInput); + final Collection emptyResult = emptyFunction.invoke(emptyInput); // Create an expected dataset from the result of the empty function, with an inverted value. final Dataset emptyResultDataset = emptyResult.getDataset(); @@ -107,13 +106,13 @@ void returnsOppositeResultsToEmpty() { // Invoke the exists function. final NamedFunction existsFunction = NamedFunction.getInstance("exists"); - final FhirPath existsResult = existsFunction.invoke(emptyInput); + final Collection existsResult = existsFunction.invoke(emptyInput); // Check the result. assertThat(existsResult) .hasExpression("code.exists()") .isSingular() - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectOrderedResult() .hasRows(expectedDataset); } @@ -137,7 +136,7 @@ void returnsSameResultsAsWhereAndExists() { .withRow("patient-5", makeEid(0), "encounter-9", "in-progress") .withRow("patient-6", null, null, null) .build(); - final ResourcePath inputPath = new ResourcePathBuilder(spark) + final ResourceCollection inputPath = new ResourcePathBuilder(spark) .expression("reverseResolve(Encounter.subject)") .dataset(inputDataset) .idEidAndValueColumns() @@ -153,7 +152,7 @@ void returnsSameResultsAsWhereAndExists() { thisPath.getDataset().col(statusColumn).equalTo("in-progress")); assertTrue(thisPath.getThisColumn().isPresent()); - final ElementPath argumentPath = new ElementPathBuilder(spark) + final PrimitivePath argumentPath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BOOLEAN) .dataset(argumentDataset) .idColumn(inputPath.getIdColumn()) @@ -169,7 +168,7 @@ void returnsSameResultsAsWhereAndExists() { // Execute the where function. final NamedFunction whereFunction = NamedFunction.getInstance("where"); - final FhirPath whereResult = whereFunction.invoke(whereInput); + final Collection whereResult = whereFunction.invoke(whereInput); // Prepare the input to the exists function. final NamedFunctionInput existsInput = new NamedFunctionInput(parserContext, @@ -177,12 +176,12 @@ void returnsSameResultsAsWhereAndExists() { // Execute the exists function. final NamedFunction existsFunction = NamedFunction.getInstance("exists"); - final FhirPath whereExistsResult = existsFunction.invoke(existsInput); + final Collection whereExistsResult = existsFunction.invoke(existsInput); // Prepare the input to the exists function (with argument). final NamedFunctionInput existsWithArgumentInput = new NamedFunctionInput(parserContext, inputPath, Collections.singletonList(argumentPath)); - final FhirPath existsWithArgumentResult = existsFunction.invoke(existsWithArgumentInput); + final Collection existsWithArgumentResult = existsFunction.invoke(existsWithArgumentInput); // Check the results. assertThat(existsWithArgumentResult.getDataset() @@ -193,8 +192,8 @@ void returnsSameResultsAsWhereAndExists() { @Test void throwsErrorIfArgumentNotBoolean() { - final ResourcePath input = new ResourcePathBuilder(spark).build(); - final ElementPath argument = new ElementPathBuilder(spark) + final ResourceCollection input = new ResourcePathBuilder(spark).build(); + final PrimitivePath argument = new ElementPathBuilder(spark) .expression("$this.gender") .fhirType(FHIRDefinedType.STRING) .build(); @@ -214,8 +213,8 @@ void throwsErrorIfArgumentNotBoolean() { @Test void throwsErrorIfArgumentNotSingular() { - final ResourcePath input = new ResourcePathBuilder(spark).build(); - final ElementPath argument = new ElementPathBuilder(spark) + final ResourceCollection input = new ResourcePathBuilder(spark).build(); + final PrimitivePath argument = new ElementPathBuilder(spark) .expression("$this.communication.preferred") .fhirType(FHIRDefinedType.BOOLEAN) .singular(false) diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExtensionFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExtensionFunctionTest.java index 31f0d21f86..5bd6543e98 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExtensionFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExtensionFunctionTest.java @@ -34,9 +34,10 @@ import static org.mockito.Mockito.when; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; @@ -95,11 +96,11 @@ public void testExtensionOnResources() { .build(); when(dataSource.read(ResourceType.PATIENT)) .thenReturn(patientDataset); - final ResourcePath inputPath = ResourcePath - .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient", false); + final ResourceCollection inputPath = ResourceCollection + .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient"); - final StringLiteralPath argumentExpression = StringLiteralPath - .fromString("'" + "uuid:myExtension" + "'", inputPath); + final StringLiteralPath argumentExpression = StringCollection + .fromLiteral("'" + "uuid:myExtension" + "'", inputPath); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); @@ -107,12 +108,12 @@ public void testExtensionOnResources() { Collections.singletonList(argumentExpression)); final NamedFunction extension = NamedFunction.getInstance("extension"); - final FhirPath result = extension.invoke(extensionInput); + final Collection result = extension.invoke(extensionInput); assertThat(result) .hasExpression("Patient.extension('uuid:myExtension')") .isNotSingular() - .isElementPath(ElementPath.class) + .isElementPath(PrimitivePath.class) .hasFhirType(FHIRDefinedType.EXTENSION) .selectOrderedResult() .hasRows( @@ -166,32 +167,33 @@ public void testExtensionOnElements() { when(dataSource.read(ResourceType.OBSERVATION)) .thenReturn(resourceLikeDataset); - final ResourcePath baseResourcePath = ResourcePath - .build(fhirContext, dataSource, ResourceType.OBSERVATION, "Observation", false); + final ResourceCollection baseResourceCollection = ResourceCollection + .build(fhirContext, dataSource, ResourceType.OBSERVATION, "Observation"); - final Dataset elementDataset = toElementDataset(resourceLikeDataset, baseResourcePath); + final Dataset elementDataset = toElementDataset(resourceLikeDataset, + baseResourceCollection); final ElementDefinition codeDefinition = checkPresent(FhirHelpers .getChildOfResource(fhirContext, "Observation", "code")); - final ElementPath inputPath = new ElementPathBuilder(spark) + final PrimitivePath inputPath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODEABLECONCEPT) .definition(codeDefinition) .dataset(elementDataset) .idAndEidAndValueColumns() .expression("code") .singular(false) - .currentResource(baseResourcePath) + .currentResource(baseResourceCollection) .buildDefined(); - final StringLiteralPath argumentExpression = StringLiteralPath - .fromString("'" + "uuid:myExtension" + "'", inputPath); + final StringLiteralPath argumentExpression = StringCollection + .fromLiteral("'" + "uuid:myExtension" + "'", inputPath); final NamedFunctionInput extensionInput = new NamedFunctionInput(parserContext, inputPath, Collections.singletonList(argumentExpression)); final NamedFunction extension = NamedFunction.getInstance("extension"); - final FhirPath result = extension.invoke(extensionInput); + final Collection result = extension.invoke(extensionInput); final Dataset expectedResult = new DatasetBuilder(spark) .withIdColumn() @@ -215,7 +217,7 @@ public void testExtensionOnElements() { assertThat(result) .hasExpression("code.extension('uuid:myExtension')") .isNotSingular() - .isElementPath(ElementPath.class) + .isElementPath(PrimitivePath.class) .hasFhirType(FHIRDefinedType.EXTENSION) .selectOrderedResultWithEid() .hasRows(expectedResult); @@ -223,7 +225,7 @@ public void testExtensionOnElements() { @Test public void throwsErrorIfArgumentIsNotString() { - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODEABLECONCEPT) .build(); final IntegerLiteralPath argument = IntegerLiteralPath.fromString("4", input); @@ -242,11 +244,11 @@ public void throwsErrorIfArgumentIsNotString() { @Test public void throwsErrorIfMoreThanOneArgument() { - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODEABLECONCEPT) .build(); - final StringLiteralPath argument1 = StringLiteralPath.fromString("'foo'", input), - argument2 = StringLiteralPath.fromString("'bar'", input); + final StringLiteralPath argument1 = StringCollection.fromLiteral("'foo'", input), + argument2 = StringCollection.fromLiteral("'bar'", input); final ParserContext context = new ParserContextBuilder(spark, fhirContext) .terminologyClientFactory(mock(TerminologyServiceFactory.class)) diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/FirstFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/FirstFunctionTest.java index d843dcf962..a8d39583b7 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/FirstFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/FirstFunctionTest.java @@ -26,10 +26,10 @@ import static org.mockito.Mockito.when; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.element.StringPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.test.SpringBootUnitTest; @@ -79,8 +79,8 @@ void firstOfRootResources() { .build(); when(dataSource.read(ResourceType.PATIENT)) .thenReturn(patientDataset); - final ResourcePath inputPath = ResourcePath - .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient", true); + final ResourceCollection inputPath = ResourceCollection + .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient"); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) @@ -89,10 +89,10 @@ void firstOfRootResources() { final NamedFunctionInput firstInput = new NamedFunctionInput(parserContext, inputPath, Collections.emptyList()); final NamedFunction firstFunction = NamedFunction.getInstance("first"); - final FhirPath result = firstFunction.invoke(firstInput); + final Collection result = firstFunction.invoke(firstInput); - assertTrue(result instanceof ResourcePath); - assertThat((ResourcePath) result) + assertTrue(result instanceof ResourceCollection); + assertThat((ResourceCollection) result) .hasExpression("Patient.first()") .isSingular() .hasResourceType(ResourceType.PATIENT); @@ -126,7 +126,7 @@ void firstOfUngroupedSubResources() { .withRow("patient-2", makeEid(0), "Encounter/3", "in-progress") .withRow("patient-3", null, null, null) .build(); - final ResourcePath inputPath = new ResourcePathBuilder(spark) + final ResourceCollection inputPath = new ResourcePathBuilder(spark) .expression("reverseResolve(Encounter.subject)") .dataset(inputDataset) .idEidAndValueColumns() @@ -141,7 +141,7 @@ void firstOfUngroupedSubResources() { final NamedFunctionInput firstInput = new NamedFunctionInput(parserContext, inputPath, Collections.emptyList()); final NamedFunction firstFunction = NamedFunction.getInstance("first"); - final FhirPath result = firstFunction.invoke(firstInput); + final Collection result = firstFunction.invoke(firstInput); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn() @@ -183,7 +183,7 @@ void firstOfUngroupedElements() { .withRow("patient-6", null, null) .build(); - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .dataset(inputDataset) .idAndEidAndValueColumns() @@ -198,10 +198,10 @@ void firstOfUngroupedElements() { Collections.emptyList()); final NamedFunction firstFunction = NamedFunction.getInstance("first"); - final FhirPath result = firstFunction.invoke(firstInput); + final Collection result = firstFunction.invoke(firstInput); - assertTrue(result instanceof StringPath); - assertThat((ElementPath) result) + assertTrue(result instanceof StringCollection); + assertThat((PrimitivePath) result) .hasExpression("name.first()") .isSingular() .hasFhirType(FHIRDefinedType.STRING); @@ -235,7 +235,7 @@ void illegalToCallFirstOnGrouping() { .withRow("patient-2", "male", true) .build(); when(dataSource.read(ResourceType.PATIENT)).thenReturn(inputDataset); - final ResourcePath inputPath = new ResourcePathBuilder(spark) + final ResourceCollection inputPath = new ResourcePathBuilder(spark) .database(dataSource) .resourceType(ResourceType.PATIENT) .expression("Patient") @@ -261,8 +261,8 @@ void illegalToCallFirstOnGrouping() { @Test void inputMustNotContainArguments() { - final ElementPath inputPath = new ElementPathBuilder(spark).build(); - final ElementPath argumentPath = new ElementPathBuilder(spark).build(); + final PrimitivePath inputPath = new ElementPathBuilder(spark).build(); + final PrimitivePath argumentPath = new ElementPathBuilder(spark).build(); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); final NamedFunctionInput firstInput = new NamedFunctionInput(parserContext, inputPath, diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/IifFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/IifFunctionTest.java index af2075e105..649b23daa8 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/IifFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/IifFunctionTest.java @@ -24,13 +24,13 @@ import static org.mockito.Mockito.when; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.UntypedResourcePath; -import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.element.IntegerPath; -import au.csiro.pathling.fhirpath.element.StringPath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.collection.UntypedResourcePath; +import au.csiro.pathling.fhirpath.collection.IntegerCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; @@ -92,7 +92,7 @@ void returnsCorrectResultsForTwoLiterals() { .withRow("observation-5") .build(); when(dataSource.read(ResourceType.OBSERVATION)).thenReturn(inputContextDataset); - final ResourcePath inputContext = new ResourcePathBuilder(spark) + final ResourceCollection inputContext = new ResourcePathBuilder(spark) .expression("Observation") .resourceType(ResourceType.OBSERVATION) .database(dataSource) @@ -110,7 +110,7 @@ void returnsCorrectResultsForTwoLiterals() { .withRow("observation-5", makeEid(0), null) .withRow("observation-5", makeEid(1), null) .build(); - final ElementPath inputPath = new ElementPathBuilder(spark) + final PrimitivePath inputPath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BOOLEAN) .dataset(inputDataset) .idAndEidAndValueColumns() @@ -121,12 +121,12 @@ void returnsCorrectResultsForTwoLiterals() { .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) .build(); final NonLiteralPath condition = inputPath.toThisPath(); - final StringLiteralPath ifTrue = StringLiteralPath.fromString("foo", inputContext); - final StringLiteralPath otherwise = StringLiteralPath.fromString("bar", inputContext); + final StringLiteralPath ifTrue = StringCollection.fromLiteral("foo", inputContext); + final StringLiteralPath otherwise = StringCollection.fromLiteral("bar", inputContext); final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, inputPath, Arrays.asList(condition, ifTrue, otherwise)); - final FhirPath result = NamedFunction.getInstance("iif").invoke(iifInput); + final Collection result = NamedFunction.getInstance("iif").invoke(iifInput); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn(ID_ALIAS) @@ -143,7 +143,7 @@ void returnsCorrectResultsForTwoLiterals() { assertThat(result) .hasExpression("valueBoolean.iif($this, 'foo', 'bar')") .isNotSingular() - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .selectOrderedResultWithEid() .hasRowsUnordered(expectedDataset); } @@ -162,7 +162,7 @@ void returnsCorrectResultsForTwoNonLiterals() { .withRow("observation-5", makeEid(0), null) .withRow("observation-5", makeEid(1), null) .build(); - final ElementPath inputPath = new ElementPathBuilder(spark) + final PrimitivePath inputPath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BOOLEAN) .dataset(inputDataset) .idAndEidAndValueColumns() @@ -184,7 +184,7 @@ void returnsCorrectResultsForTwoNonLiterals() { .withRow("observation-4", makeEid(0), 4) .withRow("observation-5", makeEid(0), 5) .build(); - final ElementPath ifTrue = new ElementPathBuilder(spark) + final PrimitivePath ifTrue = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .dataset(ifTrueDataset) .idAndEidAndValueColumns() @@ -203,7 +203,7 @@ void returnsCorrectResultsForTwoNonLiterals() { .withRow("observation-4", makeEid(0), 14) .withRow("observation-5", makeEid(0), 15) .build(); - final ElementPath otherwise = new ElementPathBuilder(spark) + final PrimitivePath otherwise = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .dataset(otherwiseDataset) .idAndEidAndValueColumns() @@ -213,7 +213,7 @@ void returnsCorrectResultsForTwoNonLiterals() { final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, inputPath, Arrays.asList(condition, ifTrue, otherwise)); - final FhirPath result = NamedFunction.getInstance("iif").invoke(iifInput); + final Collection result = NamedFunction.getInstance("iif").invoke(iifInput); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn(ID_ALIAS) @@ -231,7 +231,7 @@ void returnsCorrectResultsForTwoNonLiterals() { assertThat(result) .hasExpression("valueBoolean.iif($this, someInteger, anotherInteger)") .isNotSingular() - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .selectOrderedResultWithEid() .hasRowsUnordered(expectedDataset); } @@ -247,7 +247,7 @@ void returnsCorrectResultsForLiteralAndNonLiteral() { .withRow("observation-5") .build(); when(dataSource.read(ResourceType.OBSERVATION)).thenReturn(inputContextDataset); - final ResourcePath inputContext = new ResourcePathBuilder(spark) + final ResourceCollection inputContext = new ResourcePathBuilder(spark) .expression("Observation") .resourceType(ResourceType.OBSERVATION) .database(dataSource) @@ -265,7 +265,7 @@ void returnsCorrectResultsForLiteralAndNonLiteral() { .withRow("observation-5", makeEid(0), null) .withRow("observation-5", makeEid(1), null) .build(); - final ElementPath inputPath = new ElementPathBuilder(spark) + final PrimitivePath inputPath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BOOLEAN) .dataset(inputDataset) .idAndEidAndValueColumns() @@ -287,7 +287,7 @@ void returnsCorrectResultsForLiteralAndNonLiteral() { .withRow("observation-4", makeEid(0), 4) .withRow("observation-5", makeEid(0), 5) .build(); - final ElementPath ifTrue = new ElementPathBuilder(spark) + final PrimitivePath ifTrue = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .dataset(ifTrueDataset) .idAndEidAndValueColumns() @@ -299,7 +299,7 @@ void returnsCorrectResultsForLiteralAndNonLiteral() { final NamedFunctionInput iifInput1 = new NamedFunctionInput(parserContext, inputPath, Arrays.asList(condition, ifTrue, otherwise)); - final FhirPath result1 = NamedFunction.getInstance("iif").invoke(iifInput1); + final Collection result1 = NamedFunction.getInstance("iif").invoke(iifInput1); final Dataset expectedDataset1 = new DatasetBuilder(spark) .withIdColumn(ID_ALIAS) @@ -316,13 +316,13 @@ void returnsCorrectResultsForLiteralAndNonLiteral() { assertThat(result1) .hasExpression("valueBoolean.iif($this, someInteger, 99)") .isNotSingular() - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .selectOrderedResultWithEid() .hasRows(expectedDataset1); final NamedFunctionInput iifInput2 = new NamedFunctionInput(parserContext, inputPath, Arrays.asList(condition, otherwise, ifTrue)); - final FhirPath result2 = NamedFunction.getInstance("iif").invoke(iifInput2); + final Collection result2 = NamedFunction.getInstance("iif").invoke(iifInput2); final Dataset expectedDataset2 = new DatasetBuilder(spark) .withIdColumn(ID_ALIAS) @@ -339,20 +339,20 @@ void returnsCorrectResultsForLiteralAndNonLiteral() { assertThat(result2) .hasExpression("valueBoolean.iif($this, 99, someInteger)") .isNotSingular() - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .selectOrderedResultWithEid() .hasRows(expectedDataset2); } @Test void throwsErrorIfConditionNotBoolean() { - final ElementPath condition = new ElementPathBuilder(spark) + final PrimitivePath condition = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .expression("valueInteger") .singular(true) .build(); - final StringLiteralPath ifTrue = StringLiteralPath.fromString("foo", condition); - final StringLiteralPath otherwise = StringLiteralPath.fromString("bar", condition); + final StringLiteralPath ifTrue = StringCollection.fromLiteral("foo", condition); + final StringLiteralPath otherwise = StringCollection.fromLiteral("bar", condition); final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, Arrays.asList(condition, ifTrue, otherwise)); @@ -368,13 +368,13 @@ void throwsErrorIfConditionNotBoolean() { @Test void throwsErrorIfConditionNotSingular() { - final ElementPath condition = new ElementPathBuilder(spark) + final PrimitivePath condition = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BOOLEAN) .expression("valueBoolean") .singular(false) .build(); - final StringLiteralPath ifTrue = StringLiteralPath.fromString("foo", condition); - final StringLiteralPath otherwise = StringLiteralPath.fromString("bar", condition); + final StringLiteralPath ifTrue = StringCollection.fromLiteral("foo", condition); + final StringLiteralPath otherwise = StringCollection.fromLiteral("bar", condition); final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, Arrays.asList(condition, ifTrue, otherwise)); @@ -390,13 +390,13 @@ void throwsErrorIfConditionNotSingular() { @Test void throwsErrorIfNoThisInCondition() { - final ElementPath condition = new ElementPathBuilder(spark) + final PrimitivePath condition = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BOOLEAN) .expression("valueBoolean") .singular(true) .build(); - final StringLiteralPath ifTrue = StringLiteralPath.fromString("foo", condition); - final StringLiteralPath otherwise = StringLiteralPath.fromString("bar", condition); + final StringLiteralPath ifTrue = StringCollection.fromLiteral("foo", condition); + final StringLiteralPath otherwise = StringCollection.fromLiteral("bar", condition); final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, Arrays.asList(condition, ifTrue, otherwise)); @@ -418,7 +418,7 @@ void throwsErrorIfResultArgumentsDifferentTypes() { .singular(true) .build() .toThisPath(); - final StringLiteralPath ifTrue = StringLiteralPath.fromString("foo", condition); + final StringLiteralPath ifTrue = StringCollection.fromLiteral("foo", condition); final IntegerLiteralPath otherwise = IntegerLiteralPath.fromString("99", condition); final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, @@ -441,11 +441,11 @@ void throwsErrorIfResultArgumentsAreBackboneElements() { .singular(true) .build() .toThisPath(); - final ElementPath ifTrue = new ElementPathBuilder(spark) + final PrimitivePath ifTrue = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BACKBONEELEMENT) .expression("someBackboneElement") .build(); - final ElementPath otherwise = new ElementPathBuilder(spark) + final PrimitivePath otherwise = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BACKBONEELEMENT) .expression("anotherBackboneElement") .build(); @@ -470,11 +470,11 @@ void throwsErrorWithIncompatibleResourceResults() { .singular(true) .build() .toThisPath(); - final ResourcePath ifTrue = new ResourcePathBuilder(spark) + final ResourceCollection ifTrue = new ResourcePathBuilder(spark) .expression("someResource") .resourceType(ResourceType.PATIENT) .build(); - final ResourcePath otherwise = new ResourcePathBuilder(spark) + final ResourceCollection otherwise = new ResourcePathBuilder(spark) .expression("anotherResource") .resourceType(ResourceType.CONDITION) .build(); @@ -499,11 +499,11 @@ void throwsErrorWithResourceAndLiteralResults() { .singular(true) .build() .toThisPath(); - final ResourcePath ifTrue = new ResourcePathBuilder(spark) + final ResourceCollection ifTrue = new ResourcePathBuilder(spark) .expression("someResource") .resourceType(ResourceType.PATIENT) .build(); - final StringLiteralPath otherwise = StringLiteralPath.fromString("foo", condition); + final StringLiteralPath otherwise = StringCollection.fromLiteral("foo", condition); final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, Arrays.asList(condition, ifTrue, otherwise)); @@ -528,7 +528,7 @@ void throwsErrorWithUntypedResourceAndLiteralResults() { final UntypedResourcePath ifTrue = new UntypedResourcePathBuilder(spark) .expression("someUntypedResource") .build(); - final StringLiteralPath otherwise = StringLiteralPath.fromString("foo", condition); + final StringLiteralPath otherwise = StringCollection.fromLiteral("foo", condition); final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, Arrays.asList(condition, ifTrue, otherwise)); @@ -550,11 +550,11 @@ void throwsErrorWithElementAndResourceResults() { .singular(true) .build() .toThisPath(); - final ElementPath ifTrue = new ElementPathBuilder(spark) + final PrimitivePath ifTrue = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .expression("someString") .build(); - final ResourcePath otherwise = new ResourcePathBuilder(spark) + final ResourceCollection otherwise = new ResourcePathBuilder(spark) .expression("someResource") .resourceType(ResourceType.CONDITION) .build(); diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/NotFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/NotFunctionTest.java index 7d7ad67379..9ef49d9447 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/NotFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/NotFunctionTest.java @@ -23,11 +23,12 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.BooleanPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.DatasetBuilder; import au.csiro.pathling.test.builders.ElementPathBuilder; @@ -69,7 +70,7 @@ void returnsCorrectResults() { .withRow("observation-5", makeEid(0), null) .withRow("observation-5", makeEid(1), null) .build(); - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BOOLEAN) .dataset(dataset) .idAndEidAndValueColumns() @@ -80,7 +81,7 @@ void returnsCorrectResults() { final NamedFunctionInput notInput = new NamedFunctionInput(parserContext, input, Collections.emptyList()); final NamedFunction notFunction = NamedFunction.getInstance("not"); - final FhirPath result = notFunction.invoke(notInput); + final Collection result = notFunction.invoke(notInput); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn() @@ -96,16 +97,16 @@ void returnsCorrectResults() { assertThat(result) .hasExpression("valueBoolean.not()") .isNotSingular() - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectOrderedResult() .hasRows(expectedDataset); } @Test void inputMustNotContainArguments() { - final ElementPath input = new ElementPathBuilder(spark).build(); - final StringLiteralPath argument = StringLiteralPath - .fromString("'some argument'", input); + final PrimitivePath input = new ElementPathBuilder(spark).build(); + final StringLiteralPath argument = StringCollection + .fromLiteral("'some argument'", input); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); final NamedFunctionInput notInput = new NamedFunctionInput(parserContext, input, @@ -122,7 +123,7 @@ void inputMustNotContainArguments() { @Test void throwsErrorIfInputNotBoolean() { - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .expression("valueInteger") .build(); diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/OfTypeFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/OfTypeFunctionTest.java index 782519e0de..69131f18a2 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/OfTypeFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/OfTypeFunctionTest.java @@ -27,9 +27,10 @@ import static org.mockito.Mockito.when; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.UntypedResourcePath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.collection.UntypedResourcePath; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; @@ -99,7 +100,7 @@ void resolvesPolymorphicReference() { .build(); when(database.read(eq(ResourceType.PATIENT))) .thenReturn(argumentDataset); - final ResourcePath argumentPath = new ResourcePathBuilder(spark) + final ResourceCollection argumentPath = new ResourcePathBuilder(spark) .database(database) .resourceType(ResourceType.PATIENT) .expression("Patient") @@ -112,10 +113,10 @@ void resolvesPolymorphicReference() { final NamedFunctionInput ofTypeInput = new NamedFunctionInput(parserContext, inputPath, Collections.singletonList(argumentPath)); final NamedFunction ofType = NamedFunction.getInstance("ofType"); - final FhirPath result = ofType.invoke(ofTypeInput); + final Collection result = ofType.invoke(ofTypeInput); - assertTrue(result instanceof ResourcePath); - assertThat((ResourcePath) result) + assertTrue(result instanceof ResourceCollection); + assertThat((ResourceCollection) result) .hasExpression("subject.resolve().ofType(Patient)") .isNotSingular() .hasResourceType(ResourceType.PATIENT); @@ -140,10 +141,10 @@ void resolvesPolymorphicReference() { @Test void throwsErrorIfInputNotPolymorphic() { - final ResourcePath input = new ResourcePathBuilder(spark) + final ResourceCollection input = new ResourcePathBuilder(spark) .expression("Patient") .build(); - final ResourcePath argument = new ResourcePathBuilder(spark).build(); + final ResourceCollection argument = new ResourcePathBuilder(spark).build(); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); final NamedFunctionInput ofTypeInput = new NamedFunctionInput(parserContext, input, @@ -163,10 +164,10 @@ void throwsErrorIfMoreThanOneArgument() { final UntypedResourcePath input = new UntypedResourcePathBuilder(spark) .expression("subject") .build(); - final ResourcePath argument1 = new ResourcePathBuilder(spark) + final ResourceCollection argument1 = new ResourcePathBuilder(spark) .expression("Patient") .build(); - final ResourcePath argument2 = new ResourcePathBuilder(spark) + final ResourceCollection argument2 = new ResourcePathBuilder(spark) .expression("Condition") .build(); @@ -188,8 +189,8 @@ void throwsErrorIfArgumentNotResource() { final UntypedResourcePath input = new UntypedResourcePathBuilder(spark) .expression("subject") .build(); - final StringLiteralPath argument = StringLiteralPath - .fromString("'some string'", input); + final StringLiteralPath argument = StringCollection + .fromLiteral("'some string'", input); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); final NamedFunctionInput ofTypeInput = new NamedFunctionInput(parserContext, input, diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ResolveFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ResolveFunctionTest.java index dad57a3c15..c138c6a85b 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ResolveFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ResolveFunctionTest.java @@ -26,11 +26,12 @@ import au.csiro.pathling.encoders.FhirEncoders; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.UntypedResourcePath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.collection.StringCollection; +import au.csiro.pathling.fhirpath.collection.UntypedResourcePath; import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; @@ -95,7 +96,7 @@ void simpleResolve() { .withRow("encounter-4", makeEid(0), RowFactory.create(null, "EpisodeOfCare/episodeofcare-2", null)) .buildWithStructValue(); - final ElementPath referencePath = new ElementPathBuilder(spark) + final PrimitivePath referencePath = new ElementPathBuilder(spark) .expression("Encounter.episodeOfCare") .dataset(referenceDataset) .idAndEidAndValueColumns() @@ -113,10 +114,10 @@ void simpleResolve() { when(dataSource.read(ResourceType.EPISODEOFCARE)).thenReturn(episodeOfCareDataset); final NamedFunctionInput resolveInput = buildFunctionInput(referencePath); - final FhirPath result = invokeResolve(resolveInput); + final Collection result = invokeResolve(resolveInput); - assertTrue(result instanceof ResourcePath); - assertThat((ResourcePath) result) + assertTrue(result instanceof ResourceCollection); + assertThat((ResourceCollection) result) .hasExpression("Encounter.episodeOfCare.resolve()") .isNotSingular() .hasResourceType(ResourceType.EPISODEOFCARE); @@ -150,7 +151,7 @@ void polymorphicResolve() { .withRow("encounter-4", RowFactory.create(null, "Patient/patient-2", null)) .withRow("encounter-5", RowFactory.create(null, "Group/group-1", null)) .buildWithStructValue(); - final ElementPath referencePath = new ElementPathBuilder(spark) + final PrimitivePath referencePath = new ElementPathBuilder(spark) .expression("Encounter.subject") .dataset(referenceDataset) .idAndValueColumns() @@ -179,7 +180,7 @@ void polymorphicResolve() { .thenReturn(groupDataset); final NamedFunctionInput resolveInput = buildFunctionInput(referencePath); - final FhirPath result = invokeResolve(resolveInput); + final Collection result = invokeResolve(resolveInput); assertTrue(result instanceof UntypedResourcePath); assertThat(result) @@ -218,7 +219,7 @@ void polymorphicResolveAnyType() { .withRow("condition-2", makeEid(0), RowFactory.create(null, "ClinicalImpression/clinicalimpression-1", null)) .buildWithStructValue(); - final ElementPath referencePath = new ElementPathBuilder(spark) + final PrimitivePath referencePath = new ElementPathBuilder(spark) .expression("Condition.evidence.detail") .dataset(referenceDataset) .idAndEidAndValueColumns() @@ -243,7 +244,7 @@ void polymorphicResolveAnyType() { .thenReturn(clinicalImpressionDataset); final NamedFunctionInput resolveInput = buildFunctionInput(referencePath); - final FhirPath result = invokeResolve(resolveInput); + final Collection result = invokeResolve(resolveInput); assertTrue(result instanceof UntypedResourcePath); assertThat(result) @@ -269,7 +270,7 @@ void throwExceptionWhenInputNotReference() { .withIdColumn() .withColumn(DataTypes.StringType) .build(); - final ElementPath genderPath = new ElementPathBuilder(spark) + final PrimitivePath genderPath = new ElementPathBuilder(spark) .expression("Patient.gender") .dataset(patientDataset) .idAndValueColumns() @@ -285,14 +286,14 @@ void throwExceptionWhenInputNotReference() { @Test void throwExceptionWhenArgumentSupplied() { - final ElementPath referencePath = new ElementPathBuilder(spark) + final PrimitivePath referencePath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.REFERENCE) .build(); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .build(); - final StringLiteralPath stringLiteralPath = StringLiteralPath - .fromString("'foo'", parserContext.getInputContext()); + final StringLiteralPath stringLiteralPath = StringCollection + .fromLiteral("'foo'", parserContext.getInputContext()); final NamedFunctionInput resolveInput = new NamedFunctionInput(parserContext, referencePath, Collections.singletonList(stringLiteralPath)); @@ -310,7 +311,7 @@ NamedFunctionInput buildFunctionInput(@Nonnull final NonLiteralPath inputPath) { } @Nonnull - FhirPath invokeResolve(@Nonnull final NamedFunctionInput resolveInput) { + Collection invokeResolve(@Nonnull final NamedFunctionInput resolveInput) { final NamedFunction resolve = NamedFunction.getInstance("resolve"); return resolve.invoke(resolveInput); } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java index 50a85a1e3f..283b2cca33 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java @@ -17,7 +17,6 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.QueryHelpers.join; import static au.csiro.pathling.test.assertions.Assertions.assertThat; import static au.csiro.pathling.test.helpers.SparkHelpers.getIdAndValueColumns; import static au.csiro.pathling.test.helpers.SparkHelpers.referenceStructType; @@ -28,10 +27,10 @@ import au.csiro.pathling.QueryHelpers.JoinType; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.test.SpringBootUnitTest; @@ -88,8 +87,8 @@ void reverseResolve() { .build(); when(database.read(ResourceType.PATIENT)) .thenReturn(patientDataset); - final ResourcePath inputPath = ResourcePath - .build(fhirContext, database, ResourceType.PATIENT, "Patient", true); + final ResourceCollection inputPath = ResourceCollection + .build(fhirContext, database, ResourceType.PATIENT, "Patient"); final DatasetBuilder encounterDatasetBuilder = new ResourceDatasetBuilder(spark) .withIdColumn() @@ -101,8 +100,8 @@ void reverseResolve() { .withRow("encounter-5", "onleave"); final Dataset encounterDataset = encounterDatasetBuilder.build(); when(database.read(ResourceType.ENCOUNTER)).thenReturn(encounterDataset); - final ResourcePath originPath = ResourcePath - .build(fhirContext, database, ResourceType.ENCOUNTER, "Encounter", false); + final ResourceCollection originPath = ResourceCollection + .build(fhirContext, database, ResourceType.ENCOUNTER, "Encounter"); final Optional optionalDefinition = FhirHelpers .getChildOfResource(fhirContext, "Encounter", "subject"); @@ -124,7 +123,7 @@ void reverseResolve() { final Dataset argumentDataset = join(originPath.getDataset(), originPath.getIdColumn(), argumentDatasetPreJoin, idColumn, JoinType.LEFT_OUTER); - final FhirPath argumentPath = new ElementPathBuilder(spark) + final Collection argumentPath = new ElementPathBuilder(spark) .dataset(argumentDataset) .idColumn(originPath.getIdColumn()) .valueColumn(valueColumn) @@ -142,10 +141,10 @@ void reverseResolve() { final NamedFunctionInput reverseResolveInput = new NamedFunctionInput(parserContext, inputPath, Collections.singletonList(argumentPath)); final NamedFunction reverseResolve = NamedFunction.getInstance("reverseResolve"); - final FhirPath result = reverseResolve.invoke(reverseResolveInput); + final Collection result = reverseResolve.invoke(reverseResolveInput); - assertTrue(result instanceof ResourcePath); - assertThat((ResourcePath) result) + assertTrue(result instanceof ResourceCollection); + assertThat((ResourceCollection) result) .hasExpression("reverseResolve(Encounter.subject)") .isNotSingular() .hasResourceType(ResourceType.ENCOUNTER); @@ -166,11 +165,11 @@ void reverseResolve() { @Test void throwsErrorIfInputNotResource() { - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .expression("gender") .fhirType(FHIRDefinedType.CODE) .build(); - final ElementPath argument = new ElementPathBuilder(spark) + final PrimitivePath argument = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.REFERENCE) .build(); @@ -189,8 +188,8 @@ void throwsErrorIfInputNotResource() { @Test void throwsErrorIfArgumentIsNotReference() { - final ResourcePath input = new ResourcePathBuilder(spark).build(); - final ElementPath argument = new ElementPathBuilder(spark) + final ResourceCollection input = new ResourcePathBuilder(spark).build(); + final PrimitivePath argument = new ElementPathBuilder(spark) .expression("gender") .fhirType(FHIRDefinedType.CODE) .build(); @@ -210,14 +209,14 @@ void throwsErrorIfArgumentIsNotReference() { @Test void throwsErrorIfMoreThanOneArgument() { - final ResourcePath input = new ResourcePathBuilder(spark) + final ResourceCollection input = new ResourcePathBuilder(spark) .expression("Patient") .build(); - final ElementPath argument1 = new ElementPathBuilder(spark) + final PrimitivePath argument1 = new ElementPathBuilder(spark) .expression("Encounter.subject") .fhirType(FHIRDefinedType.REFERENCE) .build(); - final ElementPath argument2 = new ElementPathBuilder(spark) + final PrimitivePath argument2 = new ElementPathBuilder(spark) .expression("Encounter.participant.individual") .fhirType(FHIRDefinedType.REFERENCE) .build(); @@ -239,7 +238,7 @@ void throwsErrorIfMoreThanOneArgument() { @Test void throwsErrorIfArgumentTypeDoesNotMatchInput() { - final ResourcePath input = new ResourcePathBuilder(spark) + final ResourceCollection input = new ResourcePathBuilder(spark) .resourceType(ResourceType.PATIENT) .expression("Patient") .build(); @@ -248,7 +247,7 @@ void throwsErrorIfArgumentTypeDoesNotMatchInput() { final BaseRuntimeChildDefinition childDefinition = hapiResourceDef.getChildByName( "episodeOfCare"); final ElementDefinition definition = ElementDefinition.build(childDefinition); - final ElementPath argument = new ElementPathBuilder(spark) + final PrimitivePath argument = new ElementPathBuilder(spark) .expression("Encounter.episodeOfCare") .fhirType(FHIRDefinedType.REFERENCE) .definition(definition) diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/SumFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/SumFunctionTest.java index b916f2d995..865c6d85c3 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/SumFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/SumFunctionTest.java @@ -23,10 +23,10 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.DecimalPath; -import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.element.IntegerPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.DecimalCollection; +import au.csiro.pathling.fhirpath.collection.IntegerCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.DatasetBuilder; @@ -70,7 +70,7 @@ void returnsCorrectIntegerResult() { .withRow("observation-3", makeEid(0), -1) .withRow("observation-3", makeEid(1), null) .build(); - final ElementPath inputPath = new ElementPathBuilder(spark) + final PrimitivePath inputPath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .dataset(inputDataset) .idAndEidAndValueColumns() @@ -83,7 +83,7 @@ void returnsCorrectIntegerResult() { final NamedFunctionInput sumInput = new NamedFunctionInput(parserContext, inputPath, Collections.emptyList()); - final FhirPath result = NamedFunction.getInstance("sum").invoke(sumInput); + final Collection result = NamedFunction.getInstance("sum").invoke(sumInput); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn() @@ -95,7 +95,7 @@ void returnsCorrectIntegerResult() { assertThat(result) .hasExpression("valueInteger.sum()") .isSingular() - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .selectResult() .hasRows(expectedDataset); } @@ -112,7 +112,7 @@ void returnsCorrectDecimalResult() { .withRow("observation-2", null, null) .withRow("observation-3", makeEid(0), new BigDecimal("-2.50")) .build(); - final ElementPath inputPath = new ElementPathBuilder(spark) + final PrimitivePath inputPath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.DECIMAL) .dataset(inputDataset) .idAndEidAndValueColumns() @@ -125,7 +125,7 @@ void returnsCorrectDecimalResult() { final NamedFunctionInput sumInput = new NamedFunctionInput(parserContext, inputPath, Collections.emptyList()); - final FhirPath result = NamedFunction.getInstance("sum").invoke(sumInput); + final Collection result = NamedFunction.getInstance("sum").invoke(sumInput); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn() @@ -137,14 +137,14 @@ void returnsCorrectDecimalResult() { assertThat(result) .hasExpression("valueDecimal.sum()") .isSingular() - .isElementPath(DecimalPath.class) + .isElementPath(DecimalCollection.class) .selectResult() .hasRows(expectedDataset); } @Test void throwsErrorIfInputNotNumeric() { - final ElementPath inputPath = new ElementPathBuilder(spark) + final PrimitivePath inputPath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .expression("valueString") .build(); diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/UntilFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/UntilFunctionTest.java index 39275b4eac..46dd7c159a 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/UntilFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/UntilFunctionTest.java @@ -25,14 +25,14 @@ import static org.mockito.Mockito.when; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.element.DatePath; -import au.csiro.pathling.fhirpath.element.DateTimePath; -import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.element.IntegerPath; -import au.csiro.pathling.fhirpath.element.ReferencePath; -import au.csiro.pathling.fhirpath.element.StringPath; +import au.csiro.pathling.fhirpath.collection.DateCollection; +import au.csiro.pathling.fhirpath.collection.DateTimeCollection; +import au.csiro.pathling.fhirpath.collection.IntegerCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.ReferencePath; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.literal.DateLiteralPath; import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; @@ -47,7 +47,6 @@ import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.stream.Stream; @@ -139,7 +138,7 @@ static class TestParameters { NonLiteralPath input; @Nonnull - List arguments; + List arguments; @Nonnull ParserContext context; @@ -156,15 +155,15 @@ public String toString() { @Nonnull Stream parameters() { - final Collection parameters = new ArrayList<>(); + final java.util.Collection parameters = new ArrayList<>(); for (final String calendarDuration : CALENDAR_DURATION_TO_RESULT.keySet()) { - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.DATETIME) .dataset(leftDataset()) .idAndValueColumns() .singular(true) .build(); - final ElementPath argument = new ElementPathBuilder(spark) + final PrimitivePath argument = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.DATETIME) .dataset(rightDataset()) .idAndValueColumns() @@ -182,8 +181,8 @@ Stream parameters() { assertNotNull(results); return results[index]; }).build(); - final List arguments = List.of(argument, - StringLiteralPath.fromString(calendarDuration, input)); + final List arguments = List.of(argument, + StringCollection.fromLiteral(calendarDuration, input)); parameters.add( new TestParameters(calendarDuration, input, arguments, context, expectedResult)); } @@ -195,9 +194,9 @@ Stream parameters() { void test(@Nonnull final TestParameters parameters) { final NamedFunctionInput input = new NamedFunctionInput(parameters.getContext(), parameters.getInput(), parameters.getArguments()); - final FhirPath result = NamedFunction.getInstance("until").invoke(input); + final Collection result = NamedFunction.getInstance("until").invoke(input); assertThat(result) - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .selectResult() .hasRows(parameters.getExpectedResult()); } @@ -214,13 +213,13 @@ void milliseconds() { .withColumn(DataTypes.StringType) .withRow("patient-1", "2020-01-02") .build(); - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.DATE) .dataset(leftDataset) .idAndValueColumns() .singular(true) .build(); - final ElementPath argument = new ElementPathBuilder(spark) + final PrimitivePath argument = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.DATE) .dataset(rightDataset) .idAndValueColumns() @@ -234,15 +233,15 @@ void milliseconds() { .withColumn(DataTypes.IntegerType) .withRow("patient-1", 86400000) .build(); - final List arguments1 = List.of(argument, - StringLiteralPath.fromString("millisecond", input)); - final List arguments2 = List.of(argument, - StringLiteralPath.fromString("millisecond", input)); - for (final List arguments : List.of(arguments1, arguments2)) { + final List arguments1 = List.of(argument, + StringCollection.fromLiteral("millisecond", input)); + final List arguments2 = List.of(argument, + StringCollection.fromLiteral("millisecond", input)); + for (final List arguments : List.of(arguments1, arguments2)) { final NamedFunctionInput functionInput = new NamedFunctionInput(context, input, arguments); - final FhirPath result = NamedFunction.getInstance("until").invoke(functionInput); + final Collection result = NamedFunction.getInstance("until").invoke(functionInput); assertThat(result) - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .selectResult() .hasRows(expectedResult); } @@ -255,13 +254,13 @@ void dateLiteralArgument() throws ParseException { .withColumn(DataTypes.StringType) .withRow("patient-1", "2020-01-01") .build(); - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.DATE) .dataset(leftDataset) .idAndValueColumns() .singular(true) .build(); - final DateLiteralPath argument = DateLiteralPath.fromString("2020-01-02", input); + final DateLiteralPath argument = DateCollection.fromLiteral("2020-01-02", input); final ParserContext context = new ParserContextBuilder(spark, fhirContext) .groupingColumns(Collections.singletonList(input.getIdColumn())) .build(); @@ -270,12 +269,12 @@ void dateLiteralArgument() throws ParseException { .withColumn(DataTypes.IntegerType) .withRow("patient-1", 86400000) .build(); - final List arguments = List.of(argument, - StringLiteralPath.fromString("millisecond", input)); + final List arguments = List.of(argument, + StringCollection.fromLiteral("millisecond", input)); final NamedFunctionInput functionInput = new NamedFunctionInput(context, input, arguments); - final FhirPath result = NamedFunction.getInstance("until").invoke(functionInput); + final Collection result = NamedFunction.getInstance("until").invoke(functionInput); assertThat(result) - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .selectResult() .hasRows(expectedResult); } @@ -287,7 +286,7 @@ void dateTimeLiteralArgument() throws ParseException { .withColumn(DataTypes.StringType) .withRow("patient-1", "2020-01-01") .build(); - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.DATE) .dataset(leftDataset) .idAndValueColumns() @@ -303,12 +302,12 @@ void dateTimeLiteralArgument() throws ParseException { .withColumn(DataTypes.IntegerType) .withRow("patient-1", 86400000) .build(); - final List arguments = List.of(argument, - StringLiteralPath.fromString("millisecond", input)); + final List arguments = List.of(argument, + StringCollection.fromLiteral("millisecond", input)); final NamedFunctionInput functionInput = new NamedFunctionInput(context, input, arguments); - final FhirPath result = NamedFunction.getInstance("until").invoke(functionInput); + final Collection result = NamedFunction.getInstance("until").invoke(functionInput); assertThat(result) - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .selectResult() .hasRows(expectedResult); } @@ -320,19 +319,19 @@ void invalidCalendarDuration() { .withColumn(DataTypes.StringType) .withRow("patient-1", "2020-01-01") .build(); - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.DATE) .dataset(leftDataset) .idAndValueColumns() .singular(true) .build(); - final DateTimePath argument = mock(DateTimePath.class); + final DateTimeCollection argument = mock(DateTimeCollection.class); when(argument.isSingular()).thenReturn(true); final NamedFunctionInput functionInput = new NamedFunctionInput(mock(ParserContext.class), input, - List.of(argument, StringLiteralPath.fromString("nanosecond", input))); + List.of(argument, StringCollection.fromLiteral("nanosecond", input))); final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, () -> NamedFunction.getInstance("until").invoke(functionInput)); assertEquals("Invalid calendar duration: nanosecond", error.getMessage()); @@ -341,7 +340,7 @@ void invalidCalendarDuration() { @Test void wrongNumberOfArguments() { final NamedFunctionInput input = new NamedFunctionInput(mock(ParserContext.class), - mock(DateTimePath.class), List.of(mock(DateTimePath.class))); + mock(DateTimeCollection.class), List.of(mock(DateTimeCollection.class))); final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, () -> NamedFunction.getInstance("until").invoke(input)); assertEquals("until function must have two arguments", error.getMessage()); @@ -350,7 +349,8 @@ void wrongNumberOfArguments() { @Test void wrongInputType() { final NamedFunctionInput input = new NamedFunctionInput(mock(ParserContext.class), - mock(StringPath.class), List.of(mock(DateTimePath.class), mock(StringLiteralPath.class))); + mock(StringCollection.class), + List.of(mock(DateTimeCollection.class), mock(StringLiteralPath.class))); final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, () -> NamedFunction.getInstance("until").invoke(input)); assertEquals("until function must be invoked on a DateTime or Date", error.getMessage()); @@ -359,7 +359,8 @@ void wrongInputType() { @Test void wrongArgumentType() { final NamedFunctionInput input = new NamedFunctionInput(mock(ParserContext.class), - mock(DatePath.class), List.of(mock(ReferencePath.class), mock(StringLiteralPath.class))); + mock(DateCollection.class), + List.of(mock(ReferencePath.class), mock(StringLiteralPath.class))); final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, () -> NamedFunction.getInstance("until").invoke(input)); assertEquals("until function must have a DateTime or Date as the first argument", @@ -368,10 +369,10 @@ void wrongArgumentType() { @Test void inputNotSingular() { - final DateTimePath input = mock(DateTimePath.class); + final DateTimeCollection input = mock(DateTimeCollection.class); final NamedFunctionInput functionInput = new NamedFunctionInput(mock(ParserContext.class), input, List.of(mock(DateTimeLiteralPath.class), - StringLiteralPath.fromString("nanosecond", input))); + StringCollection.fromLiteral("nanosecond", input))); final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, () -> NamedFunction.getInstance("until").invoke(functionInput)); assertEquals("until function must be invoked on a singular path", error.getMessage()); @@ -379,12 +380,12 @@ void inputNotSingular() { @Test void argumentNotSingular() { - final DateTimePath input = mock(DateTimePath.class); + final DateTimeCollection input = mock(DateTimeCollection.class); when(input.isSingular()).thenReturn(true); final NamedFunctionInput functionInput = new NamedFunctionInput(mock(ParserContext.class), input, List.of(mock(DateTimeLiteralPath.class), - StringLiteralPath.fromString("nanosecond", input))); + StringCollection.fromLiteral("nanosecond", input))); final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, () -> NamedFunction.getInstance("until").invoke(functionInput)); assertEquals("until function must have the singular path as its first argument", diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/WhereFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/WhereFunctionTest.java index 473f3eabd3..a84aacf01b 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/WhereFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/WhereFunctionTest.java @@ -22,13 +22,12 @@ import static au.csiro.pathling.utilities.Strings.randomAlias; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; @@ -84,7 +83,7 @@ void whereOnResource() { .withRow("patient-5", makeEid(0), "encounter-9", "in-progress") .withRow("patient-6", null, null, null) .build(); - final ResourcePath inputPath = new ResourcePathBuilder(spark) + final ResourceCollection inputPath = new ResourcePathBuilder(spark) .expression("reverseResolve(Encounter.subject)") .dataset(inputDataset) .idEidAndValueColumns() @@ -100,7 +99,7 @@ void whereOnResource() { thisPath.getDataset().col(statusColumn).equalTo("in-progress")); assertTrue(thisPath.getThisColumn().isPresent()); - final ElementPath argumentPath = new ElementPathBuilder(spark) + final PrimitivePath argumentPath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BOOLEAN) .dataset(argumentDataset) .idColumn(inputPath.getIdColumn()) @@ -116,7 +115,7 @@ void whereOnResource() { // Execute the function. final NamedFunction whereFunction = NamedFunction.getInstance("where"); - final FhirPath result = whereFunction.invoke(whereInput); + final Collection result = whereFunction.invoke(whereInput); // Check the result dataset. final Dataset expectedDataset = new DatasetBuilder(spark) @@ -157,7 +156,7 @@ void whereOnElement() { .withRow("patient-4", makeEid(0), "fr") .withRow("patient-5", null, null) .build(); - final ElementPath inputPath = new ElementPathBuilder(spark) + final PrimitivePath inputPath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .dataset(dataset) .idAndEidAndValueColumns() @@ -171,7 +170,7 @@ void whereOnElement() { .withColumn("value", inputPath.getValueColumn().equalTo("en")); assertTrue(thisPath.getThisColumn().isPresent()); - final ElementPath argumentExpression = new ElementPathBuilder(spark) + final PrimitivePath argumentExpression = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BOOLEAN) .dataset(argumentDataset) .idColumn(inputPath.getIdColumn()) @@ -188,7 +187,7 @@ void whereOnElement() { // Execute the function. final NamedFunction whereFunction = NamedFunction.getInstance("where"); - final FhirPath result = whereFunction.invoke(whereInput); + final Collection result = whereFunction.invoke(whereInput); // Check the result dataset. final Dataset expectedDataset = new DatasetBuilder(spark) @@ -225,7 +224,7 @@ void nullValuesAreNull() { .withRow("patient-3", makeEid(2), "zh") .withRow("patient-4", makeEid(0), "ar") .build(); - final ElementPath inputPath = new ElementPathBuilder(spark) + final PrimitivePath inputPath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .dataset(dataset) .idAndEidAndValueColumns() @@ -239,7 +238,7 @@ void nullValuesAreNull() { .withColumn("value", functions.when(inputPath.getValueColumn().equalTo("en"), null).otherwise(true)); assertTrue(thisPath.getThisColumn().isPresent()); - final ElementPath argumentPath = new ElementPathBuilder(spark) + final PrimitivePath argumentPath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BOOLEAN) .dataset(argumentDataset) .idColumn(inputPath.getIdColumn()) @@ -255,7 +254,7 @@ void nullValuesAreNull() { // Execute the function. final NamedFunction whereFunction = NamedFunction.getInstance("where"); - final FhirPath result = whereFunction.invoke(whereInput); + final Collection result = whereFunction.invoke(whereInput); // Check the result dataset. final Dataset expectedDataset = new DatasetBuilder(spark) @@ -277,12 +276,12 @@ void nullValuesAreNull() { @Test void throwsErrorIfMoreThanOneArgument() { - final ResourcePath input = new ResourcePathBuilder(spark).build(); - final ElementPath argument1 = new ElementPathBuilder(spark) + final ResourceCollection input = new ResourcePathBuilder(spark).build(); + final PrimitivePath argument1 = new ElementPathBuilder(spark) .expression("$this.gender = 'female'") .fhirType(FHIRDefinedType.BOOLEAN) .build(); - final ElementPath argument2 = new ElementPathBuilder(spark) + final PrimitivePath argument2 = new ElementPathBuilder(spark) .expression("$this.gender != 'male'") .fhirType(FHIRDefinedType.BOOLEAN) .build(); @@ -300,8 +299,8 @@ void throwsErrorIfMoreThanOneArgument() { @Test void throwsErrorIfArgumentNotBoolean() { - final ResourcePath input = new ResourcePathBuilder(spark).build(); - final ElementPath argument = new ElementPathBuilder(spark) + final ResourceCollection input = new ResourcePathBuilder(spark).build(); + final PrimitivePath argument = new ElementPathBuilder(spark) .expression("$this.gender") .fhirType(FHIRDefinedType.STRING) .build(); @@ -321,8 +320,8 @@ void throwsErrorIfArgumentNotBoolean() { @Test void throwsErrorIfArgumentNotSingular() { - final ResourcePath input = new ResourcePathBuilder(spark).build(); - final ElementPath argument = new ElementPathBuilder(spark) + final ResourceCollection input = new ResourcePathBuilder(spark).build(); + final PrimitivePath argument = new ElementPathBuilder(spark) .expression("$this.communication.preferred") .fhirType(FHIRDefinedType.BOOLEAN) .singular(false) @@ -343,7 +342,7 @@ void throwsErrorIfArgumentNotSingular() { @Test void throwsErrorIfArgumentIsLiteral() { - final ResourcePath input = new ResourcePathBuilder(spark).build(); + final ResourceCollection input = new ResourcePathBuilder(spark).build(); final BooleanLiteralPath argument = BooleanLiteralPath .fromString("true", input); diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunctionTest.java index 50ad1509bc..be7005e2f1 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunctionTest.java @@ -28,16 +28,14 @@ import static org.mockito.Mockito.reset; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.CodingPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.collection.CodingCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.literal.CodingLiteral; -import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.terminology.TerminologyService; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -122,7 +120,7 @@ private void checkDesignationsOfCoding( .withRow("encounter-3", null, null) .buildWithStructValue(); - final CodingPath inputExpression = (CodingPath) new ElementPathBuilder(spark) + final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) .dataset(inputDataset) .idAndEidAndValueColumns() .expression("Encounter.class") @@ -139,18 +137,18 @@ private void checkDesignationsOfCoding( final Optional maybeUseLiteral = maybeUse.map(CodingLiteral::toLiteral); final Optional maybeLanguageLiteral = maybeLanguage.map(lang -> "'" + lang + "'"); - final List arguments = Stream.of( + final List arguments = Stream.of( maybeUseLiteral - .map(useLiteral -> CodingLiteralPath.fromString(useLiteral, inputExpression)), + .map(useLiteral -> CodingCollection.fromLiteral(useLiteral, inputExpression)), maybeLanguageLiteral.map( - languageLiteral -> StringLiteralPath.fromString(languageLiteral, inputExpression)) + languageLiteral -> StringCollection.fromLiteral(languageLiteral, inputExpression)) ).flatMap(Optional::stream).collect(Collectors.toUnmodifiableList()); final NamedFunctionInput propertyInput = new NamedFunctionInput(parserContext, inputExpression, arguments); // Invoke the function. - final FhirPath result = NamedFunction.getInstance("designation").invoke(propertyInput); + final Collection result = NamedFunction.getInstance("designation").invoke(propertyInput); final String expectedExpression = String.format("Encounter.class.designation(%s)", Stream.of(maybeUseLiteral, maybeLanguageLiteral) @@ -158,7 +156,7 @@ private void checkDesignationsOfCoding( // Check the result. assertThat(result) .hasExpression(expectedExpression) - .isElementPath(ElementPath.class) + .isElementPath(PrimitivePath.class) .hasFhirType(FHIRDefinedType.STRING) .isNotSingular() .selectOrderedResultWithEid() @@ -239,12 +237,12 @@ public void designationWithDefaultLanguageAndUse() { @Test void throwsErrorIfInputTypeIsUnsupported() { - final FhirPath mockContext = new ElementPathBuilder(spark).build(); - final ElementPath input = new ElementPathBuilder(spark) + final Collection mockContext = new ElementPathBuilder(spark).build(); + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .expression("name.given") .build(); - final FhirPath argument = StringLiteralPath.fromString("some-property", mockContext); + final Collection argument = StringCollection.fromLiteral("some-property", mockContext); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .terminologyClientFactory(mock(TerminologyServiceFactory.class)) @@ -260,14 +258,14 @@ void throwsErrorIfInputTypeIsUnsupported() { } void assertThrowsErrorForArguments(@Nonnull final String expectedError, - @Nonnull final Function> argsFactory) { + @Nonnull final Function> argsFactory) { final Optional optionalDefinition = FhirHelpers .getChildOfResource(fhirContext, "Encounter", "class"); assertTrue(optionalDefinition.isPresent()); final ElementDefinition definition = optionalDefinition.get(); - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODING) .definition(definition) .buildDefined(); @@ -296,7 +294,7 @@ void throwsErrorIfFirstArgumentIsNotCoding() { void throwsErrorIfSecondArgumentIsNotBoolean() { assertThrowsErrorForArguments("Function `designation` expects `String literal` as argument 2", input -> Arrays.asList( - CodingLiteralPath.fromString("system|code", input), + CodingCollection.fromLiteral("system|code", input), IntegerLiteralPath.fromString("5", input))); } @@ -306,18 +304,18 @@ void throwsErrorIfTooManyArguments() { assertThrowsErrorForArguments( "designation function accepts two optional arguments", input -> Arrays.asList( - CodingLiteralPath.fromString("system|code", input), - StringLiteralPath.fromString("'false'", input), - StringLiteralPath.fromString("'false'", input) + CodingCollection.fromLiteral("system|code", input), + StringCollection.fromLiteral("'false'", input), + StringCollection.fromLiteral("'false'", input) )); } @Test void throwsErrorIfTerminologyServiceNotConfigured() { - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODING) .build(); - final FhirPath argument = StringLiteralPath.fromString("some string", input); + final Collection argument = StringCollection.fromLiteral("some string", input); final ParserContext context = new ParserContextBuilder(spark, fhirContext) .build(); diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunctionTest.java index 050e122138..970c5e24ab 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunctionTest.java @@ -29,12 +29,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.CodingPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.CodingCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; @@ -106,7 +106,7 @@ private void checkDisplayCoding(final Optional maybeLanguage, .withRow("encounter-3", null, null) .buildWithStructValue(); - final CodingPath inputExpression = (CodingPath) new ElementPathBuilder(spark) + final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) .dataset(inputDataset) .idAndEidAndValueColumns() .expression("Encounter.class") @@ -121,14 +121,14 @@ private void checkDisplayCoding(final Optional maybeLanguage, .build(); final Optional maybeArgumentExpression = maybeLanguage.map( - lang -> StringLiteralPath - .fromString("'" + lang + "'", inputExpression)); + lang -> StringCollection + .fromLiteral("'" + lang + "'", inputExpression)); final NamedFunctionInput displayInput = new NamedFunctionInput(parserContext, inputExpression, maybeArgumentExpression.stream().collect(Collectors.toUnmodifiableList())); // Invoke the function. - final FhirPath result = new DisplayFunction().invoke(displayInput); + final Collection result = new DisplayFunction().invoke(displayInput); final Dataset expectedResult = new DatasetBuilder(spark) .withIdColumn() @@ -144,7 +144,7 @@ private void checkDisplayCoding(final Optional maybeLanguage, assertThat(result) .hasExpression("Encounter.class.display(" + maybeArgumentExpression.map( StringLiteralPath::getExpression).orElse("") + ")") - .isElementPath(ElementPath.class) + .isElementPath(PrimitivePath.class) .hasFhirType(FHIRDefinedType.STRING) .isNotSingular() .selectOrderedResultWithEid() @@ -174,7 +174,7 @@ public void displayCodingLanguage() { @Test void throwsErrorIfTerminologyServiceNotConfigured() { - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODING) .build(); @@ -193,12 +193,12 @@ void throwsErrorIfTerminologyServiceNotConfigured() { @Test void inputMustNotContainTwoArguments() { - final ElementPath input = new ElementPathBuilder(spark).build(); - final StringLiteralPath argument1 = StringLiteralPath - .fromString("'some argument'", input); - final StringLiteralPath argument2 = StringLiteralPath - .fromString("'some argument'", input); - final List arguments = new ArrayList<>(); + final PrimitivePath input = new ElementPathBuilder(spark).build(); + final StringLiteralPath argument1 = StringCollection + .fromLiteral("'some argument'", input); + final StringLiteralPath argument2 = StringCollection + .fromLiteral("'some argument'", input); + final List arguments = new ArrayList<>(); arguments.add(argument1); arguments.add(argument2); @@ -217,7 +217,7 @@ void inputMustNotContainTwoArguments() { @Test void inputMustNotContainNonStringArgument() { - final ElementPath input = new ElementPathBuilder(spark).build(); + final PrimitivePath input = new ElementPathBuilder(spark).build(); final IntegerLiteralPath argument = IntegerLiteralPath .fromString("9493948", input); @@ -236,7 +236,7 @@ void inputMustNotContainNonStringArgument() { @Test void throwsErrorIfInputNotCoding() { - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .expression("valueInteger") .build(); diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunctionTest.java index 94c1e630f6..08eab3b6fa 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunctionTest.java @@ -37,12 +37,12 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.BooleanPath; -import au.csiro.pathling.fhirpath.element.CodingPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.function.NamedFunctionInput; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.CodingCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; @@ -121,7 +121,7 @@ void memberOfCoding() { .withRow("encounter-6", null, null) .buildWithStructValue(); - final CodingPath inputExpression = (CodingPath) new ElementPathBuilder(spark) + final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) .dataset(inputDataset) .idAndEidAndValueColumns() .expression("Encounter.class") @@ -129,8 +129,8 @@ void memberOfCoding() { .definition(definition) .buildDefined(); - final StringLiteralPath argumentExpression = StringLiteralPath - .fromString("'" + MY_VALUE_SET_URL + "'", inputExpression); + final StringLiteralPath argumentExpression = StringCollection + .fromLiteral("'" + MY_VALUE_SET_URL + "'", inputExpression); // Setup mocks TerminologyServiceHelpers.setupValidate(terminologyService) @@ -145,7 +145,7 @@ void memberOfCoding() { Collections.singletonList(argumentExpression)); // Invoke the function. - final FhirPath result = new MemberOfFunction().invoke(memberOfInput); + final Collection result = new MemberOfFunction().invoke(memberOfInput); // The outcome is somehow random with regard to the sequence passed to MemberOfMapperAnswerer. final Dataset expectedResult = new DatasetBuilder(spark) @@ -164,7 +164,7 @@ void memberOfCoding() { // Check the result. assertThat(result) .hasExpression("Encounter.class.memberOf('" + MY_VALUE_SET_URL + "')") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .hasFhirType(FHIRDefinedType.BOOLEAN) .isNotSingular() .selectOrderedResultWithEid() @@ -193,7 +193,7 @@ void memberOfEmptyCodingDatasetDoesNotCallTerminology() { .withStructTypeColumns(codingStructType()) .buildWithStructValue(); - final CodingPath inputExpression = (CodingPath) new ElementPathBuilder(spark) + final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) .dataset(inputDataset) .idAndEidAndValueColumns() .expression("Encounter.class") @@ -201,8 +201,8 @@ void memberOfEmptyCodingDatasetDoesNotCallTerminology() { .definition(definition) .buildDefined(); - final StringLiteralPath argumentExpression = StringLiteralPath - .fromString("'" + MY_VALUE_SET_URL + "'", inputExpression); + final StringLiteralPath argumentExpression = StringCollection + .fromLiteral("'" + MY_VALUE_SET_URL + "'", inputExpression); // Prepare the inputs to the function. final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) @@ -214,7 +214,7 @@ void memberOfEmptyCodingDatasetDoesNotCallTerminology() { Collections.singletonList(argumentExpression)); // Invoke the function. - final FhirPath result = new MemberOfFunction().invoke(memberOfInput); + final Collection result = new MemberOfFunction().invoke(memberOfInput); // The outcome is somehow random with regard to the sequence passed to MemberOfMapperAnswerer. final Dataset expectedResult = new DatasetBuilder(spark) @@ -226,7 +226,7 @@ void memberOfEmptyCodingDatasetDoesNotCallTerminology() { // Check the result. assertThat(result) .hasExpression("Encounter.class.memberOf('" + MY_VALUE_SET_URL + "')") - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .hasFhirType(FHIRDefinedType.BOOLEAN) .isNotSingular() .selectOrderedResultWithEid() @@ -272,7 +272,7 @@ void memberOfCodeableConcept() { .withRow("diagnosticreport-7", null) .buildWithStructValue(); - final ElementPath inputExpression = new ElementPathBuilder(spark) + final PrimitivePath inputExpression = new ElementPathBuilder(spark) .dataset(inputDataset) .idAndValueColumns() .expression("DiagnosticReport.code") @@ -280,8 +280,8 @@ void memberOfCodeableConcept() { .definition(definition) .buildDefined(); - final StringLiteralPath argumentExpression = StringLiteralPath - .fromString("'" + MY_VALUE_SET_URL + "'", inputExpression); + final StringLiteralPath argumentExpression = StringCollection + .fromLiteral("'" + MY_VALUE_SET_URL + "'", inputExpression); // Setup mocks: true for (codeableConcept1, codeableConcept3, codeableConcept4) TerminologyServiceHelpers.setupValidate(terminologyService) @@ -295,7 +295,7 @@ void memberOfCodeableConcept() { Collections.singletonList(argumentExpression)); // Invoke the function. - final FhirPath result = new MemberOfFunction().invoke(memberOfInput); + final Collection result = new MemberOfFunction().invoke(memberOfInput); final Dataset expectedResult = new DatasetBuilder(spark) .withIdColumn() @@ -310,12 +310,12 @@ void memberOfCodeableConcept() { .build(); // Check the result. - assertTrue(result instanceof BooleanPath); - assertThat((BooleanPath) result) + assertTrue(result instanceof BooleanCollection); + assertThat((BooleanCollection) result) .hasExpression("DiagnosticReport.code.memberOf('" + MY_VALUE_SET_URL + "')") .isSingular() .hasFhirType(FHIRDefinedType.BOOLEAN) - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .selectOrderedResult() .hasRows(expectedResult); @@ -328,12 +328,12 @@ void memberOfCodeableConcept() { @Test void throwsErrorIfInputTypeIsUnsupported() { - final FhirPath mockContext = new ElementPathBuilder(spark).build(); - final ElementPath input = new ElementPathBuilder(spark) + final Collection mockContext = new ElementPathBuilder(spark).build(); + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .expression("name.given") .build(); - final FhirPath argument = StringLiteralPath.fromString(MY_VALUE_SET_URL, mockContext); + final Collection argument = StringCollection.fromLiteral(MY_VALUE_SET_URL, mockContext); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .terminologyClientFactory(mock(TerminologyServiceFactory.class)) @@ -350,7 +350,7 @@ void throwsErrorIfInputTypeIsUnsupported() { @Test void throwsErrorIfArgumentIsNotString() { - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODEABLECONCEPT) .build(); final IntegerLiteralPath argument = IntegerLiteralPath.fromString("4", input); @@ -370,11 +370,11 @@ void throwsErrorIfArgumentIsNotString() { @Test void throwsErrorIfMoreThanOneArgument() { - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODEABLECONCEPT) .build(); - final StringLiteralPath argument1 = StringLiteralPath.fromString("'foo'", input), - argument2 = StringLiteralPath.fromString("'bar'", input); + final StringLiteralPath argument1 = StringCollection.fromLiteral("'foo'", input), + argument2 = StringCollection.fromLiteral("'bar'", input); final ParserContext context = new ParserContextBuilder(spark, fhirContext) .terminologyClientFactory(mock(TerminologyServiceFactory.class)) @@ -391,10 +391,10 @@ void throwsErrorIfMoreThanOneArgument() { @Test void throwsErrorIfTerminologyServiceNotConfigured() { - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODEABLECONCEPT) .build(); - final FhirPath argument = StringLiteralPath.fromString("some string", input); + final Collection argument = StringCollection.fromLiteral("some string", input); final ParserContext context = new ParserContextBuilder(spark, fhirContext) .build(); diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunctionTest.java index edad385673..dee8092d00 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunctionTest.java @@ -28,15 +28,14 @@ import static org.mockito.Mockito.reset; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.CodingPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.collection.CodingCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteral; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.terminology.TerminologyService; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -122,7 +121,7 @@ private void checkPropertyOfCoding(final String propertyCode, .withRow("encounter-3", null, null) .buildWithStructValue(); - final CodingPath inputExpression = (CodingPath) new ElementPathBuilder(spark) + final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) .dataset(inputDataset) .idAndEidAndValueColumns() .expression("Encounter.class") @@ -141,21 +140,21 @@ private void checkPropertyOfCoding(final String propertyCode, maybeLanguage).flatMap(Optional::stream) .map(StringLiteral::toLiteral).collect(Collectors.toUnmodifiableList()); - final List arguments = parameterLiterals.stream() - .map(lit -> StringLiteralPath.fromString(lit, inputExpression)) + final List arguments = parameterLiterals.stream() + .map(lit -> StringCollection.fromLiteral(lit, inputExpression)) .collect(Collectors.toUnmodifiableList()); final NamedFunctionInput propertyInput = new NamedFunctionInput(parserContext, inputExpression, arguments); // Invoke the function. - final FhirPath result = NamedFunction.getInstance("property").invoke(propertyInput); + final Collection result = NamedFunction.getInstance("property").invoke(propertyInput); // Check the result. assertThat(result).hasExpression( String.format("Encounter.class.property(%s)", String.join(", ", parameterLiterals))) - .isElementPath(ElementPath.class) + .isElementPath(PrimitivePath.class) .hasFhirType(expectedResultType) .isNotSingular() .selectOrderedResultWithEid() @@ -164,7 +163,7 @@ private void checkPropertyOfCoding(final String propertyCode, // Check that the ElementPath for CODING type has the correct Coding definition. if (FHIRDefinedType.CODING.equals(expectedResultType)) { assertThat(result) - .isElementPath(ElementPath.class) + .isElementPath(PrimitivePath.class) .hasDefinition(definition); } } @@ -271,12 +270,12 @@ public void propertyBOfCodingLanguage(final String propertyType, final DataType @Test void throwsErrorIfInputTypeIsUnsupported() { - final FhirPath mockContext = new ElementPathBuilder(spark).build(); - final ElementPath input = new ElementPathBuilder(spark) + final Collection mockContext = new ElementPathBuilder(spark).build(); + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .expression("name.given") .build(); - final FhirPath argument = StringLiteralPath.fromString("some-property", mockContext); + final Collection argument = StringCollection.fromLiteral("some-property", mockContext); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .terminologyClientFactory(mock(TerminologyServiceFactory.class)) @@ -292,14 +291,14 @@ void throwsErrorIfInputTypeIsUnsupported() { } void assertThrowsErrorForArguments(@Nonnull final String expectedError, - @Nonnull final Function> argsFactory) { + @Nonnull final Function> argsFactory) { final Optional optionalDefinition = FhirHelpers .getChildOfResource(fhirContext, "Encounter", "class"); assertTrue(optionalDefinition.isPresent()); final ElementDefinition definition = optionalDefinition.get(); - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODING) .definition(definition) .buildDefined(); @@ -336,7 +335,7 @@ void throwsErrorIfFirstArgumentIsNotString() { void throwsErrorIfSecondArgumentIsNotBoolean() { assertThrowsErrorForArguments("Function `property` expects `String literal` as argument 2", input -> Arrays.asList( - StringLiteralPath.fromString("'foo'", input), + StringCollection.fromLiteral("'foo'", input), IntegerLiteralPath.fromString("5", input))); } @@ -344,8 +343,8 @@ void throwsErrorIfSecondArgumentIsNotBoolean() { void throwsErrorIfThirdArgumentIsNotBoolean() { assertThrowsErrorForArguments("Function `property` expects `String literal` as argument 3", input -> Arrays.asList( - StringLiteralPath.fromString("'foo'", input), - StringLiteralPath.fromString("'foo'", input), + StringCollection.fromLiteral("'foo'", input), + StringCollection.fromLiteral("'foo'", input), IntegerLiteralPath.fromString("5", input))); } @@ -354,10 +353,10 @@ void throwsErrorIfTooManyArguments() { assertThrowsErrorForArguments( "property function accepts one required and one optional arguments", input -> Arrays.asList( - StringLiteralPath.fromString("'foo'", input), - StringLiteralPath.fromString("'false'", input), - StringLiteralPath.fromString("'false'", input), - StringLiteralPath.fromString("'false'", input) + StringCollection.fromLiteral("'foo'", input), + StringCollection.fromLiteral("'false'", input), + StringCollection.fromLiteral("'false'", input), + StringCollection.fromLiteral("'false'", input) )); } @@ -366,17 +365,17 @@ void throwsErrorIfCannotParsePropertyType() { assertThrowsErrorForArguments( "Unknown FHIRDefinedType code 'not-an-fhir-type'", input -> Arrays.asList( - StringLiteralPath.fromString("'foo'", input), - StringLiteralPath.fromString("'not-an-fhir-type'", input) + StringCollection.fromLiteral("'foo'", input), + StringCollection.fromLiteral("'not-an-fhir-type'", input) )); } @Test void throwsErrorIfTerminologyServiceNotConfigured() { - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODING) .build(); - final FhirPath argument = StringLiteralPath.fromString("some string", input); + final Collection argument = StringCollection.fromLiteral("some string", input); final ParserContext context = new ParserContextBuilder(spark, fhirContext) .build(); diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunctionTest.java index 85445c77dd..dbba84211d 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunctionTest.java @@ -28,13 +28,13 @@ import static org.mockito.Mockito.mock; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.element.BooleanPath; -import au.csiro.pathling.fhirpath.element.CodingPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.CodingCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; @@ -119,7 +119,7 @@ void setUp() { .withSubsumes(CODING_LARGE, CODING_SMALL); } - CodingPath createCodingInput() { + CodingCollection createCodingInput() { final Dataset dataset = new DatasetBuilder(spark) .withIdColumn() .withEidColumn() @@ -134,17 +134,17 @@ CodingPath createCodingInput() { .withRow(RES_ID3, makeEid(0), rowFromCoding(CODING_OTHER2)) .withRow(RES_ID4, makeEid(0), rowFromCoding(CODING_OTHER2)) .buildWithStructValue(); - final ElementPath inputExpression = new ElementPathBuilder(spark) + final PrimitivePath inputExpression = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODING) .dataset(dataset) .idAndEidAndValueColumns() .singular(false) .build(); - return (CodingPath) inputExpression; + return (CodingCollection) inputExpression; } - CodingPath createSingularCodingInput() { + CodingCollection createSingularCodingInput() { final Dataset dataset = new DatasetBuilder(spark) .withIdColumn() .withStructTypeColumns(codingStructType()) @@ -154,18 +154,18 @@ CodingPath createSingularCodingInput() { .withRow(RES_ID4, rowFromCoding(CODING_OTHER1)) .withRow(RES_ID5, null /* NULL coding value */) .buildWithStructValue(); - final ElementPath inputExpression = new ElementPathBuilder(spark) + final PrimitivePath inputExpression = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODING) .dataset(dataset) .idAndValueColumns() .singular(true) .build(); - return (CodingPath) inputExpression; + return (CodingCollection) inputExpression; } - ElementPath createCodeableConceptInput() { + PrimitivePath createCodeableConceptInput() { final Dataset dataset = new DatasetBuilder(spark) .withIdColumn() .withEidColumn() @@ -195,32 +195,32 @@ CodingLiteralPath createLiteralArgOrInput() { .withColumn(DataTypes.BooleanType) .withIdsAndValue(false, ALL_RES_IDS) .build(); - final ElementPath literalContext = new ElementPathBuilder(spark) + final PrimitivePath literalContext = new ElementPathBuilder(spark) .dataset(literalContextDataset) .idAndValueColumns() .build(); - return CodingLiteralPath.fromString(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), + return CodingCollection.fromLiteral(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), literalContext); } - CodingPath createCodingArg() { + CodingCollection createCodingArg() { final Dataset dataset = new DatasetBuilder(spark) .withIdColumn() .withStructTypeColumns(codingStructType()) .withIdValueRows(ALL_RES_IDS, id -> rowFromCoding(CODING_MEDIUM)) .withIdValueRows(ALL_RES_IDS, id -> rowFromCoding(CODING_OTHER3)) .buildWithStructValue(); - final ElementPath argument = new ElementPathBuilder(spark) + final PrimitivePath argument = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODING) .dataset(dataset) .idAndValueColumns() .build(); - return (CodingPath) argument; + return (CodingCollection) argument; } - ElementPath createCodeableConceptArg() { + PrimitivePath createCodeableConceptArg() { final Dataset dataset = new DatasetBuilder(spark) .withIdColumn() .withStructTypeColumns(codeableConceptStructType()) @@ -237,23 +237,23 @@ ElementPath createCodeableConceptArg() { .build(); } - CodingPath createEmptyCodingInput() { + CodingCollection createEmptyCodingInput() { final Dataset dataset = new DatasetBuilder(spark) .withIdColumn() .withEidColumn() .withStructTypeColumns(codingStructType()) .buildWithStructValue(); - final ElementPath argument = new ElementPathBuilder(spark) + final PrimitivePath argument = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODING) .dataset(dataset) .idAndEidAndValueColumns() .build(); - return (CodingPath) argument; + return (CodingCollection) argument; } - CodingPath createNullCodingInput() { + CodingCollection createNullCodingInput() { final Dataset dataset = new DatasetBuilder(spark) .withIdColumn() .withEidColumn() @@ -261,16 +261,16 @@ CodingPath createNullCodingInput() { .withStructTypeColumns(codingStructType()) .buildWithStructValue(); - final ElementPath argument = new ElementPathBuilder(spark) + final PrimitivePath argument = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODING) .dataset(dataset) .idAndEidAndValueColumns() .build(); - return (CodingPath) argument; + return (CodingCollection) argument; } - ElementPath createEmptyCodeableConceptInput() { + PrimitivePath createEmptyCodeableConceptInput() { final Dataset dataset = new DatasetBuilder(spark) .withIdColumn() .withEidColumn() @@ -284,7 +284,7 @@ ElementPath createEmptyCodeableConceptInput() { .build(); } - ElementPath createNullCodeableConceptInput() { + PrimitivePath createNullCodeableConceptInput() { final Dataset dataset = new DatasetBuilder(spark) .withIdColumn() .withEidColumn() @@ -299,20 +299,20 @@ ElementPath createNullCodeableConceptInput() { .build(); } - CodingPath createNullCodingArg() { + CodingCollection createNullCodingArg() { final Dataset dataset = new DatasetBuilder(spark) .withIdColumn() .withStructTypeColumns(codingStructType()) .withIdValueRows(ALL_RES_IDS, id -> null) .buildWithStructValue(); - final ElementPath argument = new ElementPathBuilder(spark) + final PrimitivePath argument = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODING) .dataset(dataset) .idAndValueColumns() .build(); - return (CodingPath) argument; + return (CodingCollection) argument; } DatasetBuilder expectedSubsumes() { @@ -382,28 +382,28 @@ DatasetBuilder expectedNull() { } ElementPathAssertion assertCallSuccess(final NamedFunction function, - final NonLiteralPath inputExpression, final FhirPath argumentExpression) { + final NonLiteralPath inputExpression, final Collection argumentExpression) { final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .terminologyClientFactory(terminologyServiceFactory) .build(); final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, inputExpression, Collections.singletonList(argumentExpression)); - final FhirPath result = function.invoke(functionInput); + final Collection result = function.invoke(functionInput); return assertThat(result) - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .preservesCardinalityOf(inputExpression); } DatasetAssert assertSubsumesSuccess(final NonLiteralPath inputExpression, - final FhirPath argumentExpression) { + final Collection argumentExpression) { return assertCallSuccess(NamedFunction.getInstance("subsumes"), inputExpression, argumentExpression).selectOrderedResultWithEid(); } DatasetAssert assertSubsumedBySuccess(final NonLiteralPath inputExpression, - final FhirPath argumentExpression) { + final Collection argumentExpression) { return assertCallSuccess(NamedFunction.getInstance("subsumedBy"), inputExpression, argumentExpression).selectOrderedResultWithEid(); } @@ -532,11 +532,11 @@ void throwsErrorIfInputTypeIsUnsupported() { .terminologyClientFactory(mock(TerminologyServiceFactory.class)) .build(); - final ElementPath argument = new ElementPathBuilder(spark) + final PrimitivePath argument = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODEABLECONCEPT) .build(); - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .build(); @@ -557,11 +557,11 @@ void throwsErrorIfArgumentTypeIsUnsupported() { .terminologyClientFactory(mock(TerminologyServiceFactory.class)) .build(); - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODEABLECONCEPT) .build(); - final StringLiteralPath argument = StringLiteralPath - .fromString("'str'", input); + final StringLiteralPath argument = StringCollection + .fromLiteral("'str'", input); final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, Collections.singletonList(argument)); @@ -581,16 +581,16 @@ void throwsErrorIfMoreThanOneArgument() { .terminologyClientFactory(mock(TerminologyServiceFactory.class)) .build(); - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODEABLECONCEPT) .build(); - final CodingLiteralPath argument1 = CodingLiteralPath - .fromString(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), + final CodingLiteralPath argument1 = CodingCollection + .fromLiteral(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), input); - final CodingLiteralPath argument2 = CodingLiteralPath - .fromString(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), + final CodingLiteralPath argument2 = CodingCollection + .fromLiteral(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), input); final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, @@ -605,12 +605,12 @@ void throwsErrorIfMoreThanOneArgument() { @Test void throwsErrorIfTerminologyServiceNotConfigured() { - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODEABLECONCEPT) .build(); - final CodingLiteralPath argument = CodingLiteralPath - .fromString(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), + final CodingLiteralPath argument = CodingCollection + .fromLiteral(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), input); final ParserContext context = new ParserContextBuilder(spark, fhirContext).build(); diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunctionTest.java index c4b9cf4fd4..e347b3979c 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunctionTest.java @@ -38,11 +38,11 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.CodingPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.CodingCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.function.NamedFunctionInput; import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; @@ -155,7 +155,7 @@ void translateCodingWithDefaultArguments() { .withRow("encounter-5", null, null) .buildWithStructValue(); - final CodingPath inputExpression = (CodingPath) new ElementPathBuilder(spark) + final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) .dataset(inputDataset) .idAndEidAndValueColumns() .expression("Encounter.class") @@ -177,13 +177,13 @@ void translateCodingWithDefaultArguments() { .terminologyClientFactory(terminologyServiceFactory) .build(); - final StringLiteralPath conceptMapUrlArgument = StringLiteralPath - .fromString("'" + CONCEPT_MAP1_URI + "'", inputExpression); + final StringLiteralPath conceptMapUrlArgument = StringCollection + .fromLiteral("'" + CONCEPT_MAP1_URI + "'", inputExpression); final NamedFunctionInput translateInput = new NamedFunctionInput(parserContext, inputExpression, Collections.singletonList(conceptMapUrlArgument)); // Invoke the function. - final FhirPath result = new TranslateFunction().invoke(translateInput); + final Collection result = new TranslateFunction().invoke(translateInput); final Dataset expectedResult = new DatasetBuilder(spark) .withIdColumn() .withEidColumn() @@ -209,7 +209,7 @@ void translateCodingWithDefaultArguments() { assertThat(result) .hasExpression( "Encounter.class.translate('" + CONCEPT_MAP1_URI + "')") - .isElementPath(CodingPath.class) + .isElementPath(CodingCollection.class) .hasFhirType(FHIRDefinedType.CODING) .isNotSingular() .selectOrderedResultWithEid() @@ -274,7 +274,7 @@ void translateCodeableConceptWithNonDefaultArguments() { .withRow("encounter-6", null, null) .buildWithStructValue(); - final ElementPath inputExpression = new ElementPathBuilder(spark) + final PrimitivePath inputExpression = new ElementPathBuilder(spark) .dataset(inputDataset) .idAndEidAndValueColumns() .expression("Encounter.type") @@ -298,19 +298,19 @@ void translateCodeableConceptWithNonDefaultArguments() { .terminologyClientFactory(terminologyServiceFactory) .build(); - final StringLiteralPath conceptMapUrlArgument = StringLiteralPath - .fromString("'" + CONCEPT_MAP2_URI + "'", inputExpression); + final StringLiteralPath conceptMapUrlArgument = StringCollection + .fromLiteral("'" + CONCEPT_MAP2_URI + "'", inputExpression); final BooleanLiteralPath reverseArgument = BooleanLiteralPath .fromString("true", inputExpression); - final StringLiteralPath equivalenceArgument = StringLiteralPath - .fromString("narrower,equivalent", inputExpression); + final StringLiteralPath equivalenceArgument = StringCollection + .fromLiteral("narrower,equivalent", inputExpression); final NamedFunctionInput translateInput = new NamedFunctionInput(parserContext, inputExpression, Arrays.asList(conceptMapUrlArgument, reverseArgument, equivalenceArgument)); // Invoke the function. - final FhirPath result = new TranslateFunction().invoke(translateInput); + final Collection result = new TranslateFunction().invoke(translateInput); final Dataset expectedResult = new DatasetBuilder(spark) .withIdColumn() @@ -338,7 +338,7 @@ void translateCodeableConceptWithNonDefaultArguments() { assertThat(result) .hasExpression( "Encounter.type.translate('" + CONCEPT_MAP2_URI + "', true, 'narrower,equivalent')") - .isElementPath(CodingPath.class) + .isElementPath(CodingCollection.class) .hasFhirType(FHIRDefinedType.CODING) .isNotSingular() .selectOrderedResultWithEid() @@ -355,12 +355,12 @@ void translateCodeableConceptWithNonDefaultArguments() { @Test void throwsErrorIfInputTypeIsUnsupported() { - final FhirPath mockContext = new ElementPathBuilder(spark).build(); - final ElementPath input = new ElementPathBuilder(spark) + final Collection mockContext = new ElementPathBuilder(spark).build(); + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .expression("name.given") .build(); - final FhirPath argument = StringLiteralPath.fromString(SOURCE_SYSTEM_URI, mockContext); + final Collection argument = StringCollection.fromLiteral(SOURCE_SYSTEM_URI, mockContext); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .terminologyClientFactory(mock(TerminologyServiceFactory.class)) @@ -377,14 +377,14 @@ void throwsErrorIfInputTypeIsUnsupported() { void assertThrowsErrorForArguments(@Nonnull final String expectedError, - @Nonnull final Function> argsFactory) { + @Nonnull final Function> argsFactory) { final Optional optionalDefinition = FhirHelpers .getChildOfResource(fhirContext, "Encounter", "class"); assertTrue(optionalDefinition.isPresent()); final ElementDefinition definition = optionalDefinition.get(); - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODING) .definition(definition) .buildDefined(); @@ -421,8 +421,8 @@ void throwsErrorIfFirstArgumentIsNotString() { void throwsErrorIfSecondArgumentIsNotBoolean() { assertThrowsErrorForArguments("Function `translate` expects `Boolean literal` as argument 2", input -> Arrays.asList( - StringLiteralPath.fromString("'foo'", input), - StringLiteralPath.fromString("'bar'", input))); + StringCollection.fromLiteral("'foo'", input), + StringCollection.fromLiteral("'bar'", input))); } @@ -430,7 +430,7 @@ void throwsErrorIfSecondArgumentIsNotBoolean() { void throwsErrorIfThirdArgumentIsNotString() { assertThrowsErrorForArguments("Function `translate` expects `String literal` as argument 3", input -> Arrays.asList( - StringLiteralPath.fromString("'foo'", input), + StringCollection.fromLiteral("'foo'", input), BooleanLiteralPath.fromString("true", input), BooleanLiteralPath.fromString("false", input))); } @@ -441,10 +441,10 @@ void throwsErrorIfTooManyArguments() { assertThrowsErrorForArguments( "translate function accepts one required and two optional arguments", input -> Arrays.asList( - StringLiteralPath.fromString("'foo'", input), + StringCollection.fromLiteral("'foo'", input), BooleanLiteralPath.fromString("true", input), - StringLiteralPath.fromString("'false'", input), - StringLiteralPath.fromString("'false'", input) + StringCollection.fromLiteral("'false'", input), + StringCollection.fromLiteral("'false'", input) )); } @@ -453,18 +453,18 @@ void throwsErrorIfCannotParseEquivalences() { assertThrowsErrorForArguments( "Unknown ConceptMapEquivalence code 'not-an-equivalence'", input -> Arrays.asList( - StringLiteralPath.fromString("'foo'", input), + StringCollection.fromLiteral("'foo'", input), BooleanLiteralPath.fromString("true", input), - StringLiteralPath.fromString("'not-an-equivalence'", input) + StringCollection.fromLiteral("'not-an-equivalence'", input) )); } @Test void throwsErrorIfTerminologyServiceNotConfigured() { - final ElementPath input = new ElementPathBuilder(spark) + final PrimitivePath input = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODEABLECONCEPT) .build(); - final FhirPath argument = StringLiteralPath.fromString("some string", input); + final Collection argument = StringCollection.fromLiteral("some string", input); final ParserContext context = new ParserContextBuilder(spark, fhirContext) .build(); diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPathTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPathTest.java index 0e827f3522..dceceb465c 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPathTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPathTest.java @@ -23,7 +23,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.collection.CodingCollection; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.ResourceDatasetBuilder; @@ -46,7 +47,7 @@ class CodingLiteralPathTest { @Autowired SparkSession spark; - ResourcePath inputContext; + ResourceCollection inputContext; @BeforeEach void setUp() { @@ -71,7 +72,7 @@ void setUp() { @Test void roundTrip() { final String expression = "http://snomed.info/sct|166056000|http://snomed.info/sct/32506021000036107/version/20201231"; - final CodingLiteralPath codingLiteralPath = CodingLiteralPath.fromString( + final CodingLiteralPath codingLiteralPath = CodingCollection.fromLiteral( expression, inputContext); final Coding literalValue = codingLiteralPath.getValue(); @@ -87,8 +88,8 @@ void roundTrip() { @Test void roundTripNoVersion() { final String expression = "http://snomed.info/sct|166056000"; - final CodingLiteralPath codingLiteralPath = CodingLiteralPath - .fromString(expression, inputContext); + final CodingLiteralPath codingLiteralPath = CodingCollection + .fromLiteral(expression, inputContext); final Coding literalValue = codingLiteralPath.getValue(); assertEquals("http://snomed.info/sct", literalValue.getSystem()); assertNull(literalValue.getVersion()); @@ -105,8 +106,8 @@ void roundTripWithQuotedComponent() { + "|'397956004 |Prosthetic arthroplasty of the hip|: 363704007 |Procedure site| = " + "( 24136001 |Hip joint structure|: 272741003 |Laterality| = 7771000 |Left| )'" + "|http://snomed.info/sct/32506021000036107/version/20201231"; - final CodingLiteralPath codingLiteralPath = CodingLiteralPath - .fromString(expression, inputContext); + final CodingLiteralPath codingLiteralPath = CodingCollection + .fromLiteral(expression, inputContext); final Coding literalValue = codingLiteralPath.getValue(); assertEquals("http://snomed.info/sct", literalValue.getSystem()); assertEquals("http://snomed.info/sct/32506021000036107/version/20201231", @@ -124,8 +125,8 @@ void roundTripWithQuotedComponent() { void roundTripWithQuotedComponentWithComma() { final String expression = "http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231"; - final CodingLiteralPath codingLiteralPath = CodingLiteralPath - .fromString(expression, inputContext); + final CodingLiteralPath codingLiteralPath = CodingCollection + .fromLiteral(expression, inputContext); final Coding literalValue = codingLiteralPath.getValue(); assertEquals("http://snomed.info/sct", literalValue.getSystem()); assertEquals("http://snomed.info/sct/32506021000036107/version/20201231", @@ -139,8 +140,8 @@ void roundTripWithQuotedComponentWithComma() { @Test void roundTripWithQuotedComponentWithSingleQuote() { final String expression = "'Someone\\'s CodeSystem'|166056000"; - final CodingLiteralPath codingLiteralPath = CodingLiteralPath - .fromString(expression, inputContext); + final CodingLiteralPath codingLiteralPath = CodingCollection + .fromLiteral(expression, inputContext); final Coding literalValue = codingLiteralPath.getValue(); assertEquals("Someone's CodeSystem", literalValue.getSystem()); assertEquals("166056000", literalValue.getCode()); @@ -152,8 +153,8 @@ void roundTripWithQuotedComponentWithSingleQuote() { @Test void roundTripWithQuotedComponentWithSpace() { final String expression = "'Some CodeSystem'|166056000"; - final CodingLiteralPath codingLiteralPath = CodingLiteralPath - .fromString(expression, inputContext); + final CodingLiteralPath codingLiteralPath = CodingCollection + .fromLiteral(expression, inputContext); final Coding literalValue = codingLiteralPath.getValue(); assertEquals("Some CodeSystem", literalValue.getSystem()); assertEquals("166056000", literalValue.getCode()); @@ -164,7 +165,7 @@ void roundTripWithQuotedComponentWithSpace() { @Test void fromMalformedString() { - assertThrows(IllegalArgumentException.class, () -> CodingLiteralPath.fromString( + assertThrows(IllegalArgumentException.class, () -> CodingCollection.fromLiteral( "http://snomed.info/sct", inputContext)); } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorTest.java index 431a6d66a9..ccfca12de9 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorTest.java @@ -19,7 +19,7 @@ import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; @@ -52,8 +52,8 @@ class BooleanOperatorTest { static final String ID_ALIAS = "_abc123"; - FhirPath left; - FhirPath right; + Collection left; + Collection right; ParserContext parserContext; @BeforeEach @@ -105,10 +105,10 @@ void setUp() { @Test void and() { - final OperatorInput input = new OperatorInput(parserContext, left, right); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final Operator booleanOperator = Operator.getInstance("and"); - final FhirPath result = booleanOperator.invoke(input); + final BinaryOperator booleanOperator = BinaryOperator.getInstance("and"); + final Collection result = booleanOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), @@ -124,10 +124,10 @@ void and() { @Test void or() { - final OperatorInput input = new OperatorInput(parserContext, left, right); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final Operator booleanOperator = Operator.getInstance("or"); - final FhirPath result = booleanOperator.invoke(input); + final BinaryOperator booleanOperator = BinaryOperator.getInstance("or"); + final Collection result = booleanOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), @@ -143,10 +143,10 @@ void or() { @Test void xor() { - final OperatorInput input = new OperatorInput(parserContext, left, right); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final Operator booleanOperator = Operator.getInstance("xor"); - final FhirPath result = booleanOperator.invoke(input); + final BinaryOperator booleanOperator = BinaryOperator.getInstance("xor"); + final Collection result = booleanOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), @@ -162,10 +162,10 @@ void xor() { @Test void implies() { - final OperatorInput input = new OperatorInput(parserContext, left, right); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final Operator booleanOperator = Operator.getInstance("implies"); - final FhirPath result = booleanOperator.invoke(input); + final BinaryOperator booleanOperator = BinaryOperator.getInstance("implies"); + final Collection result = booleanOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), @@ -181,11 +181,11 @@ void implies() { @Test void leftIsLiteral() { - final FhirPath literalLeft = BooleanLiteralPath.fromString("true", left); - final OperatorInput input = new OperatorInput(parserContext, literalLeft, right); + final Collection literalLeft = BooleanLiteralPath.fromString("true", left); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, literalLeft, right); - final Operator booleanOperator = Operator.getInstance("and"); - final FhirPath result = booleanOperator.invoke(input); + final BinaryOperator booleanOperator = BinaryOperator.getInstance("and"); + final Collection result = booleanOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), @@ -201,11 +201,11 @@ void leftIsLiteral() { @Test void rightIsLiteral() { - final FhirPath literalRight = BooleanLiteralPath.fromString("true", right); - final OperatorInput input = new OperatorInput(parserContext, left, literalRight); + final Collection literalRight = BooleanLiteralPath.fromString("true", right); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, literalRight); - final Operator booleanOperator = Operator.getInstance("and"); - final FhirPath result = booleanOperator.invoke(input); + final BinaryOperator booleanOperator = BinaryOperator.getInstance("and"); + final Collection result = booleanOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorValidationTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorValidationTest.java index b7ec6b229e..5a79015047 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorValidationTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorValidationTest.java @@ -21,7 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.ElementPathBuilder; @@ -54,20 +54,20 @@ void setUp() { @Test void operandIsNotSingular() { - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BOOLEAN) .singular(false) .expression("estimatedAge") .build(); - final ElementPath right = new ElementPathBuilder(spark) + final PrimitivePath right = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BOOLEAN) .singular(true) .expression("deceasedBoolean") .build(); - final OperatorInput input = new OperatorInput(parserContext, left, right); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final Operator booleanOperator = Operator.getInstance("and"); + final BinaryOperator booleanOperator = BinaryOperator.getInstance("and"); final InvalidUserInputError error = assertThrows( InvalidUserInputError.class, () -> booleanOperator.invoke(input)); @@ -76,7 +76,7 @@ void operandIsNotSingular() { error.getMessage()); // Now test the right operand. - final OperatorInput reversedInput = new OperatorInput(parserContext, right, left); + final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); final InvalidUserInputError reversedError = assertThrows( InvalidUserInputError.class, () -> booleanOperator.invoke(reversedInput)); @@ -87,20 +87,20 @@ void operandIsNotSingular() { @Test void operandIsNotBoolean() { - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .singular(true) .expression("estimatedAge") .build(); - final ElementPath right = new ElementPathBuilder(spark) + final PrimitivePath right = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BOOLEAN) .singular(true) .expression("deceasedBoolean") .build(); - final OperatorInput input = new OperatorInput(parserContext, left, right); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final Operator booleanOperator = Operator.getInstance("and"); + final BinaryOperator booleanOperator = BinaryOperator.getInstance("and"); final InvalidUserInputError error = assertThrows( InvalidUserInputError.class, () -> booleanOperator.invoke(input)); @@ -109,7 +109,7 @@ void operandIsNotBoolean() { error.getMessage()); // Now test the right operand. - final OperatorInput reversedInput = new OperatorInput(parserContext, right, left); + final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); final InvalidUserInputError reversedError = assertThrows( InvalidUserInputError.class, () -> booleanOperator.invoke(reversedInput)); diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/CombineOperatorTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/CombineOperatorTest.java index 84ee0e347b..0fc40ea7ba 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/CombineOperatorTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/CombineOperatorTest.java @@ -25,11 +25,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.CodingPath; -import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.element.IntegerPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.collection.CodingCollection; +import au.csiro.pathling.fhirpath.collection.IntegerCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; @@ -73,7 +73,7 @@ void setUp() { .withColumn(DataTypes.BooleanType) .withIdsAndValue(null, Arrays.asList("observation-1", "observation-2", "observation-3")) .build(); - final ResourcePath inputContext = new ResourcePathBuilder(spark) + final ResourceCollection inputContext = new ResourcePathBuilder(spark) .resourceType(ResourceType.OBSERVATION) .dataset(input) .idAndValueColumns() @@ -96,7 +96,7 @@ void returnsCorrectResult() { .withRow("observation-2", null, null) .withRow("observation-3", makeEid(0), -1) .build(); - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .dataset(leftDataset) .idAndEidAndValueColumns() @@ -112,7 +112,7 @@ void returnsCorrectResult() { .withRow("observation-2", null, null) .withRow("observation-3", makeEid(0), 14) .build(); - final ElementPath right = new ElementPathBuilder(spark) + final PrimitivePath right = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .dataset(rightDataset) .idAndEidAndValueColumns() @@ -120,8 +120,8 @@ void returnsCorrectResult() { .singular(false) .build(); - final OperatorInput combineInput = new OperatorInput(parserContext, left, right); - final FhirPath result = Operator.getInstance("combine").invoke(combineInput); + final BinaryOperatorInput combineInput = new BinaryOperatorInput(parserContext, left, right); + final Collection result = BinaryOperator.getInstance("combine").invoke(combineInput); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn() @@ -139,7 +139,7 @@ void returnsCorrectResult() { assertThat(result) .hasExpression("valueInteger combine valueInteger") .isNotSingular() - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .selectResult() .hasRowsUnordered(expectedDataset); } @@ -156,7 +156,7 @@ void worksWithDifferentCombinableTypes() { .withRow("observation-2", null, null) .withRow("observation-3", makeEid(0), -1) .build(); - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .dataset(leftDataset) .idAndEidAndValueColumns() @@ -166,8 +166,8 @@ void worksWithDifferentCombinableTypes() { final IntegerLiteralPath right = IntegerLiteralPath .fromString("99", parserContext.getInputContext()); - final OperatorInput combineInput = new OperatorInput(parserContext, left, right); - final FhirPath result = Operator.getInstance("combine").invoke(combineInput); + final BinaryOperatorInput combineInput = new BinaryOperatorInput(parserContext, left, right); + final Collection result = BinaryOperator.getInstance("combine").invoke(combineInput); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn() @@ -184,7 +184,7 @@ void worksWithDifferentCombinableTypes() { assertThat(result) .hasExpression("valueInteger combine 99") .isNotSingular() - .isElementPath(IntegerPath.class) + .isElementPath(IntegerCollection.class) .selectResult() .hasRowsUnordered(expectedDataset); } @@ -198,18 +198,18 @@ void worksWithLiteralAndNonLiteralCodingValues() { .withRow("observation-1", makeEid(0), rowFromCoding(new Coding("http://snomed.info/sct", "18001011000036104", null))) .buildWithStructValue(); - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODING) .dataset(leftDataset) .idAndEidAndValueColumns() .expression("valueCoding") .singular(false) .build(); - final CodingLiteralPath right = CodingLiteralPath - .fromString("http://snomed.info/sct|373882004", parserContext.getInputContext()); + final CodingLiteralPath right = CodingCollection + .fromLiteral("http://snomed.info/sct|373882004", parserContext.getInputContext()); - final OperatorInput combineInput = new OperatorInput(parserContext, left, right); - final FhirPath result = Operator.getInstance("combine").invoke(combineInput); + final BinaryOperatorInput combineInput = new BinaryOperatorInput(parserContext, left, right); + final Collection result = BinaryOperator.getInstance("combine").invoke(combineInput); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn(idColumnName) @@ -226,24 +226,24 @@ void worksWithLiteralAndNonLiteralCodingValues() { assertThat(result) .hasExpression("valueCoding combine http://snomed.info/sct|373882004") .isNotSingular() - .isElementPath(CodingPath.class) + .isElementPath(CodingCollection.class) .selectResult() .hasRowsUnordered(expectedDataset); } @Test void throwsErrorIfInputsAreNotCombinable() { - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .expression("valueInteger") .fhirType(FHIRDefinedType.INTEGER) .build(); - final ElementPath right = new ElementPathBuilder(spark) + final PrimitivePath right = new ElementPathBuilder(spark) .expression("valueString") .fhirType(FHIRDefinedType.STRING) .build(); - final OperatorInput combineInput = new OperatorInput(parserContext, left, right); - final Operator combineOperator = Operator.getInstance("combine"); + final BinaryOperatorInput combineInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator combineOperator = BinaryOperator.getInstance("combine"); final InvalidUserInputError error = assertThrows( InvalidUserInputError.class, diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTest.java index 04e385c8fa..ec9f722c40 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTest.java @@ -21,9 +21,9 @@ 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.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.DatasetBuilder; @@ -56,8 +56,8 @@ class ComparisonOperatorDateTest { FhirContext fhirContext; static final String ID_ALIAS = "_abc123"; - private ElementPath left; - private ElementPath right; + private PrimitivePath left; + private PrimitivePath right; private ParserContext parserContext; @BeforeEach @@ -120,9 +120,9 @@ void setUp() { @Test void equals() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", true), // Equal, years, months and days @@ -140,9 +140,9 @@ void equals() { @Test void notEquals() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("!="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("!="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", false), // Equal, years, months and days @@ -160,9 +160,9 @@ void notEquals() { @Test void lessThan() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("<"); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("<"); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", false), // Equal, years, months and days @@ -180,9 +180,9 @@ void lessThan() { @Test void lessThanOrEqualTo() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("<="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("<="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", true), // Equal, years, months and days @@ -200,9 +200,9 @@ void lessThanOrEqualTo() { @Test void greaterThan() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance(">"); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance(">"); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", false), // Equal, years, months and days @@ -220,9 +220,9 @@ void greaterThan() { @Test void greaterThanOrEqualTo() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance(">="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance(">="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", true), // Equal, years, months and days diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTimeTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTimeTest.java index f67f772d4e..fd7284cc8a 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTimeTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTimeTest.java @@ -21,9 +21,9 @@ 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.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.DatasetBuilder; @@ -56,8 +56,8 @@ class ComparisonOperatorDateTimeTest { FhirContext fhirContext; static final String ID_ALIAS = "_abc123"; - private ElementPath left; - private ElementPath right; + private PrimitivePath left; + private PrimitivePath right; private ParserContext parserContext; @BeforeEach @@ -127,9 +127,9 @@ void setUp() { @Test void equals() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", true), // Equal, exact @@ -147,9 +147,9 @@ void equals() { @Test void notEquals() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("!="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("!="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", false), // Equal, exact @@ -167,9 +167,9 @@ void notEquals() { @Test void lessThan() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("<"); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("<"); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", false), // Equal, exact @@ -187,9 +187,9 @@ void lessThan() { @Test void lessThanOrEqualTo() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("<="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("<="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", true), // Equal, exact @@ -207,9 +207,9 @@ void lessThanOrEqualTo() { @Test void greaterThan() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance(">"); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance(">"); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", false), // Equal, exact @@ -227,9 +227,9 @@ void greaterThan() { @Test void greaterThanOrEqualTo() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance(">="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance(">="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", true), // Equal, exact diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java index 87c0a4acdf..19db5f3a77 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java @@ -21,9 +21,9 @@ 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.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.DatasetBuilder; @@ -57,8 +57,8 @@ class ComparisonOperatorInstantTest { FhirContext fhirContext; static final String ID_ALIAS = "_abc123"; - private ElementPath left; - private ElementPath right; + private PrimitivePath left; + private PrimitivePath right; private ParserContext parserContext; @BeforeEach @@ -114,9 +114,9 @@ void setUp() { @Test void equals() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), // Equal, exact @@ -127,9 +127,9 @@ void equals() { @Test void notEquals() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("!="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("!="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), // Equal, exact @@ -140,9 +140,9 @@ void notEquals() { @Test void lessThan() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("<"); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("<"); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), // Equal, exact @@ -153,9 +153,9 @@ void lessThan() { @Test void lessThanOrEqualTo() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("<="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("<="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), // Equal, exact @@ -166,9 +166,9 @@ void lessThanOrEqualTo() { @Test void greaterThan() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance(">"); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance(">"); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), // Equal, exact @@ -179,9 +179,9 @@ void greaterThan() { @Test void greaterThanOrEqualTo() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance(">="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance(">="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), // Equal, exact diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorQuantityTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorQuantityTest.java index 4aeeb7e9e4..c8716ede0f 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorQuantityTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorQuantityTest.java @@ -21,7 +21,8 @@ import static au.csiro.pathling.test.helpers.SparkHelpers.quantityStructType; import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromQuantity; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.QuantityCollection; import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; @@ -56,8 +57,8 @@ public class ComparisonOperatorQuantityTest { static final String ID_ALIAS = "_abc123"; - FhirPath left; - FhirPath right; + Collection left; + Collection right; ParserContext parserContext; QuantityLiteralPath ucumQuantityLiteral1; QuantityLiteralPath ucumQuantityLiteral2; @@ -164,12 +165,12 @@ void setUp() { .idAndValueColumns() .build(); - ucumQuantityLiteral1 = QuantityLiteralPath.fromUcumString("500 'mg'", left, ucumService); - ucumQuantityLiteral2 = QuantityLiteralPath.fromUcumString("0.5 'g'", left, ucumService); - ucumQuantityLiteral3 = QuantityLiteralPath.fromUcumString("1.8 'm'", left, ucumService); - calendarDurationLiteral1 = QuantityLiteralPath.fromCalendarDurationString("30 days", left); - calendarDurationLiteral2 = QuantityLiteralPath.fromCalendarDurationString("60 seconds", left); - calendarDurationLiteral3 = QuantityLiteralPath.fromCalendarDurationString("1000 milliseconds", + ucumQuantityLiteral1 = QuantityCollection.fromUcumString("500 'mg'", left, ucumService); + ucumQuantityLiteral2 = QuantityCollection.fromUcumString("0.5 'g'", left, ucumService); + ucumQuantityLiteral3 = QuantityCollection.fromUcumString("1.8 'm'", left, ucumService); + calendarDurationLiteral1 = QuantityCollection.fromCalendarDurationString("30 days", left); + calendarDurationLiteral2 = QuantityCollection.fromCalendarDurationString("60 seconds", left); + calendarDurationLiteral3 = QuantityCollection.fromCalendarDurationString("1000 milliseconds", left); parserContext = new ParserContextBuilder(spark, fhirContext) @@ -179,9 +180,9 @@ void setUp() { @Test void lessThan() { - final OperatorInput input = new OperatorInput(parserContext, left, right); - final Operator equalityOperator = Operator.getInstance("<"); - final FhirPath result = equalityOperator.invoke(input); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator equalityOperator = BinaryOperator.getInstance("<"); + final Collection result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", false), // 500 mg < 500 mg @@ -199,9 +200,9 @@ void lessThan() { @Test void lessThanOrEqualTo() { - final OperatorInput input = new OperatorInput(parserContext, left, right); - final Operator equalityOperator = Operator.getInstance("<="); - final FhirPath result = equalityOperator.invoke(input); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator equalityOperator = BinaryOperator.getInstance("<="); + final Collection result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", true), // 500 mg <= 500 mg @@ -219,9 +220,9 @@ void lessThanOrEqualTo() { @Test void greaterThanOrEqualTo() { - final OperatorInput input = new OperatorInput(parserContext, left, right); - final Operator equalityOperator = Operator.getInstance(">="); - final FhirPath result = equalityOperator.invoke(input); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator equalityOperator = BinaryOperator.getInstance(">="); + final Collection result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", true), // 500 mg >= 500 mg @@ -239,9 +240,9 @@ void greaterThanOrEqualTo() { @Test void greaterThan() { - final OperatorInput input = new OperatorInput(parserContext, left, right); - final Operator equalityOperator = Operator.getInstance(">"); - final FhirPath result = equalityOperator.invoke(input); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator equalityOperator = BinaryOperator.getInstance(">"); + final Collection result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", false), // 500 mg > 500 mg diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTest.java index 5dc6a5591a..686a15e749 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTest.java @@ -19,11 +19,12 @@ import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.literal.DateLiteralPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.DateCollection; +import au.csiro.pathling.fhirpath.collection.DecimalCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; -import au.csiro.pathling.fhirpath.literal.DecimalLiteralPath; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; import au.csiro.pathling.fhirpath.literal.LiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; @@ -73,13 +74,13 @@ static class TestParameters { String name; @Nonnull - FhirPath left; + Collection left; @Nonnull - FhirPath right; + Collection right; @Nonnull - FhirPath literal; + Collection literal; @Nonnull ParserContext context; @@ -147,7 +148,7 @@ TestParameters buildStringExpressions(final String name) { .withRow("patient-5", "Evelyn") .withRow("patient-6", null) .build(); - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .dataset(leftDataset) .idAndValueColumns() @@ -163,13 +164,13 @@ TestParameters buildStringExpressions(final String name) { .withRow("patient-5", null) .withRow("patient-6", null) .build(); - final ElementPath right = new ElementPathBuilder(spark) + final PrimitivePath right = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .dataset(rightDataset) .idAndValueColumns() .singular(true) .build(); - final StringLiteralPath literal = StringLiteralPath.fromString("'Evelyn'", left); + final StringLiteralPath literal = StringCollection.fromLiteral("'Evelyn'", left); final ParserContext context = new ParserContextBuilder(spark, fhirContext) .groupingColumns(Collections.singletonList(left.getIdColumn())) .build(); @@ -187,7 +188,7 @@ TestParameters buildIntegerExpressions(final String name) { .withRow("patient-5", 1) .withRow("patient-6", null) .build(); - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .dataset(leftDataset) .idAndValueColumns() @@ -203,7 +204,7 @@ TestParameters buildIntegerExpressions(final String name) { .withRow("patient-5", null) .withRow("patient-6", null) .build(); - final ElementPath right = new ElementPathBuilder(spark) + final PrimitivePath right = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .dataset(rightDataset) .idAndValueColumns() @@ -226,7 +227,7 @@ TestParameters buildDecimalExpressions(final String name) { .withRow("patient-5", new BigDecimal("1.0")) .withRow("patient-6", null) .build(); - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.DECIMAL) .dataset(leftDataset) .idAndValueColumns() @@ -242,13 +243,13 @@ TestParameters buildDecimalExpressions(final String name) { .withRow("patient-5", null) .withRow("patient-6", null) .build(); - final ElementPath right = new ElementPathBuilder(spark) + final PrimitivePath right = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.DECIMAL) .dataset(rightDataset) .idAndValueColumns() .singular(true) .build(); - final DecimalLiteralPath literal = DecimalLiteralPath.fromString("1.0", left); + final DecimalCollection literal = DecimalCollection.fromLiteral("1.0", left); final ParserContext context = new ParserContextBuilder(spark, fhirContext).groupingColumns( Collections.singletonList(left.getIdColumn())).build(); return new TestParameters(name, left, right, literal, context); @@ -268,7 +269,7 @@ TestParameters buildDateTimeExpressions(final String name, .withRow("patient-5", lesserDate) .withRow("patient-6", null) .build(); - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(fhirType) .dataset(leftDataset) .idAndValueColumns() @@ -284,7 +285,7 @@ TestParameters buildDateTimeExpressions(final String name, .withRow("patient-5", null) .withRow("patient-6", null) .build(); - final ElementPath right = new ElementPathBuilder(spark) + final PrimitivePath right = new ElementPathBuilder(spark) .fhirType(fhirType) .dataset(rightDataset) .idAndValueColumns() @@ -294,7 +295,7 @@ TestParameters buildDateTimeExpressions(final String name, try { literal = (fhirType == FHIRDefinedType.DATETIME) ? DateTimeLiteralPath.fromString(lesserDate, left) - : DateLiteralPath.fromString(lesserDate, left); + : DateCollection.fromLiteral(lesserDate, left); } catch (final ParseException e) { throw new RuntimeException("Error parsing literal date or date time"); } @@ -307,10 +308,11 @@ TestParameters buildDateTimeExpressions(final String name, @ParameterizedTest @MethodSource("parameters") void lessThanOrEqualTo(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLeft(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLeft(), parameters.getRight()); - final Operator comparisonOperator = Operator.getInstance("<="); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<="); + final Collection result = comparisonOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), @@ -325,10 +327,11 @@ void lessThanOrEqualTo(final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void lessThan(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLeft(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLeft(), parameters.getRight()); - final Operator comparisonOperator = Operator.getInstance("<"); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<"); + final Collection result = comparisonOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), @@ -343,10 +346,11 @@ void lessThan(final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void greaterThanOrEqualTo(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLeft(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLeft(), parameters.getRight()); - final Operator comparisonOperator = Operator.getInstance(">="); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">="); + final Collection result = comparisonOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), @@ -361,10 +365,11 @@ void greaterThanOrEqualTo(final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void greaterThan(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLeft(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLeft(), parameters.getRight()); - final Operator comparisonOperator = Operator.getInstance(">"); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">"); + final Collection result = comparisonOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), @@ -379,10 +384,11 @@ void greaterThan(final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void literalLessThanOrEqualTo(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLiteral(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLiteral(), parameters.getRight()); - final Operator comparisonOperator = Operator.getInstance("<="); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<="); + final Collection result = comparisonOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), @@ -397,10 +403,11 @@ void literalLessThanOrEqualTo(final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void literalLessThan(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLiteral(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLiteral(), parameters.getRight()); - final Operator comparisonOperator = Operator.getInstance("<"); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<"); + final Collection result = comparisonOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), @@ -415,10 +422,11 @@ void literalLessThan(final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void literalGreaterThanOrEqualTo(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLiteral(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLiteral(), parameters.getRight()); - final Operator comparisonOperator = Operator.getInstance(">="); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">="); + final Collection result = comparisonOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), @@ -433,10 +441,11 @@ void literalGreaterThanOrEqualTo(final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void literalGreaterThan(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLiteral(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLiteral(), parameters.getRight()); - final Operator comparisonOperator = Operator.getInstance(">"); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">"); + final Collection result = comparisonOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), @@ -451,10 +460,11 @@ void literalGreaterThan(final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void lessThanOrEqualToLiteral(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLeft(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLeft(), parameters.getLiteral()); - final Operator comparisonOperator = Operator.getInstance("<="); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<="); + final Collection result = comparisonOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), @@ -469,10 +479,11 @@ void lessThanOrEqualToLiteral(final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void lessThanLiteral(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLeft(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLeft(), parameters.getLiteral()); - final Operator comparisonOperator = Operator.getInstance("<"); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<"); + final Collection result = comparisonOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), @@ -487,10 +498,11 @@ void lessThanLiteral(final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void greaterThanOrEqualToLiteral(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLeft(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLeft(), parameters.getLiteral()); - final Operator comparisonOperator = Operator.getInstance(">="); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">="); + final Collection result = comparisonOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), @@ -505,10 +517,11 @@ void greaterThanOrEqualToLiteral(final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void greaterThanLiteral(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLeft(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLeft(), parameters.getLiteral()); - final Operator comparisonOperator = Operator.getInstance(">"); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">"); + final Collection result = comparisonOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTimeTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTimeTest.java index dd3988202f..3a2cecec8e 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTimeTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTimeTest.java @@ -21,9 +21,9 @@ 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.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.DatasetBuilder; @@ -56,8 +56,8 @@ class ComparisonOperatorTimeTest { FhirContext fhirContext; static final String ID_ALIAS = "_abc123"; - private ElementPath left; - private ElementPath right; + private PrimitivePath left; + private PrimitivePath right; private ParserContext parserContext; @BeforeEach @@ -120,9 +120,9 @@ void setUp() { @Test void equals() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", true), // Equal, hours, minutes and seconds @@ -140,9 +140,9 @@ void equals() { @Test void notEquals() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("!="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("!="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", false), // Equal, hours, minutes and seconds @@ -160,9 +160,9 @@ void notEquals() { @Test void lessThan() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("<"); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("<"); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", false), // Equal, hours, minutes and seconds @@ -180,9 +180,9 @@ void lessThan() { @Test void lessThanOrEqualTo() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance("<="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance("<="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", true), // Equal, hours, minutes and seconds @@ -200,9 +200,9 @@ void lessThanOrEqualTo() { @Test void greaterThan() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance(">"); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance(">"); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", false), // Equal, hours, minutes and seconds @@ -220,9 +220,9 @@ void greaterThan() { @Test void greaterThanOrEqualTo() { - final OperatorInput comparisonInput = new OperatorInput(parserContext, left, right); - final Operator lessThan = Operator.getInstance(">="); - final FhirPath result = lessThan.invoke(comparisonInput); + final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator lessThan = BinaryOperator.getInstance(">="); + final Collection result = lessThan.invoke(comparisonInput); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-01", true), // Equal, hours, minutes and seconds diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorValidationTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorValidationTest.java index 94a1fe3745..3391488eac 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorValidationTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorValidationTest.java @@ -21,7 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.ElementPathBuilder; @@ -54,19 +54,19 @@ void setUp() { @Test void operandsAreNotComparable() { - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.BOOLEAN) .singular(true) .expression("foo") .build(); - final ElementPath right = new ElementPathBuilder(spark) + final PrimitivePath right = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .singular(true) .expression("bar") .build(); - final OperatorInput input = new OperatorInput(parserContext, left, right); - final Operator comparisonOperator = Operator.getInstance(">"); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">"); final InvalidUserInputError error = assertThrows( InvalidUserInputError.class, () -> comparisonOperator.invoke(input)); @@ -74,7 +74,7 @@ void operandsAreNotComparable() { error.getMessage()); // Now test the right operand. - final OperatorInput reversedInput = new OperatorInput(parserContext, right, left); + final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); final InvalidUserInputError reversedError = assertThrows( InvalidUserInputError.class, () -> comparisonOperator.invoke(reversedInput)); diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/DateArithmeticTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/DateArithmeticTest.java index d2b83b5ff5..03e2717be8 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/DateArithmeticTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/DateArithmeticTest.java @@ -19,11 +19,13 @@ import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.DateCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.QuantityCollection; +import au.csiro.pathling.fhirpath.collection.TimeCollection; import au.csiro.pathling.fhirpath.literal.DateLiteralPath; import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; -import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; import au.csiro.pathling.fhirpath.literal.TimeLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; @@ -34,7 +36,6 @@ import java.text.ParseException; import java.time.Instant; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.stream.Stream; @@ -70,16 +71,16 @@ static class TestParameters { String name; @Nonnull - FhirPath left; + Collection left; @Nonnull - FhirPath right; + Collection right; @Nonnull ParserContext context; @Nonnull - Operator operator; + BinaryOperator operator; @Nonnull Dataset expectedResult; @@ -102,7 +103,7 @@ Stream parameters() throws ParseException { .withRow("patient-2", "2017-01-01T00:00:00Z") .withRow("patient-3", "2025-06-21T00:15:00+10:00") .build(); - final ElementPath dateTimePath = new ElementPathBuilder(spark) + final PrimitivePath dateTimePath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.DATETIME) .dataset(dateTimeDataset) .idAndValueColumns() @@ -116,7 +117,7 @@ Stream parameters() throws ParseException { .withRow("patient-2", "2017-01-01") .withRow("patient-3", "2025-06-21") .build(); - final ElementPath datePath = new ElementPathBuilder(spark) + final PrimitivePath datePath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.DATE) .dataset(dateDataset) .idAndValueColumns() @@ -130,7 +131,7 @@ Stream parameters() throws ParseException { .withRow("patient-2", "08:00") .withRow("patient-3", "00") .build(); - final ElementPath timePath = new ElementPathBuilder(spark) + final PrimitivePath timePath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.TIME) .dataset(timeDataset) .idAndValueColumns() @@ -144,7 +145,7 @@ Stream parameters() throws ParseException { .withRow("patient-2", Instant.ofEpochMilli(1667690454622L)) .withRow("patient-3", Instant.ofEpochMilli(1667690454622L)) .build(); - final ElementPath instantPath = new ElementPathBuilder(spark) + final PrimitivePath instantPath = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.DATETIME) .dataset(instantDataset) .idAndValueColumns() @@ -153,8 +154,8 @@ Stream parameters() throws ParseException { final DateTimeLiteralPath dateTimeLiteral = DateTimeLiteralPath.fromString( "@2015-02-07T18:28:17+00:00", dateTimePath); - final DateLiteralPath dateLiteral = DateLiteralPath.fromString("@2015-02-07", datePath); - final TimeLiteralPath timeLiteral = TimeLiteralPath.fromString("@T08:00", timePath); + final DateLiteralPath dateLiteral = DateCollection.fromLiteral("@2015-02-07", datePath); + final TimeLiteralPath timeLiteral = TimeCollection.fromLiteral("@T08:00", timePath); final ParserContext context = new ParserContextBuilder(spark, fhirContext) .groupingColumns(Collections.singletonList(dateTimePath.getIdColumn())) @@ -174,12 +175,12 @@ Stream parameters() throws ParseException { return parameters.stream(); } - Collection dateTimeAddition( - final FhirPath dateTimePath, final ParserContext context) { + java.util.Collection dateTimeAddition( + final Collection dateTimePath, final ParserContext context) { final List parameters = new ArrayList<>(); parameters.add(new TestParameters("DateTime + 10 years", dateTimePath, - QuantityLiteralPath.fromCalendarDurationString("10 years", dateTimePath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("10 years", dateTimePath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2025-02-07T13:28:17.000-05:00") .withRow("patient-2", "2027-01-01T00:00:00Z") @@ -188,8 +189,8 @@ Collection dateTimeAddition( ); parameters.add(new TestParameters("DateTime + 9 months", dateTimePath, - QuantityLiteralPath.fromCalendarDurationString("9 months", dateTimePath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("9 months", dateTimePath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-11-07T13:28:17.000-05:00") .withRow("patient-2", "2017-10-01T00:00:00Z") @@ -198,8 +199,8 @@ Collection dateTimeAddition( ); parameters.add(new TestParameters("DateTime + 30 days", dateTimePath, - QuantityLiteralPath.fromCalendarDurationString("30 days", dateTimePath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("30 days", dateTimePath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-03-09T13:28:17.000-05:00") .withRow("patient-2", "2017-01-31T00:00:00Z") @@ -208,8 +209,8 @@ Collection dateTimeAddition( ); parameters.add(new TestParameters("DateTime + 12 hours", dateTimePath, - QuantityLiteralPath.fromCalendarDurationString("12 hours", dateTimePath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("12 hours", dateTimePath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-08T01:28:17.000-05:00") .withRow("patient-2", "2017-01-01T12:00:00Z") @@ -218,8 +219,8 @@ Collection dateTimeAddition( ); parameters.add(new TestParameters("DateTime + 30 minutes", dateTimePath, - QuantityLiteralPath.fromCalendarDurationString("30 minutes", dateTimePath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("30 minutes", dateTimePath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-07T13:58:17.000-05:00") .withRow("patient-2", "2017-01-01T00:30:00Z") @@ -228,8 +229,8 @@ Collection dateTimeAddition( ); parameters.add(new TestParameters("DateTime + 10 seconds", dateTimePath, - QuantityLiteralPath.fromCalendarDurationString("10 seconds", dateTimePath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("10 seconds", dateTimePath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-07T13:28:27.000-05:00") .withRow("patient-2", "2017-01-01T00:00:10Z") @@ -238,8 +239,8 @@ Collection dateTimeAddition( ); parameters.add(new TestParameters("DateTime + 300 milliseconds", dateTimePath, - QuantityLiteralPath.fromCalendarDurationString("300 milliseconds", dateTimePath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("300 milliseconds", dateTimePath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-07T13:28:17.300-05:00") .withRow("patient-2", "2017-01-01T00:00:00Z") @@ -249,12 +250,12 @@ Collection dateTimeAddition( return parameters; } - Collection dateTimeSubtraction( - final FhirPath dateTimePath, final ParserContext context) { + java.util.Collection dateTimeSubtraction( + final Collection dateTimePath, final ParserContext context) { final List parameters = new ArrayList<>(); parameters.add(new TestParameters("DateTime - 10 years", dateTimePath, - QuantityLiteralPath.fromCalendarDurationString("10 years", dateTimePath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("10 years", dateTimePath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2005-02-07T13:28:17.000-05:00") .withRow("patient-2", "2007-01-01T00:00:00Z") @@ -263,8 +264,8 @@ Collection dateTimeSubtraction( ); parameters.add(new TestParameters("DateTime - 9 months", dateTimePath, - QuantityLiteralPath.fromCalendarDurationString("9 months", dateTimePath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("9 months", dateTimePath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2014-05-07T13:28:17.000-05:00") .withRow("patient-2", "2016-04-01T00:00:00Z") @@ -273,8 +274,8 @@ Collection dateTimeSubtraction( ); parameters.add(new TestParameters("DateTime - 30 days", dateTimePath, - QuantityLiteralPath.fromCalendarDurationString("30 days", dateTimePath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("30 days", dateTimePath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-01-08T13:28:17.000-05:00") .withRow("patient-2", "2016-12-02T00:00:00Z") @@ -283,8 +284,8 @@ Collection dateTimeSubtraction( ); parameters.add(new TestParameters("DateTime - 12 hours", dateTimePath, - QuantityLiteralPath.fromCalendarDurationString("12 hours", dateTimePath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("12 hours", dateTimePath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-07T01:28:17.000-05:00") .withRow("patient-2", "2016-12-31T12:00:00Z") @@ -293,8 +294,8 @@ Collection dateTimeSubtraction( ); parameters.add(new TestParameters("DateTime - 30 minutes", dateTimePath, - QuantityLiteralPath.fromCalendarDurationString("30 minutes", dateTimePath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("30 minutes", dateTimePath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-07T12:58:17.000-05:00") .withRow("patient-2", "2016-12-31T23:30:00Z") @@ -303,8 +304,8 @@ Collection dateTimeSubtraction( ); parameters.add(new TestParameters("DateTime - 10 seconds", dateTimePath, - QuantityLiteralPath.fromCalendarDurationString("10 seconds", dateTimePath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("10 seconds", dateTimePath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-07T13:28:07.000-05:00") .withRow("patient-2", "2016-12-31T23:59:50Z") @@ -313,8 +314,8 @@ Collection dateTimeSubtraction( ); parameters.add(new TestParameters("DateTime - 300 milliseconds", dateTimePath, - QuantityLiteralPath.fromCalendarDurationString("300 milliseconds", dateTimePath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("300 milliseconds", dateTimePath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-07T13:28:16.700-05:00") .withRow("patient-2", "2016-12-31T23:59:59Z") @@ -324,12 +325,12 @@ Collection dateTimeSubtraction( return parameters; } - Collection dateAddition( - final FhirPath datePath, final ParserContext context) { + java.util.Collection dateAddition( + final Collection datePath, final ParserContext context) { final List parameters = new ArrayList<>(); parameters.add(new TestParameters("Date + 10 years", datePath, - QuantityLiteralPath.fromCalendarDurationString("10 years", datePath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("10 years", datePath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2025-02-07") .withRow("patient-2", "2027-01-01") @@ -338,8 +339,8 @@ Collection dateAddition( ); parameters.add(new TestParameters("Date + 9 months", datePath, - QuantityLiteralPath.fromCalendarDurationString("9 months", datePath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("9 months", datePath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-11-07") .withRow("patient-2", "2017-10-01") @@ -348,8 +349,8 @@ Collection dateAddition( ); parameters.add(new TestParameters("Date + 30 days", datePath, - QuantityLiteralPath.fromCalendarDurationString("30 days", datePath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("30 days", datePath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-03-09") .withRow("patient-2", "2017-01-31") @@ -360,12 +361,12 @@ Collection dateAddition( return parameters; } - Collection dateSubtraction( - final FhirPath datePath, final ParserContext context) { + java.util.Collection dateSubtraction( + final Collection datePath, final ParserContext context) { final List parameters = new ArrayList<>(); parameters.add(new TestParameters("Date - 10 years", datePath, - QuantityLiteralPath.fromCalendarDurationString("10 years", datePath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("10 years", datePath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2005-02-07") .withRow("patient-2", "2007-01-01") @@ -374,8 +375,8 @@ Collection dateSubtraction( ); parameters.add(new TestParameters("Date - 9 months", datePath, - QuantityLiteralPath.fromCalendarDurationString("9 months", datePath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("9 months", datePath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2014-05-07") .withRow("patient-2", "2016-04-01") @@ -384,8 +385,8 @@ Collection dateSubtraction( ); parameters.add(new TestParameters("Date - 30 days", datePath, - QuantityLiteralPath.fromCalendarDurationString("30 days", datePath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("30 days", datePath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-01-08") .withRow("patient-2", "2016-12-02") @@ -396,12 +397,12 @@ Collection dateSubtraction( return parameters; } - Collection instantAddition( - final FhirPath instantPath, final ParserContext context) { + java.util.Collection instantAddition( + final Collection 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("+"), + QuantityCollection.fromCalendarDurationString("10 years", instantPath), context, + BinaryOperator.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") @@ -410,8 +411,8 @@ Collection instantAddition( ); parameters.add(new TestParameters("Instant + 9 months", instantPath, - QuantityLiteralPath.fromCalendarDurationString("9 months", instantPath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("9 months", instantPath), context, + BinaryOperator.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") @@ -420,8 +421,8 @@ Collection instantAddition( ); parameters.add(new TestParameters("Instant + 30 days", instantPath, - QuantityLiteralPath.fromCalendarDurationString("30 days", instantPath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("30 days", instantPath), context, + BinaryOperator.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") @@ -430,8 +431,8 @@ Collection instantAddition( ); parameters.add(new TestParameters("Instant + 12 hours", instantPath, - QuantityLiteralPath.fromCalendarDurationString("12 hours", instantPath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("12 hours", instantPath), context, + BinaryOperator.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") @@ -440,8 +441,8 @@ Collection instantAddition( ); parameters.add(new TestParameters("Instant + 30 minutes", instantPath, - QuantityLiteralPath.fromCalendarDurationString("30 minutes", instantPath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("30 minutes", instantPath), context, + BinaryOperator.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") @@ -450,8 +451,8 @@ Collection instantAddition( ); parameters.add(new TestParameters("Instant + 10 seconds", instantPath, - QuantityLiteralPath.fromCalendarDurationString("10 seconds", instantPath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("10 seconds", instantPath), context, + BinaryOperator.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") @@ -460,8 +461,8 @@ Collection instantAddition( ); parameters.add(new TestParameters("Instant + 300 milliseconds", instantPath, - QuantityLiteralPath.fromCalendarDurationString("300 milliseconds", instantPath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("300 milliseconds", instantPath), context, + BinaryOperator.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") @@ -471,12 +472,12 @@ Collection instantAddition( return parameters; } - Collection instantSubtraction( - final FhirPath instantPath, final ParserContext context) { + java.util.Collection instantSubtraction( + final Collection 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("-"), + QuantityCollection.fromCalendarDurationString("10 years", instantPath), context, + BinaryOperator.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") @@ -485,8 +486,8 @@ Collection instantSubtraction( ); parameters.add(new TestParameters("Instant - 9 months", instantPath, - QuantityLiteralPath.fromCalendarDurationString("9 months", instantPath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("9 months", instantPath), context, + BinaryOperator.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") @@ -495,8 +496,8 @@ Collection instantSubtraction( ); parameters.add(new TestParameters("Instant - 30 days", instantPath, - QuantityLiteralPath.fromCalendarDurationString("30 days", instantPath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("30 days", instantPath), context, + BinaryOperator.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") @@ -505,8 +506,8 @@ Collection instantSubtraction( ); parameters.add(new TestParameters("Instant - 12 hours", instantPath, - QuantityLiteralPath.fromCalendarDurationString("12 hours", instantPath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("12 hours", instantPath), context, + BinaryOperator.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") @@ -515,8 +516,8 @@ Collection instantSubtraction( ); parameters.add(new TestParameters("Instant - 30 minutes", instantPath, - QuantityLiteralPath.fromCalendarDurationString("30 minutes", instantPath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("30 minutes", instantPath), context, + BinaryOperator.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") @@ -525,8 +526,8 @@ Collection instantSubtraction( ); parameters.add(new TestParameters("Instant - 10 seconds", instantPath, - QuantityLiteralPath.fromCalendarDurationString("10 seconds", instantPath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("10 seconds", instantPath), context, + BinaryOperator.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") @@ -535,8 +536,8 @@ Collection instantSubtraction( ); parameters.add(new TestParameters("Instant - 300 milliseconds", instantPath, - QuantityLiteralPath.fromCalendarDurationString("300 milliseconds", instantPath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("300 milliseconds", instantPath), context, + BinaryOperator.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") @@ -546,12 +547,12 @@ Collection instantSubtraction( return parameters; } - Collection dateTimeLiteralAddition( - final FhirPath dateTimeLiteralPath, final ParserContext context) { + java.util.Collection dateTimeLiteralAddition( + final Collection dateTimeLiteralPath, final ParserContext context) { final List parameters = new ArrayList<>(); parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 + 10 years", dateTimeLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("10 years", dateTimeLiteralPath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("10 years", dateTimeLiteralPath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2025-02-07T18:28:17+00:00") .withRow("patient-2", "2025-02-07T18:28:17+00:00") @@ -560,8 +561,8 @@ Collection dateTimeLiteralAddition( ); parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 + 9 months", dateTimeLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("9 months", dateTimeLiteralPath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("9 months", dateTimeLiteralPath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-11-07T18:28:17+00:00") .withRow("patient-2", "2015-11-07T18:28:17+00:00") @@ -570,8 +571,8 @@ Collection dateTimeLiteralAddition( ); parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 + 30 days", dateTimeLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("30 days", dateTimeLiteralPath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("30 days", dateTimeLiteralPath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-03-09T18:28:17+00:00") .withRow("patient-2", "2015-03-09T18:28:17+00:00") @@ -580,8 +581,8 @@ Collection dateTimeLiteralAddition( ); parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 + 12 hours", dateTimeLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("12 hours", dateTimeLiteralPath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("12 hours", dateTimeLiteralPath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-08T06:28:17+00:00") .withRow("patient-2", "2015-02-08T06:28:17+00:00") @@ -591,9 +592,9 @@ Collection dateTimeLiteralAddition( parameters.add( new TestParameters("@2015-02-07T18:28:17+00:00 + 30 minutes", dateTimeLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("30 minutes", dateTimeLiteralPath), + QuantityCollection.fromCalendarDurationString("30 minutes", dateTimeLiteralPath), context, - Operator.getInstance("+"), + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-07T18:58:17+00:00") .withRow("patient-2", "2015-02-07T18:58:17+00:00") @@ -603,9 +604,9 @@ Collection dateTimeLiteralAddition( parameters.add( new TestParameters("@2015-02-07T18:28:17+00:00 + 10 seconds", dateTimeLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("10 seconds", dateTimeLiteralPath), + QuantityCollection.fromCalendarDurationString("10 seconds", dateTimeLiteralPath), context, - Operator.getInstance("+"), + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-07T18:28:27+00:00") .withRow("patient-2", "2015-02-07T18:28:27+00:00") @@ -615,9 +616,9 @@ Collection dateTimeLiteralAddition( parameters.add( new TestParameters("@2015-02-07T18:28:17+00:00 + 300 milliseconds", dateTimeLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("300 milliseconds", dateTimeLiteralPath), + QuantityCollection.fromCalendarDurationString("300 milliseconds", dateTimeLiteralPath), context, - Operator.getInstance("+"), + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-07T18:28:17+00:00") .withRow("patient-2", "2015-02-07T18:28:17+00:00") @@ -628,12 +629,12 @@ Collection dateTimeLiteralAddition( return parameters; } - Collection dateTimeLiteralSubtraction( - final FhirPath dateTimeLiteralPath, final ParserContext context) { + java.util.Collection dateTimeLiteralSubtraction( + final Collection dateTimeLiteralPath, final ParserContext context) { final List parameters = new ArrayList<>(); parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 - 10 years", dateTimeLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("10 years", dateTimeLiteralPath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("10 years", dateTimeLiteralPath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2005-02-07T18:28:17+00:00") .withRow("patient-2", "2005-02-07T18:28:17+00:00") @@ -642,8 +643,8 @@ Collection dateTimeLiteralSubtraction( ); parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 - 9 months", dateTimeLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("9 months", dateTimeLiteralPath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("9 months", dateTimeLiteralPath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2014-05-07T18:28:17+00:00") .withRow("patient-2", "2014-05-07T18:28:17+00:00") @@ -652,8 +653,8 @@ Collection dateTimeLiteralSubtraction( ); parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 - 30 days", dateTimeLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("30 days", dateTimeLiteralPath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("30 days", dateTimeLiteralPath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-01-08T18:28:17+00:00") .withRow("patient-2", "2015-01-08T18:28:17+00:00") @@ -662,8 +663,8 @@ Collection dateTimeLiteralSubtraction( ); parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 - 12 hours", dateTimeLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("12 hours", dateTimeLiteralPath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("12 hours", dateTimeLiteralPath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-07T06:28:17+00:00") .withRow("patient-2", "2015-02-07T06:28:17+00:00") @@ -673,9 +674,9 @@ Collection dateTimeLiteralSubtraction( parameters.add( new TestParameters("@2015-02-07T18:28:17+00:00 - 30 minutes", dateTimeLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("30 minutes", dateTimeLiteralPath), + QuantityCollection.fromCalendarDurationString("30 minutes", dateTimeLiteralPath), context, - Operator.getInstance("-"), + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-07T17:58:17+00:00") .withRow("patient-2", "2015-02-07T17:58:17+00:00") @@ -685,9 +686,9 @@ Collection dateTimeLiteralSubtraction( parameters.add( new TestParameters("@2015-02-07T18:28:17+00:00 - 10 seconds", dateTimeLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("10 seconds", dateTimeLiteralPath), + QuantityCollection.fromCalendarDurationString("10 seconds", dateTimeLiteralPath), context, - Operator.getInstance("-"), + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-07T18:28:07+00:00") .withRow("patient-2", "2015-02-07T18:28:07+00:00") @@ -697,9 +698,9 @@ Collection dateTimeLiteralSubtraction( parameters.add( new TestParameters("@2015-02-07T18:28:17+00:00 - 300 milliseconds", dateTimeLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("300 milliseconds", dateTimeLiteralPath), + QuantityCollection.fromCalendarDurationString("300 milliseconds", dateTimeLiteralPath), context, - Operator.getInstance("-"), + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-02-07T18:28:16+00:00") .withRow("patient-2", "2015-02-07T18:28:16+00:00") @@ -710,12 +711,12 @@ Collection dateTimeLiteralSubtraction( return parameters; } - Collection dateLiteralAddition( - final FhirPath dateLiteralPath, final ParserContext context) { + java.util.Collection dateLiteralAddition( + final Collection dateLiteralPath, final ParserContext context) { final List parameters = new ArrayList<>(); parameters.add(new TestParameters("@2015-02-07 + 10 years", dateLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("10 years", dateLiteralPath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("10 years", dateLiteralPath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2025-02-07") .withRow("patient-2", "2025-02-07") @@ -724,8 +725,8 @@ Collection dateLiteralAddition( ); parameters.add(new TestParameters("@2015-02-07 + 9 months", dateLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("9 months", dateLiteralPath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("9 months", dateLiteralPath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-11-07") .withRow("patient-2", "2015-11-07") @@ -734,8 +735,8 @@ Collection dateLiteralAddition( ); parameters.add(new TestParameters("@2015-02-07 + 30 days", dateLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("30 days", dateLiteralPath), context, - Operator.getInstance("+"), + QuantityCollection.fromCalendarDurationString("30 days", dateLiteralPath), context, + BinaryOperator.getInstance("+"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-03-09") .withRow("patient-2", "2015-03-09") @@ -746,12 +747,12 @@ Collection dateLiteralAddition( return parameters; } - Collection dateLiteralSubtraction( - final FhirPath dateLiteralPath, final ParserContext context) { + java.util.Collection dateLiteralSubtraction( + final Collection dateLiteralPath, final ParserContext context) { final List parameters = new ArrayList<>(); parameters.add(new TestParameters("@2015-02-07 - 10 years", dateLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("10 years", dateLiteralPath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("10 years", dateLiteralPath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2005-02-07") .withRow("patient-2", "2005-02-07") @@ -760,8 +761,8 @@ Collection dateLiteralSubtraction( ); parameters.add(new TestParameters("@2015-02-07 - 9 months", dateLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("9 months", dateLiteralPath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("9 months", dateLiteralPath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2014-05-07") .withRow("patient-2", "2014-05-07") @@ -770,8 +771,8 @@ Collection dateLiteralSubtraction( ); parameters.add(new TestParameters("@2015-02-07 - 30 days", dateLiteralPath, - QuantityLiteralPath.fromCalendarDurationString("30 days", dateLiteralPath), context, - Operator.getInstance("-"), + QuantityCollection.fromCalendarDurationString("30 days", dateLiteralPath), context, + BinaryOperator.getInstance("-"), new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) .withRow("patient-1", "2015-01-08") .withRow("patient-2", "2015-01-08") @@ -785,9 +786,9 @@ Collection dateLiteralSubtraction( @ParameterizedTest @MethodSource("parameters") void test(@Nonnull final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), parameters.getLeft(), parameters.getRight()); - final FhirPath result = parameters.getOperator().invoke(input); + final Collection result = parameters.getOperator().invoke(input); assertThat(result).selectOrderedResult().hasRows(parameters.getExpectedResult()); } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorCodingTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorCodingTest.java index 94d103452a..0cd07c74b1 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorCodingTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorCodingTest.java @@ -23,8 +23,8 @@ import static au.csiro.pathling.test.helpers.TestHelpers.LOINC_URL; import static au.csiro.pathling.test.helpers.TestHelpers.SNOMED_URL; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.CodingCollection; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.DatasetBuilder; @@ -56,10 +56,10 @@ class EqualityOperatorCodingTest { static final String ID_ALIAS = "_abc123"; - FhirPath left; - FhirPath right; - FhirPath literalSnomedAll; - FhirPath literalLoincSystemCode; + Collection left; + Collection right; + Collection literalSnomedAll; + Collection literalLoincSystemCode; ParserContext parserContext; @BeforeEach @@ -126,10 +126,10 @@ void setUp() { .dataset(rightDataset) .idAndValueColumns() .build(); - literalSnomedAll = CodingLiteralPath.fromString( + literalSnomedAll = CodingCollection.fromLiteral( "http://snomed.info/sct|56459004|http://snomed.info/sct/32506021000036107/version/20191231|'Display name'|true", left); - literalLoincSystemCode = CodingLiteralPath.fromString("http://loinc.org|'222|33'", left); + literalLoincSystemCode = CodingCollection.fromLiteral("http://loinc.org|'222|33'", left); parserContext = new ParserContextBuilder(spark, fhirContext) .groupingColumns(Collections.singletonList(left.getIdColumn())) @@ -138,9 +138,9 @@ void setUp() { @Test void equals() { - final OperatorInput input = new OperatorInput(parserContext, left, right); - final Operator equalityOperator = Operator.getInstance("="); - final FhirPath result = equalityOperator.invoke(input); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); + final Collection result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), @@ -155,9 +155,9 @@ void equals() { @Test void notEquals() { - final OperatorInput input = new OperatorInput(parserContext, left, right); - final Operator equalityOperator = Operator.getInstance("!="); - final FhirPath result = equalityOperator.invoke(input); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); + final Collection result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), @@ -172,9 +172,10 @@ void notEquals() { @Test void literalEquals() { - final OperatorInput input = new OperatorInput(parserContext, literalLoincSystemCode, left); - final Operator equalityOperator = Operator.getInstance("="); - final FhirPath result = equalityOperator.invoke(input); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, literalLoincSystemCode, + left); + final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); + final Collection result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), @@ -189,9 +190,10 @@ void literalEquals() { @Test void equalsLiteral() { - final OperatorInput input = new OperatorInput(parserContext, left, literalSnomedAll); - final Operator equalityOperator = Operator.getInstance("="); - final FhirPath result = equalityOperator.invoke(input); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, + literalSnomedAll); + final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); + final Collection result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), @@ -206,9 +208,10 @@ void equalsLiteral() { @Test void literalNotEquals() { - final OperatorInput input = new OperatorInput(parserContext, literalLoincSystemCode, left); - final Operator equalityOperator = Operator.getInstance("!="); - final FhirPath result = equalityOperator.invoke(input); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, literalLoincSystemCode, + left); + final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); + final Collection result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), @@ -223,9 +226,10 @@ void literalNotEquals() { @Test void notEqualsLiteral() { - final OperatorInput input = new OperatorInput(parserContext, left, literalSnomedAll); - final Operator equalityOperator = Operator.getInstance("!="); - final FhirPath result = equalityOperator.invoke(input); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, + literalSnomedAll); + final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); + final Collection result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorQuantityTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorQuantityTest.java index 91dbca775d..6d6286e33c 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorQuantityTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorQuantityTest.java @@ -22,7 +22,8 @@ import static au.csiro.pathling.test.helpers.SparkHelpers.rowForUcumQuantity; import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromQuantity; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.QuantityCollection; import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; @@ -59,8 +60,8 @@ public class EqualityOperatorQuantityTest { static final String ID_ALIAS = "_abc123"; - FhirPath left; - FhirPath right; + Collection left; + Collection right; ParserContext parserContext; QuantityLiteralPath ucumQuantityLiteral1; QuantityLiteralPath ucumQuantityLiteral2; @@ -172,14 +173,14 @@ void setUp() { .idAndValueColumns() .build(); - ucumQuantityLiteral1 = QuantityLiteralPath.fromUcumString("500 'mg'", left, ucumService); - ucumQuantityLiteral2 = QuantityLiteralPath.fromUcumString("0.5 'g'", left, ucumService); - ucumQuantityLiteral3 = QuantityLiteralPath.fromUcumString("1.8 'm'", left, ucumService); - ucumQuantityLiteralNoUnit = QuantityLiteralPath.fromUcumString("0.49 '1'", left, ucumService); + ucumQuantityLiteral1 = QuantityCollection.fromUcumString("500 'mg'", left, ucumService); + ucumQuantityLiteral2 = QuantityCollection.fromUcumString("0.5 'g'", left, ucumService); + ucumQuantityLiteral3 = QuantityCollection.fromUcumString("1.8 'm'", left, ucumService); + ucumQuantityLiteralNoUnit = QuantityCollection.fromUcumString("0.49 '1'", left, ucumService); - calendarDurationLiteral1 = QuantityLiteralPath.fromCalendarDurationString("30 days", left); - calendarDurationLiteral2 = QuantityLiteralPath.fromCalendarDurationString("60 seconds", left); - calendarDurationLiteral3 = QuantityLiteralPath.fromCalendarDurationString("1000 milliseconds", + calendarDurationLiteral1 = QuantityCollection.fromCalendarDurationString("30 days", left); + calendarDurationLiteral2 = QuantityCollection.fromCalendarDurationString("60 seconds", left); + calendarDurationLiteral3 = QuantityCollection.fromCalendarDurationString("1000 milliseconds", left); parserContext = new ParserContextBuilder(spark, fhirContext) @@ -189,10 +190,10 @@ void setUp() { @Test void equals() { - final OperatorInput input = new OperatorInput(parserContext, left, right); - final Operator equalityOperator = Operator.getInstance("="); - final FhirPath result = equalityOperator.invoke(input); - + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); + final Collection result = equalityOperator.invoke(input); + assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), // 500 mg = 500 mg RowFactory.create("patient-2", true), // 500 mg = 0.5 g @@ -211,9 +212,9 @@ void equals() { @Test void notEquals() { - final OperatorInput input = new OperatorInput(parserContext, left, right); - final Operator equalityOperator = Operator.getInstance("!="); - final FhirPath result = equalityOperator.invoke(input); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); + final Collection result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), // 500 mg != 500 mg @@ -233,9 +234,9 @@ void notEquals() { @Test void ucumLiteralEquals() { - OperatorInput input = new OperatorInput(parserContext, left, ucumQuantityLiteral1); - final Operator equalityOperator = Operator.getInstance("="); - FhirPath result = equalityOperator.invoke(input); + BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral1); + final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); + Collection result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", true), // 500 mg = 500 mg @@ -252,7 +253,7 @@ void ucumLiteralEquals() { RowFactory.create("patient-c", null) // non-ucum = 500 mg ); - input = new OperatorInput(parserContext, left, ucumQuantityLiteral2); + input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral2); result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( @@ -270,7 +271,7 @@ void ucumLiteralEquals() { RowFactory.create("patient-c", null) // non-ucum = 0.5 mg ); - input = new OperatorInput(parserContext, left, ucumQuantityLiteral3); + input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral3); result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( @@ -288,7 +289,7 @@ void ucumLiteralEquals() { RowFactory.create("patient-c", null) // non-ucum = 1.8 m ); - input = new OperatorInput(parserContext, left, ucumQuantityLiteralNoUnit); + input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteralNoUnit); result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( @@ -306,7 +307,7 @@ void ucumLiteralEquals() { RowFactory.create("patient-c", null) // non-ucum = 0.49 '1' ); - input = new OperatorInput(parserContext, ucumQuantityLiteral1, ucumQuantityLiteral1); + input = new BinaryOperatorInput(parserContext, ucumQuantityLiteral1, ucumQuantityLiteral1); result = equalityOperator.invoke(input); final Dataset allTrue = new DatasetBuilder(spark) @@ -321,9 +322,9 @@ void ucumLiteralEquals() { @Test void ucumLiteralNotEquals() { - OperatorInput input = new OperatorInput(parserContext, left, ucumQuantityLiteral1); - final Operator equalityOperator = Operator.getInstance("!="); - FhirPath result = equalityOperator.invoke(input); + BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral1); + final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); + Collection result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", false), // 500 mg != 500 mg @@ -340,7 +341,7 @@ void ucumLiteralNotEquals() { RowFactory.create("patient-c", null) // non-ucum != 500 mg ); - input = new OperatorInput(parserContext, left, ucumQuantityLiteral2); + input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral2); result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( @@ -358,7 +359,7 @@ void ucumLiteralNotEquals() { RowFactory.create("patient-c", null) // non-ucum != 0.5 mg ); - input = new OperatorInput(parserContext, left, ucumQuantityLiteral3); + input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral3); result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( @@ -376,7 +377,7 @@ void ucumLiteralNotEquals() { RowFactory.create("patient-c", null) // non-ucum != 1.8 m ); - input = new OperatorInput(parserContext, left, ucumQuantityLiteralNoUnit); + input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteralNoUnit); result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( @@ -394,7 +395,7 @@ void ucumLiteralNotEquals() { RowFactory.create("patient-c", null) // non-ucum != 0.49 '' ); - input = new OperatorInput(parserContext, ucumQuantityLiteral1, ucumQuantityLiteral1); + input = new BinaryOperatorInput(parserContext, ucumQuantityLiteral1, ucumQuantityLiteral1); result = equalityOperator.invoke(input); final Dataset allFalse = new DatasetBuilder(spark) @@ -409,9 +410,10 @@ void ucumLiteralNotEquals() { @Test void calendarLiteralEquals() { - OperatorInput input = new OperatorInput(parserContext, left, calendarDurationLiteral1); - final Operator equalityOperator = Operator.getInstance("="); - FhirPath result = equalityOperator.invoke(input); + BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, + calendarDurationLiteral1); + final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); + Collection result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", null), // 500 mg = 30 days @@ -428,7 +430,7 @@ void calendarLiteralEquals() { RowFactory.create("patient-c", null) // non-ucum = 30 days ); - input = new OperatorInput(parserContext, left, calendarDurationLiteral2); + input = new BinaryOperatorInput(parserContext, left, calendarDurationLiteral2); result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( @@ -446,7 +448,7 @@ void calendarLiteralEquals() { RowFactory.create("patient-c", null) // non-ucum = 60 seconds ); - input = new OperatorInput(parserContext, left, calendarDurationLiteral3); + input = new BinaryOperatorInput(parserContext, left, calendarDurationLiteral3); result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( @@ -464,7 +466,8 @@ void calendarLiteralEquals() { RowFactory.create("patient-c", null) // non-ucum = 1000 milliseconds ); - input = new OperatorInput(parserContext, calendarDurationLiteral2, calendarDurationLiteral2); + input = new BinaryOperatorInput(parserContext, calendarDurationLiteral2, + calendarDurationLiteral2); result = equalityOperator.invoke(input); final Dataset allTrue = new DatasetBuilder(spark) @@ -479,9 +482,10 @@ void calendarLiteralEquals() { @Test void calendarLiteralNotEquals() { - OperatorInput input = new OperatorInput(parserContext, left, calendarDurationLiteral1); - final Operator equalityOperator = Operator.getInstance("!="); - FhirPath result = equalityOperator.invoke(input); + BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, + calendarDurationLiteral1); + final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); + Collection result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( RowFactory.create("patient-1", null), // 500 mg != 30 days @@ -498,7 +502,7 @@ void calendarLiteralNotEquals() { RowFactory.create("patient-c", null) // non-ucum != 30 days ); - input = new OperatorInput(parserContext, left, calendarDurationLiteral2); + input = new BinaryOperatorInput(parserContext, left, calendarDurationLiteral2); result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( @@ -516,7 +520,7 @@ void calendarLiteralNotEquals() { RowFactory.create("patient-c", null) // non-ucum != 60 seconds ); - input = new OperatorInput(parserContext, left, calendarDurationLiteral3); + input = new BinaryOperatorInput(parserContext, left, calendarDurationLiteral3); result = equalityOperator.invoke(input); assertThat(result).selectOrderedResult().hasRows( @@ -534,7 +538,8 @@ void calendarLiteralNotEquals() { RowFactory.create("patient-c", null) // non-ucum != 1000 milliseconds ); - input = new OperatorInput(parserContext, calendarDurationLiteral2, calendarDurationLiteral2); + input = new BinaryOperatorInput(parserContext, calendarDurationLiteral2, + calendarDurationLiteral2); result = equalityOperator.invoke(input); final Dataset allFalse = new DatasetBuilder(spark) diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorQuantityTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorQuantityTest.java index 9a73636dfc..22abb6ee0f 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorQuantityTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorQuantityTest.java @@ -22,8 +22,8 @@ import static au.csiro.pathling.test.helpers.SparkHelpers.rowForUcumQuantity; import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromQuantity; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.QuantityCollection; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.DatasetBuilder; @@ -33,7 +33,6 @@ import ca.uhn.fhir.context.FhirContext; import java.math.BigDecimal; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.stream.Stream; @@ -75,16 +74,16 @@ static class TestParameters { String name; @Nonnull - FhirPath left; + Collection left; @Nonnull - FhirPath right; + Collection right; @Nonnull ParserContext context; @Nonnull - Operator operator; + BinaryOperator operator; @Nonnull Dataset expectedResult; @@ -98,16 +97,17 @@ public String toString() { @Nonnull Stream parameters() { - final Collection parameters = new ArrayList<>(); + final java.util.Collection parameters = new ArrayList<>(); for (final String operator : OPERATORS) { final String name = "Quantity " + operator + " Quantity"; - final FhirPath left = buildQuantityExpression(true); - final FhirPath right = buildQuantityExpression(false); + final Collection left = buildQuantityExpression(true); + final Collection right = buildQuantityExpression(false); final ParserContext context = new ParserContextBuilder(spark, fhirContext) .groupingColumns(Collections.singletonList(left.getIdColumn())) .build(); - parameters.add(new TestParameters(name, left, right, context, Operator.getInstance(operator), - expectedResult(operator))); + parameters.add( + new TestParameters(name, left, right, context, BinaryOperator.getInstance(operator), + expectedResult(operator))); } return parameters.stream(); } @@ -151,7 +151,7 @@ Dataset expectedResult(@Nonnull final String operator) { } @Nonnull - FhirPath buildQuantityExpression(final boolean leftOperand) { + Collection buildQuantityExpression(final boolean leftOperand) { final Quantity nonUcumQuantity = new Quantity(); nonUcumQuantity.setValue(15); nonUcumQuantity.setUnit("mSv"); @@ -194,9 +194,9 @@ FhirPath buildQuantityExpression(final boolean leftOperand) { @ParameterizedTest @MethodSource("parameters") void test(@Nonnull final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), parameters.getLeft(), parameters.getRight()); - final FhirPath result = parameters.getOperator().invoke(input); + final Collection result = parameters.getOperator().invoke(input); assertThat(result).selectOrderedResult().hasRows(parameters.getExpectedResult()); } @@ -212,20 +212,20 @@ void volumeArithmetic() { .withStructTypeColumns(quantityStructType()) .withRow("patient-1", rowForUcumQuantity(new BigDecimal("1.0"), "mmol/L")) .buildWithStructValue(); - final FhirPath right = new ElementPathBuilder(spark) + final Collection right = new ElementPathBuilder(spark) .expression("valueQuantity") .fhirType(FHIRDefinedType.QUANTITY) .singular(true) .dataset(rightDataset) .idAndValueColumns() .build(); - final FhirPath left = QuantityLiteralPath.fromUcumString("1.0 'mmol/L'", right, + final Collection left = QuantityCollection.fromUcumString("1.0 'mmol/L'", right, ucumService); final ParserContext context = new ParserContextBuilder(spark, fhirContext) .groupingColumns(Collections.singletonList(left.getIdColumn())) .build(); - final OperatorInput input = new OperatorInput(context, left, right); - final FhirPath result = Operator.getInstance("+").invoke(input); + final BinaryOperatorInput input = new BinaryOperatorInput(context, left, right); + final Collection result = BinaryOperator.getInstance("+").invoke(input); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn() diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorTest.java index 9da4251fb7..c2af233dec 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorTest.java @@ -19,9 +19,9 @@ import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.ElementPath; -import au.csiro.pathling.fhirpath.literal.DecimalLiteralPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.DecimalCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; @@ -32,7 +32,6 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.stream.Stream; @@ -44,13 +43,11 @@ 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.Tag; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; /** * @author John Grimes @@ -76,10 +73,10 @@ static class TestParameters { String name; @Nonnull - FhirPath left; + Collection left; @Nonnull - FhirPath right; + Collection right; @Nonnull ParserContext context; @@ -98,11 +95,11 @@ public String toString() { } Stream parameters() { - final Collection parameters = new ArrayList<>(); + final java.util.Collection parameters = new ArrayList<>(); for (final String leftType : EXPRESSION_TYPES) { for (final String rightType : EXPRESSION_TYPES) { - final FhirPath left = getExpressionForType(leftType, true); - final FhirPath right = getExpressionForType(rightType, false); + final Collection left = getExpressionForType(leftType, true); + final Collection right = getExpressionForType(rightType, false); final boolean leftOperandIsInteger = leftType.equals("Integer") || leftType.equals("Integer (literal)"); final boolean leftTypeIsLiteral = @@ -119,7 +116,7 @@ Stream parameters() { return parameters.stream(); } - FhirPath getExpressionForType(final String expressionType, + Collection getExpressionForType(final String expressionType, final boolean leftOperand) { final Dataset literalContextDataset = new DatasetBuilder(spark) .withIdColumn(ID_ALIAS) @@ -127,7 +124,7 @@ FhirPath getExpressionForType(final String expressionType, .withIdsAndValue(false, Arrays .asList("patient-1", "patient-2", "patient-3", "patient-4")) .build(); - final ElementPath literalContext = new ElementPathBuilder(spark) + final PrimitivePath literalContext = new ElementPathBuilder(spark) .dataset(literalContextDataset) .idAndValueColumns() .build(); @@ -141,7 +138,7 @@ FhirPath getExpressionForType(final String expressionType, case "Decimal": return buildDecimalExpression(leftOperand); case "Decimal (literal)": - return DecimalLiteralPath.fromString(leftOperand + return DecimalCollection.fromLiteral(leftOperand ? "1.0" : "2.0", literalContext); default: @@ -149,7 +146,7 @@ FhirPath getExpressionForType(final String expressionType, } } - FhirPath buildIntegerExpression(final boolean leftOperand) { + Collection buildIntegerExpression(final boolean leftOperand) { final Dataset dataset = new DatasetBuilder(spark) .withIdColumn(ID_ALIAS) .withColumn(DataTypes.IntegerType) @@ -172,7 +169,7 @@ FhirPath buildIntegerExpression(final boolean leftOperand) { .build(); } - FhirPath buildDecimalExpression(final boolean leftOperand) { + Collection buildDecimalExpression(final boolean leftOperand) { final Dataset dataset = new DatasetBuilder(spark) .withIdColumn(ID_ALIAS) .withColumn(DataTypes.createDecimalType()) @@ -198,10 +195,11 @@ FhirPath buildDecimalExpression(final boolean leftOperand) { @ParameterizedTest @MethodSource("parameters") void addition(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLeft(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLeft(), parameters.getRight()); - final Operator comparisonOperator = Operator.getInstance("+"); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance("+"); + final Collection result = comparisonOperator.invoke(input); final Object value = parameters.isLeftOperandIsInteger() ? 3 : new BigDecimal("3.0"); @@ -225,10 +223,11 @@ void addition(final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void subtraction(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLeft(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLeft(), parameters.getRight()); - final Operator comparisonOperator = Operator.getInstance("-"); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance("-"); + final Collection result = comparisonOperator.invoke(input); final Object value = parameters.isLeftOperandIsInteger() ? -1 : new BigDecimal("-1.0"); @@ -252,10 +251,11 @@ void subtraction(final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void multiplication(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLeft(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLeft(), parameters.getRight()); - final Operator comparisonOperator = Operator.getInstance("*"); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance("*"); + final Collection result = comparisonOperator.invoke(input); final Object value = parameters.isLeftOperandIsInteger() ? 2 : new BigDecimal("2.0"); @@ -279,10 +279,11 @@ void multiplication(final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void division(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLeft(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLeft(), parameters.getRight()); - final Operator comparisonOperator = Operator.getInstance("/"); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance("/"); + final Collection result = comparisonOperator.invoke(input); final Object value = new BigDecimal("0.5"); assertThat(result).selectOrderedResult().hasRows( @@ -304,10 +305,11 @@ void division(final TestParameters parameters) { @ParameterizedTest @MethodSource("parameters") void modulus(final TestParameters parameters) { - final OperatorInput input = new OperatorInput(parameters.getContext(), parameters.getLeft(), + final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + parameters.getLeft(), parameters.getRight()); - final Operator comparisonOperator = Operator.getInstance("mod"); - final FhirPath result = comparisonOperator.invoke(input); + final BinaryOperator comparisonOperator = BinaryOperator.getInstance("mod"); + final Collection result = comparisonOperator.invoke(input); final Object value = 1; assertThat(result).selectOrderedResult().hasRows( diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorValidationTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorValidationTest.java index d14b062602..1df25ff421 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorValidationTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorValidationTest.java @@ -21,7 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.ElementPathBuilder; @@ -54,19 +54,19 @@ void setUp() { @Test void operandIsNotCorrectType() { - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.DATETIME) .singular(true) .expression("foo") .build(); - final ElementPath right = new ElementPathBuilder(spark) + final PrimitivePath right = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .singular(true) .expression("bar") .build(); - final OperatorInput input = new OperatorInput(parserContext, left, right); - final Operator mathOperator = Operator.getInstance("+"); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator mathOperator = BinaryOperator.getInstance("+"); final InvalidUserInputError error = assertThrows( InvalidUserInputError.class, () -> mathOperator.invoke(input)); @@ -74,7 +74,7 @@ void operandIsNotCorrectType() { error.getMessage()); // Now test the right operand. - final OperatorInput reversedInput = new OperatorInput(parserContext, right, left); + final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); final InvalidUserInputError reversedError = assertThrows( InvalidUserInputError.class, () -> mathOperator.invoke(reversedInput)); @@ -85,19 +85,19 @@ void operandIsNotCorrectType() { @Test void operandIsNotSingular() { - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .singular(false) .expression("foo") .build(); - final ElementPath right = new ElementPathBuilder(spark) + final PrimitivePath right = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .singular(true) .expression("bar") .build(); - final OperatorInput input = new OperatorInput(parserContext, left, right); - final Operator mathOperator = Operator.getInstance("+"); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator mathOperator = BinaryOperator.getInstance("+"); final InvalidUserInputError error = assertThrows( InvalidUserInputError.class, () -> mathOperator.invoke(input)); @@ -105,7 +105,7 @@ void operandIsNotSingular() { error.getMessage()); // Now test the right operand. - final OperatorInput reversedInput = new OperatorInput(parserContext, right, left); + final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); final InvalidUserInputError reversedError = assertThrows( InvalidUserInputError.class, () -> mathOperator.invoke(reversedInput)); @@ -116,19 +116,19 @@ void operandIsNotSingular() { @Test void operandsAreNotComparable() { - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.INTEGER) .singular(true) .expression("foo") .build(); - final ElementPath right = new ElementPathBuilder(spark) + final PrimitivePath right = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.QUANTITY) .singular(true) .expression("bar") .build(); - final OperatorInput input = new OperatorInput(parserContext, left, right); - final Operator mathOperator = Operator.getInstance("+"); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator mathOperator = BinaryOperator.getInstance("+"); final InvalidUserInputError error = assertThrows( InvalidUserInputError.class, () -> mathOperator.invoke(input)); @@ -136,7 +136,7 @@ void operandsAreNotComparable() { error.getMessage()); // Now test the right operand. - final OperatorInput reversedInput = new OperatorInput(parserContext, right, left); + final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); final InvalidUserInputError reversedError = assertThrows( InvalidUserInputError.class, () -> mathOperator.invoke(reversedInput)); diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MembershipOperatorTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MembershipOperatorTest.java index 2bc0cd7425..1b495ddaf8 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MembershipOperatorTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MembershipOperatorTest.java @@ -26,9 +26,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.BooleanPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.CodingCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; import au.csiro.pathling.fhirpath.literal.StringLiteralPath; @@ -75,20 +77,20 @@ static Stream parameters() { return Stream.of("in", "contains"); } - FhirPath testOperator(final String operator, final FhirPath collection, - final FhirPath element) { - final OperatorInput operatorInput; + Collection testOperator(final String operator, final Collection collection, + final Collection element) { + final BinaryOperatorInput operatorInput; if ("in".equals(operator)) { - operatorInput = new OperatorInput(parserContext, element, collection); + operatorInput = new BinaryOperatorInput(parserContext, element, collection); } else if ("contains".equals(operator)) { - operatorInput = new OperatorInput(parserContext, collection, element); + operatorInput = new BinaryOperatorInput(parserContext, collection, element); } else { throw new IllegalArgumentException("Membership operator '" + operator + "' cannot be tested"); } - final FhirPath result = Operator.getInstance(operator).invoke(operatorInput); + final Collection result = BinaryOperator.getInstance(operator).invoke(operatorInput); assertThat(result) - .isElementPath(BooleanPath.class) + .isElementPath(BooleanCollection.class) .isSingular(); return result; } @@ -96,17 +98,17 @@ FhirPath testOperator(final String operator, final FhirPath collection, @ParameterizedTest @MethodSource("parameters") void returnsCorrectResultWhenElementIsLiteral(final String operator) { - final ElementPath collection = new ElementPathBuilder(spark) + final PrimitivePath collection = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .dataset(StringPrimitiveRowFixture.createCompleteDataset(spark)) .idAndValueColumns() .build(); - final StringLiteralPath element = StringLiteralPath.fromString("'Samuel'", collection); + final StringLiteralPath element = StringCollection.fromLiteral("'Samuel'", collection); parserContext = new ParserContextBuilder(spark, fhirContext) .groupingColumns(Collections.singletonList(collection.getIdColumn())) .build(); - final FhirPath result = testOperator(operator, collection, element); + final Collection result = testOperator(operator, collection, element); assertThat(result) .selectOrderedResult() .hasRows( @@ -120,12 +122,12 @@ void returnsCorrectResultWhenElementIsLiteral(final String operator) { @ParameterizedTest @MethodSource("parameters") void returnsCorrectResultWhenElementIsExpression(final String operator) { - final ElementPath collection = new ElementPathBuilder(spark) + final PrimitivePath collection = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .dataset(StringPrimitiveRowFixture.createCompleteDataset(spark)) .idAndValueColumns() .build(); - final ElementPath element = new ElementPathBuilder(spark) + final PrimitivePath element = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .dataset(StringPrimitiveRowFixture .createDataset(spark, RowFactory.create(StringPrimitiveRowFixture.ROW_ID_1, "Eva"), @@ -141,7 +143,7 @@ void returnsCorrectResultWhenElementIsExpression(final String operator) { .groupingColumns(Collections.singletonList(collection.getIdColumn())) .build(); - final FhirPath result = testOperator(operator, collection, element); + final Collection result = testOperator(operator, collection, element); assertThat(result) .selectOrderedResult() .hasRows( @@ -155,17 +157,17 @@ void returnsCorrectResultWhenElementIsExpression(final String operator) { @ParameterizedTest @MethodSource("parameters") void resultIsFalseWhenCollectionIsEmpty(final String operator) { - final ElementPath collection = new ElementPathBuilder(spark) + final PrimitivePath collection = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .dataset(StringPrimitiveRowFixture.createNullRowsDataset(spark)) .idAndValueColumns() .build(); - final StringLiteralPath element = StringLiteralPath.fromString("'Samuel'", collection); + final StringLiteralPath element = StringCollection.fromLiteral("'Samuel'", collection); parserContext = new ParserContextBuilder(spark, fhirContext) .groupingColumns(Collections.singletonList(collection.getIdColumn())) .build(); - final FhirPath result = testOperator(operator, collection, element); + final Collection result = testOperator(operator, collection, element); assertThat(result) .selectOrderedResult() .hasRows( @@ -176,12 +178,12 @@ void resultIsFalseWhenCollectionIsEmpty(final String operator) { @ParameterizedTest @MethodSource("parameters") void returnsEmptyWhenElementIsEmpty(final String operator) { - final ElementPath collection = new ElementPathBuilder(spark) + final PrimitivePath collection = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .dataset(StringPrimitiveRowFixture.createCompleteDataset(spark)) .idAndValueColumns() .build(); - final ElementPath element = new ElementPathBuilder(spark) + final PrimitivePath element = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .dataset(StringPrimitiveRowFixture.createAllRowsNullDataset(spark)) .idAndValueColumns() @@ -192,7 +194,7 @@ void returnsEmptyWhenElementIsEmpty(final String operator) { .groupingColumns(Collections.singletonList(collection.getIdColumn())) .build(); - final FhirPath result = testOperator(operator, collection, element); + final Collection result = testOperator(operator, collection, element); assertThat(result) .selectOrderedResult() .hasRows( @@ -228,19 +230,19 @@ void worksForCodingLiterals(final String operator) { .withRow(StringPrimitiveRowFixture.ROW_ID_4, null) .buildWithStructValue(); - final ElementPath collection = new ElementPathBuilder(spark) + final PrimitivePath collection = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODING) .dataset(codingDataset) .idAndValueColumns() .build(); - final CodingLiteralPath element = CodingLiteralPath - .fromString("http://loinc.org|56459004", collection); + final CodingLiteralPath element = CodingCollection + .fromLiteral("http://loinc.org|56459004", collection); parserContext = new ParserContextBuilder(spark, fhirContext) .groupingColumns(Collections.singletonList(collection.getIdColumn())) .build(); - final FhirPath result = testOperator(operator, collection, element); + final Collection result = testOperator(operator, collection, element); assertThat(result) .selectOrderedResult() .hasRows( @@ -253,10 +255,10 @@ void worksForCodingLiterals(final String operator) { @ParameterizedTest @MethodSource("parameters") void throwExceptionWhenElementIsNotSingular(final String operator) { - final ElementPath collection = new ElementPathBuilder(spark) + final PrimitivePath collection = new ElementPathBuilder(spark) .singular(false) .build(); - final ElementPath element = new ElementPathBuilder(spark) + final PrimitivePath element = new ElementPathBuilder(spark) .singular(false) .expression("name.given") .build(); @@ -272,7 +274,7 @@ void throwExceptionWhenElementIsNotSingular(final String operator) { @ParameterizedTest @MethodSource("parameters") void throwExceptionWhenIncompatibleTypes(final String operator) { - final ElementPath collection = new ElementPathBuilder(spark) + final PrimitivePath collection = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .expression("foo") .build(); diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperatorTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperatorTest.java index eb1a0ca961..d970738fec 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperatorTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperatorTest.java @@ -32,11 +32,11 @@ import static org.mockito.Mockito.when; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.element.StringPath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.test.SpringBootUnitTest; @@ -95,7 +95,7 @@ void singularTraversalFromSingular() { .withRow("patient-2", null) .build(); when(dataSource.read(ResourceType.PATIENT)).thenReturn(leftDataset); - final ResourcePath left = new ResourcePathBuilder(spark) + final ResourceCollection left = new ResourcePathBuilder(spark) .fhirContext(fhirContext) .resourceType(ResourceType.PATIENT) .database(dataSource) @@ -103,7 +103,7 @@ void singularTraversalFromSingular() { .build(); final PathTraversalInput input = new PathTraversalInput(parserContext, left, "gender"); - final FhirPath result = new PathTraversalOperator().invoke(input); + final Collection result = new PathTraversalOperator().invoke(input); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn() @@ -113,7 +113,7 @@ void singularTraversalFromSingular() { .withRow("patient-2", null, null) .build(); assertThat(result) - .isElementPath(StringPath.class) + .isElementPath(StringCollection.class) .isSingular() .selectOrderedResultWithEid() .hasRows(expectedDataset); @@ -130,7 +130,7 @@ void manyTraversalFromSingular() { .withRow("patient-3", null, true) .build(); when(dataSource.read(ResourceType.PATIENT)).thenReturn(leftDataset); - final ResourcePath left = new ResourcePathBuilder(spark) + final ResourceCollection left = new ResourcePathBuilder(spark) .fhirContext(fhirContext) .resourceType(ResourceType.PATIENT) .database(dataSource) @@ -138,7 +138,7 @@ void manyTraversalFromSingular() { .build(); final PathTraversalInput input = new PathTraversalInput(parserContext, left, "name"); - final FhirPath result = new PathTraversalOperator().invoke(input); + final Collection result = new PathTraversalOperator().invoke(input); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn() @@ -152,7 +152,7 @@ void manyTraversalFromSingular() { .withRow("patient-3", null, null) .build(); assertThat(result) - .isElementPath(ElementPath.class) + .isElementPath(PrimitivePath.class) .hasExpression("Patient.name") .hasFhirType(FHIRDefinedType.HUMANNAME) .isNotSingular() @@ -180,7 +180,7 @@ void manyTraversalFromNonSingular() { .getChildOfResource(fhirContext, "Patient", "name"); assertTrue(definition.isPresent()); - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .dataset(inputDataset) .idAndEidAndValueColumns() @@ -189,7 +189,7 @@ void manyTraversalFromNonSingular() { .buildDefined(); final PathTraversalInput input = new PathTraversalInput(parserContext, left, "given"); - final FhirPath result = new PathTraversalOperator().invoke(input); + final Collection result = new PathTraversalOperator().invoke(input); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn() @@ -206,7 +206,7 @@ void manyTraversalFromNonSingular() { .build(); assertThat(result) - .isElementPath(ElementPath.class) + .isElementPath(PrimitivePath.class) .hasExpression("Patient.name.given") .hasFhirType(FHIRDefinedType.STRING) .isNotSingular() @@ -234,7 +234,7 @@ void singularTraversalFromNonSingular() { .getChildOfResource(fhirContext, "Patient", "name"); assertTrue(definition.isPresent()); - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.STRING) .dataset(inputDataset) .idAndEidAndValueColumns() @@ -243,7 +243,7 @@ void singularTraversalFromNonSingular() { .buildDefined(); final PathTraversalInput input = new PathTraversalInput(parserContext, left, "family"); - final FhirPath result = new PathTraversalOperator().invoke(input); + final Collection result = new PathTraversalOperator().invoke(input); final Dataset expectedDataset = new DatasetBuilder(spark) .withIdColumn() @@ -257,7 +257,7 @@ void singularTraversalFromNonSingular() { .build(); assertThat(result) - .isElementPath(ElementPath.class) + .isElementPath(PrimitivePath.class) .hasExpression("Patient.name.family") .hasFhirType(FHIRDefinedType.STRING) .isNotSingular() @@ -284,17 +284,17 @@ void testExtensionTraversalOnResources() { .build(); when(dataSource.read(ResourceType.PATIENT)) .thenReturn(patientDataset); - final ResourcePath left = ResourcePath - .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient", false); + final ResourceCollection left = ResourceCollection + .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient"); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); final PathTraversalInput input = new PathTraversalInput(parserContext, left, "extension"); - final FhirPath result = new PathTraversalOperator().invoke(input); + final Collection result = new PathTraversalOperator().invoke(input); assertThat(result) .hasExpression("Patient.extension") .isNotSingular() - .isElementPath(ElementPath.class) + .isElementPath(PrimitivePath.class) .hasFhirType(FHIRDefinedType.EXTENSION) .selectOrderedResult() .hasRows( @@ -346,27 +346,28 @@ void testExtensionTraversalOnElements() { when(dataSource.read(ResourceType.OBSERVATION)) .thenReturn(resourceLikeDataset); - final ResourcePath baseResourcePath = ResourcePath - .build(fhirContext, dataSource, ResourceType.OBSERVATION, "Observation", false); + final ResourceCollection baseResourceCollection = ResourceCollection + .build(fhirContext, dataSource, ResourceType.OBSERVATION, "Observation"); - final Dataset elementDataset = toElementDataset(resourceLikeDataset, baseResourcePath); + final Dataset elementDataset = toElementDataset(resourceLikeDataset, + baseResourceCollection); final ElementDefinition codeDefinition = checkPresent(FhirHelpers .getChildOfResource(fhirContext, "Observation", "code")); - final ElementPath left = new ElementPathBuilder(spark) + final PrimitivePath left = new ElementPathBuilder(spark) .fhirType(FHIRDefinedType.CODEABLECONCEPT) .definition(codeDefinition) .dataset(elementDataset) .idAndEidAndValueColumns() .expression("code") .singular(false) - .currentResource(baseResourcePath) + .currentResource(baseResourceCollection) .buildDefined(); final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); final PathTraversalInput input = new PathTraversalInput(parserContext, left, "extension"); - final FhirPath result = new PathTraversalOperator().invoke(input); + final Collection result = new PathTraversalOperator().invoke(input); final Dataset expectedResult = new DatasetBuilder(spark) .withIdColumn() @@ -384,7 +385,7 @@ void testExtensionTraversalOnElements() { assertThat(result) .hasExpression("code.extension") .isNotSingular() - .isElementPath(ElementPath.class) + .isElementPath(PrimitivePath.class) .hasFhirType(FHIRDefinedType.EXTENSION) .selectOrderedResultWithEid() .hasRows(expectedResult); @@ -393,7 +394,7 @@ void testExtensionTraversalOnElements() { @Test void throwsErrorOnNonExistentChild() { - final ResourcePath left = new ResourcePathBuilder(spark) + final ResourceCollection left = new ResourcePathBuilder(spark) .fhirContext(fhirContext) .resourceType(ResourceType.ENCOUNTER) .expression("Encounter") diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/QuantityOperatorsPrecisionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/QuantityOperatorsPrecisionTest.java index 7e905a956d..92dd57415f 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/QuantityOperatorsPrecisionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/QuantityOperatorsPrecisionTest.java @@ -21,8 +21,8 @@ import static au.csiro.pathling.test.helpers.SparkHelpers.quantityStructType; import static au.csiro.pathling.test.helpers.SparkHelpers.rowForUcumQuantity; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.DatasetBuilder; @@ -90,7 +90,7 @@ private static String unitToRowId(@Nonnull final String unit) { } @Nonnull - private ElementPath buildQuantityPathForUnits(@Nonnull final String value, + private PrimitivePath buildQuantityPathForUnits(@Nonnull final String value, final List units) { DatasetBuilder datasetBuilder = new DatasetBuilder(spark) .withIdColumn(ID_ALIAS) @@ -143,23 +143,23 @@ private static List createResult(@Nonnull final List unitRange, fin @Nonnull - private FhirPath callOperator(@Nonnull final ElementPath left, @Nonnull final String operator, - @Nonnull final ElementPath right) { + private Collection callOperator(@Nonnull final PrimitivePath left, @Nonnull final String operator, + @Nonnull final PrimitivePath right) { final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) .groupingColumns(Collections.singletonList(left.getIdColumn())) .build(); - final OperatorInput input = new OperatorInput(parserContext, left, right); - final Operator equalityOperator = Operator.getInstance(operator); + final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + final BinaryOperator equalityOperator = BinaryOperator.getInstance(operator); return equalityOperator.invoke(input); } @Test void equalityPrecisionForReasonableDecimals() { final List unitRange = getAllPrefixedUnits("m"); - final ElementPath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); - final ElementPath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); - final FhirPath result = callOperator(left, "=", right); + final PrimitivePath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); + final PrimitivePath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); + final Collection result = callOperator(left, "=", right); assertThat(result).selectResult().hasRows(createResult(unitRange, true)); } @@ -167,9 +167,9 @@ void equalityPrecisionForReasonableDecimals() { @Test void nonEqualityPrecisionForReasonableDecimals() { final List unitRange = getAllPrefixedUnits("m"); - final ElementPath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); - final ElementPath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_02, unitRange); - final FhirPath result = callOperator(left, "!=", right); + final PrimitivePath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); + final PrimitivePath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_02, unitRange); + final Collection result = callOperator(left, "!=", right); assertThat(result).selectResult().hasRows(createResult(unitRange, true)); } @@ -177,9 +177,9 @@ void nonEqualityPrecisionForReasonableDecimals() { @Test void comparisonPrecisionForReasonableDecimals() { final List unitRange = getAllPrefixedUnits("m"); - final ElementPath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); - final ElementPath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_02, unitRange); - final FhirPath result = callOperator(left, "<", right); + final PrimitivePath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); + final PrimitivePath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_02, unitRange); + final Collection result = callOperator(left, "<", right); assertThat(result).selectResult().hasRows(createResult(unitRange, true)); } @@ -187,9 +187,9 @@ void comparisonPrecisionForReasonableDecimals() { @Test void equalityPrecisionForFullDecimals() { final List unitRange = getAllPrefixedUnits("m"); - final ElementPath left = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); - final ElementPath right = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); - final FhirPath result = callOperator(left, "=", right); + final PrimitivePath left = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); + final PrimitivePath right = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); + final Collection result = callOperator(left, "=", right); assertThat(result).selectResult() .hasRows(createResult(unitRange, true, UNSUPPORTED_FULL_DECIMAL_UNITS)); } @@ -197,9 +197,9 @@ void equalityPrecisionForFullDecimals() { @Test void nonEqualityPrecisionForFullDecimals() { final List unitRange = getAllPrefixedUnits("m"); - final ElementPath left = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); - final ElementPath right = buildQuantityPathForUnits(FULL_DECIMAL_02, unitRange); - final FhirPath result = callOperator(left, "!=", right); + final PrimitivePath left = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); + final PrimitivePath right = buildQuantityPathForUnits(FULL_DECIMAL_02, unitRange); + final Collection result = callOperator(left, "!=", right); assertThat(result).selectResult() .hasRows(createResult(unitRange, true, UNSUPPORTED_FULL_DECIMAL_UNITS)); } @@ -207,9 +207,9 @@ void nonEqualityPrecisionForFullDecimals() { @Test void comparisonPrecisionForFullDecimals() { final List unitRange = getAllPrefixedUnits("m"); - final ElementPath left = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); - final ElementPath right = buildQuantityPathForUnits(FULL_DECIMAL_02, unitRange); - final FhirPath result = callOperator(left, "<", right); + final PrimitivePath left = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); + final PrimitivePath right = buildQuantityPathForUnits(FULL_DECIMAL_02, unitRange); + final Collection result = callOperator(left, "<", right); assertThat(result).selectResult() .hasRows(createResult(unitRange, true, UNSUPPORTED_FULL_DECIMAL_UNITS)); } @@ -217,9 +217,9 @@ void comparisonPrecisionForFullDecimals() { @Test void equalityPrecisionForReasonableDecimalsWithMoles() { final List unitRange = getAllPrefixedUnits("mol"); - final ElementPath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); - final ElementPath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); - final FhirPath result = callOperator(left, "=", right); + final PrimitivePath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); + final PrimitivePath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); + final Collection result = callOperator(left, "=", right); assertThat(result).selectResult() .hasRows(createResult(unitRange, true, UNSUPPORTED_REASONABLE_DECIMAL_MOL_UNITS)); } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java index 477386c22a..8f52dc966b 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java @@ -19,9 +19,9 @@ import static au.csiro.pathling.test.TestResources.getResourceAsUrl; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -38,17 +38,17 @@ public abstract class Assertions { @Nonnull - public static FhirPathAssertion assertThat(@Nonnull final FhirPath fhirPath) { - return new FhirPathAssertion(fhirPath); + public static FhirPathAssertion assertThat(@Nonnull final Collection result) { + return new FhirPathAssertion(result); } @Nonnull - public static ResourcePathAssertion assertThat(@Nonnull final ResourcePath fhirPath) { + public static ResourcePathAssertion assertThat(@Nonnull final ResourceCollection fhirPath) { return new ResourcePathAssertion(fhirPath); } @Nonnull - public static ElementPathAssertion assertThat(@Nonnull final ElementPath fhirPath) { + public static ElementPathAssertion assertThat(@Nonnull final PrimitivePath fhirPath) { return new ElementPathAssertion(fhirPath); } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java index 9d3bf7b23a..c96c3e338d 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java @@ -22,9 +22,9 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.ResourcePath; -import au.csiro.pathling.fhirpath.element.ElementPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.fhirpath.literal.LiteralPath; import java.util.ArrayList; import java.util.List; @@ -39,23 +39,23 @@ public abstract class BaseFhirPathAssertion> { @Nonnull - private final FhirPath fhirPath; + private final Collection result; - BaseFhirPathAssertion(@Nonnull final FhirPath fhirPath) { - this.fhirPath = fhirPath; + BaseFhirPathAssertion(@Nonnull final Collection result) { + this.result = result; } @Nonnull public DatasetAssert selectResult() { - final Column[] selection = new Column[]{fhirPath.getIdColumn(), fhirPath.getValueColumn()}; - return new DatasetAssert(fhirPath.getDataset().select(selection)); + final Column[] selection = new Column[]{result.getIdColumn(), result.getValueColumn()}; + return new DatasetAssert(result.getDataset().select(selection)); } @Nonnull public DatasetAssert selectOrderedResult() { - final Column[] selection = new Column[]{fhirPath.getIdColumn(), fhirPath.getValueColumn()}; + final Column[] selection = new Column[]{result.getIdColumn(), result.getValueColumn()}; // TODO: Update this to make sure that it is ordered. - return new DatasetAssert(fhirPath.getDataset().select(selection)); + return new DatasetAssert(result.getDataset().select(selection)); } @Nonnull @@ -68,55 +68,55 @@ private DatasetAssert selectGroupingResult(@Nonnull final List groupingC final boolean preserveOrder) { check(!groupingColumns.isEmpty()); final ArrayList allColumnsList = new ArrayList<>(groupingColumns); - allColumnsList.add(fhirPath.getValueColumn()); + allColumnsList.add(result.getValueColumn()); final Column[] allColumns = allColumnsList.toArray(new Column[0]); return new DatasetAssert(preserveOrder - ? fhirPath.getDataset().select(allColumns) - : fhirPath.getDataset().select(allColumns).orderBy(allColumns)); + ? result.getDataset().select(allColumns) + : result.getDataset().select(allColumns).orderBy(allColumns)); } @Nonnull public DatasetAssert selectOrderedResultWithEid() { - final Column[] selection = new Column[]{fhirPath.getIdColumn(), fhirPath.getValueColumn()}; + final Column[] selection = new Column[]{result.getIdColumn(), result.getValueColumn()}; // TODO: Update this to make sure that it is ordered. - return new DatasetAssert(fhirPath.getDataset().select(selection)); + return new DatasetAssert(result.getDataset().select(selection)); } @Nonnull public T hasExpression(@Nonnull final String expression) { - assertEquals(expression, fhirPath.getExpression()); + assertEquals(expression, result.getExpression()); return self(); } public T isSingular() { - assertTrue(fhirPath.isSingular()); + assertTrue(result.isSingular()); return self(); } public T isNotSingular() { - assertFalse(fhirPath.isSingular()); + assertFalse(result.isSingular()); return self(); } - public T preservesCardinalityOf(final FhirPath otherFhirPath) { - assertEquals(otherFhirPath.isSingular(), fhirPath.isSingular()); + public T preservesCardinalityOf(final Collection otherResult) { + assertEquals(otherResult.isSingular(), result.isSingular()); return self(); } - public ElementPathAssertion isElementPath(final Class ofType) { - assertTrue(ofType.isAssignableFrom(fhirPath.getClass())); - return new ElementPathAssertion((ElementPath) fhirPath); + public ElementPathAssertion isElementPath(final Class ofType) { + assertTrue(ofType.isAssignableFrom(result.getClass())); + return new ElementPathAssertion((PrimitivePath) result); } public ResourcePathAssertion isResourcePath() { - assertTrue(ResourcePath.class.isAssignableFrom(fhirPath.getClass())); - return new ResourcePathAssertion((ResourcePath) fhirPath); + assertTrue(ResourceCollection.class.isAssignableFrom(result.getClass())); + return new ResourcePathAssertion((ResourceCollection) result); } public LiteralPathAssertion isLiteralPath(final Class ofType) { - assertTrue(ofType.isAssignableFrom(fhirPath.getClass())); - return new LiteralPathAssertion((LiteralPath) fhirPath); + assertTrue(ofType.isAssignableFrom(result.getClass())); + return new LiteralPathAssertion((LiteralPath) result); } @SuppressWarnings("unchecked") diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java index 03d52b74c8..35342ad0a4 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java @@ -17,11 +17,8 @@ package au.csiro.pathling.test.assertions; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import au.csiro.pathling.fhirpath.element.ElementPath; import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import javax.annotation.Nonnull; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; @@ -32,9 +29,9 @@ public class ElementPathAssertion extends BaseFhirPathAssertion { @Nonnull - private final ElementPath fhirPath; + private final PrimitivePath fhirPath; - ElementPathAssertion(@Nonnull final ElementPath fhirPath) { + ElementPathAssertion(@Nonnull final PrimitivePath fhirPath) { super(fhirPath); this.fhirPath = fhirPath; } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/FhirPathAssertion.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/FhirPathAssertion.java index 790d95fa82..c79c46dfb7 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/FhirPathAssertion.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/FhirPathAssertion.java @@ -17,7 +17,7 @@ package au.csiro.pathling.test.assertions; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import javax.annotation.Nonnull; /** @@ -25,8 +25,8 @@ */ public class FhirPathAssertion extends BaseFhirPathAssertion { - FhirPathAssertion(@Nonnull final FhirPath fhirPath) { - super(fhirPath); + FhirPathAssertion(@Nonnull final Collection result) { + super(result); } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ResourcePathAssertion.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ResourcePathAssertion.java index 50989cb586..8cf6388c5c 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ResourcePathAssertion.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ResourcePathAssertion.java @@ -19,7 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import javax.annotation.Nonnull; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -29,9 +29,9 @@ public class ResourcePathAssertion extends BaseFhirPathAssertion { @Nonnull - private final ResourcePath fhirPath; + private final ResourceCollection fhirPath; - ResourcePathAssertion(@Nonnull final ResourcePath fhirPath) { + ResourcePathAssertion(@Nonnull final ResourceCollection fhirPath) { super(fhirPath); this.fhirPath = fhirPath; } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java index 898c96b691..18e4984180 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java @@ -22,10 +22,10 @@ import static org.mockito.Mockito.mock; import au.csiro.pathling.fhirpath.Nesting; -import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.definition.BasicElementDefinition; -import au.csiro.pathling.fhirpath.element.ElementPath; import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.test.helpers.SparkHelpers.IdAndValueColumns; import java.util.Optional; import javax.annotation.Nonnull; @@ -63,7 +63,7 @@ public class ElementPathBuilder { private FHIRDefinedType fhirType; @Nullable - private ResourcePath currentResource; + private ResourceCollection currentResource; @Nullable private Column thisColumn; @@ -144,7 +144,7 @@ public ElementPathBuilder fhirType(@Nonnull final FHIRDefinedType fhirType) { } @Nonnull - public ElementPathBuilder currentResource(@Nonnull final ResourcePath currentResource) { + public ElementPathBuilder currentResource(@Nonnull final ResourceCollection currentResource) { this.currentResource = currentResource; return this; } @@ -162,14 +162,16 @@ public ElementPathBuilder definition(@Nonnull final ElementDefinition definition } @Nonnull - public ElementPath build() { - return ElementPath.build(expression, dataset, idColumn, valueColumn, Optional.empty(), singular, + public PrimitivePath build() { + return PrimitivePath.build(expression, dataset, idColumn, valueColumn, Optional.empty(), + singular, Optional.ofNullable(currentResource), Optional.ofNullable(thisColumn), fhirType); } @Nonnull - public ElementPath buildDefined() { - return ElementPath.build(expression, dataset, idColumn, valueColumn, Optional.empty(), singular, + public PrimitivePath buildDefined() { + return PrimitivePath.build(expression, dataset, idColumn, valueColumn, Optional.empty(), + singular, Optional.ofNullable(currentResource), Optional.ofNullable(thisColumn), definition); } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java index d99e12a062..056737c8c3 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java @@ -21,7 +21,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -45,7 +45,7 @@ public class ParserContextBuilder { @Nonnull - private FhirPath inputContext; + private Collection inputContext; @Nonnull private final FhirContext fhirContext; @@ -69,7 +69,7 @@ public ParserContextBuilder(@Nonnull final SparkSession spark, @Nonnull final FhirContext fhirContext) { this.fhirContext = fhirContext; this.spark = spark; - inputContext = mock(FhirPath.class); + inputContext = mock(Collection.class); when(inputContext.getIdColumn()).thenReturn(lit(null)); when(inputContext.getDataset()).thenReturn(spark.emptyDataFrame()); dataSource = Mockito.mock(DataSource.class, new DefaultAnswer()); @@ -78,7 +78,7 @@ public ParserContextBuilder(@Nonnull final SparkSession spark, } @Nonnull - public ParserContextBuilder inputContext(@Nonnull final FhirPath inputContext) { + public ParserContextBuilder inputContext(@Nonnull final Collection inputContext) { this.inputContext = inputContext; return this; } @@ -117,7 +117,8 @@ public ParserContextBuilder groupingColumns(@Nonnull final List grouping @Nonnull public ParserContext build() { return new ParserContext(inputContext, fhirContext, spark, dataSource, - Optional.ofNullable(terminologyServiceFactory), groupingColumns, Optional.empty()); + Optional.ofNullable(terminologyServiceFactory), functionRegistry, groupingColumns, + Optional.empty()); } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java index cef0564692..552ab9daca 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java @@ -23,8 +23,8 @@ import static org.mockito.Mockito.when; import au.csiro.pathling.QueryHelpers.DatasetWithColumn; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.definition.ResourceDefinition; -import au.csiro.pathling.fhirpath.ResourcePath; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.test.fixtures.PatientResourceRowFixture; import ca.uhn.fhir.context.FhirContext; @@ -160,12 +160,12 @@ public ResourcePathBuilder thisColumn(final Column thisColumn) { } @Nonnull - public ResourcePath build() { - return ResourcePath.build(fhirContext, dataSource, resourceType, expression, singular); + public ResourceCollection build() { + return ResourceCollection.build(fhirContext, dataSource, resourceType, expression); } @Nonnull - public ResourcePath buildCustom() { + public ResourceCollection buildCustom() { final String resourceCode = resourceType.toCode(); final RuntimeResourceDefinition hapiDefinition = fhirContext .getResourceDefinition(resourceCode); @@ -180,7 +180,7 @@ public ResourcePath buildCustom() { } try { - final Constructor constructor = ResourcePath.class + final Constructor constructor = ResourceCollection.class .getDeclaredConstructor(String.class, Dataset.class, Column.class, Optional.class, Column.class, boolean.class, Optional.class, ResourceDefinition.class, Map.class); constructor.setAccessible(true); diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/UntypedResourcePathBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/UntypedResourcePathBuilder.java index d6ba70b9ed..5ce148b81f 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/UntypedResourcePathBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/UntypedResourcePathBuilder.java @@ -21,8 +21,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import au.csiro.pathling.fhirpath.UntypedResourcePath; -import au.csiro.pathling.fhirpath.element.ReferencePath; +import au.csiro.pathling.fhirpath.collection.UntypedResourcePath; +import au.csiro.pathling.fhirpath.collection.ReferencePath; import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/fixtures/ExtensionFixture.java b/fhirpath/src/test/java/au/csiro/pathling/test/fixtures/ExtensionFixture.java index f683497a03..1812ed9d78 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/fixtures/ExtensionFixture.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/fixtures/ExtensionFixture.java @@ -19,7 +19,7 @@ import static au.csiro.pathling.utilities.Preconditions.checkPresent; -import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.test.helpers.SparkHelpers; import au.csiro.pathling.test.helpers.SparkHelpers.IdAndValueColumns; import com.google.common.collect.ImmutableMap; @@ -70,18 +70,18 @@ static Map nullEntryMap(final int key) { } static Dataset toElementDataset(final Dataset resourceLikeDataset, - final ResourcePath baseResourcePath) { + final ResourceCollection baseResourceCollection) { // Construct element dataset from the resource dataset so that the resource path // can be used as the current resource for this element path. final IdAndValueColumns idAndValueColumns = SparkHelpers .getIdAndValueColumns(resourceLikeDataset, true); - final Dataset resourceDataset = baseResourcePath.getDataset(); + final Dataset resourceDataset = baseResourceCollection.getDataset(); final Column[] elementColumns = Stream.of( idAndValueColumns.getId().named().name(), checkPresent(idAndValueColumns.getEid()).named().name(), idAndValueColumns.getValues().get(0).named().name(), "_extension") - .map(baseResourcePath::getElementColumn) + .map(baseResourceCollection::getElementColumn) .toArray(Column[]::new); return resourceDataset.select(elementColumns); diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java index 08b6f24ff3..2162ac024c 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java @@ -22,7 +22,7 @@ import static org.apache.spark.sql.functions.col; import au.csiro.pathling.encoders.datatypes.DecimalCustomCoder; -import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.encoding.QuantityEncoding; import au.csiro.pathling.fhirpath.parser.ParserContext; import java.math.BigDecimal; @@ -182,11 +182,11 @@ public static Row rowForUcumQuantity(@Nonnull final String value, @Nonnull public static Dataset selectValuesAndNodes(@Nonnull final Dataset dataset, - @Nonnull final List paths, @Nonnull final ParserContext context) { + @Nonnull final List paths, @Nonnull final ParserContext context) { final List columns = new ArrayList<>(); columns.addAll( paths.stream() - .map(FhirPath::getValueColumn) + .map(Collection::getValueColumn) .collect(toList()) ); columns.addAll(new ArrayList<>(context.getNesting().getOrderingColumns())); diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/TestHelpers.java b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/TestHelpers.java index 10b27c626f..eb018cb483 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/TestHelpers.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/TestHelpers.java @@ -22,7 +22,7 @@ import au.csiro.pathling.QueryHelpers; import au.csiro.pathling.encoders.FhirEncoders; -import au.csiro.pathling.fhirpath.ResourcePath; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.io.source.DataSource; import io.delta.tables.DeltaTable; import java.net.URLDecoder; @@ -83,7 +83,7 @@ public static void mockResource(@Nonnull final DataSource dataSource, public static void mockAllEmptyResources(@Nonnull final DataSource dataSource, @Nonnull final SparkSession spark, @Nonnull final FhirEncoders fhirEncoders) { - final Set resourceTypes = ResourcePath.supportedResourceTypes(); + final Set resourceTypes = ResourceCollection.supportedResourceTypes(); for (final ResourceType resourceType : resourceTypes) { final Dataset dataset = QueryHelpers.createEmptyDataset(spark, fhirEncoders, resourceType); From 9a2542045323ebe2a41512285c7b4e82daffdfc3 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Fri, 25 Aug 2023 14:56:38 +1000 Subject: [PATCH 60/95] Commenting out unimplemented code and marking it with @NotImplemented to achieve 'main' sources compilation. --- .../pathling/aggregate/DrillDownBuilder.java | 57 +- .../csiro/pathling/search/SearchExecutor.java | 140 +-- .../security/ga4gh/PassportScopeEnforcer.java | 3 +- .../fhirpath/parser/AbstractParserTest.java | 4 +- .../pathling/fhirpath/parser/ParserTest.java | 2 +- .../java/au/csiro/pathling/QueryExecutor.java | 165 ++-- .../java/au/csiro/pathling/QueryHelpers.java | 890 +++++++++--------- .../aggregate/AggregateQueryExecutor.java | 77 +- .../extract/ExtractQueryExecutor.java | 72 +- .../au/csiro/pathling/fhirpath/FhirPath.java | 7 +- .../au/csiro/pathling/fhirpath/Nesting.java | 172 ++-- .../au/csiro/pathling/fhirpath/Referrer.java | 17 +- .../au/csiro/pathling/fhirpath/Temporal.java | 6 +- .../pathling/fhirpath/TerminologyUtils.java | 29 +- .../pathling/fhirpath/annotations/Name.java | 27 + .../fhirpath/annotations/NotImplemented.java | 26 + .../fhirpath/collection/Collection.java | 22 +- .../collection/DateTimeCollection.java | 15 + .../collection/ResourceCollection.java | 15 +- .../definition/ElementDefinition.java | 10 +- .../pathling/fhirpath/function/Arguments.java | 168 ++-- .../function/BooleansTestFunction.java | 41 +- .../fhirpath/function/CountFunction.java | 14 +- .../fhirpath/function/EmptyFunction.java | 18 +- .../fhirpath/function/ExistsFunction.java | 16 +- .../fhirpath/function/ExtensionFunction.java | 82 +- .../fhirpath/function/FirstFunction.java | 51 +- .../fhirpath/function/GetIdFunction.java | 51 +- .../fhirpath/function/IifFunction.java | 81 +- .../fhirpath/function/NamedFunction.java | 11 +- .../fhirpath/function/NotFunction.java | 56 +- .../fhirpath/function/OfTypeFunction.java | 94 +- .../fhirpath/function/ResolveFunction.java | 167 ++-- .../function/ReverseResolveFunction.java | 128 ++- .../fhirpath/function/SumFunction.java | 61 +- .../fhirpath/function/ToStringFunction.java | 51 +- .../fhirpath/function/UntilFunction.java | 110 +-- .../fhirpath/function/WhereFunction.java | 14 +- .../function/registry/FunctionRegistry.java | 6 +- .../registry/InMemoryFunctionRegistry.java | 2 +- .../registry/StaticFunctionRegistry.java | 7 + .../terminology/DesignationFunction.java | 116 +-- .../function/terminology/DisplayFunction.java | 102 +- .../terminology/MemberOfFunction.java | 102 +- .../terminology/PropertyFunction.java | 152 ++- .../terminology/SubsumesFunction.java | 247 +++-- .../terminology/TranslateFunction.java | 160 ++-- .../fhirpath/operator/BinaryOperator.java | 5 +- .../fhirpath/operator/CombineOperator.java | 46 +- .../fhirpath/operator/ComparisonOperator.java | 54 +- .../operator/DateArithmeticOperator.java | 58 +- .../fhirpath/operator/MathOperator.java | 79 +- .../fhirpath/operator/MembershipOperator.java | 98 +- .../fhirpath/operator/PathTraversalInput.java | 58 -- .../fhirpath/parser/InvocationVisitor.java | 36 +- .../fhirpath/parser/LiteralTermVisitor.java | 25 +- .../pathling/fhirpath/parser/Parser.java | 17 +- .../fhirpath/parser/ParserContext.java | 4 +- .../pathling/fhirpath/parser/TermVisitor.java | 23 +- .../pathling/fhirpath/parser/Visitor.java | 45 +- .../fhirpath/path/EvalOperatorPath.java | 39 + .../fhirpath/path/ExtConsFhirPath.java | 41 + .../pathling/views/ContextAndSelection.java | 22 - .../pathling/views/FhirViewExecutor.java | 35 +- .../library/io/source/AbstractSource.java | 4 +- .../library/query/FhirViewBuilder.java | 91 +- 66 files changed, 2256 insertions(+), 2388 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/annotations/Name.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/annotations/NotImplemented.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalInput.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/EvalOperatorPath.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/ExtConsFhirPath.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/ContextAndSelection.java diff --git a/fhir-server/src/main/java/au/csiro/pathling/aggregate/DrillDownBuilder.java b/fhir-server/src/main/java/au/csiro/pathling/aggregate/DrillDownBuilder.java index dbf4d8444d..1684fd0749 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/aggregate/DrillDownBuilder.java +++ b/fhir-server/src/main/java/au/csiro/pathling/aggregate/DrillDownBuilder.java @@ -18,10 +18,9 @@ package au.csiro.pathling.aggregate; import static au.csiro.pathling.utilities.Preconditions.checkArgument; -import static au.csiro.pathling.utilities.Strings.parentheses; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.literal.LiteralPath; import au.csiro.pathling.utilities.Strings; import java.util.ArrayList; import java.util.LinkedHashSet; @@ -54,7 +53,8 @@ public class DrillDownBuilder { * @param filters The filters from the request */ public DrillDownBuilder(@Nonnull final List> labels, - @Nonnull final List groupings, @Nonnull final java.util.Collection filters) { + @Nonnull final List groupings, + @Nonnull final java.util.Collection filters) { checkArgument(labels.size() == groupings.size(), "Labels should be same size as groupings"); this.labels = labels; this.groupings = groupings; @@ -86,37 +86,40 @@ public Optional build() { : Optional.empty(); } + @NotImplemented private void addGroupings(final java.util.Collection fhirPaths) { - for (int i = 0; i < groupings.size(); i++) { - final Collection grouping = groupings.get(i); - final Optional label = labels.get(i); - if (label.isPresent()) { - final String literal = LiteralPath - .expressionFor(grouping.getDataset(), grouping.getIdColumn(), label.get()); - final String equality = grouping.isSingular() - ? " = " - : " contains "; - // We need to add parentheses around the grouping expression, as some expressions will not - // play well with the equality or membership operator due to precedence. - final String expression = literal.equals("true") && grouping.isSingular() - ? grouping.getExpression() - : parentheses(grouping.getExpression()) + equality + literal; - fhirPaths.add(expression); - } else { - fhirPaths.add(parentheses(grouping.getExpression()) + ".empty()"); - } - } + // for (int i = 0; i < groupings.size(); i++) { + // final Collection grouping = groupings.get(i); + // final Optional label = labels.get(i); + // if (label.isPresent()) { + // final String literal = LiteralPath + // .expressionFor(grouping.getDataset(), grouping.getIdColumn(), label.get()); + // final String equality = grouping.isSingular() + // ? " = " + // : " contains "; + // // We need to add parentheses around the grouping expression, as some expressions will not + // // play well with the equality or membership operator due to precedence. + // final String expression = literal.equals("true") && grouping.isSingular() + // ? grouping.getExpression() + // : parentheses(grouping.getExpression()) + equality + literal; + // fhirPaths.add(expression); + // } else { + // fhirPaths.add(parentheses(grouping.getExpression()) + ".empty()"); + // } + // } } + @NotImplemented private void addFilters(@Nonnull final java.util.Collection fhirPaths) { - final List filterExpressions = filters.stream() - .map(Collection::getExpression) - .collect(Collectors.toList()); - fhirPaths.addAll(filterExpressions); + // final List filterExpressions = filters.stream() + // .map(Collection::getExpression) + // .collect(Collectors.toList()); + // fhirPaths.addAll(filterExpressions); } @Nonnull - private List parenthesiseExpressions(@Nonnull final java.util.Collection fhirPaths) { + private List parenthesiseExpressions( + @Nonnull final java.util.Collection fhirPaths) { if (fhirPaths.size() > 1) { return fhirPaths.stream() .map(Strings::parentheses) diff --git a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java index 1493bba3ed..36248d019c 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java @@ -26,8 +26,10 @@ import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.function.registry.StaticFunctionRegistry; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.Database; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -115,73 +117,79 @@ public SearchExecutor(@Nonnull final QueryConfiguration configuration, } @Nonnull + @NotImplemented private Dataset initializeDataset() { - final ResourceCollection resourceCollection = ResourceCollection - .build(getFhirContext(), getDataSource(), subjectResource, subjectResource.toCode()); - final Dataset subjectDataset = resourceCollection.getDataset(); - final Dataset dataset; - - if (filters.isEmpty() || filters.get().getValuesAsQueryTokens().isEmpty()) { - // If there are no filters, return all resources. - dataset = subjectDataset; - - } else { - final ParserContext parserContext = new ParserContext(resourceCollection, fhirContext, sparkSession, - dataSource, terminologyServiceFactory, - functionRegistry, Collections.singletonList(resourceCollection.getIdColumn()), - Optional.empty()); - Dataset currentDataset = subjectDataset; - @Nullable Column filterCondition = null; - - // Parse each of the supplied filter expressions, building up a filter column. This captures - // the AND/OR conditions possible through the FHIR API, see - // https://hl7.org/fhir/R4/search.html#combining. - for (final StringOrListParam orParam : filters.get().getValuesAsQueryTokens()) { - - // Parse all the filter expressions within this AND condition. - final List filterExpressions = orParam.getValuesAsQueryTokens().stream() - .map(StringParam::getValue) - .collect(toList()); - checkUserInput(filterExpressions.stream().noneMatch(String::isBlank), - "Filter expression cannot be blank"); - final List filters = parseExpressions(parserContext, filterExpressions, - Optional.of(currentDataset)); - validateFilters(filters); - - // Get the dataset from the last filter. - currentDataset = filters.get(filters.size() - 1).getDataset(); - - // Combine all the columns with OR logic. - final Column orColumn = filters.stream() - .map(Collection::getValueColumn) - .reduce(Column::or) - .orElseThrow(); - - // Combine OR-grouped columns with AND logic. - filterCondition = filterCondition == null - ? orColumn - : filterCondition.and(orColumn); - } - dataset = currentDataset.filter(filterCondition); - } - - final Column[] resourceColumns = resourceCollection.getElementsToColumns().keySet().stream() - .map(colName -> resourceCollection.getElementsToColumns().get(colName).alias(colName)) - .toArray(Column[]::new); - // Resources are ordered by ID to ensure consistent paging. - final Dataset result = dataset.select(resourceColumns); - final WindowSpec window = Window.orderBy(col("id")); - final Dataset withRowNumbers = result.withColumn("row_number", - row_number().over(window)); - - if (getConfiguration().getCacheResults()) { - // We cache the dataset because we know it will be accessed for both the total and the record - // retrieval. - log.debug("Caching search dataset"); - withRowNumbers.cache(); - } - - return withRowNumbers; + // final ResourceCollection resourceCollection = ResourceCollection + // .build(getFhirContext(), getDataSource(), subjectResource); + // final Dataset subjectDataset = resourceCollection.getDataset(); + // final Dataset dataset; + // + // if (filters.isEmpty() || filters.get().getValuesAsQueryTokens().isEmpty()) { + // // If there are no filters, return all resources. + // dataset = subjectDataset; + // + // } else { + // final ParserContext parserContext = new ParserContext(resourceCollection, + // resourceCollection, + // fhirContext, sparkSession, + // dataSource, + // StaticFunctionRegistry.getInstance(), + // terminologyServiceFactory, + // Optional.empty() + // ); + // Dataset currentDataset = subjectDataset; + // @Nullable Column filterCondition = null; + // + // // Parse each of the supplied filter expressions, building up a filter column. This captures + // // the AND/OR conditions possible through the FHIR API, see + // // https://hl7.org/fhir/R4/search.html#combining. + // for (final StringOrListParam orParam : filters.get().getValuesAsQueryTokens()) { + // + // // Parse all the filter expressions within this AND condition. + // final List filterExpressions = orParam.getValuesAsQueryTokens().stream() + // .map(StringParam::getValue) + // .collect(toList()); + // checkUserInput(filterExpressions.stream().noneMatch(String::isBlank), + // "Filter expression cannot be blank"); + // final List filters = parseExpressions(parserContext, filterExpressions, + // Optional.of(currentDataset)); + // validateFilters(filters); + // + // // Get the dataset from the last filter. + // currentDataset = filters.get(filters.size() - 1).getDataset(); + // + // // Combine all the columns with OR logic. + // final Column orColumn = filters.stream() + // .map(Collection::getValueColumn) + // .reduce(Column::or) + // .orElseThrow(); + // + // // Combine OR-grouped columns with AND logic. + // filterCondition = filterCondition == null + // ? orColumn + // : filterCondition.and(orColumn); + // } + // dataset = currentDataset.filter(filterCondition); + // } + // + // final Column[] resourceColumns = resourceCollection.getElementsToColumns().keySet().stream() + // .map(colName -> resourceCollection.getElementsToColumns().get(colName).alias(colName)) + // .toArray(Column[]::new); + // // Resources are ordered by ID to ensure consistent paging. + // final Dataset result = dataset.select(resourceColumns); + // final WindowSpec window = Window.orderBy(col("id")); + // final Dataset withRowNumbers = result.withColumn("row_number", + // row_number().over(window)); + // + // if (getConfiguration().getCacheResults()) { + // // We cache the dataset because we know it will be accessed for both the total and the record + // // retrieval. + // log.debug("Caching search dataset"); + // withRowNumbers.cache(); + // } + // + // return withRowNumbers; + return null; } @Override diff --git a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java index 5df8cbf150..6b638242c8 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java +++ b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java @@ -84,8 +84,7 @@ public Dataset enforce(@Nonnull final ResourceType subjectResource, // Build a new expression parser, and parse all the column expressions within the query. final ResourceCollection inputContext = ResourceCollection - .build(getFhirContext(), getDataSource(), subjectResource, - subjectResource.toCode()); + .build(getFhirContext(), getDataSource(), subjectResource); return filterDataset(inputContext, filters, dataset, dataset.col("id"), Column::or); } diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java index 688a6df2b1..72c293a1e2 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java @@ -106,12 +106,12 @@ protected FhirPathAssertion assertThatResultOf(@Nonnull final ResourceType resou .inputContext(subjectResource) .build(); final Parser resourceParser = new Parser(parserContext); - return assertThat(resourceParser.parse(expression)); + return assertThat(resourceParser.evaluate(expression)); } @SuppressWarnings("SameParameterValue") FhirPathAssertion assertThatResultOf(final String expression) { - return assertThat(parser.parse(expression)); + return assertThat(parser.evaluate(expression)); } } diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java index d05a787563..24a3280386 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java @@ -66,7 +66,7 @@ public class ParserTest extends AbstractParserTest { @SuppressWarnings("SameParameterValue") private T assertThrows(final Class errorType, final String expression) { - return Assertions.assertThrows(errorType, () -> parser.parse(expression)); + return Assertions.assertThrows(errorType, () -> parser.evaluate(expression)); } private TranslateExpectations setupMockTranslationFor_195662009_444814009( diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index 7c3e5ea501..f696ff1f93 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -17,31 +17,22 @@ package au.csiro.pathling; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static au.csiro.pathling.utilities.Strings.randomAlias; -import static java.util.Objects.requireNonNull; -import static org.apache.spark.sql.functions.col; - import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.config.QueryConfiguration; -import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; -import au.csiro.pathling.fhirpath.parser.Parser; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import com.google.common.collect.Streams; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.BinaryOperator; import java.util.stream.Stream; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import lombok.Getter; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; @@ -54,6 +45,7 @@ * @author John Grimes */ @Getter +@NotImplemented public abstract class QueryExecutor { @Nonnull @@ -94,48 +86,56 @@ protected List parseExpressions( @Nonnull final ParserContext parserContext, @Nonnull final java.util.Collection expressions, @Nonnull final Optional> contextDataset) { - final List parsed = new ArrayList<>(); - ParserContext currentContext = contextDataset.map(parserContext::withContextDataset).orElse( - parserContext); - for (final String expression : expressions) { - if (parsed.size() > 0) { - final Collection lastParsed = parsed.get(parsed.size() - 1); - // If there are no grouping columns and the root nesting level has been erased by the - // parsing of the previous expression (i.e. there has been aggregation or use of where), - // disaggregate the input context before parsing the right expression. - final boolean disaggregationRequired = currentContext.getNesting().isRootErased() && - !(currentContext.getGroupingColumns().size() == 1 - && currentContext.getGroupingColumns().get(0) - .equals(currentContext.getInputContext().getIdColumn())); - currentContext = disaggregationRequired - ? currentContext.disaggregate(lastParsed) - : currentContext.withContextDataset(lastParsed.getDataset()); - } - final Parser parser = new Parser(currentContext); - // Add the parse result to the list of parsed expressions. - parsed.add(parser.parse(expression)); - } - return parsed; + + // TODO: implement this + + // final List parsed = new ArrayList<>(); + // ParserContext currentContext = contextDataset.map(parserContext::withContextDataset).orElse( + // parserContext); + // for (final String expression : expressions) { + // if (parsed.size() > 0) { + // final Collection lastParsed = parsed.get(parsed.size() - 1); + // // If there are no grouping columns and the root nesting level has been erased by the + // // parsing of the previous expression (i.e. there has been aggregation or use of where), + // // disaggregate the input context before parsing the right expression. + // final boolean disaggregationRequired = currentContext.getNesting().isRootErased() && + // !(currentContext.getGroupingColumns().size() == 1 + // && currentContext.getGroupingColumns().get(0) + // .equals(currentContext.getInputContext().getIdColumn())); + // currentContext = disaggregationRequired + // ? currentContext.disaggregate(lastParsed) + // : currentContext.withContextDataset(lastParsed.getDataset()); + // } + // final Parser parser = new Parser(currentContext); + // // Add the parse result to the list of parsed expressions. + // parsed.add(parser.evaluate(expression)); + // } + // return parsed; + return Collections.emptyList(); } @Nonnull protected void validateFilters(@Nonnull final java.util.Collection filters) { - for (final Collection filter : filters) { - // Each filter expression must evaluate to a singular Boolean value, or a user error will be - // thrown. - checkUserInput(filter instanceof BooleanCollection || filter instanceof BooleanLiteralPath, - "Filter expression must be a Boolean: " + filter.getExpression()); - checkUserInput(filter.isSingular(), - "Filter expression must be a singular value: " + filter.getExpression()); - } - } - protected Dataset filterDataset(@Nonnull final ResourceCollection inputContext, - @Nonnull final java.util.Collection filters, @Nonnull final Dataset dataset, - @Nonnull final BinaryOperator operator) { - return filterDataset(inputContext, filters, dataset, inputContext.getIdColumn(), operator); + // TODO: implement this + + // for (final Collection filter : filters) { + // // Each filter expression must evaluate to a singular Boolean value, or a user error will be + // // thrown. + // checkUserInput(filter instanceof BooleanCollection || filter instanceof BooleanLiteralPath, + // "Filter expression must be a Boolean: " + filter.getExpression()); + // checkUserInput(filter.isSingular(), + // "Filter expression must be a singular value: " + filter.getExpression()); + // } } + // TODO: delete + // protected Dataset filterDataset(@Nonnull final ResourceCollection inputContext, + // @Nonnull final java.util.Collection filters, @Nonnull final Dataset dataset, + // @Nonnull final BinaryOperator operator) { + // return filterDataset(inputContext, filters, dataset, inputContext.getIdColumn(), operator); + // } + protected Dataset filterDataset(@Nonnull final ResourceCollection inputContext, @Nonnull final java.util.Collection filters, @Nonnull final Dataset dataset, @Nonnull final Column idColumn, @Nonnull final BinaryOperator operator) { @@ -166,42 +166,45 @@ protected static Stream labelColumns(@Nonnull final Stream colum private DatasetWithColumn getFilteredIds(@Nonnull final Iterable filters, @Nonnull final ResourceCollection inputContext, @Nonnull final BinaryOperator operator) { - ResourceCollection currentContext = inputContext; - @Nullable Column filterColumn = null; - - for (final String filter : filters) { - // Parse the filter expression. - final ParserContext parserContext = new ParserContext(currentContext, fhirContext, - sparkSession, dataSource, - terminologyServiceFactory, functionRegistry, - Collections.singletonList(currentContext.getIdColumn()), - Optional.empty()); - final Parser parser = new Parser(parserContext); - final Collection result = parser.parse(filter); - - // Check that it is a Boolean expression. - checkUserInput(result instanceof BooleanCollection || result instanceof BooleanLiteralPath, - "Filter expression must be of Boolean type: " + result.getExpression()); - - // Add the filter column to the overall filter expression using the supplied operator. - final Column filterValue = result.getValueColumn(); - filterColumn = filterColumn == null - ? filterValue - : operator.apply(filterColumn, filterValue); - - // Update the context to build the next expression from the same dataset. - currentContext = currentContext.copy(currentContext.getExpression(), result.getDataset(), - currentContext.getIdColumn(), currentContext.getValueColumn(), - currentContext.getOrderingColumn(), currentContext.isSingular(), - currentContext.getThisColumn()); - } - requireNonNull(filterColumn); - - // Return a dataset of filtered IDs with an aliased ID column, ready for joining. - final String filterIdAlias = randomAlias(); - final Dataset dataset = currentContext.getDataset().select( - currentContext.getIdColumn().alias(filterIdAlias)); - return new DatasetWithColumn(dataset.filter(filterColumn), col(filterIdAlias)); + // TODO: implement this + + // ResourceCollection currentContext = inputContext; + // @Nullable Column filterColumn = null; + // + // for (final String filter : filters) { + // // Parse the filter expression. + // final ParserContext parserContext = new ParserContext(currentContext, fhirContext, + // sparkSession, dataSource, + // terminologyServiceFactory, functionRegistry, + // Collections.singletonList(currentContext.getIdColumn()), + // Optional.empty()); + // final Parser parser = new Parser(parserContext); + // final Collection result = parser.evaluate(filter); + // + // // Check that it is a Boolean expression. + // checkUserInput(result instanceof BooleanCollection || result instanceof BooleanLiteralPath, + // "Filter expression must be of Boolean type: " + result.getExpression()); + // + // // Add the filter column to the overall filter expression using the supplied operator. + // final Column filterValue = result.getValueColumn(); + // filterColumn = filterColumn == null + // ? filterValue + // : operator.apply(filterColumn, filterValue); + // + // // Update the context to build the next expression from the same dataset. + // currentContext = currentContext.copy(currentContext.getExpression(), result.getDataset(), + // currentContext.getIdColumn(), currentContext.getValueColumn(), + // currentContext.getOrderingColumn(), currentContext.isSingular(), + // currentContext.getThisColumn()); + // } + // requireNonNull(filterColumn); + // + // // Return a dataset of filtered IDs with an aliased ID column, ready for joining. + // final String filterIdAlias = randomAlias(); + // final Dataset dataset = currentContext.getDataset().select( + // currentContext.getIdColumn().alias(filterIdAlias)); + // return new DatasetWithColumn(dataset.filter(filterColumn), col(filterIdAlias)); + return null; } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java b/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java index dfab45fbbf..22c0a7d978 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryHelpers.java @@ -17,44 +17,18 @@ package au.csiro.pathling; -import static au.csiro.pathling.utilities.Preconditions.checkArgument; -import static au.csiro.pathling.utilities.Strings.randomAlias; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; -import static org.apache.spark.sql.functions.col; -import static org.apache.spark.sql.functions.flatten; -import static org.apache.spark.sql.functions.lit; -import static org.apache.spark.sql.functions.posexplode_outer; - import au.csiro.pathling.encoders.FhirEncoders; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.literal.LiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.utilities.Strings; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import java.util.Map; import java.util.Optional; -import java.util.Set; -import java.util.stream.Stream; import javax.annotation.Nonnull; import lombok.Getter; import lombok.Value; -import org.apache.commons.lang3.tuple.MutablePair; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder; -import org.apache.spark.sql.functions; -import org.apache.spark.sql.types.ArrayType; -import org.apache.spark.sql.types.DataType; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -63,392 +37,396 @@ * * @author John Grimes */ +@NotImplemented public abstract class QueryHelpers { - /** - * Adds to the columns within a {@link Dataset} with an aliased version of the supplied column. - * - * @param dataset the Dataset on which to perform the operation - * @param column a new {@link Column} - * @return a new Dataset, along with the new column name, as a {@link DatasetWithColumn} - */ - @Nonnull - public static DatasetWithColumn createColumn(@Nonnull final Dataset dataset, - @Nonnull final Column column) { - final DatasetWithColumnMap datasetWithColumnMap = aliasColumns(dataset, - Collections.singletonList(column)); - return new DatasetWithColumn(datasetWithColumnMap.getDataset(), - datasetWithColumnMap.getColumnMap().get(column)); - } - - /** - * Adds to the columns within a {@link Dataset} with aliased versions of the supplied columns. - * - * @param dataset the Dataset on which to perform the operation - * @param columns the new {@link Column} objects - * @return a new Dataset, along with the new column names, as a {@link DatasetWithColumnMap} - */ - @Nonnull - public static DatasetWithColumnMap createColumns(@Nonnull final Dataset dataset, - @Nonnull final Column... columns) { - return aliasColumns(dataset, Arrays.asList(columns)); - } - - /** - * Replaces all unaliased columns within a {@link Dataset} with new aliased columns. - * - * @param dataset the Dataset on which to perform the operation - * @return a new Dataset, with a mapping from the old columns to the new as a - * {@link DatasetWithColumnMap} - */ - @Nonnull - public static DatasetWithColumnMap aliasAllColumns(@Nonnull final Dataset dataset) { - - final List columns = Stream.of(dataset.columns()) - .map(dataset::col) - .collect(toList()); - final DatasetWithColumnMap datasetWithColumnMap = aliasColumns(dataset, columns); - - final Dataset finalDataset = datasetWithColumnMap.getDataset(); - final Map columnMap = datasetWithColumnMap.getColumnMap(); - return new DatasetWithColumnMap(finalDataset, columnMap); - } - - /** - * Adds aliased versions of the supplied columns to a {@link Dataset}. - * - * @param dataset the Dataset on which to perform the operation - * @param columns a list of new {@link Column} objects - * @return a new Dataset, along with a map from the supplied columns to the new columns, as a - * {@link DatasetWithColumnMap} - */ - @Nonnull - private static DatasetWithColumnMap aliasColumns(@Nonnull final Dataset dataset, - @Nonnull final Iterable columns) { - - // Use LinkedHashMap to preserve the original order of columns while iterating map entries. - final Map columnMap = new LinkedHashMap<>(); - final List selection = Stream.of(dataset.columns()) - // Don't preserve anything that is not already aliased. - .filter(Strings::looksLikeAlias) - .map(dataset::col) - .collect(toList()); - - // Create an aliased column for each of the new columns, and add it to the selection and the - // map. - for (final Column column : columns) { - final String alias = randomAlias(); - final Column aliasedColumn = column.alias(alias); - selection.add(aliasedColumn); - columnMap.put(column, col(alias)); - } - - // Create a new dataset from the selection. - final Dataset result = dataset.select(selection.toArray(new Column[0])); - - return new DatasetWithColumnMap(result, columnMap); - } - - private static Dataset join(@Nonnull final Dataset left, - @Nonnull final List leftColumns, @Nonnull final Dataset right, - @Nonnull final List rightColumns, @Nonnull final Optional additionalCondition, - @Nonnull final JoinType joinType) { - checkArgument(leftColumns.size() == rightColumns.size(), - "Left columns should be same size as right columns"); - - Dataset aliasedLeft = left; - final java.util.Collection joinConditions = new ArrayList<>(); - for (int i = 0; i < leftColumns.size(); i++) { - // We alias the join columns on the left-hand side to disambiguate them from columns named the - // same on the right-hand side. - final DatasetWithColumn leftWithColumn = createColumn(aliasedLeft, leftColumns.get(i)); - aliasedLeft = leftWithColumn.getDataset(); - joinConditions.add(leftWithColumn.getColumn().eqNullSafe(rightColumns.get(i))); - } - additionalCondition.ifPresent(joinConditions::add); - final Column joinCondition = joinConditions.stream() - .reduce(Column::and) - .orElse(lit(true)); - - final List leftColumnNames = Arrays.asList(aliasedLeft.columns()); - final List rightColumnNames = rightColumns.stream() - .map(Column::toString) - .collect(toList()); - - // Exclude the columns in the right dataset from the trimmed left dataset. - final Dataset trimmedLeft = applySelection(aliasedLeft, Collections.emptyList(), - rightColumnNames); - - // The right dataset will only contain columns that were not in the left dataset, except for the - // columns on the right-hand side of the join conditions. - final Dataset trimmedRight = applySelection(right, rightColumnNames, leftColumnNames); - - return trimmedLeft.join(trimmedRight, joinCondition, joinType.getSparkName()); - } - - /** - * Join two datasets based on the equality of an arbitrary set of columns. The same number of - * columns must be provided for each dataset, and it is assumed that they are matched on their - * position within their respective lists. - * - * @param left the first {@link Dataset} - * @param leftColumns the columns for the first Dataset - * @param right the second Dataset - * @param rightColumns the columns for the second Dataset - * @param joinType the type of join to use - * @return the joined Dataset - */ - @Nonnull - public static Dataset join(@Nonnull final Dataset left, - @Nonnull final List leftColumns, @Nonnull final Dataset right, - @Nonnull final List rightColumns, @Nonnull final JoinType joinType) { - return join(left, leftColumns, right, rightColumns, Optional.empty(), joinType); - } - - /** - * Joins a {@link Dataset} to another Dataset, using the equality of two columns. - * - * @param left a {@link Dataset} - * @param leftColumn the {@link Column} in the left Dataset - * @param right another Dataset - * @param rightColumn the column in the right Dataset - * @param joinType a {@link JoinType} - * @return a new {@link Dataset} - */ - @Nonnull - public static Dataset join(@Nonnull final Dataset left, - @Nonnull final Column leftColumn, @Nonnull final Dataset right, - @Nonnull final Column rightColumn, @Nonnull final JoinType joinType) { - return join(left, Collections.singletonList(leftColumn), right, - Collections.singletonList(rightColumn), joinType); - } - - /** - * Joins a {@link Dataset} to another Dataset, using the equality of two columns. - * - * @param left a {@link Dataset} - * @param leftColumn the {@link Column} in the left Dataset - * @param right another Dataset - * @param rightColumn the column in the right Dataset - * @param additionalCondition an additional Column to be added to the join condition, using AND - * @param joinType a {@link JoinType} - * @return a new {@link Dataset} - */ - @Nonnull - public static Dataset join(@Nonnull final Dataset left, - @Nonnull final Column leftColumn, @Nonnull final Dataset right, - @Nonnull final Column rightColumn, @Nonnull final Column additionalCondition, - @Nonnull final JoinType joinType) { - return join(left, Collections.singletonList(leftColumn), right, - Collections.singletonList(rightColumn), Optional.of(additionalCondition), joinType); - } - - /** - * Joins a {@link Dataset} to another Dataset, using the equality of two columns. - * - * @param left a {@link Dataset} - * @param leftColumns the columns for the first Dataset - * @param right another Dataset - * @param rightColumns the columns for the second Dataset - * @param additionalCondition an additional Column to be added to the join condition, using AND - * @param joinType a {@link JoinType} - * @return a new {@link Dataset} - */ - @Nonnull - public static Dataset join(@Nonnull final Dataset left, - @Nonnull final List leftColumns, @Nonnull final Dataset right, - @Nonnull final List rightColumns, @Nonnull final Column additionalCondition, - @Nonnull final JoinType joinType) { - return join(left, leftColumns, right, rightColumns, Optional.of(additionalCondition), joinType); - } - - /** - * Joins a {@link Dataset} to another Dataset, using a custom join condition. - * - * @param left a {@link Dataset} - * @param right another Dataset - * @param joinCondition a custom join condition - * @param joinType a {@link JoinType} - * @return a new {@link Dataset} - */ - @Nonnull - public static Dataset join(@Nonnull final Dataset left, - @Nonnull final Dataset right, @Nonnull final Column joinCondition, - @Nonnull final JoinType joinType) { - return join(left, Collections.emptyList(), right, Collections.emptyList(), - Optional.of(joinCondition), joinType); - } - - /** - * Joins two {@link Collection} expressions, using equality between their respective resource ID - * columns. - * - * @param parserContext the current {@link ParserContext} - * @param left a {@link Collection} expression - * @param right another FhirPath expression - * @param joinType a {@link JoinType} - * @return a new {@link Dataset} - */ - @Nonnull - public static Dataset join(@Nonnull final ParserContext parserContext, - @Nonnull final Collection left, @Nonnull final Collection right, - @Nonnull final JoinType joinType) { - return join(parserContext, Arrays.asList(left, right), joinType); - } - - /** - * Joins any number of {@link Collection} expressions, using equality between their respective - * resource ID columns. - * - * @param parserContext the current {@link ParserContext} - * @param results a list of {@link Collection} expressions - * @param joinType a {@link JoinType} - * @return a new {@link Dataset} - */ - @Nonnull - public static Dataset join(@Nonnull final ParserContext parserContext, - @Nonnull final List results, @Nonnull final JoinType joinType) { - checkArgument(results.size() > 1, "fhirPaths must contain more than one FhirPath"); - - final Collection left = results.get(0); - final List joinTargets = results.subList(1, results.size()); - - // Only non-literal paths will trigger a join. - final List nonLiteralTargets = joinTargets.stream() - .filter(t -> t instanceof NonLiteralPath) - .collect(toList()); - if (left instanceof NonLiteralPath && nonLiteralTargets.isEmpty()) { - // If the only non-literal path is on the left, we can just return the left without any need - // to join. - return left.getDataset(); - } else if (left instanceof LiteralPath && !nonLiteralTargets.isEmpty()) { - // If non-literal paths are confined to the right, we can just return the first dataset on the - // right without any need to join. - return nonLiteralTargets.get(0).getDataset(); - } - - Dataset dataset = left.getDataset(); - final List groupingColumns = parserContext.getGroupingColumns(); - final Column idColumn = parserContext.getInputContext().getIdColumn(); - final List leftColumns = checkColumnsAndFallback(left.getDataset(), groupingColumns, - idColumn); - for (final Collection right : nonLiteralTargets) { - final List resolvedGroupingColumns = checkColumnsAndFallback(right.getDataset(), - leftColumns, idColumn); - dataset = join(dataset, resolvedGroupingColumns, right.getDataset(), resolvedGroupingColumns, - joinType); - } - return dataset; - } - - /** - * Joins a {@link Dataset} to a {@link Collection}, using equality between the resource ID in the - * FhirPath and the supplied column. - * - * @param left a {@link Collection} expression - * @param right a {@link Dataset} - * @param rightColumn the {@link Column} in the right Dataset - * @param joinType a {@link JoinType} - * @return A new {@link Dataset} - */ - @Nonnull - public static Dataset join(@Nonnull final Collection left, @Nonnull final Dataset right, - @Nonnull final Column rightColumn, @Nonnull final JoinType joinType) { - return join(left.getDataset(), left.getIdColumn(), right, rightColumn, joinType); - } - - /** - * Joins a {@link Dataset} to a {@link Collection}, using equality between the resource ID in the - * FhirPath and the supplied column. - * - * @param left a {@link Collection} expression - * @param right a {@link Dataset} - * @param rightColumn the {@link Column} in the right Dataset - * @param additionalCondition an additional Column to be added to the join condition, using AND - * @param joinType a {@link JoinType} - * @return A new {@link Dataset} - */ - @Nonnull - public static Dataset join(@Nonnull final Collection left, @Nonnull final Dataset right, - @Nonnull final Column rightColumn, @Nonnull final Column additionalCondition, - @Nonnull final JoinType joinType) { - return join(left.getDataset(), Collections.singletonList(left.getIdColumn()), right, - Collections.singletonList(rightColumn), Optional.of(additionalCondition), joinType); - } - - /** - * Joins a {@link Dataset} to a {@link Collection}, using equality between the resource ID in the - * FhirPath and the supplied column. - * - * @param left a {@link Dataset} - * @param leftColumn the {@link Column} in the left Dataset - * @param right a {@link Collection} expression - * @param joinType a {@link JoinType} - * @return A new {@link Dataset} - */ - @Nonnull - public static Dataset join(@Nonnull final Dataset left, - @Nonnull final Column leftColumn, @Nonnull final Collection right, - @Nonnull final JoinType joinType) { - return join(left, leftColumn, right.getDataset(), right.getIdColumn(), joinType); - } - - /** - * This is used to find a set of fallback join columns in cases where a path does not contain all - * grouping columns. - *

- * This can happen in the context of a function's arguments, when a path originates from something - * other than `$this`, e.g. `%resource`. - */ - private static List checkColumnsAndFallback(@Nonnull final Dataset dataset, - @Nonnull final List groupingColumns, @Nonnull final Column fallback) { - final Set columnList = new HashSet<>(List.of(dataset.columns())); - final Set groupingColumnNames = groupingColumns.stream().map(Column::toString) - .collect(toSet()); - if (columnList.containsAll(groupingColumnNames)) { - return groupingColumns; - } else { - final Set fallbackGroupingColumnNames = new HashSet<>(groupingColumnNames); - fallbackGroupingColumnNames.retainAll(columnList); - fallbackGroupingColumnNames.add(fallback.toString()); - return fallbackGroupingColumnNames.stream().map(dataset::col).collect(toList()); - } - } - - /** - * @param datasets A bunch of {@link Dataset} objects - * @return A new Dataset that is the union of all the inputs - */ - @Nonnull - public static Dataset union(@Nonnull final java.util.Collection> datasets) { - return datasets.stream() - .reduce(Dataset::union) - .orElseThrow(); - } - - @Nonnull - private static Dataset applySelection(@Nonnull final Dataset dataset, - @Nonnull final java.util.Collection includes, @Nonnull final java.util.Collection excludes) { - return dataset.select(Stream.of(dataset.columns()) - .filter(column -> includes.contains(column) || !excludes.contains(column)) - .map(dataset::col) - .toArray(Column[]::new)); - } - - @Nonnull - public static List getUnionableColumns(@Nonnull final Collection source, - @Nonnull final Collection target) { - // The columns will be those common to both datasets, plus the value column of the source. - final Set commonColumnNames = new HashSet<>(List.of(source.getDataset().columns())); - commonColumnNames.retainAll(List.of(target.getDataset().columns())); - final List columns = commonColumnNames.stream() - .map(functions::col) - // We sort the columns so that they line up when we execute the union. - .sorted(Comparator.comparing(Column::toString)) - .collect(toList()); - columns.add(source.getValueColumn()); - return columns; - } + // TODO: review what of this is necessary + + // /** + // * Adds to the columns within a {@link Dataset} with an aliased version of the supplied column. + // * + // * @param dataset the Dataset on which to perform the operation + // * @param column a new {@link Column} + // * @return a new Dataset, along with the new column name, as a {@link DatasetWithColumn} + // */ + // @Nonnull + // public static DatasetWithColumn createColumn(@Nonnull final Dataset dataset, + // @Nonnull final Column column) { + // final DatasetWithColumnMap datasetWithColumnMap = aliasColumns(dataset, + // Collections.singletonList(column)); + // return new DatasetWithColumn(datasetWithColumnMap.getDataset(), + // datasetWithColumnMap.getColumnMap().get(column)); + // } + // + // /** + // * Adds to the columns within a {@link Dataset} with aliased versions of the supplied columns. + // * + // * @param dataset the Dataset on which to perform the operation + // * @param columns the new {@link Column} objects + // * @return a new Dataset, along with the new column names, as a {@link DatasetWithColumnMap} + // */ + // @Nonnull + // public static DatasetWithColumnMap createColumns(@Nonnull final Dataset dataset, + // @Nonnull final Column... columns) { + // return aliasColumns(dataset, Arrays.asList(columns)); + // } + // + // /** + // * Replaces all unaliased columns within a {@link Dataset} with new aliased columns. + // * + // * @param dataset the Dataset on which to perform the operation + // * @return a new Dataset, with a mapping from the old columns to the new as a + // * {@link DatasetWithColumnMap} + // */ + // @Nonnull + // public static DatasetWithColumnMap aliasAllColumns(@Nonnull final Dataset dataset) { + // + // final List columns = Stream.of(dataset.columns()) + // .map(dataset::col) + // .collect(toList()); + // final DatasetWithColumnMap datasetWithColumnMap = aliasColumns(dataset, columns); + // + // final Dataset finalDataset = datasetWithColumnMap.getDataset(); + // final Map columnMap = datasetWithColumnMap.getColumnMap(); + // return new DatasetWithColumnMap(finalDataset, columnMap); + // } + // + // /** + // * Adds aliased versions of the supplied columns to a {@link Dataset}. + // * + // * @param dataset the Dataset on which to perform the operation + // * @param columns a list of new {@link Column} objects + // * @return a new Dataset, along with a map from the supplied columns to the new columns, as a + // * {@link DatasetWithColumnMap} + // */ + // @Nonnull + // private static DatasetWithColumnMap aliasColumns(@Nonnull final Dataset dataset, + // @Nonnull final Iterable columns) { + // + // // Use LinkedHashMap to preserve the original order of columns while iterating map entries. + // final Map columnMap = new LinkedHashMap<>(); + // final List selection = Stream.of(dataset.columns()) + // // Don't preserve anything that is not already aliased. + // .filter(Strings::looksLikeAlias) + // .map(dataset::col) + // .collect(toList()); + // + // // Create an aliased column for each of the new columns, and add it to the selection and the + // // map. + // for (final Column column : columns) { + // final String alias = randomAlias(); + // final Column aliasedColumn = column.alias(alias); + // selection.add(aliasedColumn); + // columnMap.put(column, col(alias)); + // } + // + // // Create a new dataset from the selection. + // final Dataset result = dataset.select(selection.toArray(new Column[0])); + // + // return new DatasetWithColumnMap(result, columnMap); + // } + // + // private static Dataset join(@Nonnull final Dataset left, + // @Nonnull final List leftColumns, @Nonnull final Dataset right, + // @Nonnull final List rightColumns, @Nonnull final Optional additionalCondition, + // @Nonnull final JoinType joinType) { + // checkArgument(leftColumns.size() == rightColumns.size(), + // "Left columns should be same size as right columns"); + // + // Dataset aliasedLeft = left; + // final java.util.Collection joinConditions = new ArrayList<>(); + // for (int i = 0; i < leftColumns.size(); i++) { + // // We alias the join columns on the left-hand side to disambiguate them from columns named the + // // same on the right-hand side. + // final DatasetWithColumn leftWithColumn = createColumn(aliasedLeft, leftColumns.get(i)); + // aliasedLeft = leftWithColumn.getDataset(); + // joinConditions.add(leftWithColumn.getColumn().eqNullSafe(rightColumns.get(i))); + // } + // additionalCondition.ifPresent(joinConditions::add); + // final Column joinCondition = joinConditions.stream() + // .reduce(Column::and) + // .orElse(lit(true)); + // + // final List leftColumnNames = Arrays.asList(aliasedLeft.columns()); + // final List rightColumnNames = rightColumns.stream() + // .map(Column::toString) + // .collect(toList()); + // + // // Exclude the columns in the right dataset from the trimmed left dataset. + // final Dataset trimmedLeft = applySelection(aliasedLeft, Collections.emptyList(), + // rightColumnNames); + // + // // The right dataset will only contain columns that were not in the left dataset, except for the + // // columns on the right-hand side of the join conditions. + // final Dataset trimmedRight = applySelection(right, rightColumnNames, leftColumnNames); + // + // return trimmedLeft.join(trimmedRight, joinCondition, joinType.getSparkName()); + // } + // + // /** + // * Join two datasets based on the equality of an arbitrary set of columns. The same number of + // * columns must be provided for each dataset, and it is assumed that they are matched on their + // * position within their respective lists. + // * + // * @param left the first {@link Dataset} + // * @param leftColumns the columns for the first Dataset + // * @param right the second Dataset + // * @param rightColumns the columns for the second Dataset + // * @param joinType the type of join to use + // * @return the joined Dataset + // */ + // @Nonnull + // public static Dataset join(@Nonnull final Dataset left, + // @Nonnull final List leftColumns, @Nonnull final Dataset right, + // @Nonnull final List rightColumns, @Nonnull final JoinType joinType) { + // return join(left, leftColumns, right, rightColumns, Optional.empty(), joinType); + // } + // + // /** + // * Joins a {@link Dataset} to another Dataset, using the equality of two columns. + // * + // * @param left a {@link Dataset} + // * @param leftColumn the {@link Column} in the left Dataset + // * @param right another Dataset + // * @param rightColumn the column in the right Dataset + // * @param joinType a {@link JoinType} + // * @return a new {@link Dataset} + // */ + // @Nonnull + // public static Dataset join(@Nonnull final Dataset left, + // @Nonnull final Column leftColumn, @Nonnull final Dataset right, + // @Nonnull final Column rightColumn, @Nonnull final JoinType joinType) { + // return join(left, Collections.singletonList(leftColumn), right, + // Collections.singletonList(rightColumn), joinType); + // } + // + // /** + // * Joins a {@link Dataset} to another Dataset, using the equality of two columns. + // * + // * @param left a {@link Dataset} + // * @param leftColumn the {@link Column} in the left Dataset + // * @param right another Dataset + // * @param rightColumn the column in the right Dataset + // * @param additionalCondition an additional Column to be added to the join condition, using AND + // * @param joinType a {@link JoinType} + // * @return a new {@link Dataset} + // */ + // @Nonnull + // public static Dataset join(@Nonnull final Dataset left, + // @Nonnull final Column leftColumn, @Nonnull final Dataset right, + // @Nonnull final Column rightColumn, @Nonnull final Column additionalCondition, + // @Nonnull final JoinType joinType) { + // return join(left, Collections.singletonList(leftColumn), right, + // Collections.singletonList(rightColumn), Optional.of(additionalCondition), joinType); + // } + // + // /** + // * Joins a {@link Dataset} to another Dataset, using the equality of two columns. + // * + // * @param left a {@link Dataset} + // * @param leftColumns the columns for the first Dataset + // * @param right another Dataset + // * @param rightColumns the columns for the second Dataset + // * @param additionalCondition an additional Column to be added to the join condition, using AND + // * @param joinType a {@link JoinType} + // * @return a new {@link Dataset} + // */ + // @Nonnull + // public static Dataset join(@Nonnull final Dataset left, + // @Nonnull final List leftColumns, @Nonnull final Dataset right, + // @Nonnull final List rightColumns, @Nonnull final Column additionalCondition, + // @Nonnull final JoinType joinType) { + // return join(left, leftColumns, right, rightColumns, Optional.of(additionalCondition), joinType); + // } + // + // /** + // * Joins a {@link Dataset} to another Dataset, using a custom join condition. + // * + // * @param left a {@link Dataset} + // * @param right another Dataset + // * @param joinCondition a custom join condition + // * @param joinType a {@link JoinType} + // * @return a new {@link Dataset} + // */ + // @Nonnull + // public static Dataset join(@Nonnull final Dataset left, + // @Nonnull final Dataset right, @Nonnull final Column joinCondition, + // @Nonnull final JoinType joinType) { + // return join(left, Collections.emptyList(), right, Collections.emptyList(), + // Optional.of(joinCondition), joinType); + // } + // + // /** + // * Joins two {@link Collection} expressions, using equality between their respective resource ID + // * columns. + // * + // * @param parserContext the current {@link ParserContext} + // * @param left a {@link Collection} expression + // * @param right another FhirPath expression + // * @param joinType a {@link JoinType} + // * @return a new {@link Dataset} + // */ + // @Nonnull + // public static Dataset join(@Nonnull final ParserContext parserContext, + // @Nonnull final Collection left, @Nonnull final Collection right, + // @Nonnull final JoinType joinType) { + // return join(parserContext, Arrays.asList(left, right), joinType); + // } + // + // /** + // * Joins any number of {@link Collection} expressions, using equality between their respective + // * resource ID columns. + // * + // * @param parserContext the current {@link ParserContext} + // * @param results a list of {@link Collection} expressions + // * @param joinType a {@link JoinType} + // * @return a new {@link Dataset} + // */ + // @Nonnull + // public static Dataset join(@Nonnull final ParserContext parserContext, + // @Nonnull final List results, @Nonnull final JoinType joinType) { + // checkArgument(results.size() > 1, "fhirPaths must contain more than one FhirPath"); + // + // final Collection left = results.get(0); + // final List joinTargets = results.subList(1, results.size()); + // + // // Only non-literal paths will trigger a join. + // final List nonLiteralTargets = joinTargets.stream() + // .filter(t -> t instanceof NonLiteralPath) + // .collect(toList()); + // if (left instanceof NonLiteralPath && nonLiteralTargets.isEmpty()) { + // // If the only non-literal path is on the left, we can just return the left without any need + // // to join. + // return left.getDataset(); + // } else if (left instanceof LiteralPath && !nonLiteralTargets.isEmpty()) { + // // If non-literal paths are confined to the right, we can just return the first dataset on the + // // right without any need to join. + // return nonLiteralTargets.get(0).getDataset(); + // } + // + // Dataset dataset = left.getDataset(); + // final List groupingColumns = parserContext.getGroupingColumns(); + // final Column idColumn = parserContext.getInputContext().getIdColumn(); + // final List leftColumns = checkColumnsAndFallback(left.getDataset(), groupingColumns, + // idColumn); + // for (final Collection right : nonLiteralTargets) { + // final List resolvedGroupingColumns = checkColumnsAndFallback(right.getDataset(), + // leftColumns, idColumn); + // dataset = join(dataset, resolvedGroupingColumns, right.getDataset(), resolvedGroupingColumns, + // joinType); + // } + // return dataset; + // } + // + // /** + // * Joins a {@link Dataset} to a {@link Collection}, using equality between the resource ID in the + // * FhirPath and the supplied column. + // * + // * @param left a {@link Collection} expression + // * @param right a {@link Dataset} + // * @param rightColumn the {@link Column} in the right Dataset + // * @param joinType a {@link JoinType} + // * @return A new {@link Dataset} + // */ + // @Nonnull + // public static Dataset join(@Nonnull final Collection left, @Nonnull final Dataset right, + // @Nonnull final Column rightColumn, @Nonnull final JoinType joinType) { + // return join(left.getDataset(), left.getIdColumn(), right, rightColumn, joinType); + // } + // + // /** + // * Joins a {@link Dataset} to a {@link Collection}, using equality between the resource ID in the + // * FhirPath and the supplied column. + // * + // * @param left a {@link Collection} expression + // * @param right a {@link Dataset} + // * @param rightColumn the {@link Column} in the right Dataset + // * @param additionalCondition an additional Column to be added to the join condition, using AND + // * @param joinType a {@link JoinType} + // * @return A new {@link Dataset} + // */ + // @Nonnull + // public static Dataset join(@Nonnull final Collection left, @Nonnull final Dataset right, + // @Nonnull final Column rightColumn, @Nonnull final Column additionalCondition, + // @Nonnull final JoinType joinType) { + // return join(left.getDataset(), Collections.singletonList(left.getIdColumn()), right, + // Collections.singletonList(rightColumn), Optional.of(additionalCondition), joinType); + // } + // + // /** + // * Joins a {@link Dataset} to a {@link Collection}, using equality between the resource ID in the + // * FhirPath and the supplied column. + // * + // * @param left a {@link Dataset} + // * @param leftColumn the {@link Column} in the left Dataset + // * @param right a {@link Collection} expression + // * @param joinType a {@link JoinType} + // * @return A new {@link Dataset} + // */ + // @Nonnull + // public static Dataset join(@Nonnull final Dataset left, + // @Nonnull final Column leftColumn, @Nonnull final Collection right, + // @Nonnull final JoinType joinType) { + // return join(left, leftColumn, right.getDataset(), right.getIdColumn(), joinType); + // } + // + // /** + // * This is used to find a set of fallback join columns in cases where a path does not contain all + // * grouping columns. + // *

+ // * This can happen in the context of a function's arguments, when a path originates from something + // * other than `$this`, e.g. `%resource`. + // */ + // private static List checkColumnsAndFallback(@Nonnull final Dataset dataset, + // @Nonnull final List groupingColumns, @Nonnull final Column fallback) { + // final Set columnList = new HashSet<>(List.of(dataset.columns())); + // final Set groupingColumnNames = groupingColumns.stream().map(Column::toString) + // .collect(toSet()); + // if (columnList.containsAll(groupingColumnNames)) { + // return groupingColumns; + // } else { + // final Set fallbackGroupingColumnNames = new HashSet<>(groupingColumnNames); + // fallbackGroupingColumnNames.retainAll(columnList); + // fallbackGroupingColumnNames.add(fallback.toString()); + // return fallbackGroupingColumnNames.stream().map(dataset::col).collect(toList()); + // } + // } + // + // /** + // * @param datasets A bunch of {@link Dataset} objects + // * @return A new Dataset that is the union of all the inputs + // */ + // @Nonnull + // public static Dataset union(@Nonnull final java.util.Collection> datasets) { + // return datasets.stream() + // .reduce(Dataset::union) + // .orElseThrow(); + // } + // + // @Nonnull + // private static Dataset applySelection(@Nonnull final Dataset dataset, + // @Nonnull final java.util.Collection includes, @Nonnull final java.util.Collection excludes) { + // return dataset.select(Stream.of(dataset.columns()) + // .filter(column -> includes.contains(column) || !excludes.contains(column)) + // .map(dataset::col) + // .toArray(Column[]::new)); + // } + // + // @Nonnull + // public static List getUnionableColumns(@Nonnull final Collection source, + // @Nonnull final Collection target) { + // // The columns will be those common to both datasets, plus the value column of the source. + // final Set commonColumnNames = new HashSet<>(List.of(source.getDataset().columns())); + // commonColumnNames.retainAll(List.of(target.getDataset().columns())); + // final List columns = commonColumnNames.stream() + // .map(functions::col) + // // We sort the columns so that they line up when we execute the union. + // .sorted(Comparator.comparing(Column::toString)) + // .collect(toList()); + // columns.add(source.getValueColumn()); + // return columns; + // } + // /** * Creates an empty dataset with the schema of the supplied resource type. @@ -465,51 +443,51 @@ public static Dataset createEmptyDataset(@Nonnull final SparkSession spark, final ExpressionEncoder encoder = fhirEncoders.of(resourceType.toCode()); return spark.emptyDataset(encoder).toDF(); } - - /** - * Explodes an array column from a provided dataset, preserving all the columns from this one and - * producing a new value and ordering column. - * - * @param dataset The dataset containing the array column. It should also contain all other - * columns that should be preserved alongside the new columns. - * @param arrayColumn The array column to explode - * @param outputColumns The output pair of columns: `left` is set to the new value column and - * `right` to the new ordering column - * @return the {@link Dataset} with the exploded array - */ - @Nonnull - public static Dataset explodeArray(@Nonnull final Dataset dataset, - @Nonnull final Column arrayColumn, - @Nonnull final MutablePair outputColumns) { - final Column[] allColumns = Stream.concat(Arrays.stream(dataset.columns()) - .map(dataset::col), Stream - .of(posexplode_outer(arrayColumn) - .as(new String[]{"index", "value"}))) - .toArray(Column[]::new); - final Dataset resultDataset = dataset.select(allColumns); - outputColumns.setLeft(resultDataset.col("value")); - outputColumns.setRight(resultDataset.col("index")); - return resultDataset; - } - - public static Column flattenValue(final Dataset dataset, @Nonnull final Column value) { - return valueIsArrayOfArrays(dataset, value) - ? flatten(value) - : value; - } - - private static boolean valueIsArrayOfArrays(@Nonnull final Dataset dataset, - final Column value) { - final boolean arrayOfArrays; - final DataType dataType = dataset.select(value).schema().fields()[0].dataType(); - if (dataType instanceof ArrayType) { - final ArrayType arrayType = (ArrayType) dataType; - arrayOfArrays = arrayType.elementType() instanceof ArrayType; - } else { - arrayOfArrays = false; - } - return arrayOfArrays; - } + // + // /** + // * Explodes an array column from a provided dataset, preserving all the columns from this one and + // * producing a new value and ordering column. + // * + // * @param dataset The dataset containing the array column. It should also contain all other + // * columns that should be preserved alongside the new columns. + // * @param arrayColumn The array column to explode + // * @param outputColumns The output pair of columns: `left` is set to the new value column and + // * `right` to the new ordering column + // * @return the {@link Dataset} with the exploded array + // */ + // @Nonnull + // public static Dataset explodeArray(@Nonnull final Dataset dataset, + // @Nonnull final Column arrayColumn, + // @Nonnull final MutablePair outputColumns) { + // final Column[] allColumns = Stream.concat(Arrays.stream(dataset.columns()) + // .map(dataset::col), Stream + // .of(posexplode_outer(arrayColumn) + // .as(new String[]{"index", "value"}))) + // .toArray(Column[]::new); + // final Dataset resultDataset = dataset.select(allColumns); + // outputColumns.setLeft(resultDataset.col("value")); + // outputColumns.setRight(resultDataset.col("index")); + // return resultDataset; + // } + // + // public static Column flattenValue(final Dataset dataset, @Nonnull final Column value) { + // return valueIsArrayOfArrays(dataset, value) + // ? flatten(value) + // : value; + // } + // + // private static boolean valueIsArrayOfArrays(@Nonnull final Dataset dataset, + // final Column value) { + // final boolean arrayOfArrays; + // final DataType dataType = dataset.select(value).schema().fields()[0].dataType(); + // if (dataType instanceof ArrayType) { + // final ArrayType arrayType = (ArrayType) dataType; + // arrayOfArrays = arrayType.elementType() instanceof ArrayType; + // } else { + // arrayOfArrays = false; + // } + // return arrayOfArrays; + // } /** * Represents a type of join that can be made between two {@link Dataset} objects. diff --git a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java index 73bf9e2eac..e7398956a6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java @@ -17,15 +17,10 @@ package au.csiro.pathling.aggregate; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static au.csiro.pathling.utilities.Strings.randomAlias; -import static org.apache.spark.sql.functions.col; - import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.config.QueryConfiguration; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.Materializable; -import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.Database; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -35,7 +30,6 @@ import javax.annotation.Nonnull; import lombok.Value; import lombok.extern.slf4j.Slf4j; -import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; @@ -46,6 +40,7 @@ * @author John Grimes */ @Slf4j +@NotImplemented public class AggregateQueryExecutor extends QueryExecutor { /** @@ -158,40 +153,40 @@ public ResultWithExpressions buildQuery(@Nonnull final AggregateRequest query) { return null; } - private void validateAggregations(@Nonnull final java.util.Collection aggregations) { - for (final Collection aggregation : aggregations) { - // An aggregation expression must be able to be extracted into a FHIR value. - checkUserInput(aggregation instanceof Materializable, - "Aggregation expression is not of a supported type: " + aggregation.getExpression()); - // An aggregation expression must be singular, relative to its input context. - checkUserInput(aggregation.isSingular(), - "Aggregation expression does not evaluate to a singular value: " - + aggregation.getExpression()); - } - } - - private void validateGroupings(@Nonnull final java.util.Collection groupings) { - for (final Collection grouping : groupings) { - // A grouping expression must be able to be extracted into a FHIR value. - checkUserInput(grouping instanceof Materializable, - "Grouping expression is not of a supported type: " + grouping.getExpression()); - } - } - - @Nonnull - private static Dataset applyAggregation(@Nonnull final ParserContext context, - @Nonnull final List aggregationColumns, @Nonnull final Collection lastAggregation, - @Nonnull final Dataset disaggregated) { - // Group the dataset by the grouping columns, and take the first row. - final Column[] groupBy = context.getGroupingColumns().toArray(new Column[0]); - final String aggregatedColumnName = randomAlias(); - final Dataset aggregated = disaggregated.groupBy(groupBy) - .agg(first(lastAggregation.getValueColumn()).as(aggregatedColumnName)); - // Replace the last column with the aggregated column. - aggregationColumns.remove(aggregationColumns.size() - 1); - aggregationColumns.add(col(aggregatedColumnName)); - return aggregated; - } + // private void validateAggregations(@Nonnull final java.util.Collection aggregations) { + // for (final Collection aggregation : aggregations) { + // // An aggregation expression must be able to be extracted into a FHIR value. + // checkUserInput(aggregation instanceof Materializable, + // "Aggregation expression is not of a supported type: " + aggregation.getExpression()); + // // An aggregation expression must be singular, relative to its input context. + // checkUserInput(aggregation.isSingular(), + // "Aggregation expression does not evaluate to a singular value: " + // + aggregation.getExpression()); + // } + // } + // + // private void validateGroupings(@Nonnull final java.util.Collection groupings) { + // for (final Collection grouping : groupings) { + // // A grouping expression must be able to be extracted into a FHIR value. + // checkUserInput(grouping instanceof Materializable, + // "Grouping expression is not of a supported type: " + grouping.getExpression()); + // } + // } + // + // @Nonnull + // private static Dataset applyAggregation(@Nonnull final ParserContext context, + // @Nonnull final List aggregationColumns, @Nonnull final Collection lastAggregation, + // @Nonnull final Dataset disaggregated) { + // // Group the dataset by the grouping columns, and take the first row. + // final Column[] groupBy = context.getGroupingColumns().toArray(new Column[0]); + // final String aggregatedColumnName = randomAlias(); + // final Dataset aggregated = disaggregated.groupBy(groupBy) + // .agg(first(lastAggregation.getValueColumn()).as(aggregatedColumnName)); + // // Replace the last column with the aggregated column. + // aggregationColumns.remove(aggregationColumns.size() - 1); + // aggregationColumns.add(col(aggregatedColumnName)); + // return aggregated; + // } @Value public static class ResultWithExpressions { diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java index 2f1af93571..ab307aae05 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java @@ -6,6 +6,7 @@ import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.AbstractPath; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.StringCoercible; import au.csiro.pathling.io.source.DataSource; @@ -25,6 +26,7 @@ * @author John Grimes */ @Slf4j +@NotImplemented public class ExtractQueryExecutor extends QueryExecutor { public ExtractQueryExecutor(@Nonnull final QueryConfiguration configuration, @@ -115,39 +117,39 @@ public Dataset buildQuery(@Nonnull final ExtractRequest query, return null; } - private List validateAndCoerceColumns( - @Nonnull final List columnParseResult, - @Nonnull final ExtractResultType resultType) { - - // Perform any necessary String coercion. - final List coerced = columnParseResult.stream() - .map(column -> { - if (resultType == ExtractResultType.FLAT && !(column instanceof Flat) - && column instanceof StringCoercible) { - // If the result type is flat and the path is string-coercible, we can coerce it. - final StringCoercible stringCoercible = (StringCoercible) column; - return stringCoercible.asStringPath(column.getExpression(), - stringCoercible.getExpression()); - } else { - return column; - } - }).collect(toList()); - - // Validate the final set of paths. - for (final Collection column : coerced) { - final boolean condition; - if (resultType == ExtractResultType.FLAT) { - // In flat mode, only flat columns are allowed. - condition = column instanceof Flat; - } else { - // Otherwise, a column can be of any type, as long as it has not been specifically flagged - // as being abstract, e.g. an UntypedResourcePath. - condition = !(column instanceof AbstractPath); - } - checkArgument(condition, "Column is not of a supported type: " + column.getExpression()); - } - - return coerced; - } - + // private List validateAndCoerceColumns( + // @Nonnull final List columnParseResult, + // @Nonnull final ExtractResultType resultType) { + // + // // Perform any necessary String coercion. + // final List coerced = columnParseResult.stream() + // .map(column -> { + // if (resultType == ExtractResultType.FLAT && !(column instanceof Flat) + // && column instanceof StringCoercible) { + // // If the result type is flat and the path is string-coercible, we can coerce it. + // final StringCoercible stringCoercible = (StringCoercible) column; + // return stringCoercible.asStringPath(column.getExpression(), + // stringCoercible.getExpression()); + // } else { + // return column; + // } + // }).collect(toList()); + // + // // Validate the final set of paths. + // for (final Collection column : coerced) { + // final boolean condition; + // if (resultType == ExtractResultType.FLAT) { + // // In flat mode, only flat columns are allowed. + // condition = column instanceof Flat; + // } else { + // // Otherwise, a column can be of any type, as long as it has not been specifically flagged + // // as being abstract, e.g. an UntypedResourcePath. + // condition = !(column instanceof AbstractPath); + // } + // checkArgument(condition, "Column is not of a supported type: " + column.getExpression()); + // } + // + // return coerced; + // } + // } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java index 508619a55e..00194820fa 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java @@ -1,6 +1,8 @@ package au.csiro.pathling.fhirpath; import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.parser.ParserContext; +import javax.annotation.Nonnull; import java.util.function.Function; /** @@ -10,6 +12,7 @@ * @param The output type of {@link Collection} * @author John Grimes */ -public interface FhirPath extends Function { - +@FunctionalInterface +public interface FhirPath { + O apply(@Nonnull final I input, @Nonnull final ParserContext context); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java index 075da5ef11..ec960fc363 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Nesting.java @@ -1,19 +1,6 @@ package au.csiro.pathling.fhirpath; -import static java.util.stream.Collectors.toList; - -import au.csiro.pathling.fhirpath.collection.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; -import javax.annotation.Nonnull; -import lombok.Getter; -import lombok.Setter; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; /** * Stores information about traversal of nested elements and resources within a FHIRPath expression. @@ -23,85 +10,86 @@ * * @author John Grimes */ +@NotImplemented public class Nesting { - @Nonnull - private final Map nesting; - - /** - * Indicates whether the root level of the dataset has been rolled up due to aggregation. This is - * a trigger to reconstitute the root level when traversing back into the root. - */ - @Getter - @Setter - private boolean rootErased = false; - - public Nesting() { - this.nesting = new LinkedHashMap<>(); - } - - @Nonnull - public NonLiteralPath updateOrRetrieve(@Nonnull final NestingKey key, - @Nonnull final String expression, @Nonnull final Dataset dataset, - final boolean singular, @Nonnull final Optional thisColumn, - @Nonnull final Function updater) { - final boolean hit = nesting.containsKey(key); - final NonLiteralPath result = nesting.computeIfAbsent(key, updater); - if (hit) { - // If the path has been retrieved from the cache, we need to copy it and substitute the - // supplied values for expression, dataset, singular and this column. - return result.copy(expression, dataset, result.getIdColumn(), result.getValueColumn(), - result.getOrderingColumn(), singular, thisColumn); - } else { - // If the path has been computed fresh, we don't need to update anything. - return result; - } - } - - /** - * @return whether the traversed nesting levels are all orderable, which tells us whether ordering - * can be determined in this context - */ - public boolean isOrderable() { - return nesting.values().stream() - .allMatch(path -> path.getOrderingColumn().isPresent()); - } - - /** - * @return all value columns within nesting paths that have been traversed - */ - @Nonnull - public List getColumns() { - return nesting.values().stream() - .map(Collection::getValueColumn) - .collect(toList()); - } - - /** - * @return all ordering columns within nesting paths that have been traversed - */ - @Nonnull - public List getOrderingColumns() { - return nesting.values().stream() - .map(Collection::getOrderingColumn) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(toList()); - } - - /** - * @return whether the nesting stack is empty - */ - public boolean isEmpty() { - return nesting.isEmpty(); - } - - /** - * Clears the nesting stack. - */ - public void clear() { - nesting.clear(); - rootErased = true; - } + // @Nonnull + // private final Map nesting; + // + // /** + // * Indicates whether the root level of the dataset has been rolled up due to aggregation. This is + // * a trigger to reconstitute the root level when traversing back into the root. + // */ + // @Getter + // @Setter + // private boolean rootErased = false; + // + // public Nesting() { + // this.nesting = new LinkedHashMap<>(); + // } + // + // @Nonnull + // public NonLiteralPath updateOrRetrieve(@Nonnull final NestingKey key, + // @Nonnull final String expression, @Nonnull final Dataset dataset, + // final boolean singular, @Nonnull final Optional thisColumn, + // @Nonnull final Function updater) { + // final boolean hit = nesting.containsKey(key); + // final NonLiteralPath result = nesting.computeIfAbsent(key, updater); + // if (hit) { + // // If the path has been retrieved from the cache, we need to copy it and substitute the + // // supplied values for expression, dataset, singular and this column. + // return result.copy(expression, dataset, result.getIdColumn(), result.getValueColumn(), + // result.getOrderingColumn(), singular, thisColumn); + // } else { + // // If the path has been computed fresh, we don't need to update anything. + // return result; + // } + // } + // + // /** + // * @return whether the traversed nesting levels are all orderable, which tells us whether ordering + // * can be determined in this context + // */ + // public boolean isOrderable() { + // return nesting.values().stream() + // .allMatch(path -> path.getOrderingColumn().isPresent()); + // } + // + // /** + // * @return all value columns within nesting paths that have been traversed + // */ + // @Nonnull + // public List getColumns() { + // return nesting.values().stream() + // .map(Collection::getValueColumn) + // .collect(toList()); + // } + // + // /** + // * @return all ordering columns within nesting paths that have been traversed + // */ + // @Nonnull + // public List getOrderingColumns() { + // return nesting.values().stream() + // .map(Collection::getOrderingColumn) + // .filter(Optional::isPresent) + // .map(Optional::get) + // .collect(toList()); + // } + // + // /** + // * @return whether the nesting stack is empty + // */ + // public boolean isEmpty() { + // return nesting.isEmpty(); + // } + // + // /** + // * Clears the nesting stack. + // */ + // public void clear() { + // nesting.clear(); + // rootErased = true; + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java index 3fd02b8e8e..c02bd12807 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java @@ -22,6 +22,7 @@ import static org.apache.spark.sql.functions.lit; import static org.apache.spark.sql.functions.split; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.ResourceCollection; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -65,14 +66,18 @@ static Column referenceIdColumnFor(@Nonnull final Column referenceColumn) { * @return a {@link Column} representing the matching condition */ @Nonnull + @NotImplemented static Column resourceEqualityFor(@Nonnull final Referrer referrer, @Nonnull final ResourceCollection resourceCollection) { - final Column targetId = resourceCollection.getCurrentResource() - .map(ResourceCollection::getIdColumn) - .orElse(resourceCollection.getIdColumn()); - final Column targetCode = lit(resourceCollection.getResourceType().toCode()); - - return Referrer.resourceEqualityFor(referrer, targetCode, targetId); + + // TODO: implement + // final Column targetId = resourceCollection.getCurrentResource() + // .map(ResourceCollection::getIdColumn) + // .orElse(resourceCollection.getIdColumn()); + // final Column targetCode = lit(resourceCollection.getResourceType().toCode()); + // + // return Referrer.resourceEqualityFor(referrer, targetCode, targetId); + return null; } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java index e82f82d20f..15f5a59c57 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java @@ -38,8 +38,8 @@ public interface Temporal { /** * Gets a function that can take the {@link QuantityCollection} representing a time duration and * return a {@link Collection} that contains the result of date arithmetic operation for this path - * and the provided duration. The type of operation is controlled by supplying a - * {@link MathOperation}. + * and the provided duration. The type of operation is controlled by supplying a {@link + * MathOperation}. * * @param operation The {@link MathOperation} type to retrieve a result for * @return A {@link Function} that takes a {@link QuantityCollection} as its parameter, and @@ -84,7 +84,7 @@ static Function buildDateArithmeticOperation( final Column valueColumn = functions.callUDF(functionName, source.getColumn(), target.getColumn()); - return DateTimeCollection.build(valueColumn, expression, Optional.empty()); + return DateTimeCollection.build(valueColumn, Optional.empty()); }; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TerminologyUtils.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TerminologyUtils.java index 7e1c920a33..b4ce83d11a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TerminologyUtils.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TerminologyUtils.java @@ -19,9 +19,8 @@ import static au.csiro.pathling.utilities.Preconditions.check; +import au.csiro.pathling.fhirpath.collection.CodingCollection; import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; @@ -38,8 +37,18 @@ public interface TerminologyUtils { * @return true if the path is a codeable concept */ static boolean isCodeableConcept(@Nonnull final Collection result) { - return (result instanceof PrimitivePath && - FHIRDefinedType.CODEABLECONCEPT.equals(((PrimitivePath) result).getFhirType())); + return result.getFhirType().map(FHIRDefinedType.CODEABLECONCEPT::equals).orElse(false); + } + + /** + * Checks if a path is a coding path + * + * @param result a path to check + * @return true if the path is a codeable concept + */ + static boolean isCoding(@Nonnull final Collection result) { + return result instanceof CodingCollection || + result.getFhirType().map(FHIRDefinedType.CODING::equals).orElse(false); } /** @@ -49,22 +58,14 @@ static boolean isCodeableConcept(@Nonnull final Collection result) { * @return true if the path is coding or codeable concept */ static boolean isCodingOrCodeableConcept(@Nonnull final Collection result) { - if (result instanceof CodingLiteralPath) { - return true; - } else if (result instanceof PrimitivePath) { - final FHIRDefinedType elementFhirType = ((PrimitivePath) result).getFhirType(); - return FHIRDefinedType.CODING.equals(elementFhirType) - || FHIRDefinedType.CODEABLECONCEPT.equals(elementFhirType); - } else { - return false; - } + return isCoding(result) || isCodeableConcept(result); } @Nonnull static Column getCodingColumn(@Nonnull final Collection result) { check(isCodingOrCodeableConcept(result), "Coding or CodeableConcept path expected"); - final Column conceptColumn = result.getValueColumn(); + final Column conceptColumn = result.getColumn(); return isCodeableConcept(result) ? conceptColumn.getField("coding") : conceptColumn; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/annotations/Name.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/annotations/Name.java new file mode 100644 index 0000000000..2a3a3f3dd6 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/annotations/Name.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 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.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Name { + + String value(); +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/annotations/NotImplemented.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/annotations/NotImplemented.java new file mode 100644 index 0000000000..02da871e4b --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/annotations/NotImplemented.java @@ -0,0 +1,26 @@ +/* + * Copyright 2023 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.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.SOURCE) +public @interface NotImplemented { + +} 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 7bdcb2c5f3..b123230fd6 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 @@ -33,6 +33,7 @@ import lombok.Getter; import org.apache.spark.sql.Column; import org.apache.spark.sql.SparkSession; +import org.apache.spark.sql.functions; import org.apache.spark.sql.types.ArrayType; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; @@ -97,8 +98,8 @@ public class Collection implements Comparable, Numeric { private Optional definition; /** - * Builds the appropriate subtype of {@link Collection} based upon the supplied - * {@link ElementDefinition}. + * Builds the appropriate subtype of {@link Collection} based upon the supplied {@link + * ElementDefinition}. *

* Use this builder when the path is the child of another path, and will need to be traversable. * @@ -119,8 +120,8 @@ public static Collection build(@Nonnull final Column column, } /** - * Builds the appropriate subtype of {@link Collection} based upon the supplied - * {@link FHIRDefinedType}. + * Builds the appropriate subtype of {@link Collection} based upon the supplied {@link + * FHIRDefinedType}. *

* Use this builder when the path is derived, e.g. the result of a function. * @@ -153,7 +154,7 @@ private static Collection getInstance(@Nonnull final Column column, return constructor .newInstance(column, Optional.of(fhirPathType), Optional.of(fhirType), definition); } catch (final NoSuchMethodException | InstantiationException | IllegalAccessException | - InvocationTargetException e) { + InvocationTargetException e) { throw new RuntimeException("Problem building an FhirPath object", e); } } @@ -261,4 +262,15 @@ public Optional getNumericContextColumn() { return Optional.empty(); } + /** + * Creates a null {@link Collection}. + * + * @return the null collection. + */ + @Nonnull + public static Collection nullCollection() { + return new Collection(functions.lit(null), Optional.empty(), Optional.empty(), + Optional.empty()); + } + } 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 e1c76a6d7f..84ae2398d4 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 @@ -19,6 +19,7 @@ import static au.csiro.pathling.fhirpath.Temporal.buildDateArithmeticOperation; import static org.apache.spark.sql.functions.date_format; +import static org.apache.spark.sql.functions.lit; import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPathType; @@ -30,6 +31,7 @@ import au.csiro.pathling.sql.dates.datetime.DateTimeAddDurationFunction; import au.csiro.pathling.sql.dates.datetime.DateTimeSubtractDurationFunction; import com.google.common.collect.ImmutableSet; +import java.text.ParseException; import java.util.Optional; import java.util.function.Function; import javax.annotation.Nonnull; @@ -58,6 +60,19 @@ public DateTimeCollection(@Nonnull final Column column, definition); } + /** + * Returns a new instance, parsed from a FHIRPath literal. + * + * @param fhirPath The FHIRPath representation of the literal + * @return A new instance of {@link DateTimeCollection} + * @throws ParseException if the literal is malformed + */ + @Nonnull + public static DateTimeCollection fromLiteral(@Nonnull final String fhirPath) throws ParseException { + final String dateString = fhirPath.replaceFirst("^@", ""); + return DateTimeCollection.build(lit(dateString), Optional.empty()); + } + @Nonnull public static DateTimeCollection build(@Nonnull final Column column, @Nonnull final Optional definition) { 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 f21429530a..9b170cfb11 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 @@ -58,13 +58,24 @@ public class ResourceCollection extends Collection { @Nonnull private final ResourceDefinition resourceDefinition; - public ResourceCollection(@Nonnull final Column column, @Nonnull final ResourceDefinition definition, + @Nonnull + private final Dataset dataset; + + + public ResourceCollection(@Nonnull final Dataset dataset, @Nonnull final Column column, + @Nonnull final ResourceDefinition definition, @Nonnull final Map elementsToColumns) { super(column, Optional.empty(), getFhirType(definition.getResourceType()), Optional.of(definition)); + this.dataset = dataset; this.elementsToColumns = elementsToColumns; this.resourceDefinition = definition; } + + @Nonnull + public Dataset getDataset() { + return dataset; + } @Nonnull private static Optional getFhirType(@Nonnull final ResourceType resourceType) { @@ -101,7 +112,7 @@ public static ResourceCollection build(@Nonnull final FhirContext fhirContext, .collect(Collectors.toMap(Function.identity(), functions::col, (a, b) -> null)); // We use the ID column as the value column for a ResourcePath. - return new ResourceCollection(functions.col("id"), definition, elementsToColumns); + return new ResourceCollection(dataset, functions.col("id"), definition, elementsToColumns); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java index 2cdf12b6e6..4a604f2c8f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java @@ -19,10 +19,12 @@ public interface ElementDefinition extends NodeDefinition { */ @Nonnull static ElementDefinition build(@Nonnull final BaseRuntimeChildDefinition childDefinition) { - if (childDefinition instanceof RuntimeChildAny && "valueReference".equals(childDefinition - .getElementName())) { - return new ReferenceExtensionDefinition((RuntimeChildAny) childDefinition); - } else if (childDefinition instanceof RuntimeChildResourceDefinition) { + // TODO: check why this is safe to remove + // if (childDefinition instanceof RuntimeChildAny && "valueReference".equals(childDefinition + // .getElementName())) { + // return new ReferenceExtensionDefinition((RuntimeChildAny) childDefinition); + // } else + if (childDefinition instanceof RuntimeChildResourceDefinition) { return new ReferenceDefinition((RuntimeChildResourceDefinition) childDefinition); } else if (childDefinition instanceof RuntimeChildChoiceDefinition) { return new ChoiceElementDefinition((RuntimeChildChoiceDefinition) childDefinition); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/Arguments.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/Arguments.java index 571575681e..c0a704164b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/Arguments.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/Arguments.java @@ -17,97 +17,91 @@ package au.csiro.pathling.fhirpath.function; -import static java.util.Objects.requireNonNull; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.literal.LiteralPath; -import java.util.List; -import java.util.Optional; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.hl7.fhir.r4.model.Type; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; /** * Helper class for dealing with optional arguments. */ +@NotImplemented public class Arguments { - @Nonnull - private final List arguments; - - private Arguments(@Nonnull final List arguments) { - this.arguments = arguments; - } - - /** - * Gets the value of an optional literal argument or the default value it the argument is - * missing. - * - * @param index the 0-based index of the argument - * @param defaultValue the default value - * @param the Java type of the argument value - * @return the java value of the requested argument - */ - @SuppressWarnings("unchecked") - @Nonnull - public T getValueOr(final int index, @Nonnull final T defaultValue) { - return (index < arguments.size()) - ? getValue(index, (Class) defaultValue.getClass()) - : defaultValue; - } - - /** - * Gets the value of an optional literal argument that does not have a default value. - * - * @param index the 0-based index of the argument - * @param valueClass the expected Java class of the argument value - * @param the Java type of the argument value - * @return the java value of the requested argument - */ - @Nullable - public T getNullableValue(final int index, - @Nonnull final Class valueClass) { - return index < arguments.size() - ? valueClass.cast(((LiteralPath) arguments.get(index)).getValue()) - : null; - } - - /** - * Gets the value of an optional literal argument that does not have a default value. - * - * @param index the 0-based index of the argument - * @param valueClass the expected Java class of the argument value - * @param the Java type of the argument value - * @return an {@link Optional} containing the Java value of the requested argument, or an empty - * {@link Optional} if the argument is missing - */ - @Nonnull - public Optional getOptionalValue(final int index, - @Nonnull final Class valueClass) { - return Optional.ofNullable(getNullableValue(index, valueClass)); - } - - /** - * Gets the value of the required literal argument. - * - * @param index the 0-based index of the argument - * @param valueClass the expected Java class of the argument value - * @param the HAPI type of the argument value - * @return the java value of the requested argument - */ - @Nonnull - public T getValue(final int index, @Nonnull final Class valueClass) { - return requireNonNull(getNullableValue(index, valueClass)); - } - - /** - * Construct Arguments for given {@link NamedFunctionInput} - * - * @param input the function input - * @return the Arguments for the input - */ - @Nonnull - public static Arguments of(@Nonnull final NamedFunctionInput input) { - return new Arguments(input.getArguments()); - } + // TODO: implement for columns + // @Nonnull + // private final List arguments; + // + // private Arguments(@Nonnull final List arguments) { + // this.arguments = arguments; + // } + // + // /** + // * Gets the value of an optional literal argument or the default value it the argument is + // * missing. + // * + // * @param index the 0-based index of the argument + // * @param defaultValue the default value + // * @param the Java type of the argument value + // * @return the java value of the requested argument + // */ + // @SuppressWarnings("unchecked") + // @Nonnull + // public T getValueOr(final int index, @Nonnull final T defaultValue) { + // return (index < arguments.size()) + // ? getValue(index, (Class) defaultValue.getClass()) + // : defaultValue; + // } + // + // /** + // * Gets the value of an optional literal argument that does not have a default value. + // * + // * @param index the 0-based index of the argument + // * @param valueClass the expected Java class of the argument value + // * @param the Java type of the argument value + // * @return the java value of the requested argument + // */ + // @Nullable + // public T getNullableValue(final int index, + // @Nonnull final Class valueClass) { + // return index < arguments.size() + // ? valueClass.cast(((LiteralPath) arguments.get(index)).getValue()) + // : null; + // } + // + // /** + // * Gets the value of an optional literal argument that does not have a default value. + // * + // * @param index the 0-based index of the argument + // * @param valueClass the expected Java class of the argument value + // * @param the Java type of the argument value + // * @return an {@link Optional} containing the Java value of the requested argument, or an empty + // * {@link Optional} if the argument is missing + // */ + // @Nonnull + // public Optional getOptionalValue(final int index, + // @Nonnull final Class valueClass) { + // return Optional.ofNullable(getNullableValue(index, valueClass)); + // } + // + // /** + // * Gets the value of the required literal argument. + // * + // * @param index the 0-based index of the argument + // * @param valueClass the expected Java class of the argument value + // * @param the HAPI type of the argument value + // * @return the java value of the requested argument + // */ + // @Nonnull + // public T getValue(final int index, @Nonnull final Class valueClass) { + // return requireNonNull(getNullableValue(index, valueClass)); + // } + // + // /** + // * Construct Arguments for given {@link NamedFunctionInput} + // * + // * @param input the function input + // * @return the Arguments for the input + // */ + // @Nonnull + // public static Arguments of(@Nonnull final FunctionInput input) { + // return new Arguments(input.getArguments()); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java index 2ef080dc75..9d8456dc4d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/BooleansTestFunction.java @@ -17,17 +17,12 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.coalesce; import static org.apache.spark.sql.functions.lit; import static org.apache.spark.sql.functions.max; import static org.apache.spark.sql.functions.min; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import java.util.function.UnaryOperator; import javax.annotation.Nonnull; import lombok.AllArgsConstructor; @@ -37,7 +32,8 @@ /** * @author John Grimes */ -public class BooleansTestFunction extends AggregateFunction implements NamedFunction { +@NotImplemented +public class BooleansTestFunction implements NamedFunction { @Nonnull private final BooleansTestType type; @@ -49,21 +45,28 @@ public BooleansTestFunction(@Nonnull final BooleansTestType type) { this.type = type; } - @Nonnull @Override - public Collection invoke(@Nonnull final NamedFunctionInput input) { - checkNoArguments(type.getFunctionName(), input); - final NonLiteralPath inputPath = input.getInput(); - checkUserInput(inputPath instanceof BooleanCollection, - "Input to " + type + " function must be Boolean"); - final Column inputColumn = inputPath.getValueColumn(); - final String expression = expressionFromInput(input, type.getFunctionName(), input.getInput()); - - final Column valueColumn = type.getEquality().apply(inputColumn); - return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, valueColumn, - expression); + public String getName() { + return type.getFunctionName(); } + // TODO: implement with columns + + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final NamedFunctionInput input) { + // checkNoArguments(type.getFunctionName(), input); + // final NonLiteralPath inputPath = input.getInput(); + // checkUserInput(inputPath instanceof BooleanCollection, + // "Input to " + type + " function must be Boolean"); + // final Column inputColumn = inputPath.getValueColumn(); + // final String expression = expressionFromInput(input, type.getFunctionName(), input.getInput()); + // + // final Column valueColumn = type.getEquality().apply(inputColumn); + // return buildAggregateResult(inputPath.getDataset(), input.getContext(), inputPath, valueColumn, + // expression); + // } + /** * Represents a type of test that can be performed against a collection of Boolean values. */ diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java index cd7083c902..0a8f6d46a5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java @@ -18,11 +18,11 @@ package au.csiro.pathling.fhirpath.function; import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import static org.apache.spark.sql.functions.size; -import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.FunctionInput; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.collection.IntegerCollection; import java.util.Optional; import javax.annotation.Nonnull; @@ -34,22 +34,16 @@ * @author John Grimes * @see count */ +@Name("count") public class CountFunction implements NamedFunction { - @Nonnull - @Override - public String getName() { - return "count"; - } - @Nonnull @Override public Collection invoke(@Nonnull final FunctionInput input) { checkNoArguments(getName(), input); final Collection inputPath = input.getInput(); final Column valueColumn = size(inputPath.getColumn()); - final String expression = expressionFromInput(input, getName(), input.getInput()); - return IntegerCollection.build(valueColumn, expression, Optional.empty()); + return IntegerCollection.build(valueColumn, Optional.empty()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java index 2de42ccb68..d7f4688a74 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java @@ -18,11 +18,11 @@ package au.csiro.pathling.fhirpath.function; import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import au.csiro.pathling.fhirpath.FunctionInput; -import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.annotations.Name; import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.Collection; import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -33,27 +33,17 @@ * @author John Grimes * @see empty */ +@Name("empty") public class EmptyFunction implements NamedFunction { - private static final String NAME = "empty"; - - @Nonnull - @Override - public String getName() { - return NAME; - } - @Nonnull @Override public Collection invoke(@Nonnull final FunctionInput input) { checkNoArguments(getName(), input); - // We use the count function to determine whether there are zero items in the input collection. final Collection countResult = new CountFunction().invoke(input); final Column valueColumn = countResult.getColumn().equalTo(0); - final String expression = expressionFromInput(input, getName(), input.getInput()); - - return BooleanCollection.build(valueColumn, expression, Optional.empty()); + return BooleanCollection.build(valueColumn, Optional.empty()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java index 2c51299648..7b98cf112d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java @@ -17,13 +17,13 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.not; -import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.FunctionInput; +import au.csiro.pathling.fhirpath.annotations.Name; import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.Collection; import java.util.Collections; import java.util.Optional; import javax.annotation.Nonnull; @@ -36,23 +36,15 @@ * @author John Grimes * @see exists */ +@Name("exists") public class ExistsFunction implements NamedFunction { - private static final String NAME = "exists"; - - @Nonnull - @Override - public String getName() { - return NAME; - } - @Nonnull @Override public Collection invoke(@Nonnull final FunctionInput input) { final int numberOfArguments = input.getArguments().size(); checkUserInput(numberOfArguments <= 1, "exists function accepts at most one argument"); - final String expression = expressionFromInput(input, getName(), input.getInput()); final Column column; if (numberOfArguments == 0) { @@ -69,7 +61,7 @@ public Collection invoke(@Nonnull final FunctionInput input) { column = existsResult.getColumn(); } - return BooleanCollection.build(column, expression, Optional.empty()); + return BooleanCollection.build(column, Optional.empty()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java index dceaf5eab5..d57d8773aa 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java @@ -17,20 +17,8 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.utilities.Preconditions.checkPresent; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; - -import au.csiro.pathling.encoders.ExtensionSupport; -import au.csiro.pathling.fhirpath.Comparable.ComparisonOperation; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.FunctionInput; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.operator.ComparisonOperator; -import au.csiro.pathling.fhirpath.operator.PathTraversalOperator; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import java.util.Collections; -import java.util.List; -import javax.annotation.Nonnull; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; /** * A function that returns the extensions of the current element that match a given URL. @@ -38,39 +26,43 @@ * @author Piotr Szul * @see extension */ +@Name("extension") +@NotImplemented public class ExtensionFunction implements NamedFunction { - private static final String NAME = "extension"; - - @Nonnull - @Override - public String getName() { - return NAME; - } - - @Nonnull - @Override - public Collection invoke(@Nonnull final FunctionInput input) { - final String expression = NamedFunction.expressionFromInput(input, getName(), - input.getInput()); - checkUserInput(input.getArguments().size() == 1, - "extension function must have one argument: " + expression); - - final Collection inputPath = input.getInput(); - final Collection urlArgument = input.getArguments().get(0).apply(inputPath); - checkUserInput(urlArgument instanceof StringCollection, - "extension function must have argument of type String: " + expression); - final Collection extensionPath = checkPresent( - inputPath.traverse(ExtensionSupport.EXTENSION_ELEMENT_NAME())); - - final Collection extensionUrlPath = checkPresent(extensionPath.traverse("url")); - final Collection extensionUrCondition = new ComparisonOperator(ComparisonOperation.EQUALS) - .invoke(new FunctionInput(input.getContext(), extensionUrlPath, List.of(i -> urlArgument))); + // TODO: implement as columns - // Override the expression in the function input. - return new WhereFunction() - .invoke(new NamedFunctionInput(input.getContext(), extensionPath, - Collections.singletonList(extensionUrCondition))); - } + // private static final String NAME = "extension"; + // + // @Nonnull + // @Override + // public String getName() { + // return NAME; + // } + // + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final FunctionInput input) { + // final String expression = NamedFunction.expressionFromInput(input, getName(), + // input.getInput()); + // checkUserInput(input.getArguments().size() == 1, + // "extension function must have one argument: " + expression); + // + // final Collection inputPath = input.getInput(); + // final Collection urlArgument = input.getArguments().get(0).apply(inputPath); + // checkUserInput(urlArgument instanceof StringCollection, + // "extension function must have argument of type String: " + expression); + // final Collection extensionPath = checkPresent( + // inputPath.traverse(ExtensionSupport.EXTENSION_ELEMENT_NAME())); + // + // final Collection extensionUrlPath = checkPresent(extensionPath.traverse("url")); + // final Collection extensionUrCondition = new ComparisonOperator(ComparisonOperation.EQUALS) + // .invoke(new FunctionInput(input.getContext(), extensionUrlPath, List.of(i -> urlArgument))); + // + // // Override the expression in the function input. + // return new WhereFunction() + // .invoke(new NamedFunctionInput(input.getContext(), extensionPath, + // Collections.singletonList(extensionUrCondition))); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java index b8ff361612..c0ae61167c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java @@ -17,17 +17,8 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static org.apache.spark.sql.functions.first; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.FunctionInput; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; /** * This function allows the selection of only the first element of a collection. @@ -36,27 +27,23 @@ * @author Piotr Szul * @see first */ +@Name("first") +@NotImplemented public class FirstFunction implements NamedFunction { - private static final String NAME = "first"; - - @Nonnull - @Override - public String getName() { - return NAME; - } - - @Nonnull - @Override - public Collection invoke(@Nonnull final FunctionInput input) { - checkNoArguments(getName(), input); - - final NonLiteralPath inputPath = input.getInput(); - final Dataset dataset = inputPath.getOrderedDataset(nesting); - final String expression = expressionFromInput(input, NAME, input.getInput()); - final Column aggregateColumn = first(inputPath.getValueColumn(), true); - - return buildAggregateResult(dataset, input.getContext(), inputPath, aggregateColumn, - expression); - } + // TODO: implement as columns + + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final FunctionInput input) { + // checkNoArguments(getName(), input); + // + // final NonLiteralPath inputPath = input.getInput(); + // final Dataset dataset = inputPath.getOrderedDataset(nesting); + // final String expression = expressionFromInput(input, NAME, input.getInput()); + // final Column aggregateColumn = first(inputPath.getValueColumn(), true); + // + // return buildAggregateResult(dataset, input.getContext(), inputPath, aggregateColumn, + // expression); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java index 3c751acc3d..fe71aa1934 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java @@ -1,35 +1,30 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.QueryHelpers.createColumn; -import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; - -import au.csiro.pathling.QueryHelpers.DatasetWithColumn; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.ReferencePath; -import javax.annotation.Nonnull; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; +@Name("getId") +@NotImplemented public class GetIdFunction implements NamedFunction { - private static final String NAME = "getId"; - - @Nonnull - @Override - public Collection invoke(@Nonnull final NamedFunctionInput input) { - checkNoArguments(NAME, input); - checkUserInput(input.getInput() instanceof ReferencePath, "Input to getId must be a Reference"); - final ReferencePath referencePath = (ReferencePath) input.getInput(); - final DatasetWithColumn datasetWithColumn = createColumn(referencePath.getDataset(), - referencePath.getReferenceIdColumn()); - final String expression = expressionFromInput(input, NAME, input.getInput()); - - return PrimitivePath.build(expression, datasetWithColumn.getDataset(), - referencePath.getIdColumn(), datasetWithColumn.getColumn(), - referencePath.getOrderingColumn(), referencePath.isSingular(), - referencePath.getCurrentResource(), referencePath.getThisColumn(), FHIRDefinedType.STRING); - } + // TODO: impelement as columns + + // private static final String NAME = "getId"; + // + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final NamedFunctionInput input) { + // checkNoArguments(NAME, input); + // checkUserInput(input.getInput() instanceof ReferencePath, "Input to getId must be a Reference"); + // final ReferencePath referencePath = (ReferencePath) input.getInput(); + // final DatasetWithColumn datasetWithColumn = createColumn(referencePath.getDataset(), + // referencePath.getReferenceIdColumn()); + // final String expression = expressionFromInput(input, NAME, input.getInput()); + // + // return PrimitivePath.build(expression, datasetWithColumn.getDataset(), + // referencePath.getIdColumn(), datasetWithColumn.getColumn(), + // referencePath.getOrderingColumn(), referencePath.isSingular(), + // referencePath.getCurrentResource(), referencePath.getThisColumn(), FHIRDefinedType.STRING); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java index 0e74270114..8b0fed1845 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/IifFunction.java @@ -17,17 +17,8 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.QueryHelpers.createColumn; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static org.apache.spark.sql.functions.when; - -import au.csiro.pathling.QueryHelpers.DatasetWithColumn; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.collection.BooleanCollection; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; /** * This function takes three arguments, Returns the second argument if the first argument evaluates @@ -36,40 +27,44 @@ * @author John Grimes * @see iif */ +@Name("iif") +@NotImplemented public class IifFunction implements NamedFunction { - private static final String NAME = "iif"; - - @Nonnull - @Override - public Collection invoke(@Nonnull final NamedFunctionInput input) { - final NonLiteralPath inputPath = input.getInput(); - checkUserInput(input.getArguments().size() == 3, - "3 arguments must be supplied to iif function"); - final Collection condition = input.getArguments().get(0); - checkUserInput(condition instanceof BooleanCollection, - "Condition argument to iif must be Boolean: " + condition.getExpression()); - checkUserInput(condition.isSingular(), - "Condition argument to iif must be singular: " + condition.getExpression()); - final BooleanCollection conditionBoolean = (BooleanCollection) condition; - checkUserInput(conditionBoolean.getThisColumn().isPresent(), - "Condition argument to iif function must be navigable from collection item (use $this): " - + conditionBoolean.getExpression()); - - // Join the three datasets together and create a value column. - final Collection ifTrue = input.getArguments().get(1); - final Collection otherwise = input.getArguments().get(2); - final Column valueColumn = - when(conditionBoolean.getValueColumn().equalTo(true), ifTrue.getValueColumn()) - .otherwise(otherwise.getValueColumn()); - final DatasetWithColumn datasetWithColumn = createColumn(otherwise.getDataset(), valueColumn); + // TODO: implement as columns - // Build a new ElementPath based on the type of the literal `ifTrue` and `otherwise` arguments, - // and populate it with the dataset and calculated value column. - final String expression = expressionFromInput(input, NAME, input.getInput()); - return ifTrue.combineWith(otherwise, datasetWithColumn.getDataset(), expression, - inputPath.getIdColumn(), datasetWithColumn.getColumn(), inputPath.isSingular(), - inputPath.getThisColumn()); - } + // private static final String NAME = "iif"; + // + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final NamedFunctionInput input) { + // final NonLiteralPath inputPath = input.getInput(); + // checkUserInput(input.getArguments().size() == 3, + // "3 arguments must be supplied to iif function"); + // final Collection condition = input.getArguments().get(0); + // checkUserInput(condition instanceof BooleanCollection, + // "Condition argument to iif must be Boolean: " + condition.getExpression()); + // checkUserInput(condition.isSingular(), + // "Condition argument to iif must be singular: " + condition.getExpression()); + // final BooleanCollection conditionBoolean = (BooleanCollection) condition; + // checkUserInput(conditionBoolean.getThisColumn().isPresent(), + // "Condition argument to iif function must be navigable from collection item (use $this): " + // + conditionBoolean.getExpression()); + // + // // Join the three datasets together and create a value column. + // final Collection ifTrue = input.getArguments().get(1); + // final Collection otherwise = input.getArguments().get(2); + // final Column valueColumn = + // when(conditionBoolean.getValueColumn().equalTo(true), ifTrue.getValueColumn()) + // .otherwise(otherwise.getValueColumn()); + // final DatasetWithColumn datasetWithColumn = createColumn(otherwise.getDataset(), valueColumn); + // + // // Build a new ElementPath based on the type of the literal `ifTrue` and `otherwise` arguments, + // // and populate it with the dataset and calculated value column. + // final String expression = expressionFromInput(input, NAME, input.getInput()); + // return ifTrue.combineWith(otherwise, datasetWithColumn.getDataset(), expression, + // inputPath.getIdColumn(), datasetWithColumn.getColumn(), inputPath.isSingular(), + // inputPath.getThisColumn()); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java index b28c2bfb06..90f3453289 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java @@ -19,8 +19,11 @@ import static au.csiro.pathling.utilities.Preconditions.check; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; +import static java.util.Objects.nonNull; +import static java.util.Objects.requireNonNull; import au.csiro.pathling.fhirpath.FunctionInput; +import au.csiro.pathling.fhirpath.annotations.Name; import au.csiro.pathling.fhirpath.collection.Collection; import javax.annotation.Nonnull; @@ -35,7 +38,9 @@ public interface NamedFunction { * @return the name of this function */ @Nonnull - String getName(); + default String getName() { + return requireNonNull(this.getClass().getAnnotation(Name.class)).value(); + } /** * Invokes this function with the specified inputs. @@ -44,7 +49,9 @@ public interface NamedFunction { * @return a {@link Collection} object representing the resulting expression */ @Nonnull - O invoke(@Nonnull FunctionInput input); + default O invoke(@Nonnull FunctionInput input) { + throw new UnsupportedOperationException("Not implemented: " + getName()); + } /** * Check that no arguments have been passed within the supplied {@link FunctionInput}. diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NotFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NotFunction.java index 96338916e0..2e9d7a71c0 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NotFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NotFunction.java @@ -17,19 +17,8 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static org.apache.spark.sql.functions.not; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.collection.BooleanCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import java.util.Optional; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; /** * Returns {@code true} if the input collection evaluates to {@code false}, and {@code false} if it @@ -38,26 +27,29 @@ * @author John Grimes * @see not */ +@Name("not") +@NotImplemented public class NotFunction implements NamedFunction { - private static final String NAME = "not"; - - @Nonnull - @Override - public Collection invoke(@Nonnull final NamedFunctionInput input) { - checkNoArguments(NAME, input); - final NonLiteralPath inputPath = input.getInput(); - checkUserInput(inputPath instanceof BooleanCollection, - "Input to not function must be Boolean: " + inputPath.getExpression()); - final String expression = expressionFromInput(input, NAME, input.getInput()); - - // The not function is just a thin wrapper over the Spark not function. - final Column valueColumn = not(inputPath.getValueColumn()); - - return PrimitivePath - .build(expression, inputPath.getDataset(), inputPath.getIdColumn(), valueColumn, - inputPath.getOrderingColumn(), inputPath.isSingular(), Optional.empty(), - inputPath.getThisColumn(), FHIRDefinedType.BOOLEAN); - } + // TODO: implement as columns + // private static final String NAME = "not"; + // + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final NamedFunctionInput input) { + // checkNoArguments(NAME, input); + // final NonLiteralPath inputPath = input.getInput(); + // checkUserInput(inputPath instanceof BooleanCollection, + // "Input to not function must be Boolean: " + inputPath.getExpression()); + // final String expression = expressionFromInput(input, NAME, input.getInput()); + // + // // The not function is just a thin wrapper over the Spark not function. + // final Column valueColumn = not(inputPath.getValueColumn()); + // + // return PrimitivePath + // .build(expression, inputPath.getDataset(), inputPath.getIdColumn(), valueColumn, + // inputPath.getOrderingColumn(), inputPath.isSingular(), Optional.empty(), + // inputPath.getThisColumn(), FHIRDefinedType.BOOLEAN); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/OfTypeFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/OfTypeFunction.java index ba1c835570..5bb0db3690 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/OfTypeFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/OfTypeFunction.java @@ -17,20 +17,8 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.QueryHelpers.createColumn; -import static au.csiro.pathling.utilities.Preconditions.checkPresent; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; - -import au.csiro.pathling.QueryHelpers.DatasetWithColumn; -import au.csiro.pathling.fhirpath.FhirPathType; -import au.csiro.pathling.fhirpath.FunctionInput; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.MixedCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import java.util.Optional; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; /** * A function filters items in the input collection to only those that are of the given type. @@ -38,49 +26,45 @@ * @author John Grimes * @see ofType */ +@Name("ofType") +@NotImplemented public class OfTypeFunction implements NamedFunction { - private static final String NAME = "ofType"; - - @Nonnull - @Override - public String getName() { - return NAME; - } - - @Nonnull - @Override - public Collection invoke(@Nonnull final FunctionInput input) { - final Collection inputCollection = input.getInput(); - final boolean choiceElement = inputCollection instanceof MixedCollection; - checkUserInput(choiceElement, - "Input to ofType function must be a mixed collection"); - checkUserInput(input.getArguments().size() == 1, - "ofType function must have one argument"); - - final MixedCollection mixedCollection = (MixedCollection) inputCollection; - final Collection argument = input.getArguments().get(0).apply(inputCollection); - - // If the input is a choice element, check that the argument is a type specifier. - checkUserInput(argument.getType().isPresent() && FhirPathType.TYPE_SPECIFIER.equals( - argument.getType().get()), - "Argument to ofType function must be a type specifier"); - final Optional maybeDefinition = mixedCollection.resolveChoiceDefinition( - type); - checkUserInput(maybeDefinition.isPresent(), - "Choice element does not have a child element with name " + type + ": " - + mixedCollection.getExpression()); - final ElementDefinition definition = maybeDefinition.get(); - final Column valueColumn = checkPresent( - mixedCollection.resolveChoice(type, mixedCollection.getExpression())); - final DatasetWithColumn datasetWithColumn = createColumn(mixedCollection.getDataset(), - valueColumn); + // TODO: implements with columns - return PrimitivePath.build(expression, datasetWithColumn.getDataset(), - mixedCollection.getIdColumn(), - datasetWithColumn.getColumn(), mixedCollection.getOrderingColumn(), - mixedCollection.isSingular(), - mixedCollection.getCurrentResource(), mixedCollection.getThisColumn(), definition); - } + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final FunctionInput input) { + // final Collection inputCollection = input.getInput(); + // final boolean choiceElement = inputCollection instanceof MixedCollection; + // checkUserInput(choiceElement, + // "Input to ofType function must be a mixed collection"); + // checkUserInput(input.getArguments().size() == 1, + // "ofType function must have one argument"); + // + // final MixedCollection mixedCollection = (MixedCollection) inputCollection; + // final Collection argument = input.getArguments().get(0).apply(inputCollection); + // + // // If the input is a choice element, check that the argument is a type specifier. + // checkUserInput(argument.getType().isPresent() && FhirPathType.TYPE_SPECIFIER.equals( + // argument.getType().get()), + // "Argument to ofType function must be a type specifier"); + // final Optional maybeDefinition = mixedCollection.resolveChoiceDefinition( + // type); + // checkUserInput(maybeDefinition.isPresent(), + // "Choice element does not have a child element with name " + type + ": " + // + mixedCollection.getExpression()); + // final ElementDefinition definition = maybeDefinition.get(); + // final Column valueColumn = checkPresent( + // mixedCollection.resolveChoice(type, mixedCollection.getExpression())); + // final DatasetWithColumn datasetWithColumn = createColumn(mixedCollection.getDataset(), + // valueColumn); + // + // return PrimitivePath.build(expression, datasetWithColumn.getDataset(), + // mixedCollection.getIdColumn(), + // datasetWithColumn.getColumn(), mixedCollection.getOrderingColumn(), + // mixedCollection.isSingular(), + // mixedCollection.getCurrentResource(), mixedCollection.getThisColumn(), definition); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java index 4cbd33a593..af737b6d0c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ResolveFunction.java @@ -17,28 +17,8 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.utilities.Preconditions.check; -import static au.csiro.pathling.utilities.Preconditions.checkPresent; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; - -import au.csiro.pathling.QueryHelpers.JoinType; -import au.csiro.pathling.fhirpath.ReferenceNestingKey; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.ReferencePath; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.collection.UntypedResourcePath; -import au.csiro.pathling.fhirpath.definition.BasicElementDefinition; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.io.source.DataSource; -import ca.uhn.fhir.context.FhirContext; -import java.util.Set; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; /** * A function for resolving a Reference element in order to access the elements of the target @@ -48,77 +28,80 @@ * @author John Grimes * @see resolve */ +@Name("resolve") +@NotImplemented public class ResolveFunction implements NamedFunction { - private static final String NAME = "resolve"; - - public ResolveFunction() { - } - - @Nonnull - @Override - public Collection invoke(@Nonnull final NamedFunctionInput input) { - checkUserInput(input.getInput() instanceof ReferencePath, - "Input to " + NAME + " function must be a Reference: " + input.getInput().getExpression()); - checkNoArguments(NAME, input); - final ReferencePath inputPath = (ReferencePath) input.getInput(); - final DataSource dataSource = input.getContext().getDataSource(); - - // Get the allowed types for the input reference. This gives us the set of possible resource - // types that this reference could resolve to. - Set referenceTypes = inputPath.getResourceTypes(); - // If the type is Resource, all resource types need to be looked at. - if (referenceTypes.contains(ResourceType.RESOURCE)) { - referenceTypes = ResourceCollection.supportedResourceTypes(); - } - check(referenceTypes.size() > 0); - final boolean isPolymorphic = referenceTypes.size() > 1; - - final String expression = expressionFromInput(input, NAME, input.getInput()); - - if (isPolymorphic) { - final ReferencePath referencePath = (ReferencePath) input.getInput(); - return UntypedResourcePath.build(referencePath, expression); - } else { - final FhirContext fhirContext = input.getContext().getFhirContext(); - final ResourceType resourceType = (ResourceType) referenceTypes.toArray()[0]; - return resolveMonomorphicReference(inputPath, dataSource, fhirContext, resourceType, - expression, input.getContext()); - } - } - - @Nonnull - public static Collection resolveMonomorphicReference(@Nonnull final ReferencePath referencePath, - @Nonnull final DataSource dataSource, @Nonnull final FhirContext fhirContext, - @Nonnull final ResourceType resourceType, @Nonnull final String expression, - @Nonnull final ParserContext context) { - // If this is a monomorphic reference, we just need to retrieve the appropriate table and - // create a dataset with the full resources. - final ResourceCollection resourceCollection = ResourceCollection.build(fhirContext, dataSource, - resourceType, - expression, referencePath.isSingular()); - final BasicElementDefinition referenceDefinition = checkPresent(referencePath.getDefinition()); - final ReferenceNestingKey referenceNestingKey = new ReferenceNestingKey(referenceDefinition, - resourceCollection.getDefinition()); - - return context.getNesting() - .updateOrRetrieve(referenceNestingKey, expression, referencePath.getDataset(), - referencePath.isSingular(), referencePath.getThisColumn(), key -> { - // Join the resource dataset to the reference dataset. - final Column joinCondition = referencePath.getResourceEquality(resourceCollection); - final Dataset dataset = join(referencePath.getDataset(), - resourceCollection.getDataset(), - joinCondition, JoinType.LEFT_OUTER); - - final Column inputId = referencePath.getIdColumn(); - final ResourceCollection result = resourceCollection.copy(expression, dataset, - inputId, - resourceCollection.getValueColumn(), resourceCollection.getOrderingColumn(), - referencePath.isSingular(), referencePath.getThisColumn()); - result.setCurrentResource(resourceCollection); - - return result; - }); - } + // TODO: implement with columns + // private static final String NAME = "resolve"; + // + // public ResolveFunction() { + // } + // + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final NamedFunctionInput input) { + // checkUserInput(input.getInput() instanceof ReferencePath, + // "Input to " + NAME + " function must be a Reference: " + input.getInput().getExpression()); + // checkNoArguments(NAME, input); + // final ReferencePath inputPath = (ReferencePath) input.getInput(); + // final DataSource dataSource = input.getContext().getDataSource(); + // + // // Get the allowed types for the input reference. This gives us the set of possible resource + // // types that this reference could resolve to. + // Set referenceTypes = inputPath.getResourceTypes(); + // // If the type is Resource, all resource types need to be looked at. + // if (referenceTypes.contains(ResourceType.RESOURCE)) { + // referenceTypes = ResourceCollection.supportedResourceTypes(); + // } + // check(referenceTypes.size() > 0); + // final boolean isPolymorphic = referenceTypes.size() > 1; + // + // final String expression = expressionFromInput(input, NAME, input.getInput()); + // + // if (isPolymorphic) { + // final ReferencePath referencePath = (ReferencePath) input.getInput(); + // return UntypedResourcePath.build(referencePath, expression); + // } else { + // final FhirContext fhirContext = input.getContext().getFhirContext(); + // final ResourceType resourceType = (ResourceType) referenceTypes.toArray()[0]; + // return resolveMonomorphicReference(inputPath, dataSource, fhirContext, resourceType, + // expression, input.getContext()); + // } + // } + // + // @Nonnull + // public static Collection resolveMonomorphicReference(@Nonnull final ReferencePath referencePath, + // @Nonnull final DataSource dataSource, @Nonnull final FhirContext fhirContext, + // @Nonnull final ResourceType resourceType, @Nonnull final String expression, + // @Nonnull final ParserContext context) { + // // If this is a monomorphic reference, we just need to retrieve the appropriate table and + // // create a dataset with the full resources. + // final ResourceCollection resourceCollection = ResourceCollection.build(fhirContext, dataSource, + // resourceType, + // expression, referencePath.isSingular()); + // final BasicElementDefinition referenceDefinition = checkPresent(referencePath.getDefinition()); + // final ReferenceNestingKey referenceNestingKey = new ReferenceNestingKey(referenceDefinition, + // resourceCollection.getDefinition()); + // + // return context.getNesting() + // .updateOrRetrieve(referenceNestingKey, expression, referencePath.getDataset(), + // referencePath.isSingular(), referencePath.getThisColumn(), key -> { + // // Join the resource dataset to the reference dataset. + // final Column joinCondition = referencePath.getResourceEquality(resourceCollection); + // final Dataset dataset = join(referencePath.getDataset(), + // resourceCollection.getDataset(), + // joinCondition, JoinType.LEFT_OUTER); + // + // final Column inputId = referencePath.getIdColumn(); + // final ResourceCollection result = resourceCollection.copy(expression, dataset, + // inputId, + // resourceCollection.getValueColumn(), resourceCollection.getOrderingColumn(), + // referencePath.isSingular(), referencePath.getThisColumn()); + // result.setCurrentResource(resourceCollection); + // + // return result; + // }); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java index aac9f85611..2c7d92afab 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java @@ -17,84 +17,72 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.QueryHelpers.join; -import static au.csiro.pathling.utilities.Preconditions.checkPresent; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; - -import au.csiro.pathling.QueryHelpers.JoinType; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.ReferenceNestingKey; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.definition.BasicElementDefinition; -import au.csiro.pathling.fhirpath.collection.ReferencePath; -import java.util.Set; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; /** * A function for accessing elements of resources which refer to the input resource. The path to the * referring element is supplied as an argument. * * @author John Grimes - * @see reverseResolve + * @see reverseResolve */ +@Name("reverseResolve") +@NotImplemented public class ReverseResolveFunction implements NamedFunction { - private static final String NAME = "reverseResolve"; - - @Nonnull - @Override - public Collection invoke(@Nonnull final NamedFunctionInput input) { - checkUserInput(input.getInput() instanceof ResourceCollection, - "Input to " + NAME + " function must be a resource: " + input.getInput().getExpression()); - final ResourceCollection inputPath = (ResourceCollection) input.getInput(); - final String expression = NamedFunction.expressionFromInput(input, NAME, input.getInput()); - checkUserInput(input.getArguments().size() == 1, - "reverseResolve function accepts a single argument: " + expression); - final Collection argument = input.getArguments().get(0); - checkUserInput(argument instanceof ReferencePath, - "Argument to reverseResolve function must be a Reference: " + argument.getExpression()); - - // Check the argument for information about the current resource that it originated from - if it - // is not present, reverse reference resolution will not be possible. - final NonLiteralPath nonLiteralArgument = (NonLiteralPath) argument; - checkUserInput(nonLiteralArgument.getCurrentResource().isPresent(), - "Argument to reverseResolve must be an element that is navigable from a " - + "target resource type: " + expression); - final ResourceCollection currentResource = nonLiteralArgument.getCurrentResource().get(); - - final ReferencePath referencePath = (ReferencePath) argument; - final BasicElementDefinition referenceDefinition = checkPresent(referencePath.getDefinition()); - final ReferenceNestingKey referenceNestingKey = new ReferenceNestingKey(referenceDefinition, - currentResource.getDefinition()); - - return input.getContext().getNesting() - .updateOrRetrieve(referenceNestingKey, expression, inputPath.getDataset(), false, - inputPath.getThisColumn(), key -> { - // Check that the input type is one of the possible types specified by the argument. - final Set argumentTypes = ((ReferencePath) argument).getResourceTypes(); - final ResourceType inputType = inputPath.getResourceType(); - checkUserInput(argumentTypes.contains(inputType), - "Reference in argument to reverseResolve does not support input resource type: " - + expression); - - // Do a left outer join from the input to the argument dataset using the reference field in - // the argument. - final Column joinCondition = referencePath.getResourceEquality(inputPath); - final Dataset dataset = join(inputPath.getDataset(), referencePath.getDataset(), - joinCondition, JoinType.LEFT_OUTER); + // TODO: Implement with columns - final ResourceCollection result = currentResource.copy(expression, dataset, - inputPath.getIdColumn(), - currentResource.getValueColumn(), currentResource.getOrderingColumn(), false, - inputPath.getThisColumn()); - result.setCurrentResource(currentResource); - return result; - }); - } + // private static final String NAME = "reverseResolve"; + // + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final NamedFunctionInput input) { + // checkUserInput(input.getInput() instanceof ResourceCollection, + // "Input to " + NAME + " function must be a resource: " + input.getInput().getExpression()); + // final ResourceCollection inputPath = (ResourceCollection) input.getInput(); + // final String expression = NamedFunction.expressionFromInput(input, NAME, input.getInput()); + // checkUserInput(input.getArguments().size() == 1, + // "reverseResolve function accepts a single argument: " + expression); + // final Collection argument = input.getArguments().get(0); + // checkUserInput(argument instanceof ReferencePath, + // "Argument to reverseResolve function must be a Reference: " + argument.getExpression()); + // + // // Check the argument for information about the current resource that it originated from - if it + // // is not present, reverse reference resolution will not be possible. + // final NonLiteralPath nonLiteralArgument = (NonLiteralPath) argument; + // checkUserInput(nonLiteralArgument.getCurrentResource().isPresent(), + // "Argument to reverseResolve must be an element that is navigable from a " + // + "target resource type: " + expression); + // final ResourceCollection currentResource = nonLiteralArgument.getCurrentResource().get(); + // + // final ReferencePath referencePath = (ReferencePath) argument; + // final BasicElementDefinition referenceDefinition = checkPresent(referencePath.getDefinition()); + // final ReferenceNestingKey referenceNestingKey = new ReferenceNestingKey(referenceDefinition, + // currentResource.getDefinition()); + // + // return input.getContext().getNesting() + // .updateOrRetrieve(referenceNestingKey, expression, inputPath.getDataset(), false, + // inputPath.getThisColumn(), key -> { + // // Check that the input type is one of the possible types specified by the argument. + // final Set argumentTypes = ((ReferencePath) argument).getResourceTypes(); + // final ResourceType inputType = inputPath.getResourceType(); + // checkUserInput(argumentTypes.contains(inputType), + // "Reference in argument to reverseResolve does not support input resource type: " + // + expression); + // + // // Do a left outer join from the input to the argument dataset using the reference field in + // // the argument. + // final Column joinCondition = referencePath.getResourceEquality(inputPath); + // final Dataset dataset = join(inputPath.getDataset(), referencePath.getDataset(), + // joinCondition, JoinType.LEFT_OUTER); + // + // final ResourceCollection result = currentResource.copy(expression, dataset, + // inputPath.getIdColumn(), + // currentResource.getValueColumn(), currentResource.getOrderingColumn(), false, + // inputPath.getThisColumn()); + // result.setCurrentResource(currentResource); + // return result; + // }); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java index fa5aa5bbc8..c365883fe4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java @@ -17,17 +17,8 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.Numeric; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; /** * A function for computing the sum of a collection of numeric values. @@ -35,27 +26,31 @@ * @author John Grimes * @see sum */ -public class SumFunction extends AggregateFunction implements NamedFunction { - - private static final String NAME = "sum"; - - public SumFunction() { - } - - @Nonnull - @Override - public Collection invoke(@Nonnull final NamedFunctionInput input) { - checkNoArguments("sum", input); - checkUserInput(input.getInput() instanceof Numeric, - "Input to sum function must be numeric: " + input.getInput().getExpression()); - - final NonLiteralPath inputPath = input.getInput(); - final Dataset dataset = inputPath.getDataset(); - final String expression = expressionFromInput(input, NAME, input.getInput()); - final Column aggregateColumn = sum(inputPath.getValueColumn()); - - return buildAggregateResult(dataset, input.getContext(), inputPath, aggregateColumn, - expression); - } +@Name("sum") +@NotImplemented +public class SumFunction implements NamedFunction { + + // TODO: implement as columns + + // private static final String NAME = "sum"; + // + // public SumFunction() { + // } + // + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final NamedFunctionInput input) { + // checkNoArguments("sum", input); + // checkUserInput(input.getInput() instanceof Numeric, + // "Input to sum function must be numeric: " + input.getInput().getExpression()); + // + // final NonLiteralPath inputPath = input.getInput(); + // final Dataset dataset = inputPath.getDataset(); + // final String expression = expressionFromInput(input, NAME, input.getInput()); + // final Column aggregateColumn = sum(inputPath.getValueColumn()); + // + // return buildAggregateResult(dataset, input.getContext(), inputPath, aggregateColumn, + // expression); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ToStringFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ToStringFunction.java index fcf1cb901e..0a9860f4e1 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ToStringFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ToStringFunction.java @@ -1,39 +1,36 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.StringCoercible; -import javax.annotation.Nonnull; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; /** * A function that converts a path to a String, if the operation is supported. * * @author John Grimes */ +@Name("toString") +@NotImplemented public class ToStringFunction implements NamedFunction { - private static final String NAME = "toString"; - - @Nonnull - @Override - public Collection invoke(@Nonnull final NamedFunctionInput input) { - // Check that the function has no arguments. - checkNoArguments(NAME, input); - final Collection inputPath = input.getInput(); - - // Check that the input is coercible to a String. - checkUserInput(inputPath instanceof StringCoercible, - "Cannot coerce path to a String type: " + inputPath.getExpression()); - final StringCoercible stringCoercible = (StringCoercible) inputPath; - - // Create an expression for the new path. - final String expression = expressionFromInput(input, NAME, input.getInput()); - - // Coerce the input to a String. - return stringCoercible.asStringPath(expression, stringCoercible.getExpression()); - } + // TODO: implement as columns + + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final NamedFunctionInput input) { + // // Check that the function has no arguments. + // checkNoArguments(NAME, input); + // final Collection inputPath = input.getInput(); + // + // // Check that the input is coercible to a String. + // checkUserInput(inputPath instanceof StringCoercible, + // "Cannot coerce path to a String type: " + inputPath.getExpression()); + // final StringCoercible stringCoercible = (StringCoercible) inputPath; + // + // // Create an expression for the new path. + // final String expression = expressionFromInput(input, NAME, input.getInput()); + // + // // Coerce the input to a String. + // return stringCoercible.asStringPath(expression, stringCoercible.getExpression()); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/UntilFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/UntilFunction.java index 8a322ad050..3b1345661d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/UntilFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/UntilFunction.java @@ -17,26 +17,8 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.fhirpath.NonLiteralPath.findThisColumn; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static org.apache.spark.sql.functions.callUDF; - -import au.csiro.pathling.QueryHelpers.JoinType; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.literal.DateLiteralPath; -import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.collection.DateCollection; -import au.csiro.pathling.fhirpath.collection.DateTimeCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.sql.misc.TemporalDifferenceFunction; -import java.util.Optional; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; /** * This function computes the time interval (duration) between two paths representing dates or dates @@ -45,50 +27,54 @@ * @author John Grimes * @see until */ +@Name("until") +@NotImplemented public class UntilFunction implements NamedFunction { - private static final String NAME = "until"; - - @Nonnull - @Override - public Collection invoke(@Nonnull final NamedFunctionInput input) { - checkUserInput(input.getArguments().size() == 2, - "until function must have two arguments"); - final NonLiteralPath fromArgument = input.getInput(); - final Collection toArgument = input.getArguments().get(0); - final Collection calendarDurationArgument = input.getArguments().get(1); - - checkUserInput( - fromArgument instanceof DateTimeCollection || fromArgument instanceof DateCollection, - "until function must be invoked on a DateTime or Date"); - checkUserInput( - toArgument instanceof DateTimeCollection || toArgument instanceof DateTimeLiteralPath - || toArgument instanceof DateCollection || toArgument instanceof DateLiteralPath, - "until function must have a DateTime or Date as the first argument"); - - checkUserInput(fromArgument.isSingular(), - "until function must be invoked on a singular path"); - checkUserInput(toArgument.isSingular(), - "until function must have the singular path as its first argument"); - - checkUserInput(calendarDurationArgument instanceof StringLiteralPath, - "until function must have a String as the second argument"); - final String literalValue = ((StringLiteralPath) calendarDurationArgument).getValue() - .asStringValue(); - checkUserInput(TemporalDifferenceFunction.isValidCalendarDuration(literalValue), - "Invalid calendar duration: " + literalValue); - - final Dataset dataset = join(input.getContext(), fromArgument, toArgument, - JoinType.LEFT_OUTER); - final Column valueColumn = callUDF(TemporalDifferenceFunction.FUNCTION_NAME, - fromArgument.getValueColumn(), toArgument.getValueColumn(), - calendarDurationArgument.getValueColumn()); - final String expression = NamedFunction.expressionFromInput(input, NAME, input.getInput()); - final Optional thisColumn = findThisColumn(fromArgument, toArgument); + // TODO: implement as columns - return PrimitivePath.build(expression, dataset, fromArgument.getIdColumn(), valueColumn, - fromArgument.getOrderingColumn(), true, fromArgument.getCurrentResource(), thisColumn, - FHIRDefinedType.INTEGER); - } + // private static final String NAME = "until"; + // + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final NamedFunctionInput input) { + // checkUserInput(input.getArguments().size() == 2, + // "until function must have two arguments"); + // final NonLiteralPath fromArgument = input.getInput(); + // final Collection toArgument = input.getArguments().get(0); + // final Collection calendarDurationArgument = input.getArguments().get(1); + // + // checkUserInput( + // fromArgument instanceof DateTimeCollection || fromArgument instanceof DateCollection, + // "until function must be invoked on a DateTime or Date"); + // checkUserInput( + // toArgument instanceof DateTimeCollection || toArgument instanceof DateTimeLiteralPath + // || toArgument instanceof DateCollection || toArgument instanceof DateLiteralPath, + // "until function must have a DateTime or Date as the first argument"); + // + // checkUserInput(fromArgument.isSingular(), + // "until function must be invoked on a singular path"); + // checkUserInput(toArgument.isSingular(), + // "until function must have the singular path as its first argument"); + // + // checkUserInput(calendarDurationArgument instanceof StringLiteralPath, + // "until function must have a String as the second argument"); + // final String literalValue = ((StringLiteralPath) calendarDurationArgument).getValue() + // .asStringValue(); + // checkUserInput(TemporalDifferenceFunction.isValidCalendarDuration(literalValue), + // "Invalid calendar duration: " + literalValue); + // + // final Dataset dataset = join(input.getContext(), fromArgument, toArgument, + // JoinType.LEFT_OUTER); + // final Column valueColumn = callUDF(TemporalDifferenceFunction.FUNCTION_NAME, + // fromArgument.getValueColumn(), toArgument.getValueColumn(), + // calendarDurationArgument.getValueColumn()); + // final String expression = NamedFunction.expressionFromInput(input, NAME, input.getInput()); + // final Optional thisColumn = findThisColumn(fromArgument, toArgument); + // + // return PrimitivePath.build(expression, dataset, fromArgument.getIdColumn(), valueColumn, + // fromArgument.getOrderingColumn(), true, fromArgument.getCurrentResource(), thisColumn, + // FHIRDefinedType.INTEGER); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java index 7a748637dd..a6bd098340 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java @@ -24,6 +24,7 @@ import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.FunctionInput; +import au.csiro.pathling.fhirpath.annotations.Name; import au.csiro.pathling.fhirpath.collection.Collection; import java.util.Optional; import javax.annotation.Nonnull; @@ -38,16 +39,9 @@ * @author John Grimes * @see where */ +@Name("where") public class WhereFunction implements NamedFunction { - - private static final String NAME = "where"; - - @Nonnull - @Override - public String getName() { - return NAME; - } - + @Nonnull @Override public Collection invoke(@Nonnull final FunctionInput input) { @@ -59,7 +53,7 @@ public Collection invoke(@Nonnull final FunctionInput input) { final Column column = filter(previous.getColumn(), element -> { final Collection result = argument.apply( new Collection(element, previous.getType(), previous.getFhirType(), - Optional.empty())); + Optional.empty()), input.getContext()); final SparkSession spark = input.getContext().getSparkSession(); final FhirPathType type = checkPresent(result.getType()); checkUserInput(type.equals(FhirPathType.BOOLEAN) && result.isSingular(spark), diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/FunctionRegistry.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/FunctionRegistry.java index fed3b12ca9..df5dedb67f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/FunctionRegistry.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/FunctionRegistry.java @@ -8,16 +8,16 @@ * * @author John Grimes */ -public interface FunctionRegistry { +public interface FunctionRegistry { /** * Retrieves an instance of the function with the specified name in the specified context. * * @param name The name of the function - * @return An instance of a NamedFunction + * @return An instance of a T */ @Nonnull - NamedFunction getInstance(@Nonnull final String name) throws NoSuchFunctionException; + T getInstance(@Nonnull final String name) throws NoSuchFunctionException; class NoSuchFunctionException extends Exception { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/InMemoryFunctionRegistry.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/InMemoryFunctionRegistry.java index 7cc2c02223..dcc8be56ce 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/InMemoryFunctionRegistry.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/InMemoryFunctionRegistry.java @@ -21,7 +21,7 @@ public InMemoryFunctionRegistry(@Nonnull final Map functions) { @Nonnull @Override - public T getInstance(@Nonnull final String name) throws NoSuchFunctionException { + public T getInstance(@Nonnull final String name) throws NoSuchFunctionException { final T function = functions.get(name); if (function == null) { throw new NoSuchFunctionException("Unsupported function: " + name); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java index 066d7df59d..5d5e5d531d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java @@ -38,6 +38,9 @@ */ public class StaticFunctionRegistry extends InMemoryFunctionRegistry { + + private static final StaticFunctionRegistry INSTANCE = new StaticFunctionRegistry(); + public StaticFunctionRegistry() { super(new ImmutableMap.Builder() .put("count", new CountFunction()) @@ -69,4 +72,8 @@ public StaticFunctionRegistry() { .build()); } + public static StaticFunctionRegistry getInstance() { + return INSTANCE; + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunction.java index ce7129a392..89eb22dcaf 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunction.java @@ -17,27 +17,9 @@ package au.csiro.pathling.fhirpath.function.terminology; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static org.apache.spark.sql.functions.explode_outer; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.function.Arguments; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import java.util.List; -import java.util.Optional; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.StringType; /** * This function returns the designations of a Coding. @@ -45,52 +27,56 @@ * @author Piotr Szul * @see designation */ +@Name("designation") +@NotImplemented public class DesignationFunction implements NamedFunction { - private static final String NAME = "designation"; - - @Nonnull - @Override - public Collection invoke(@Nonnull final NamedFunctionInput input) { - - validateInput(input); - final PrimitivePath inputPath = (PrimitivePath) input.getInput(); - final String expression = expressionFromInput(input, NAME, input.getInput()); - - final Arguments arguments = Arguments.of(input); - @Nullable final Coding use = arguments.getOptionalValue(0, Coding.class).orElse(null); - @Nullable final String languageCode = arguments.getOptionalValue(1, StringType.class) - .map(StringType::getValue) - .orElse(null); - - final Dataset dataset = inputPath.getDataset(); - final Column designations = designation(inputPath.getValueColumn(), use, languageCode); - - // The result is an array of designations per each input element, which we now need to explode - // in the same way as for path traversal, creating unique element ids. - final Column explodedDesignations = explode_outer(designations); - return PrimitivePath.build(expression, dataset, inputPath.getIdColumn(), explodedDesignations, - Optional.empty(), inputPath.isSingular(), inputPath.getCurrentResource(), - inputPath.getThisColumn(), FHIRDefinedType.STRING); - } - - private void validateInput(@Nonnull final NamedFunctionInput input) { - final ParserContext context = input.getContext(); - checkUserInput(context.getTerminologyServiceFactory() - .isPresent(), "Attempt to call terminology function " + NAME - + " when terminology service has not been configured"); - - final Collection inputPath = input.getInput(); - checkUserInput(inputPath instanceof PrimitivePath - && (((PrimitivePath) inputPath).getFhirType().equals(FHIRDefinedType.CODING)), - "Input to " + NAME + " function must be Coding but is: " + inputPath.getExpression()); + // TODO: implement as columns - final List arguments = input.getArguments(); - checkUserInput(arguments.size() <= 2, - NAME + " function accepts two optional arguments"); - checkUserInput(arguments.isEmpty() || arguments.get(0) instanceof CodingLiteralPath, - String.format("Function `%s` expects `%s` as argument %s", NAME, "Coding literal", 1)); - checkUserInput(arguments.size() <= 1 || arguments.get(1) instanceof StringLiteralPath, - String.format("Function `%s` expects `%s` as argument %s", NAME, "String literal", 2)); - } + // private static final String NAME = "designation"; + // + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final NamedFunctionInput input) { + // + // validateInput(input); + // final PrimitivePath inputPath = (PrimitivePath) input.getInput(); + // final String expression = expressionFromInput(input, NAME, input.getInput()); + // + // final Arguments arguments = Arguments.of(input); + // @Nullable final Coding use = arguments.getOptionalValue(0, Coding.class).orElse(null); + // @Nullable final String languageCode = arguments.getOptionalValue(1, StringType.class) + // .map(StringType::getValue) + // .orElse(null); + // + // final Dataset dataset = inputPath.getDataset(); + // final Column designations = designation(inputPath.getValueColumn(), use, languageCode); + // + // // The result is an array of designations per each input element, which we now need to explode + // // in the same way as for path traversal, creating unique element ids. + // final Column explodedDesignations = explode_outer(designations); + // return PrimitivePath.build(expression, dataset, inputPath.getIdColumn(), explodedDesignations, + // Optional.empty(), inputPath.isSingular(), inputPath.getCurrentResource(), + // inputPath.getThisColumn(), FHIRDefinedType.STRING); + // } + // + // private void validateInput(@Nonnull final NamedFunctionInput input) { + // final ParserContext context = input.getContext(); + // checkUserInput(context.getTerminologyServiceFactory() + // .isPresent(), "Attempt to call terminology function " + NAME + // + " when terminology service has not been configured"); + // + // final Collection inputPath = input.getInput(); + // checkUserInput(inputPath instanceof PrimitivePath + // && (((PrimitivePath) inputPath).getFhirType().equals(FHIRDefinedType.CODING)), + // "Input to " + NAME + " function must be Coding but is: " + inputPath.getExpression()); + // + // final List arguments = input.getArguments(); + // checkUserInput(arguments.size() <= 2, + // NAME + " function accepts two optional arguments"); + // checkUserInput(arguments.isEmpty() || arguments.get(0) instanceof CodingLiteralPath, + // String.format("Function `%s` expects `%s` as argument %s", NAME, "Coding literal", 1)); + // checkUserInput(arguments.size() <= 1 || arguments.get(1) instanceof StringLiteralPath, + // String.format("Function `%s` expects `%s` as argument %s", NAME, "String literal", 2)); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunction.java index 8dc93f2ac3..a5ae21a3e3 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunction.java @@ -17,23 +17,9 @@ package au.csiro.pathling.fhirpath.function.terminology; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.sql.Terminology.display; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.function.Arguments; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import java.util.Optional; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.StringType; /** * This function returns the display name for given Coding @@ -41,47 +27,51 @@ * @author Piotr Szul * @see display */ +@Name("display") +@NotImplemented public class DisplayFunction implements NamedFunction { - private static final String NAME = "display"; - - @Nonnull - @Override - public Collection invoke(@Nonnull final NamedFunctionInput input) { - - validateInput(input); - final PrimitivePath inputPath = (PrimitivePath) input.getInput(); - final String expression = expressionFromInput(input, NAME, input.getInput()); - - final Arguments arguments = Arguments.of(input); - final Optional acceptLanguage = arguments.getOptionalValue(0, StringType.class); - - final Dataset dataset = inputPath.getDataset(); - final Column resultColumn = display(inputPath.getValueColumn(), - acceptLanguage.map(StringType::getValue).orElse(null)); - return PrimitivePath.build(expression, dataset, inputPath.getIdColumn(), resultColumn, - inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getCurrentResource(), - inputPath.getThisColumn(), FHIRDefinedType.STRING); - } - - private void validateInput(@Nonnull final NamedFunctionInput input) { - final ParserContext context = input.getContext(); - - checkUserInput(input.getArguments().size() <= 1, - NAME + " function accepts one optional language argument"); - if (input.getArguments().size() == 1) { - checkUserInput(input.getArguments().get(0) instanceof StringLiteralPath, - String.format("Function `%s` expects `%s` as argument %s", NAME, "String literal", 1)); - } - - checkUserInput(context.getTerminologyServiceFactory() - .isPresent(), "Attempt to call terminology function " + NAME - + " when terminology service has not been configured"); - - final Collection inputPath = input.getInput(); - checkUserInput(inputPath instanceof PrimitivePath - && (((PrimitivePath) inputPath).getFhirType().equals(FHIRDefinedType.CODING)), - "Input to display function must be Coding but is: " + inputPath.getExpression()); + // TODO: implement as columns - } + // private static final String NAME = "display"; + // + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final NamedFunctionInput input) { + // + // validateInput(input); + // final PrimitivePath inputPath = (PrimitivePath) input.getInput(); + // final String expression = expressionFromInput(input, NAME, input.getInput()); + // + // final Arguments arguments = Arguments.of(input); + // final Optional acceptLanguage = arguments.getOptionalValue(0, StringType.class); + // + // final Dataset dataset = inputPath.getDataset(); + // final Column resultColumn = display(inputPath.getValueColumn(), + // acceptLanguage.map(StringType::getValue).orElse(null)); + // return PrimitivePath.build(expression, dataset, inputPath.getIdColumn(), resultColumn, + // inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getCurrentResource(), + // inputPath.getThisColumn(), FHIRDefinedType.STRING); + // } + // + // private void validateInput(@Nonnull final NamedFunctionInput input) { + // final ParserContext context = input.getContext(); + // + // checkUserInput(input.getArguments().size() <= 1, + // NAME + " function accepts one optional language argument"); + // if (input.getArguments().size() == 1) { + // checkUserInput(input.getArguments().get(0) instanceof StringLiteralPath, + // String.format("Function `%s` expects `%s` as argument %s", NAME, "String literal", 1)); + // } + // + // checkUserInput(context.getTerminologyServiceFactory() + // .isPresent(), "Attempt to call terminology function " + NAME + // + " when terminology service has not been configured"); + // + // final Collection inputPath = input.getInput(); + // checkUserInput(inputPath instanceof PrimitivePath + // && (((PrimitivePath) inputPath).getFhirType().equals(FHIRDefinedType.CODING)), + // "Input to display function must be Coding but is: " + inputPath.getExpression()); + // + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunction.java index ee092a72ee..80c5677cd9 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunction.java @@ -17,20 +17,10 @@ package au.csiro.pathling.fhirpath.function.terminology; -import static au.csiro.pathling.fhirpath.TerminologyUtils.getCodingColumn; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.sql.Terminology.member_of; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; -import org.apache.spark.sql.Column; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** * A function that takes a set of Codings or CodeableConcepts as inputs and returns a set of boolean @@ -41,50 +31,54 @@ * @see memberOf */ @Slf4j +@Name("memberOf") +@NotImplemented public class MemberOfFunction implements NamedFunction { - private static final String NAME = "memberOf"; - - /** - * Returns a new instance of this function. - */ - public MemberOfFunction() { - } - - @Nonnull - @Override - public Collection invoke(@Nonnull final NamedFunctionInput input) { - validateInput(input); - final PrimitivePath inputPath = (PrimitivePath) input.getInput(); - final StringLiteralPath argument = (StringLiteralPath) input.getArguments().get(0); - final String valueSetUrl = argument.getValue().asStringValue(); - - final Column resultColumn = member_of(getCodingColumn(inputPath), valueSetUrl); - // Construct a new result expression. - final String expression = expressionFromInput(input, NAME, input.getInput()); - - return PrimitivePath - .build(expression, inputPath.getDataset(), inputPath.getIdColumn(), resultColumn, - inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getCurrentResource(), - inputPath.getThisColumn(), FHIRDefinedType.BOOLEAN); - } - - private void validateInput(@Nonnull final NamedFunctionInput input) { - final ParserContext context = input.getContext(); - checkUserInput(context.getTerminologyServiceFactory() - .isPresent(), "Attempt to call terminology function " + NAME - + " when terminology service has not been configured"); + // TODO: implement as columns - final Collection inputPath = input.getInput(); - checkUserInput(inputPath instanceof PrimitivePath - && (((PrimitivePath) inputPath).getFhirType().equals(FHIRDefinedType.CODING) - || ((PrimitivePath) inputPath).getFhirType().equals(FHIRDefinedType.CODEABLECONCEPT)), - "Input to memberOf function is of unsupported type: " + inputPath.getExpression()); - checkUserInput(input.getArguments().size() == 1, - "memberOf function accepts one argument of type String"); - final Collection argument = input.getArguments().get(0); - checkUserInput(argument instanceof StringLiteralPath, - "memberOf function accepts one argument of type String literal"); - } + // private static final String NAME = "memberOf"; + // + // /** + // * Returns a new instance of this function. + // */ + // public MemberOfFunction() { + // } + // + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final NamedFunctionInput input) { + // validateInput(input); + // final PrimitivePath inputPath = (PrimitivePath) input.getInput(); + // final StringLiteralPath argument = (StringLiteralPath) input.getArguments().get(0); + // final String valueSetUrl = argument.getValue().asStringValue(); + // + // final Column resultColumn = member_of(getCodingColumn(inputPath), valueSetUrl); + // // Construct a new result expression. + // final String expression = expressionFromInput(input, NAME, input.getInput()); + // + // return PrimitivePath + // .build(expression, inputPath.getDataset(), inputPath.getIdColumn(), resultColumn, + // inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getCurrentResource(), + // inputPath.getThisColumn(), FHIRDefinedType.BOOLEAN); + // } + // + // private void validateInput(@Nonnull final NamedFunctionInput input) { + // final ParserContext context = input.getContext(); + // checkUserInput(context.getTerminologyServiceFactory() + // .isPresent(), "Attempt to call terminology function " + NAME + // + " when terminology service has not been configured"); + // + // final Collection inputPath = input.getInput(); + // checkUserInput(inputPath instanceof PrimitivePath + // && (((PrimitivePath) inputPath).getFhirType().equals(FHIRDefinedType.CODING) + // || ((PrimitivePath) inputPath).getFhirType().equals(FHIRDefinedType.CODEABLECONCEPT)), + // "Input to memberOf function is of unsupported type: " + inputPath.getExpression()); + // checkUserInput(input.getArguments().size() == 1, + // "memberOf function accepts one argument of type String"); + // final Collection argument = input.getArguments().get(0); + // checkUserInput(argument instanceof StringLiteralPath, + // "memberOf function accepts one argument of type String literal"); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunction.java index 7b4335e680..bfd48c002f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunction.java @@ -17,27 +17,9 @@ package au.csiro.pathling.fhirpath.function.terminology; -import static au.csiro.pathling.QueryHelpers.explodeArray; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static au.csiro.pathling.utilities.Preconditions.wrapInUserInputError; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.function.Arguments; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.sql.udf.PropertyUdf; -import java.util.List; -import java.util.Optional; -import javax.annotation.Nonnull; -import org.apache.commons.lang3.tuple.MutablePair; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.StringType; /** * This function returns the value of a property for a Coding. @@ -45,70 +27,74 @@ * @author Piotr Szul * @see property */ +@Name("property") +@NotImplemented public class PropertyFunction implements NamedFunction { - private static final String NAME = "property"; - - @Nonnull - @Override - public Collection invoke(@Nonnull final NamedFunctionInput input) { - - validateInput(input); - final PrimitivePath inputPath = (PrimitivePath) input.getInput(); - final String expression = expressionFromInput(input, NAME, input.getInput()); - - final Arguments arguments = Arguments.of(input); - final String propertyCode = arguments.getValue(0, StringType.class).asStringValue(); - final String propertyTypeAsString = arguments.getValueOr(1, - new StringType(PropertyUdf.DEFAULT_PROPERTY_TYPE.toCode())).asStringValue(); - final Optional preferredLanguage = arguments.getOptionalValue(2, StringType.class); - - final FHIRDefinedType propertyType = wrapInUserInputError(FHIRDefinedType::fromCode).apply( - propertyTypeAsString); - - final Dataset dataset = inputPath.getDataset(); - final Column propertyValues = property_of(inputPath.getValueColumn(), propertyCode, - propertyType, preferredLanguage.map(StringType::getValue).orElse(null)); - - // // The result is an array of property values per each input element, which we now - // // need to explode in the same way as for path traversal, creating unique element ids. - final MutablePair valueAndOrderingColumns = new MutablePair<>(); - final Dataset resultDataset = explodeArray(dataset, propertyValues, - valueAndOrderingColumns); - - if (FHIRDefinedType.CODING.equals(propertyType)) { - // Special case for CODING properties: we use the Coding definition form - // the input path so that the results can be further traversed. - return inputPath.copy(expression, resultDataset, inputPath.getIdColumn(), - valueAndOrderingColumns.getLeft(), Optional.of(valueAndOrderingColumns.getRight()), - inputPath.isSingular(), inputPath.getThisColumn()); - } else { - return PrimitivePath.build(expression, resultDataset, inputPath.getIdColumn(), - valueAndOrderingColumns.getLeft(), Optional.of(valueAndOrderingColumns.getRight()), - inputPath.isSingular(), inputPath.getCurrentResource(), inputPath.getThisColumn(), - propertyType); - } - } - - private void validateInput(@Nonnull final NamedFunctionInput input) { - final ParserContext context = input.getContext(); - checkUserInput(context.getTerminologyServiceFactory() - .isPresent(), "Attempt to call terminology function " + NAME - + " when terminology service has not been configured"); - - final Collection inputPath = input.getInput(); - checkUserInput(inputPath instanceof PrimitivePath - && (((PrimitivePath) inputPath).getFhirType().equals(FHIRDefinedType.CODING)), - "Input to property function must be Coding but is: " + inputPath.getExpression()); + // TODO: implement with columns - final List arguments = input.getArguments(); - checkUserInput(arguments.size() >= 1 && arguments.size() <= 3, - NAME + " function accepts one required and one optional arguments"); - checkUserInput(arguments.get(0) instanceof StringLiteralPath, - String.format("Function `%s` expects `%s` as argument %s", NAME, "String literal", 1)); - checkUserInput(arguments.size() <= 1 || arguments.get(1) instanceof StringLiteralPath, - String.format("Function `%s` expects `%s` as argument %s", NAME, "String literal", 2)); - checkUserInput(arguments.size() <= 2 || arguments.get(2) instanceof StringLiteralPath, - String.format("Function `%s` expects `%s` as argument %s", NAME, "String literal", 3)); - } + // private static final String NAME = "property"; + // + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final NamedFunctionInput input) { + // + // validateInput(input); + // final PrimitivePath inputPath = (PrimitivePath) input.getInput(); + // final String expression = expressionFromInput(input, NAME, input.getInput()); + // + // final Arguments arguments = Arguments.of(input); + // final String propertyCode = arguments.getValue(0, StringType.class).asStringValue(); + // final String propertyTypeAsString = arguments.getValueOr(1, + // new StringType(PropertyUdf.DEFAULT_PROPERTY_TYPE.toCode())).asStringValue(); + // final Optional preferredLanguage = arguments.getOptionalValue(2, StringType.class); + // + // final FHIRDefinedType propertyType = wrapInUserInputError(FHIRDefinedType::fromCode).apply( + // propertyTypeAsString); + // + // final Dataset dataset = inputPath.getDataset(); + // final Column propertyValues = property_of(inputPath.getValueColumn(), propertyCode, + // propertyType, preferredLanguage.map(StringType::getValue).orElse(null)); + // + // // // The result is an array of property values per each input element, which we now + // // // need to explode in the same way as for path traversal, creating unique element ids. + // final MutablePair valueAndOrderingColumns = new MutablePair<>(); + // final Dataset resultDataset = explodeArray(dataset, propertyValues, + // valueAndOrderingColumns); + // + // if (FHIRDefinedType.CODING.equals(propertyType)) { + // // Special case for CODING properties: we use the Coding definition form + // // the input path so that the results can be further traversed. + // return inputPath.copy(expression, resultDataset, inputPath.getIdColumn(), + // valueAndOrderingColumns.getLeft(), Optional.of(valueAndOrderingColumns.getRight()), + // inputPath.isSingular(), inputPath.getThisColumn()); + // } else { + // return PrimitivePath.build(expression, resultDataset, inputPath.getIdColumn(), + // valueAndOrderingColumns.getLeft(), Optional.of(valueAndOrderingColumns.getRight()), + // inputPath.isSingular(), inputPath.getCurrentResource(), inputPath.getThisColumn(), + // propertyType); + // } + // } + // + // private void validateInput(@Nonnull final NamedFunctionInput input) { + // final ParserContext context = input.getContext(); + // checkUserInput(context.getTerminologyServiceFactory() + // .isPresent(), "Attempt to call terminology function " + NAME + // + " when terminology service has not been configured"); + // + // final Collection inputPath = input.getInput(); + // checkUserInput(inputPath instanceof PrimitivePath + // && (((PrimitivePath) inputPath).getFhirType().equals(FHIRDefinedType.CODING)), + // "Input to property function must be Coding but is: " + inputPath.getExpression()); + // + // final List arguments = input.getArguments(); + // checkUserInput(arguments.size() >= 1 && arguments.size() <= 3, + // NAME + " function accepts one required and one optional arguments"); + // checkUserInput(arguments.get(0) instanceof StringLiteralPath, + // String.format("Function `%s` expects `%s` as argument %s", NAME, "String literal", 1)); + // checkUserInput(arguments.size() <= 1 || arguments.get(1) instanceof StringLiteralPath, + // String.format("Function `%s` expects `%s` as argument %s", NAME, "String literal", 2)); + // checkUserInput(arguments.size() <= 2 || arguments.get(2) instanceof StringLiteralPath, + // String.format("Function `%s` expects `%s` as argument %s", NAME, "String literal", 3)); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunction.java index eef2720ec1..08cc4ced33 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunction.java @@ -17,29 +17,10 @@ package au.csiro.pathling.fhirpath.function.terminology; -import static au.csiro.pathling.fhirpath.TerminologyUtils.isCodeableConcept; -import static au.csiro.pathling.fhirpath.TerminologyUtils.isCodingOrCodeableConcept; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.sql.Terminology.subsumed_by; -import static au.csiro.pathling.sql.Terminology.subsumes; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static org.apache.spark.sql.functions.array; -import static org.apache.spark.sql.functions.col; -import static org.apache.spark.sql.functions.collect_set; -import static org.apache.spark.sql.functions.explode_outer; -import static org.apache.spark.sql.functions.when; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.parser.ParserContext; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** @@ -53,6 +34,7 @@ */ @Slf4j +@NotImplemented public class SubsumesFunction implements NamedFunction { /** @@ -93,115 +75,122 @@ public SubsumesFunction(final boolean inverted) { @Nonnull @Override - public Collection invoke(@Nonnull final NamedFunctionInput input) { - validateInput(input); - - final NonLiteralPath inputPath = input.getInput(); - final Dataset idAndCodingSet = createJoinedDataset(input.getInput(), - input.getArguments().get(0)); - final Column leftCodings = idAndCodingSet.col(COL_INPUT_CODINGS); - final Column rightCodings = idAndCodingSet.col(COL_ARG_CODINGS); - final Column resultColumn = inverted - ? subsumed_by(leftCodings, rightCodings) - : subsumes(leftCodings, rightCodings); - - // Construct a new result expression. - final String expression = expressionFromInput(input, functionName, input.getInput()); - return PrimitivePath.build(expression, idAndCodingSet, inputPath.getIdColumn(), resultColumn, - inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getCurrentResource(), - inputPath.getThisColumn(), FHIRDefinedType.BOOLEAN); - } - - /** - * Creates a dataset that preserves previous columns and adds three new ones: resource ID, input - * codings and argument codings. - * - * @see #toInputDataset(Collection) - * @see #toArgDataset(Collection) - */ - @Nonnull - private Dataset createJoinedDataset(@Nonnull final Collection inputResult, - @Nonnull final Collection argResult) { - - final Dataset inputCodingSet = toInputDataset(inputResult); - final Dataset argCodingSet = toArgDataset(argResult); - - return inputCodingSet.join(argCodingSet, col(COL_ID).equalTo(col(COL_ARG_ID)), "left_outer"); - } - - /** - * Creates a {@link Dataset} with a new column, which is an array of all the codings within the - * values. Each CodeableConcept is converted to an array that includes all its codings. Each - * Coding is converted to an array that only includes that coding. - *

- * Null Codings and CodeableConcepts are represented as null. - * - * @param result the {@link Collection} object to convert - * @return the resulting Dataset - */ - @Nonnull - private Dataset toInputDataset(@Nonnull final Collection result) { - final Column valueColumn = result.getValueColumn(); - - assert (isCodingOrCodeableConcept(result)); - - final Dataset expressionDataset = result.getDataset() - .withColumn(COL_ID, result.getIdColumn()); - final Column codingArrayCol = (isCodeableConcept(result)) - ? valueColumn.getField(FIELD_CODING) - : array(valueColumn); - - return expressionDataset.withColumn(COL_INPUT_CODINGS, - when(valueColumn.isNotNull(), codingArrayCol).otherwise(null)); - } - - /** - * Converts the argument {@link Collection} to a Dataset with the schema: STRING id, ARRAY(struct - * CODING) codingSet. - *

- * All codings are collected in a single `array` per resource. - *

- * Null Codings and CodeableConcepts are ignored. In the case where the resource does not have any - * non-null elements, an empty array will be created. - * - * @param result to convert - * @return input dataset - */ - @Nonnull - private Dataset toArgDataset(@Nonnull final Collection result) { - final Column valueColumn = result.getValueColumn(); - - assert (isCodingOrCodeableConcept(result)); - - final Dataset expressionDataset = result.getDataset(); - final Column codingCol = (isCodeableConcept(result)) - ? explode_outer(valueColumn.getField(FIELD_CODING)) - : valueColumn; - - final Dataset systemAndCodeDataset = expressionDataset.select( - result.getIdColumn().alias(COL_ARG_ID), codingCol.alias(COL_CODING)); - - return systemAndCodeDataset.groupBy(systemAndCodeDataset.col(COL_ARG_ID)) - .agg(collect_set(systemAndCodeDataset.col(COL_CODING)).alias(COL_ARG_CODINGS)); + public String getName() { + return functionName; } - private void validateInput(@Nonnull final NamedFunctionInput input) { - - final ParserContext context = input.getContext(); - checkUserInput(context.getTerminologyServiceFactory().isPresent(), - "Attempt to call terminology function " + functionName - + " when terminology service has not been configured"); - - checkUserInput(input.getArguments().size() == 1, - functionName + " function accepts one argument of type Coding or CodeableConcept"); - - validateExpressionType(input.getInput(), "input"); - validateExpressionType(input.getArguments().get(0), "argument"); - } - - private void validateExpressionType(@Nonnull final Collection inputPath, - @Nonnull final String pathRole) { - checkUserInput(isCodingOrCodeableConcept(inputPath), - functionName + " function accepts " + pathRole + " of type Coding or CodeableConcept"); - } + // + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final NamedFunctionInput input) { + // validateInput(input); + // + // final NonLiteralPath inputPath = input.getInput(); + // final Dataset idAndCodingSet = createJoinedDataset(input.getInput(), + // input.getArguments().get(0)); + // final Column leftCodings = idAndCodingSet.col(COL_INPUT_CODINGS); + // final Column rightCodings = idAndCodingSet.col(COL_ARG_CODINGS); + // final Column resultColumn = inverted + // ? subsumed_by(leftCodings, rightCodings) + // : subsumes(leftCodings, rightCodings); + // + // // Construct a new result expression. + // final String expression = expressionFromInput(input, functionName, input.getInput()); + // return PrimitivePath.build(expression, idAndCodingSet, inputPath.getIdColumn(), resultColumn, + // inputPath.getOrderingColumn(), inputPath.isSingular(), inputPath.getCurrentResource(), + // inputPath.getThisColumn(), FHIRDefinedType.BOOLEAN); + // } + // + // /** + // * Creates a dataset that preserves previous columns and adds three new ones: resource ID, input + // * codings and argument codings. + // * + // * @see #toInputDataset(Collection) + // * @see #toArgDataset(Collection) + // */ + // @Nonnull + // private Dataset createJoinedDataset(@Nonnull final Collection inputResult, + // @Nonnull final Collection argResult) { + // + // final Dataset inputCodingSet = toInputDataset(inputResult); + // final Dataset argCodingSet = toArgDataset(argResult); + // + // return inputCodingSet.join(argCodingSet, col(COL_ID).equalTo(col(COL_ARG_ID)), "left_outer"); + // } + // + // /** + // * Creates a {@link Dataset} with a new column, which is an array of all the codings within the + // * values. Each CodeableConcept is converted to an array that includes all its codings. Each + // * Coding is converted to an array that only includes that coding. + // *

+ // * Null Codings and CodeableConcepts are represented as null. + // * + // * @param result the {@link Collection} object to convert + // * @return the resulting Dataset + // */ + // @Nonnull + // private Dataset toInputDataset(@Nonnull final Collection result) { + // final Column valueColumn = result.getValueColumn(); + // + // assert (isCodingOrCodeableConcept(result)); + // + // final Dataset expressionDataset = result.getDataset() + // .withColumn(COL_ID, result.getIdColumn()); + // final Column codingArrayCol = (isCodeableConcept(result)) + // ? valueColumn.getField(FIELD_CODING) + // : array(valueColumn); + // + // return expressionDataset.withColumn(COL_INPUT_CODINGS, + // when(valueColumn.isNotNull(), codingArrayCol).otherwise(null)); + // } + // + // /** + // * Converts the argument {@link Collection} to a Dataset with the schema: STRING id, ARRAY(struct + // * CODING) codingSet. + // *

+ // * All codings are collected in a single `array` per resource. + // *

+ // * Null Codings and CodeableConcepts are ignored. In the case where the resource does not have any + // * non-null elements, an empty array will be created. + // * + // * @param result to convert + // * @return input dataset + // */ + // @Nonnull + // private Dataset toArgDataset(@Nonnull final Collection result) { + // final Column valueColumn = result.getValueColumn(); + // + // assert (isCodingOrCodeableConcept(result)); + // + // final Dataset expressionDataset = result.getDataset(); + // final Column codingCol = (isCodeableConcept(result)) + // ? explode_outer(valueColumn.getField(FIELD_CODING)) + // : valueColumn; + // + // final Dataset systemAndCodeDataset = expressionDataset.select( + // result.getIdColumn().alias(COL_ARG_ID), codingCol.alias(COL_CODING)); + // + // return systemAndCodeDataset.groupBy(systemAndCodeDataset.col(COL_ARG_ID)) + // .agg(collect_set(systemAndCodeDataset.col(COL_CODING)).alias(COL_ARG_CODINGS)); + // } + // + // private void validateInput(@Nonnull final NamedFunctionInput input) { + // + // final ParserContext context = input.getContext(); + // checkUserInput(context.getTerminologyServiceFactory().isPresent(), + // "Attempt to call terminology function " + functionName + // + " when terminology service has not been configured"); + // + // checkUserInput(input.getArguments().size() == 1, + // functionName + " function accepts one argument of type Coding or CodeableConcept"); + // + // validateExpressionType(input.getInput(), "input"); + // validateExpressionType(input.getArguments().get(0), "argument"); + // } + // + // private void validateExpressionType(@Nonnull final Collection inputPath, + // @Nonnull final String pathRole) { + // checkUserInput(isCodingOrCodeableConcept(inputPath), + // functionName + " function accepts " + pathRole + " of type Coding or CodeableConcept"); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java index 9c6eb4a50a..7d58d818ab 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunction.java @@ -17,33 +17,9 @@ package au.csiro.pathling.fhirpath.function.terminology; -import static au.csiro.pathling.QueryHelpers.explodeArray; -import static au.csiro.pathling.fhirpath.TerminologyUtils.getCodingColumn; -import static au.csiro.pathling.fhirpath.TerminologyUtils.isCodeableConcept; -import static au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput; -import static au.csiro.pathling.sql.Terminology.translate; -import static au.csiro.pathling.sql.TerminologySupport.parseCsvEquivalences; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.TerminologyUtils; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.function.Arguments; +import au.csiro.pathling.fhirpath.annotations.Name; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import java.util.List; -import java.util.Optional; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.apache.commons.lang3.tuple.MutablePair; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.BooleanType; -import org.hl7.fhir.r4.model.StringType; /** * A function that takes a set of Codings or CodeableConcepts as inputs and returns a set Codings @@ -61,6 +37,8 @@ * @author Piotr Szul * @see translate */ +@Name("translate") +@NotImplemented public class TranslateFunction implements NamedFunction { private static final String NAME = "translate"; @@ -69,69 +47,71 @@ public class TranslateFunction implements NamedFunction { private static final String DEFAULT_EQUIVALENCE = "equivalent"; - - @Nonnull - @Override - public Collection invoke(@Nonnull final NamedFunctionInput input) { - validateInput(input); - - final PrimitivePath inputPath = (PrimitivePath) input.getInput(); - - final Column idColumn = inputPath.getIdColumn(); - final boolean isCodeableConcept = isCodeableConcept(inputPath); - // The definition of the result is always the Coding element. - @SuppressWarnings("OptionalGetWithoutIsPresent") - final ElementDefinition resultDefinition = isCodeableConcept - ? inputPath.getChildElement("coding").get() - : inputPath.getDefinition().get(); - - final Arguments arguments = Arguments.of(input); - final String conceptMapUrl = arguments.getValue(0, StringType.class).asStringValue(); - final boolean reverse = arguments.getValueOr(1, new BooleanType(DEFAULT_REVERSE)) - .booleanValue(); - final String equivalencesCsv = arguments.getValueOr(2, new StringType(DEFAULT_EQUIVALENCE)) - .asStringValue(); - @Nullable final String target = Optional.ofNullable( - arguments.getNullableValue(3, StringType.class)) - .map(StringType::asStringValue) - .orElse(null); - final Dataset dataset = inputPath.getDataset(); - final Column translatedCodings = translate(getCodingColumn(inputPath), conceptMapUrl, reverse, - parseCsvEquivalences(equivalencesCsv), target); - - // // The result is an array of translations per each input element, which we now - // // need to explode in the same way as for path traversal, creating unique element ids. - final MutablePair valueAndOrderingColumns = new MutablePair<>(); - final Dataset resultDataset = explodeArray(dataset, translatedCodings, - valueAndOrderingColumns); - - final String expression = expressionFromInput(input, NAME, input.getInput()); - - return PrimitivePath.build(expression, resultDataset, idColumn, - valueAndOrderingColumns.getLeft(), - Optional.of(valueAndOrderingColumns.getRight()), false, inputPath.getCurrentResource(), - inputPath.getThisColumn(), resultDefinition); - } - - private void validateInput(@Nonnull final NamedFunctionInput input) { - final ParserContext context = input.getContext(); - checkUserInput(context.getTerminologyServiceFactory().isPresent(), - "Attempt to call terminology function " + NAME - + " when terminology service has not been configured"); - - final Collection inputPath = input.getInput(); - checkUserInput(TerminologyUtils.isCodingOrCodeableConcept(inputPath), - String.format("Input to %s function is of unsupported type: %s", NAME, - inputPath.getExpression())); - final List arguments = input.getArguments(); - checkUserInput(arguments.size() >= 1 && arguments.size() <= 3, - NAME + " function accepts one required and two optional arguments"); - checkUserInput(arguments.get(0) instanceof StringLiteralPath, - String.format("Function `%s` expects `%s` as argument %s", NAME, "String literal", 1)); - checkUserInput(arguments.size() <= 1 || arguments.get(1) instanceof BooleanLiteralPath, - String.format("Function `%s` expects `%s` as argument %s", NAME, "Boolean literal", 2)); - checkUserInput(arguments.size() <= 2 || arguments.get(2) instanceof StringLiteralPath, - String.format("Function `%s` expects `%s` as argument %s", NAME, "String literal", 3)); - } + // TODO: implement with columns + + // + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final NamedFunctionInput input) { + // validateInput(input); + // + // final PrimitivePath inputPath = (PrimitivePath) input.getInput(); + // + // final Column idColumn = inputPath.getIdColumn(); + // final boolean isCodeableConcept = isCodeableConcept(inputPath); + // // The definition of the result is always the Coding element. + // @SuppressWarnings("OptionalGetWithoutIsPresent") + // final ElementDefinition resultDefinition = isCodeableConcept + // ? inputPath.getChildElement("coding").get() + // : inputPath.getDefinition().get(); + // + // final Arguments arguments = Arguments.of(input); + // final String conceptMapUrl = arguments.getValue(0, StringType.class).asStringValue(); + // final boolean reverse = arguments.getValueOr(1, new BooleanType(DEFAULT_REVERSE)) + // .booleanValue(); + // final String equivalencesCsv = arguments.getValueOr(2, new StringType(DEFAULT_EQUIVALENCE)) + // .asStringValue(); + // @Nullable final String target = Optional.ofNullable( + // arguments.getNullableValue(3, StringType.class)) + // .map(StringType::asStringValue) + // .orElse(null); + // final Dataset dataset = inputPath.getDataset(); + // final Column translatedCodings = translate(getCodingColumn(inputPath), conceptMapUrl, reverse, + // parseCsvEquivalences(equivalencesCsv), target); + // + // // // The result is an array of translations per each input element, which we now + // // // need to explode in the same way as for path traversal, creating unique element ids. + // final MutablePair valueAndOrderingColumns = new MutablePair<>(); + // final Dataset resultDataset = explodeArray(dataset, translatedCodings, + // valueAndOrderingColumns); + // + // final String expression = expressionFromInput(input, NAME, input.getInput()); + // + // return PrimitivePath.build(expression, resultDataset, idColumn, + // valueAndOrderingColumns.getLeft(), + // Optional.of(valueAndOrderingColumns.getRight()), false, inputPath.getCurrentResource(), + // inputPath.getThisColumn(), resultDefinition); + // } + // + // private void validateInput(@Nonnull final NamedFunctionInput input) { + // final ParserContext context = input.getContext(); + // checkUserInput(context.getTerminologyServiceFactory().isPresent(), + // "Attempt to call terminology function " + NAME + // + " when terminology service has not been configured"); + // + // final Collection inputPath = input.getInput(); + // checkUserInput(TerminologyUtils.isCodingOrCodeableConcept(inputPath), + // String.format("Input to %s function is of unsupported type: %s", NAME, + // inputPath.getExpression())); + // final List arguments = input.getArguments(); + // checkUserInput(arguments.size() >= 1 && arguments.size() <= 3, + // NAME + " function accepts one required and two optional arguments"); + // checkUserInput(arguments.get(0) instanceof StringLiteralPath, + // String.format("Function `%s` expects `%s` as argument %s", NAME, "String literal", 1)); + // checkUserInput(arguments.size() <= 1 || arguments.get(1) instanceof BooleanLiteralPath, + // String.format("Function `%s` expects `%s` as argument %s", NAME, "Boolean literal", 2)); + // checkUserInput(arguments.size() <= 2 || arguments.get(2) instanceof StringLiteralPath, + // String.format("Function `%s` expects `%s` as argument %s", NAME, "String literal", 3)); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperator.java index 28ac09b5ec..f66d1575d6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperator.java @@ -34,6 +34,9 @@ public interface BinaryOperator { * @return A {@link Collection} object representing the resulting expression */ @Nonnull - Collection invoke(@Nonnull BinaryOperatorInput input); + default Collection invoke(@Nonnull BinaryOperatorInput input) { + // TODO: revert to abstract method once all operators are implemented + throw new UnsupportedOperationException("Not implemented"); + } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java index 595c393bef..82df36063b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/CombineOperator.java @@ -17,18 +17,10 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.QueryHelpers.createColumn; -import static org.apache.spark.sql.functions.array; -import static org.apache.spark.sql.functions.array_except; -import static org.apache.spark.sql.functions.explode_outer; -import static org.apache.spark.sql.functions.lit; +import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import java.util.Optional; import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; /** * Merges the left and right operands into a single collection. @@ -40,26 +32,18 @@ public class CombineOperator implements BinaryOperator { private static final String NAME = "combine"; - @Nonnull - @Override - public Collection invoke(@Nonnull final BinaryOperatorInput input) { - final String expression = BinaryOperator.buildExpression(input, NAME); - final Collection left = input.getLeft(); - final Collection right = input.getRight(); - - // Create an array of the two operands, excluding nulls, then explode it into rows. - final Column valueColumn = explode_outer( - array_except( - array(left.getValueColumn(), right.getValueColumn()), - array(lit(null)) - ) - ); - final DatasetWithColumn datasetWithColumn = createColumn(right.getDataset(), valueColumn); - final Optional thisColumn = left instanceof NonLiteralPath - ? ((NonLiteralPath) left).getThisColumn() - : Optional.empty(); - return left.combineWith(right, datasetWithColumn.getDataset(), expression, left.getIdColumn(), - datasetWithColumn.getColumn(), false, thisColumn); - } - + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final BinaryOperatorInput input) { + // final Collection left = input.getLeft(); + // final Collection right = input.getRight(); + // + // // TODO: check the condition + // checkUserInput(left.getFhirType().equals(right.getFhirType()), + // "Collection must have the same type"); + // // and also need to + // + // return null; + // // return functions.concat(left.getColumn(), right.getColumn()); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java index d3591abfb6..a4a23fd511 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java @@ -17,18 +17,9 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.fhirpath.operator.BinaryOperator.buildExpression; -import static au.csiro.pathling.fhirpath.operator.BinaryOperator.checkArgumentsAreComparable; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; - import au.csiro.pathling.fhirpath.Comparable.ComparisonOperation; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import java.util.Optional; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.SparkSession; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** * Provides the functionality of the family of comparison operators within FHIRPath, i.e. {@code =}, @@ -38,6 +29,7 @@ * @see Equality * @see Comparison */ +@NotImplemented public class ComparisonOperator implements BinaryOperator { @Nonnull @@ -50,26 +42,28 @@ public ComparisonOperator(@Nonnull final ComparisonOperation type) { this.type = type; } - @Nonnull - @Override - public Collection invoke(@Nonnull final BinaryOperatorInput input) { - final Collection left = input.getLeft(); - final Collection right = input.getRight(); - final SparkSession spark = input.getContext().getSparkSession(); - checkUserInput(left.isSingular(spark), - "Left operand must be singular: " + left.getExpression()); - - checkUserInput(right.isSingular(spark), - "Right operand must be singular: " + right.getExpression()); - checkArgumentsAreComparable(input, type.toString()); + // TODO: implement with columns - final String expression = buildExpression(input, type.toString()); - - final Column valueColumn = left.getComparison(type).apply(right); - - return PrimitivePath.build(expression, datasetWithColumn.getDataset(), idColumn, - datasetWithColumn.getColumn(), Optional.empty(), true, Optional.empty(), thisColumn, - FHIRDefinedType.BOOLEAN); - } + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final BinaryOperatorInput input) { + // final Collection left = input.getLeft(); + // final Collection right = input.getRight(); + // final SparkSession spark = input.getContext().getSparkSession(); + // checkUserInput(left.isSingular(spark), + // "Left operand must be singular: " + left.getExpression()); + // + // checkUserInput(right.isSingular(spark), + // "Right operand must be singular: " + right.getExpression()); + // checkArgumentsAreComparable(input, type.toString()); + // + // final String expression = buildExpression(input, type.toString()); + // + // final Column valueColumn = left.getComparison(type).apply(right); + // + // return PrimitivePath.build(expression, datasetWithColumn.getDataset(), idColumn, + // datasetWithColumn.getColumn(), Optional.empty(), true, Optional.empty(), thisColumn, + // FHIRDefinedType.BOOLEAN); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/DateArithmeticOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/DateArithmeticOperator.java index a192751106..a5481b355a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/DateArithmeticOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/DateArithmeticOperator.java @@ -17,15 +17,8 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.QueryHelpers.join; -import static au.csiro.pathling.fhirpath.operator.BinaryOperator.buildExpression; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; - -import au.csiro.pathling.fhirpath.CalendarDurationUtils; -import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.Numeric.MathOperation; -import au.csiro.pathling.fhirpath.Temporal; -import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import javax.annotation.Nonnull; /** @@ -35,6 +28,7 @@ * @author John Grimes * @see Math */ +@NotImplemented public class DateArithmeticOperator implements BinaryOperator { @Nonnull @@ -47,29 +41,31 @@ public DateArithmeticOperator(@Nonnull final MathOperation type) { this.type = type; } - @Nonnull - @Override - public Collection invoke(@Nonnull final BinaryOperatorInput input) { - final Collection left = input.getLeft(); - final Collection right = input.getRight(); - checkUserInput(left instanceof Temporal, - type + " operator does not support left operand: " + left.getExpression()); - - checkUserInput(right instanceof QuantityLiteralPath, - type + " operator does not support right operand: " + right.getExpression()); - final QuantityLiteralPath calendarDuration = (QuantityLiteralPath) right; - checkUserInput(CalendarDurationUtils.isCalendarDuration(calendarDuration.getValue()), - "Right operand of " + type + " operator must be a calendar duration"); - checkUserInput(left.isSingular(), - "Left operand to " + type + " operator must be singular: " + left.getExpression()); - checkUserInput(right.isSingular(), - "Right operand to " + type + " operator must be singular: " + right.getExpression()); + // TODO: implement with columns - final Temporal temporal = (Temporal) left; - final String expression = buildExpression(input, type.toString()); - - return temporal.getDateArithmeticOperation(type, right.getDataset(), expression) - .apply(calendarDuration); - } + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final BinaryOperatorInput input) { + // final Collection left = input.getLeft(); + // final Collection right = input.getRight(); + // checkUserInput(left instanceof Temporal, + // type + " operator does not support left operand: " + left.getExpression()); + // + // checkUserInput(right instanceof QuantityLiteralPath, + // type + " operator does not support right operand: " + right.getExpression()); + // final QuantityLiteralPath calendarDuration = (QuantityLiteralPath) right; + // checkUserInput(CalendarDurationUtils.isCalendarDuration(calendarDuration.getValue()), + // "Right operand of " + type + " operator must be a calendar duration"); + // checkUserInput(left.isSingular(), + // "Left operand to " + type + " operator must be singular: " + left.getExpression()); + // checkUserInput(right.isSingular(), + // "Right operand to " + type + " operator must be singular: " + right.getExpression()); + // + // final Temporal temporal = (Temporal) left; + // final String expression = buildExpression(input, type.toString()); + // + // return temporal.getDateArithmeticOperation(type, right.getDataset(), expression) + // .apply(calendarDuration); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MathOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MathOperator.java index 872166576d..9516dca8f0 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MathOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MathOperator.java @@ -17,16 +17,8 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.QueryHelpers.join; -import static au.csiro.pathling.fhirpath.operator.BinaryOperator.buildExpression; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; - -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.Numeric; import au.csiro.pathling.fhirpath.Numeric.MathOperation; -import au.csiro.pathling.fhirpath.Temporal; -import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import javax.annotation.Nonnull; /** @@ -36,6 +28,7 @@ * @author John Grimes * @see Math */ +@NotImplemented public class MathOperator implements BinaryOperator { @Nonnull @@ -48,39 +41,41 @@ public MathOperator(@Nonnull final MathOperation type) { this.type = type; } - @Nonnull - @Override - public Collection invoke(@Nonnull final BinaryOperatorInput input) { - final Collection left = input.getLeft(); - final Collection right = input.getRight(); - - // Check whether this needs to be delegated off to the DateArithmeticOperator. - if (left instanceof Temporal && right instanceof QuantityLiteralPath) { - return new DateArithmeticOperator(type).invoke(input); - } + // TODO: implement with columns - checkUserInput(left instanceof Numeric, - type + " operator does not support left operand: " + left.getExpression()); - checkUserInput(right instanceof Numeric, - type + " operator does not support right operand: " + right.getExpression()); - checkUserInput(left.isSingular(), - "Left operand to " + type + " operator must be singular: " + left.getExpression()); - checkUserInput(right.isSingular(), - "Right operand to " + type + " operator must be singular: " + right.getExpression()); - checkUserInput(left instanceof Comparable && right instanceof Comparable, - "Left and right operands are not comparable: " + left.getExpression() + " " - + type + " " + right.getExpression()); - final Comparable comparableLeft = (Comparable) left; - final Comparable comparableRight = (Comparable) right; - checkUserInput(comparableLeft.isComparableTo(comparableRight.getClass()), - "Left and right operands are not comparable: " + left.getExpression() + " " - + type + " " + right.getExpression()); - - final String expression = buildExpression(input, type.toString()); - - final Numeric leftNumeric = (Numeric) left; - final Numeric rightNumeric = (Numeric) right; - return leftNumeric.getMathOperation(type, expression, right.getDataset()).apply(rightNumeric); - } + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final BinaryOperatorInput input) { + // final Collection left = input.getLeft(); + // final Collection right = input.getRight(); + // + // // Check whether this needs to be delegated off to the DateArithmeticOperator. + // if (left instanceof Temporal && right instanceof QuantityLiteralPath) { + // return new DateArithmeticOperator(type).invoke(input); + // } + // + // checkUserInput(left instanceof Numeric, + // type + " operator does not support left operand: " + left.getExpression()); + // checkUserInput(right instanceof Numeric, + // type + " operator does not support right operand: " + right.getExpression()); + // checkUserInput(left.isSingular(), + // "Left operand to " + type + " operator must be singular: " + left.getExpression()); + // checkUserInput(right.isSingular(), + // "Right operand to " + type + " operator must be singular: " + right.getExpression()); + // checkUserInput(left instanceof Comparable && right instanceof Comparable, + // "Left and right operands are not comparable: " + left.getExpression() + " " + // + type + " " + right.getExpression()); + // final Comparable comparableLeft = (Comparable) left; + // final Comparable comparableRight = (Comparable) right; + // checkUserInput(comparableLeft.isComparableTo(comparableRight.getClass()), + // "Left and right operands are not comparable: " + left.getExpression() + " " + // + type + " " + right.getExpression()); + // + // final String expression = buildExpression(input, type.toString()); + // + // final Numeric leftNumeric = (Numeric) left; + // final Numeric rightNumeric = (Numeric) right; + // return leftNumeric.getMathOperation(type, expression, right.getDataset()).apply(rightNumeric); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java index 704bcaa198..1cb3bcebce 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java @@ -17,20 +17,8 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.fhirpath.operator.BinaryOperator.checkArgumentsAreComparable; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static org.apache.spark.sql.functions.lit; -import static org.apache.spark.sql.functions.max; -import static org.apache.spark.sql.functions.when; - -import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.Comparable.ComparisonOperation; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.function.AggregateFunction; -import java.util.Arrays; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** * An expression that tests whether a singular value is present within a collection. @@ -38,7 +26,8 @@ * @author John Grimes * @see Membership */ -public class MembershipOperator extends AggregateFunction implements BinaryOperator { +@NotImplemented +public class MembershipOperator /*extends AggregateFunction */ implements BinaryOperator { private final MembershipOperatorType type; @@ -49,45 +38,48 @@ public MembershipOperator(final MembershipOperatorType type) { this.type = type; } - @Nonnull - @Override - public Collection invoke(@Nonnull final BinaryOperatorInput input) { - final Collection left = input.getLeft(); - final Collection right = input.getRight(); - final Collection element = type.equals(MembershipOperatorType.IN) - ? left - : right; - final Collection collection = type.equals(MembershipOperatorType.IN) - ? right - : left; - - checkUserInput(element.isSingular(), - "Element operand used with " + type + " operator is not singular: " + element - .getExpression()); - checkArgumentsAreComparable(input, type.toString()); - final Column elementValue = element.getValueColumn(); - final Column collectionValue = collection.getValueColumn(); - - final String expression = left.getExpression() + " " + type + " " + right.getExpression(); - final Comparable leftComparable = (Comparable) left; - final Comparable rightComparable = (Comparable) right; - final Column equality = leftComparable.getComparison(ComparisonOperation.EQUALS) - .apply(rightComparable); - - // If the left-hand side of the operator (element) is empty, the result is empty. If the - // right-hand side (collection) is empty, the result is false. Otherwise, a Boolean is returned - // based on whether the element is present in the collection, using equality semantics. - final Column equalityWithNullChecks = when(elementValue.isNull(), lit(null)) - .when(collectionValue.isNull(), lit(false)) - .otherwise(equality); - - // In order to reduce the result to a single Boolean, we take the max of the boolean equality - // values. - final Column aggregateColumn = max(equalityWithNullChecks); - - return buildAggregateResult(right.getDataset(), input.getContext(), Arrays.asList(left, right), - aggregateColumn, expression, FHIRDefinedType.BOOLEAN); - } + + // TODO: implement with columns + + // @Nonnull + // @Override + // public Collection invoke(@Nonnull final BinaryOperatorInput input) { + // final Collection left = input.getLeft(); + // final Collection right = input.getRight(); + // final Collection element = type.equals(MembershipOperatorType.IN) + // ? left + // : right; + // final Collection collection = type.equals(MembershipOperatorType.IN) + // ? right + // : left; + // + // checkUserInput(element.isSingular(), + // "Element operand used with " + type + " operator is not singular: " + element + // .getExpression()); + // checkArgumentsAreComparable(input, type.toString()); + // final Column elementValue = element.getValueColumn(); + // final Column collectionValue = collection.getValueColumn(); + // + // final String expression = left.getExpression() + " " + type + " " + right.getExpression(); + // final Comparable leftComparable = (Comparable) left; + // final Comparable rightComparable = (Comparable) right; + // final Column equality = leftComparable.getComparison(ComparisonOperation.EQUALS) + // .apply(rightComparable); + // + // // If the left-hand side of the operator (element) is empty, the result is empty. If the + // // right-hand side (collection) is empty, the result is false. Otherwise, a Boolean is returned + // // based on whether the element is present in the collection, using equality semantics. + // final Column equalityWithNullChecks = when(elementValue.isNull(), lit(null)) + // .when(collectionValue.isNull(), lit(false)) + // .otherwise(equality); + // + // // In order to reduce the result to a single Boolean, we take the max of the boolean equality + // // values. + // final Column aggregateColumn = max(equalityWithNullChecks); + // + // return buildAggregateResult(right.getDataset(), input.getContext(), Arrays.asList(left, right), + // aggregateColumn, expression, FHIRDefinedType.BOOLEAN); + // } /** * Represents a type of membership operator. diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalInput.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalInput.java deleted file mode 100644 index 5eb75585e5..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/PathTraversalInput.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2023 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 au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.FunctionInput; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import javax.annotation.Nonnull; -import lombok.Getter; - -/** - * Represents the inputs to the path traversal operator in FHIRPath. - * - * @author John Grimes - */ -@Getter -public class PathTraversalInput extends FunctionInput { - - /** - * An expression representing the left operand. - */ - @Nonnull - private final Collection left; - - /** - * An expression representing the right operand. - */ - @Nonnull - private final String right; - - /** - * @param context the {@link ParserContext} that the operator should be executed within - * @param left the {@link Collection} representing the left operand - * @param right the FHIRPath expression on the right-hand side of the operator - */ - public PathTraversalInput(@Nonnull final ParserContext context, @Nonnull final Collection left, - @Nonnull final String right) { - super(context, input, arguments); - this.left = left; - this.right = right; - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index 4e361aa2bd..7f6d24fc1f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -79,8 +79,9 @@ public FhirPath visitMemberInvocation( @Nullable final MemberInvocationContext ctx) { @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - - return input -> { + + // TODO: refactor to an expression + return (input, c) -> { try { // Attempt path traversal. final Optional result = input.traverse(fhirPath); @@ -89,6 +90,7 @@ public FhirPath visitMemberInvocation( } catch (final InvalidUserInputError e) { try { + // TODO: what is this about? // If it is not a valid path traversal, see if it is a valid type specifier. final FHIRDefinedType fhirType = FHIRDefinedType.fromCode(fhirPath); return new Collection(functions.lit(null), Optional.of(FhirPathType.TYPE_SPECIFIER), @@ -113,26 +115,28 @@ public FhirPath visitMemberInvocation( @Nonnull public FhirPath visitFunctionInvocation( @Nullable final FunctionInvocationContext ctx) { + final String functionIdentifier = requireNonNull(ctx).function().identifier().getText(); + @Nullable final ParamListContext paramList = ctx.function().paramList(); - - return input -> { + final Visitor paramListVisitor = new Visitor(context); + final List> arguments = Optional.ofNullable(paramList) + .map(ParamListContext::expression) + .map(p -> p.stream() + .map(paramListVisitor::visit) + .collect(toList()) + ).orElse(new ArrayList<>()); + + + // TODO: refactor to an expression + return (input, c) -> { final NamedFunction function; try { - function = context.getFunctionRegistry().getInstance(functionIdentifier); + function = c.getFunctionRegistry().getInstance(functionIdentifier); } catch (final NoSuchFunctionException e) { throw new InvalidUserInputError(e.getMessage()); } - final Visitor paramListVisitor = new Visitor(context); - - final List> arguments = Optional.ofNullable(paramList) - .map(ParamListContext::expression) - .map(p -> p.stream() - .map(paramListVisitor::visit) - .collect(toList()) - ).orElse(new ArrayList<>()); - - final FunctionInput functionInput = new FunctionInput(context, input, arguments); + final FunctionInput functionInput = new FunctionInput(c, input, arguments); return function.invoke(functionInput); }; } @@ -140,7 +144,7 @@ public FhirPath visitFunctionInvocation( @Override @Nonnull public FhirPath visitThisInvocation(@Nullable final ThisInvocationContext ctx) { - return input -> context.getInputContext(); + return (input, c) -> input; } @Override diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java index b342e11351..9dc35634c3 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java @@ -21,18 +21,17 @@ import au.csiro.pathling.encoders.terminology.ucum.Ucum; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; 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.DateCollection; +import au.csiro.pathling.fhirpath.collection.DateTimeCollection; import au.csiro.pathling.fhirpath.collection.DecimalCollection; import au.csiro.pathling.fhirpath.collection.IntegerCollection; -import au.csiro.pathling.fhirpath.collection.NullPath; import au.csiro.pathling.fhirpath.collection.QuantityCollection; import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.collection.TimeCollection; -import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.BooleanLiteralContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.CodingLiteralContext; @@ -63,7 +62,7 @@ public FhirPath visitStringLiteral( @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return input -> StringCollection.fromLiteral(fhirPath); + return (input, c) -> StringCollection.fromLiteral(fhirPath); } @Override @@ -71,7 +70,7 @@ public FhirPath visitDateLiteral(@Nullable final DateLit @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return input -> { + return (input, c) -> { try { return DateCollection.fromLiteral(fhirPath); } catch (final ParseException e) { @@ -87,9 +86,9 @@ public FhirPath visitDateTimeLiteral( @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return input -> { + return (input, c) -> { try { - return DateTimeLiteralPath.fromString(fhirPath); + return DateTimeCollection.fromLiteral(fhirPath); } catch (final ParseException e) { throw new InvalidUserInputError("Unable to parse date/time format: " + fhirPath); } @@ -102,7 +101,7 @@ public FhirPath visitTimeLiteral(@Nullable final TimeLit @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return input -> TimeCollection.fromLiteral(fhirPath); + return (input, c) -> TimeCollection.fromLiteral(fhirPath); } @Override @@ -112,7 +111,7 @@ public FhirPath visitNumberLiteral( @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return input -> { + return (input, c) -> { // The FHIRPath grammar lumps these two types together, so we tease them apart by trying to // parse them. try { @@ -135,13 +134,13 @@ public FhirPath visitBooleanLiteral( @Nullable final String fhirPath = ctx.getText(); requireNonNull(fhirPath); - return input -> BooleanCollection.fromLiteral(fhirPath); + return (input, c) -> BooleanCollection.fromLiteral(fhirPath); } @Override @Nonnull public FhirPath visitNullLiteral(@Nullable final NullLiteralContext ctx) { - return input -> NullPath.build(); + return (input, c) -> Collection.nullCollection(); } @Override @@ -153,7 +152,7 @@ public FhirPath visitQuantityLiteral( requireNonNull(number); @Nullable final TerminalNode ucumUnit = ctx.quantity().unit().STRING(); - return input -> { + return (input, c) -> { if (ucumUnit == null) { // Create a calendar duration literal. final String fhirPath = String.format("%s %s", number, ctx.quantity().unit().getText()); @@ -177,7 +176,7 @@ public FhirPath visitCodingLiteral( @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return input -> { + return (input, c) -> { try { return CodingCollection.fromLiteral(fhirPath); } catch (final IllegalArgumentException e) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java index 1c7751136d..46d01b0666 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java @@ -46,6 +46,19 @@ public Parser(@Nonnull final ParserContext context) { this.context = context; } + /** + * Evaluates a FHIRPath expression. + * + * @param expression The String representation of the FHIRPath expression + * @return a new {@link Collection} object + */ + @Nonnull + public Collection evaluate(@Nonnull final String expression) { + final FhirPath fhirPath = parse(expression); + return fhirPath.apply(context.getInputContext(), context); + } + + /** * Parses a FHIRPath expression. * @@ -53,13 +66,13 @@ public Parser(@Nonnull final ParserContext context) { * @return a new {@link Collection} object */ @Nonnull - public FhirPath parse(@Nonnull final String expression) { + public FhirPath parse(@Nonnull final String expression) { final FhirPathParser parser = buildParser(expression); final Visitor visitor = new Visitor(context); return visitor.visit(parser.expression()); } - + @Nonnull static FhirPathParser buildParser(final @Nonnull String expression) { final FhirPathLexer lexer = new FhirPathLexer(CharStreams.fromString(expression)); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java index 51c3b2fde1..39b7e69f51 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java @@ -19,6 +19,7 @@ import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.function.NamedFunction; import au.csiro.pathling.fhirpath.function.registry.FunctionRegistry; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyService; @@ -34,6 +35,7 @@ * * @author John Grimes */ +// TODO: Split into two classes, one for parsing and one for evaluation @Getter public class ParserContext { @@ -83,7 +85,7 @@ public class ParserContext { * A registry of FHIRPath function implementations. */ @Nonnull - private final FunctionRegistry functionRegistry; + private final FunctionRegistry functionRegistry; /** * A factory for creating new {@link TerminologyService} objects, which is needed within blocks of diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java index baeaf77d34..3f31760698 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java @@ -26,6 +26,7 @@ import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.InvocationTermContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.LiteralTermContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ParenthesizedTermContext; +import au.csiro.pathling.fhirpath.path.ExtConsFhirPath; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -61,27 +62,17 @@ public FhirPath visitExternalConstantTerm( @Nullable final ExternalConstantTermContext ctx) { @Nullable final String term = requireNonNull(ctx).getText(); requireNonNull(term); - - return input -> { - if (term.equals("%context")) { - return context.getInputContext(); - } else if (term.equals("%resource") || term.equals("%rootResource")) { - return context.getResource(); - } else { - throw new IllegalArgumentException("Unknown constant: " + term); - } - }; + return new ExtConsFhirPath(term); } @Override @Nonnull public FhirPath visitParenthesizedTerm( @Nullable final ParenthesizedTermContext ctx) { - return input -> { - // Parentheses are ignored in the standalone term case. - return new Visitor(context).visit( - requireNonNull(ctx).expression()).apply(input); - }; + // TODO: maybe we do not need that and just use the subExpression directly? + // Parentheses are ignored in the standalone term case. + final FhirPath subExpression = new Visitor(context).visit( + requireNonNull(ctx).expression()); + return (input, c) -> subExpression.apply(input, c); } - } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java index 5be533be8f..ed4c876fff 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java @@ -43,6 +43,7 @@ import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.UnionExpressionContext; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import au.csiro.pathling.fhirpath.path.EvalOperatorPath; import org.antlr.v4.runtime.tree.ParseTree; /** @@ -68,7 +69,8 @@ class Visitor extends FhirPathBaseVisitor> { */ @Override @Nonnull - public FhirPath visitTermExpression(@Nullable final TermExpressionContext ctx) { + public FhirPath visitTermExpression( + @Nullable final TermExpressionContext ctx) { return requireNonNull(ctx).term().accept(new TermVisitor(context)); } @@ -82,27 +84,28 @@ public FhirPath visitTermExpression(@Nullable final Term @Nonnull public FhirPath visitInvocationExpression( @Nullable final InvocationExpressionContext ctx) { - return (input) -> { - final Collection expression = new Visitor(context).visit(requireNonNull(ctx).expression()) - .apply(input); - // The input context is passed through to the invocation visitor as the invoker. - return ctx.invocation().accept(new InvocationVisitor(context)).apply(expression); + + // TODO: Is this really OK (now I am a bit confused of what the context is vs input) + + final FhirPath invocationSubject = new Visitor(context).visit( + requireNonNull(ctx).expression()); + final FhirPath invocationVerb = ctx.invocation() + .accept(new InvocationVisitor(context)); + return (input, c) -> { + // TODO: perhpas we should also create the new cotext here (with different %context) + return invocationVerb.apply(invocationSubject.apply(input, c), c); }; } @Nonnull - private FhirPath visitBinaryOperator(@Nullable final ParseTree leftContext, + private FhirPath visitBinaryOperator( + @Nullable final ParseTree leftContext, @Nullable final ParseTree rightContext, @Nullable final String operatorName) { requireNonNull(operatorName); - return (input) -> { - final Collection left = new Visitor(context).visit(leftContext).apply(input); - final Collection right = new Visitor(context).visit(rightContext).apply(input); + return new EvalOperatorPath(new Visitor(context).visit(leftContext), + new Visitor(context).visit(rightContext), + BinaryOperatorType.fromSymbol(operatorName).getInstance()); - final BinaryOperator operator = BinaryOperatorType.fromSymbol(operatorName).getInstance(); - - final BinaryOperatorInput operatorInput = new BinaryOperatorInput(context, left, right); - return operator.invoke(operatorInput); - }; } @Override @@ -122,14 +125,16 @@ public FhirPath visitInequalityExpression( @Override @Nonnull - public FhirPath visitAndExpression(@Nullable final AndExpressionContext ctx) { + public FhirPath visitAndExpression( + @Nullable final AndExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); } @Override @Nonnull - public FhirPath visitOrExpression(@Nullable final OrExpressionContext ctx) { + public FhirPath visitOrExpression( + @Nullable final OrExpressionContext ctx) { return visitBinaryOperator(requireNonNull(ctx).expression(0), ctx.expression(1), ctx.children.get(1).toString()); } @@ -177,13 +182,15 @@ public FhirPath visitCombineExpression( @Override @Nonnull - public FhirPath visitIndexerExpression(final IndexerExpressionContext ctx) { + public FhirPath visitIndexerExpression( + final IndexerExpressionContext ctx) { throw new InvalidUserInputError("Indexer operation is not supported"); } @Override @Nonnull - public FhirPath visitPolarityExpression(final PolarityExpressionContext ctx) { + public FhirPath visitPolarityExpression( + final PolarityExpressionContext ctx) { throw new InvalidUserInputError("Polarity operator is not supported"); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/EvalOperatorPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/EvalOperatorPath.java new file mode 100644 index 0000000000..d61b8fae4d --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/EvalOperatorPath.java @@ -0,0 +1,39 @@ +/* + * Copyright 2023 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.path; + +import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.operator.BinaryOperator; +import au.csiro.pathling.fhirpath.operator.BinaryOperatorInput; +import au.csiro.pathling.fhirpath.parser.ParserContext; +import lombok.Value; +import javax.annotation.Nonnull; + +@Value +public class EvalOperatorPath implements FhirPath { + + FhirPath leftPath; + FhirPath rightPath; + BinaryOperator operator; + + @Override + public Collection apply(@Nonnull final Collection input, @Nonnull final ParserContext context) { + return operator.invoke(new BinaryOperatorInput(context, leftPath.apply(input, context), rightPath.apply(input, context))); + } +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/ExtConsFhirPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/ExtConsFhirPath.java new file mode 100644 index 0000000000..4254a8a4f4 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/ExtConsFhirPath.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 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.path; + +import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.parser.ParserContext; +import lombok.Value; +import javax.annotation.Nonnull; + +@Value +public class ExtConsFhirPath implements FhirPath { + + String name; + + @Override + public Collection apply(@Nonnull final Collection input, @Nonnull final ParserContext context) { + if (name.equals("%context")) { + return context.getInputContext(); + } else if (name.equals("%resource") || name.equals("%rootResource")) { + return context.getResource(); + } else { + throw new IllegalArgumentException("Unknown constant: " + name); + } + } +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ContextAndSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/ContextAndSelection.java deleted file mode 100644 index 3fe8ea5e2c..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/views/ContextAndSelection.java +++ /dev/null @@ -1,22 +0,0 @@ -package au.csiro.pathling.views; - -import au.csiro.pathling.fhirpath.collection.Collection; -import java.util.List; -import javax.annotation.Nonnull; -import lombok.Value; -import org.apache.spark.sql.Column; - -@Value -public class ContextAndSelection { - - @Nonnull - Collection context; - - @Nonnull - List selection; - - public void show() { - context.getDataset().select(selection.toArray(new Column[0])).show(false); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index 78e0a088ba..fcf251c27f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -3,8 +3,10 @@ import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.function.registry.StaticFunctionRegistry; import au.csiro.pathling.fhirpath.parser.ConstantReplacer; import au.csiro.pathling.fhirpath.parser.Parser; import au.csiro.pathling.fhirpath.parser.ParserContext; @@ -41,6 +43,7 @@ public class FhirViewExecutor { @Nonnull private final Optional terminologyServiceFactory; + public FhirViewExecutor(@Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataSource, @Nonnull final Optional terminologyServiceFactory) { @@ -60,10 +63,11 @@ public Dataset buildQuery(@Nonnull final FhirView view) { // Build a new expression parser, and parse all the column expressions within the query. final ResourceType resourceType = ResourceType.fromCode(view.getResource()); - final ResourceCollection inputContext = ResourceCollection.build(fhirContext, dataSource, resourceType, - view.getResource()); + final ResourceCollection inputContext = ResourceCollection.build(fhirContext, dataSource, + resourceType); final ParserContext parserContext = new ParserContext(inputContext, inputContext, fhirContext, - sparkSession, dataSource, functionRegistry, terminologyServiceFactory, constantReplacer); + sparkSession, dataSource, StaticFunctionRegistry.getInstance(), terminologyServiceFactory, + constantReplacer); final List select = parseSelect(view.getSelect(), parserContext, Collections.emptyList()); @@ -73,12 +77,15 @@ public Dataset buildQuery(@Nonnull final FhirView view) { .map(WhereClause::getExpression) .collect(toList()); final Optional filterCondition = where.stream() - .map(e -> parseExpression(e, parserContext)) - .map(Collection::getValueColumn) + .map(e -> evalExpression(e, parserContext)) + .map(Collection::getColumn) .reduce(Column::and); + + final Dataset subjectDataset = dataSource.read(resourceType); + return filterCondition - .map(inputContext.getDataset()::filter) - .orElse(inputContext.getDataset()) + .map(subjectDataset::filter) + .orElse(subjectDataset) .select(select.toArray(new Column[0])); } @@ -110,32 +117,34 @@ private List parseSelect(@Nonnull final List selectGroup, @Nonnull private List directSelection(@Nonnull final ParserContext context, @Nonnull final DirectSelection select, @Nonnull final List currentSelection) { - final Collection path = parseExpression(select.getExpression(), context); + final Collection path = evalExpression(select.getExpression(), context); final List newColumns = new ArrayList<>(currentSelection); - newColumns.add(path.getValueColumn().alias(select.getName())); + newColumns.add(path.getColumn().alias(select.getName())); return newColumns; } @Nonnull + @NotImplemented private List nestedSelection(final @Nonnull ParserContext context, @Nonnull final NestedSelectClause select, @Nonnull final List currentSelection, final boolean unnest) { - final Collection from = parseExpression(select.getExpression(), context); + final Collection from = evalExpression(select.getExpression(), context); final Collection nextInputContext = unnest - ? from.unnest() + // TODO: FIX FIRST + ? from // .unnest() : from; final ParserContext nextContext = context.withInputContext(nextInputContext); return parseSelect(select.getSelect(), nextContext, currentSelection); } @Nonnull - private Collection parseExpression(@Nonnull final String expression, + private Collection evalExpression(@Nonnull final String expression, @Nonnull final ParserContext context) { final Parser parser = new Parser(context); final String updatedExpression = context.getConstantReplacer() .map(replacer -> replacer.execute(expression)) .orElse(expression); - return parser.parse(updatedExpression).apply(context.getInputContext()); + return parser.evaluate(updatedExpression); } } diff --git a/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java b/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java index d725a2234f..0ae6f5d7af 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java +++ b/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java @@ -68,8 +68,8 @@ private QueryDispatcher buildDispatcher(final @Nonnull PathlingContext context, final ExtractQueryExecutor extractExecutor = new ExtractQueryExecutor(queryConfiguration, context.getFhirContext(), context.getSpark(), dataSource, Optional.of(context.getTerminologyServiceFactory())); - final FhirViewExecutor viewExecutor = new FhirViewExecutor(queryConfiguration, - context.getFhirContext(), context.getSpark(), dataSource, + final FhirViewExecutor viewExecutor = new FhirViewExecutor(context.getFhirContext(), + context.getSpark(), dataSource, Optional.of(context.getTerminologyServiceFactory())); // Build the dispatcher using the executors. diff --git a/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java b/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java index c1f3868e26..becfebe839 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java +++ b/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java @@ -17,13 +17,8 @@ package au.csiro.pathling.library.query; -import static au.csiro.pathling.utilities.Preconditions.requireNonBlank; - -import au.csiro.pathling.views.FhirView; -import java.util.ArrayList; -import java.util.List; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -33,55 +28,61 @@ * * @author John Grimes */ +@NotImplemented public class FhirViewBuilder extends QueryBuilder { - @Nonnull - private final List columns = new ArrayList<>(); - - @Nonnull - private final List variables = new ArrayList<>(); - public FhirViewBuilder(@Nonnull final QueryDispatcher dispatcher, @Nonnull final ResourceType subjectResource) { super(dispatcher, subjectResource); } - /** - * Adds an expression that represents a column to be included in the view. - * - * @param expression the column expression - * @param name the name of the column - * @return this query - */ - @Nonnull - public FhirViewBuilder column(@Nullable final String expression, @Nullable final String name) { - columns.add( - new NamedExpression(requireNonBlank(expression, "Column expression cannot be blank"), - requireNonBlank(name, "Column name cannot be blank"))); - return this; - } - - /** - * Adds an expression that represents a variable to be defined within the view. - * - * @param expression the variable expression - * @param name the name of the variable - * @return this query - */ - @Nonnull - public FhirViewBuilder variable(@Nullable final String expression, @Nullable final String name, - @Nullable final String whenMany) { - variables.add( - new VariableExpression(requireNonBlank(expression, "Variable expression cannot be blank"), - requireNonBlank(name, "Variable name cannot be blank"), WhenMany.fromCode(whenMany))); - return this; - } + // @Nonnull + // private final List columns = new ArrayList<>(); + // + // @Nonnull + // private final List variables = new ArrayList<>(); + // + // public FhirViewBuilder(@Nonnull final QueryDispatcher dispatcher, + // @Nonnull final ResourceType subjectResource) { + // super(dispatcher, subjectResource); + // } + // + // /** + // * Adds an expression that represents a column to be included in the view. + // * + // * @param expression the column expression + // * @param name the name of the column + // * @return this query + // */ + // @Nonnull + // public FhirViewBuilder column(@Nullable final String expression, @Nullable final String name) { + // columns.add( + // new NamedExpression(requireNonBlank(expression, "Column expression cannot be blank"), + // requireNonBlank(name, "Column name cannot be blank"))); + // return this; + // } + // + // /** + // * Adds an expression that represents a variable to be defined within the view. + // * + // * @param expression the variable expression + // * @param name the name of the variable + // * @return this query + // */ + // @Nonnull + // public FhirViewBuilder variable(@Nullable final String expression, @Nullable final String name, + // @Nullable final String whenMany) { + // variables.add( + // new VariableExpression(requireNonBlank(expression, "Variable expression cannot be blank"), + // requireNonBlank(name, "Variable name cannot be blank"), WhenMany.fromCode(whenMany))); + // return this; + // } @Nonnull @Override public Dataset execute() { - final FhirView view = new FhirView(subjectResource, columns, variables, filters); - return dispatcher.dispatch(view); + throw new UnsupportedOperationException("Not implemented yet"); + // final FhirView view = new FhirView(subjectResource, columns, variables, filters); + // return dispatcher.dispatch(view); } - } From 4e98e908434802b2c9aa376654152bd73ac39693 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Fri, 25 Aug 2023 15:58:25 +1000 Subject: [PATCH 61/95] Commenting out unimplemented code and marking it with @NotImplemented to achieve 'test' sources compilation in 'fhirpath' module. --- .../fhirpath/CanBeCombinedWithTest.java | 346 ++-- .../pathling/fhirpath/NonLiteralPathTest.java | 4 + .../pathling/fhirpath/OrderableTest.java | 4 + .../function/BooleansTestFunctionTest.java | 335 ++-- .../fhirpath/function/CountFunctionTest.java | 329 ++-- .../fhirpath/function/EmptyFunctionTest.java | 188 +- .../fhirpath/function/ExistsFunctionTest.java | 394 ++--- .../function/ExtensionFunctionTest.java | 432 +++-- .../fhirpath/function/FirstFunctionTest.java | 476 +++--- .../fhirpath/function/IifFunctionTest.java | 1058 ++++++------ .../fhirpath/function/NotFunctionTest.java | 213 ++- .../fhirpath/function/OfTypeFunctionTest.java | 329 ++-- .../function/ResolveFunctionTest.java | 548 +++--- .../function/ReverseResolveFunctionTest.java | 451 +++-- .../fhirpath/function/SumFunctionTest.java | 253 ++- .../fhirpath/function/UntilFunctionTest.java | 688 ++++---- .../fhirpath/function/WhereFunctionTest.java | 638 ++++--- .../terminology/DesignationFunctionTest.java | 564 +++--- .../terminology/DisplayFunctionTest.java | 420 +++-- .../terminology/MemberOfFunctionTest.java | 719 ++++---- .../terminology/PropertyFunctionTest.java | 681 ++++---- .../terminology/SubsumesFunctionTest.java | 1096 ++++++------ .../terminology/TranslateFunctionTest.java | 855 +++++----- .../literal/CodingLiteralPathTest.java | 274 ++- .../operator/BooleanOperatorTest.java | 372 ++-- .../BooleanOperatorValidationTest.java | 177 +- .../operator/CombineOperatorTest.java | 429 +++-- .../operator/ComparisonOperatorDateTest.java | 408 +++-- .../ComparisonOperatorDateTimeTest.java | 421 +++-- .../ComparisonOperatorInstantTest.java | 310 ++-- .../ComparisonOperatorQuantityTest.java | 451 +++-- .../operator/ComparisonOperatorTest.java | 982 ++++++----- .../operator/ComparisonOperatorTimeTest.java | 407 +++-- .../ComparisonOperatorValidationTest.java | 105 +- .../fhirpath/operator/DateArithmeticTest.java | 1506 ++++++++--------- .../operator/EqualityOperatorCodingTest.java | 414 +++-- .../EqualityOperatorQuantityTest.java | 1038 ++++++------ .../operator/MathOperatorQuantityTest.java | 395 ++--- .../fhirpath/operator/MathOperatorTest.java | 575 +++---- .../operator/MathOperatorValidationTest.java | 229 ++- .../operator/MembershipOperatorTest.java | 498 +++--- .../operator/PathTraversalOperatorTest.java | 724 ++++---- .../QuantityOperatorsPrecisionTest.java | 380 ++--- .../pathling/test/assertions/Assertions.java | 9 +- .../assertions/BaseFhirPathAssertion.java | 162 +- .../test/assertions/ElementPathAssertion.java | 50 +- .../test/assertions/LiteralPathAssertion.java | 57 +- .../test/builders/ElementPathBuilder.java | 30 +- .../test/builders/ParserContextBuilder.java | 37 +- .../test/builders/ResourcePathBuilder.java | 80 +- .../builders/UntypedResourcePathBuilder.java | 32 +- .../pathling/test/helpers/FhirHelpers.java | 2 +- .../pathling/test/helpers/SparkHelpers.java | 6 +- 53 files changed, 10241 insertions(+), 11340 deletions(-) diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/CanBeCombinedWithTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/CanBeCombinedWithTest.java index b272279bba..f7b86f0195 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/CanBeCombinedWithTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/CanBeCombinedWithTest.java @@ -17,204 +17,172 @@ package au.csiro.pathling.fhirpath; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import au.csiro.pathling.fhirpath.collection.CodingCollection; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.DateCollection; -import au.csiro.pathling.fhirpath.collection.DecimalCollection; -import au.csiro.pathling.fhirpath.collection.NullPath; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.ReferencePath; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.collection.TimeCollection; -import au.csiro.pathling.fhirpath.collection.UntypedResourcePath; -import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; -import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; -import au.csiro.pathling.fhirpath.literal.DateLiteralPath; -import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; -import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.literal.TimeLiteralPath; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.ElementPathBuilder; -import au.csiro.pathling.test.builders.ResourcePathBuilder; -import java.text.ParseException; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import lombok.EqualsAndHashCode; -import lombok.Value; -import org.apache.spark.sql.SparkSession; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest @TestInstance(Lifecycle.PER_CLASS) +@NotImplemented class CanBeCombinedWithTest { - final ResourceCollection resourceCollection; - final UntypedResourcePath untypedResourcePath; - - final PrimitivePath booleanPath; - final PrimitivePath codingPath; - final PrimitivePath datePath; - final PrimitivePath dateTimePath; - final PrimitivePath decimalPath; - final PrimitivePath integerPath; - final ReferencePath referencePath; - final PrimitivePath stringPath; - final PrimitivePath timePath; - - final BooleanLiteralPath booleanLiteralPath; - final CodingLiteralPath codingLiteralPath; - final DateLiteralPath dateLiteralPath; - final DateTimeLiteralPath dateTimeLiteralPath; - final DecimalCollection decimalLiteralPath; - final IntegerLiteralPath integerLiteralPath; - final NullPath nullPath; - final StringLiteralPath stringLiteralPath; - final TimeLiteralPath timeLiteralPath; - - final Set paths; - - @Autowired - CanBeCombinedWithTest(@Nonnull final SparkSession spark) throws ParseException { - resourceCollection = new ResourcePathBuilder(spark) - .build(); - - booleanPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .build(); - codingPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .build(); - datePath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DATE) - .build(); - dateTimePath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DATETIME) - .build(); - decimalPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DECIMAL) - .build(); - integerPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .build(); - referencePath = (ReferencePath) new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.REFERENCE) - .build(); - untypedResourcePath = UntypedResourcePath.build(referencePath, referencePath.getExpression()); - stringPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .build(); - timePath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.TIME) - .build(); - - booleanLiteralPath = BooleanLiteralPath.fromString("true", resourceCollection); - codingLiteralPath = CodingCollection - .fromLiteral("http://snomed.info/sct|27699000", resourceCollection); - dateLiteralPath = DateCollection.fromLiteral("@1983-06-21", resourceCollection); - dateTimeLiteralPath = DateTimeLiteralPath - .fromString("@2015-02-08T13:28:17-05:00", resourceCollection); - decimalLiteralPath = DecimalCollection.fromLiteral("9.5", resourceCollection); - integerLiteralPath = IntegerLiteralPath.fromString("14", resourceCollection); - nullPath = NullPath.build(resourceCollection); - stringLiteralPath = StringCollection.fromLiteral("'foo'", resourceCollection); - timeLiteralPath = TimeCollection.fromLiteral("T12:30", resourceCollection); - - paths = new HashSet<>(Arrays.asList( - resourceCollection, - untypedResourcePath, - booleanPath, - codingPath, - datePath, - dateTimePath, - decimalPath, - integerPath, - referencePath, - stringPath, - timePath, - booleanLiteralPath, - codingLiteralPath, - dateLiteralPath, - dateTimeLiteralPath, - decimalLiteralPath, - integerLiteralPath, - nullPath, - stringLiteralPath, - timeLiteralPath - )); - } - - @Value - @EqualsAndHashCode - static class TestParameters { - - @Nonnull - Collection source; - - @Nonnull - Collection target; - - boolean expectation; - - @Override - public String toString() { - return source.getClass().getSimpleName() + ", " + target.getClass().getSimpleName() + ": " - + expectation; - } - - } - - Stream parameters() { - return Stream.of( - buildParameters(resourceCollection, Collections.singletonList(resourceCollection)), - buildParameters(untypedResourcePath, Collections.singletonList(untypedResourcePath)), - buildParameters(booleanPath, Arrays.asList(booleanPath, booleanLiteralPath)), - buildParameters(codingPath, Arrays.asList(codingPath, codingLiteralPath)), - buildParameters(datePath, Arrays.asList(datePath, dateLiteralPath)), - buildParameters(dateTimePath, Arrays.asList(dateTimePath, dateTimeLiteralPath)), - buildParameters(decimalPath, Arrays.asList(decimalPath, decimalLiteralPath)), - buildParameters(integerPath, Arrays.asList(integerPath, integerLiteralPath)), - buildParameters(referencePath, Collections.singletonList(referencePath)), - buildParameters(stringPath, Arrays.asList(stringPath, stringLiteralPath)), - buildParameters(timePath, Arrays.asList(timePath, timeLiteralPath)), - buildParameters(booleanLiteralPath, Arrays.asList(booleanLiteralPath, booleanPath)), - buildParameters(codingLiteralPath, Arrays.asList(codingLiteralPath, codingPath)), - buildParameters(dateLiteralPath, Arrays.asList(dateLiteralPath, datePath)), - buildParameters(dateTimeLiteralPath, Arrays.asList(dateTimeLiteralPath, dateTimePath)), - buildParameters(decimalLiteralPath, Arrays.asList(decimalLiteralPath, decimalPath)), - buildParameters(integerLiteralPath, Arrays.asList(integerLiteralPath, integerPath)), - buildParameters(stringLiteralPath, Arrays.asList(stringLiteralPath, stringPath)), - buildParameters(timeLiteralPath, Arrays.asList(timeLiteralPath, timePath)), - buildParameters(nullPath, paths) - ).flatMap(x -> x).distinct(); - } - - Stream buildParameters(@Nonnull final Collection source, - @Nonnull final java.util.Collection canBeCombined) { - return paths.stream().map(path -> new TestParameters(source, path, - canBeCombined.contains(path) || path == nullPath)); - } - - @ParameterizedTest - @MethodSource("parameters") - void canBeCombined(@Nonnull final TestParameters parameters) { - final boolean result = parameters.getSource().canBeCombinedWith(parameters.getTarget()); - assertEquals(parameters.isExpectation(), result); - } - + // TODO: implement with columns + + // final ResourceCollection resourceCollection; + // final UntypedResourcePath untypedResourcePath; + // + // final PrimitivePath booleanPath; + // final PrimitivePath codingPath; + // final PrimitivePath datePath; + // final PrimitivePath dateTimePath; + // final PrimitivePath decimalPath; + // final PrimitivePath integerPath; + // final ReferencePath referencePath; + // final PrimitivePath stringPath; + // final PrimitivePath timePath; + // + // final BooleanLiteralPath booleanLiteralPath; + // final CodingLiteralPath codingLiteralPath; + // final DateLiteralPath dateLiteralPath; + // final DateTimeLiteralPath dateTimeLiteralPath; + // final DecimalCollection decimalLiteralPath; + // final IntegerLiteralPath integerLiteralPath; + // final NullPath nullPath; + // final StringLiteralPath stringLiteralPath; + // final TimeLiteralPath timeLiteralPath; + // + // final Set paths; + // + // @Autowired + // CanBeCombinedWithTest(@Nonnull final SparkSession spark) throws ParseException { + // resourceCollection = new ResourcePathBuilder(spark) + // .build(); + // + // booleanPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .build(); + // codingPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .build(); + // datePath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DATE) + // .build(); + // dateTimePath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DATETIME) + // .build(); + // decimalPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DECIMAL) + // .build(); + // integerPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .build(); + // referencePath = (ReferencePath) new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.REFERENCE) + // .build(); + // untypedResourcePath = UntypedResourcePath.build(referencePath, referencePath.getExpression()); + // stringPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .build(); + // timePath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.TIME) + // .build(); + // + // booleanLiteralPath = BooleanLiteralPath.fromString("true", resourceCollection); + // codingLiteralPath = CodingCollection + // .fromLiteral("http://snomed.info/sct|27699000", resourceCollection); + // dateLiteralPath = DateCollection.fromLiteral("@1983-06-21", resourceCollection); + // dateTimeLiteralPath = DateTimeLiteralPath + // .fromString("@2015-02-08T13:28:17-05:00", resourceCollection); + // decimalLiteralPath = DecimalCollection.fromLiteral("9.5", resourceCollection); + // integerLiteralPath = IntegerLiteralPath.fromString("14", resourceCollection); + // nullPath = NullPath.build(resourceCollection); + // stringLiteralPath = StringCollection.fromLiteral("'foo'", resourceCollection); + // timeLiteralPath = TimeCollection.fromLiteral("T12:30", resourceCollection); + // + // paths = new HashSet<>(Arrays.asList( + // resourceCollection, + // untypedResourcePath, + // booleanPath, + // codingPath, + // datePath, + // dateTimePath, + // decimalPath, + // integerPath, + // referencePath, + // stringPath, + // timePath, + // booleanLiteralPath, + // codingLiteralPath, + // dateLiteralPath, + // dateTimeLiteralPath, + // decimalLiteralPath, + // integerLiteralPath, + // nullPath, + // stringLiteralPath, + // timeLiteralPath + // )); + // } + // + // @Value + // @EqualsAndHashCode + // static class TestParameters { + // + // @Nonnull + // Collection source; + // + // @Nonnull + // Collection target; + // + // boolean expectation; + // + // @Override + // public String toString() { + // return source.getClass().getSimpleName() + ", " + target.getClass().getSimpleName() + ": " + // + expectation; + // } + // + // } + // + // Stream parameters() { + // return Stream.of( + // buildParameters(resourceCollection, Collections.singletonList(resourceCollection)), + // buildParameters(untypedResourcePath, Collections.singletonList(untypedResourcePath)), + // buildParameters(booleanPath, Arrays.asList(booleanPath, booleanLiteralPath)), + // buildParameters(codingPath, Arrays.asList(codingPath, codingLiteralPath)), + // buildParameters(datePath, Arrays.asList(datePath, dateLiteralPath)), + // buildParameters(dateTimePath, Arrays.asList(dateTimePath, dateTimeLiteralPath)), + // buildParameters(decimalPath, Arrays.asList(decimalPath, decimalLiteralPath)), + // buildParameters(integerPath, Arrays.asList(integerPath, integerLiteralPath)), + // buildParameters(referencePath, Collections.singletonList(referencePath)), + // buildParameters(stringPath, Arrays.asList(stringPath, stringLiteralPath)), + // buildParameters(timePath, Arrays.asList(timePath, timeLiteralPath)), + // buildParameters(booleanLiteralPath, Arrays.asList(booleanLiteralPath, booleanPath)), + // buildParameters(codingLiteralPath, Arrays.asList(codingLiteralPath, codingPath)), + // buildParameters(dateLiteralPath, Arrays.asList(dateLiteralPath, datePath)), + // buildParameters(dateTimeLiteralPath, Arrays.asList(dateTimeLiteralPath, dateTimePath)), + // buildParameters(decimalLiteralPath, Arrays.asList(decimalLiteralPath, decimalPath)), + // buildParameters(integerLiteralPath, Arrays.asList(integerLiteralPath, integerPath)), + // buildParameters(stringLiteralPath, Arrays.asList(stringLiteralPath, stringPath)), + // buildParameters(timeLiteralPath, Arrays.asList(timeLiteralPath, timePath)), + // buildParameters(nullPath, paths) + // ).flatMap(x -> x).distinct(); + // } + // + // Stream buildParameters(@Nonnull final Collection source, + // @Nonnull final java.util.Collection canBeCombined) { + // return paths.stream().map(path -> new TestParameters(source, path, + // canBeCombined.contains(path) || path == nullPath)); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void canBeCombined(@Nonnull final TestParameters parameters) { + // final boolean result = parameters.getSource().canBeCombinedWith(parameters.getTarget()); + // assertEquals(parameters.isExpectation(), result); + // } + // } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/NonLiteralPathTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/NonLiteralPathTest.java index 4e4d8e8c4d..c5a12bd442 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/NonLiteralPathTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/NonLiteralPathTest.java @@ -17,6 +17,7 @@ package au.csiro.pathling.fhirpath; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; import org.apache.spark.sql.SparkSession; import org.springframework.beans.factory.annotation.Autowired; @@ -27,11 +28,14 @@ * @author Piotr Szul */ @SpringBootUnitTest +@NotImplemented class NonLiteralPathTest { @Autowired SparkSession spark; + // TODO: implement with columns + // @Test // void testSingularNonLiteralEidExpansion() { // // Check the result. diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/OrderableTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/OrderableTest.java index bb0f1d93c2..260ebe13eb 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/OrderableTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/OrderableTest.java @@ -17,6 +17,7 @@ package au.csiro.pathling.fhirpath; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; /** @@ -25,8 +26,11 @@ * @author Piotr Szul */ @SpringBootUnitTest +@NotImplemented class OrderableTest { + // TODO: implement with columns + // @Autowired // SparkSession spark; // diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/BooleansTestFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/BooleansTestFunctionTest.java index ef1692fe01..f1dd555ffa 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/BooleansTestFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/BooleansTestFunctionTest.java @@ -17,36 +17,10 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.BooleanCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.DatasetBuilder; -import au.csiro.pathling.test.builders.ElementPathBuilder; -import au.csiro.pathling.test.builders.ParserContextBuilder; -import ca.uhn.fhir.context.FhirContext; -import java.util.Collections; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import lombok.Value; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -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.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.springframework.beans.factory.annotation.Autowired; /** * These functions perform tests across a collection of Boolean input values. @@ -59,159 +33,162 @@ */ @SpringBootUnitTest @TestInstance(Lifecycle.PER_CLASS) +@NotImplemented class BooleansTestFunctionTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @Value - static class TestParameters { - - @Nonnull - String functionName; - - @Nonnull - Dataset expectedResult; - - @Override - public String toString() { - return functionName; - } - - } - - Stream parameters() { - return Stream.of( - new TestParameters("anyTrue", new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.BooleanType) - .withRow("observation-1", true) - .withRow("observation-2", true) - .withRow("observation-3", false) - .withRow("observation-4", true) - .withRow("observation-5", false) - .withRow("observation-6", false) - .withRow("observation-7", false) - .build()), - new TestParameters("anyFalse", new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.BooleanType) - .withRow("observation-1", true) - .withRow("observation-2", false) - .withRow("observation-3", true) - .withRow("observation-4", false) - .withRow("observation-5", true) - .withRow("observation-6", false) - .withRow("observation-7", false) - .build()), - new TestParameters("allTrue", new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.BooleanType) - .withRow("observation-1", false) - .withRow("observation-2", true) - .withRow("observation-3", false) - .withRow("observation-4", true) - .withRow("observation-5", false) - .withRow("observation-6", true) - .withRow("observation-7", true) - .build()), - new TestParameters("allFalse", new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.BooleanType) - .withRow("observation-1", false) - .withRow("observation-2", false) - .withRow("observation-3", true) - .withRow("observation-4", false) - .withRow("observation-5", true) - .withRow("observation-6", true) - .withRow("observation-7", true) - .build()) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void returnsCorrectResults(@Nonnull final TestParameters parameters) { - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.BooleanType) - .withRow("observation-1", true) - .withRow("observation-1", false) - .withRow("observation-2", true) - .withRow("observation-2", true) - .withRow("observation-3", false) - .withRow("observation-3", false) - .withRow("observation-4", true) - .withRow("observation-4", null) - .withRow("observation-5", false) - .withRow("observation-5", null) - .withRow("observation-6", null) - .withRow("observation-6", null) - .withRow("observation-7", null) - .build(); - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .dataset(dataset) - .idAndValueColumns() - .expression("valueBoolean") - .build(); - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(input.getIdColumn())) - .build(); - - final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, - Collections.emptyList()); - final NamedFunction function = NamedFunction.getInstance(parameters.getFunctionName()); - final Collection result = function.invoke(functionInput); - - assertThat(result) - .hasExpression("valueBoolean." + parameters.getFunctionName() + "()") - .isSingular() - .isElementPath(BooleanCollection.class) - .selectOrderedResult() - .hasRows(parameters.getExpectedResult()); - } - - @ParameterizedTest - @MethodSource("parameters") - void inputMustNotContainArguments(@Nonnull final TestParameters parameters) { - final PrimitivePath input = new ElementPathBuilder(spark).build(); - final StringLiteralPath argument = StringCollection - .fromLiteral("'some argument'", input); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - final NamedFunction function = NamedFunction.getInstance(parameters.getFunctionName()); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> function.invoke(functionInput)); - assertEquals( - "Arguments can not be passed to " + parameters.getFunctionName() + " function", - error.getMessage()); - } - - @ParameterizedTest - @MethodSource("parameters") - void inputMustBeBoolean(@Nonnull final TestParameters parameters) { - final PrimitivePath input = new ElementPathBuilder(spark) - .expression("valueString") - .fhirType(FHIRDefinedType.STRING) - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, - Collections.emptyList()); - final NamedFunction function = NamedFunction.getInstance(parameters.getFunctionName()); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> function.invoke(functionInput)); - assertEquals( - "Input to " + parameters.getFunctionName() + " function must be Boolean", - error.getMessage()); - } + // TODO: implement with columns + + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @Value + // static class TestParameters { + // + // @Nonnull + // String functionName; + // + // @Nonnull + // Dataset expectedResult; + // + // @Override + // public String toString() { + // return functionName; + // } + // } + // + // Stream parameters() { + // return Stream.of( + // new TestParameters("anyTrue", new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow("observation-1", true) + // .withRow("observation-2", true) + // .withRow("observation-3", false) + // .withRow("observation-4", true) + // .withRow("observation-5", false) + // .withRow("observation-6", false) + // .withRow("observation-7", false) + // .build()), + // new TestParameters("anyFalse", new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow("observation-1", true) + // .withRow("observation-2", false) + // .withRow("observation-3", true) + // .withRow("observation-4", false) + // .withRow("observation-5", true) + // .withRow("observation-6", false) + // .withRow("observation-7", false) + // .build()), + // new TestParameters("allTrue", new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow("observation-1", false) + // .withRow("observation-2", true) + // .withRow("observation-3", false) + // .withRow("observation-4", true) + // .withRow("observation-5", false) + // .withRow("observation-6", true) + // .withRow("observation-7", true) + // .build()), + // new TestParameters("allFalse", new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow("observation-1", false) + // .withRow("observation-2", false) + // .withRow("observation-3", true) + // .withRow("observation-4", false) + // .withRow("observation-5", true) + // .withRow("observation-6", true) + // .withRow("observation-7", true) + // .build()) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void returnsCorrectResults(@Nonnull final TestParameters parameters) { + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow("observation-1", true) + // .withRow("observation-1", false) + // .withRow("observation-2", true) + // .withRow("observation-2", true) + // .withRow("observation-3", false) + // .withRow("observation-3", false) + // .withRow("observation-4", true) + // .withRow("observation-4", null) + // .withRow("observation-5", false) + // .withRow("observation-5", null) + // .withRow("observation-6", null) + // .withRow("observation-6", null) + // .withRow("observation-7", null) + // .build(); + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .dataset(dataset) + // .idAndValueColumns() + // .expression("valueBoolean") + // .build(); + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(input.getIdColumn())) + // .build(); + // + // final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, + // Collections.emptyList()); + // final NamedFunction function = NamedFunction.getInstance(parameters.getFunctionName()); + // final Collection result = function.invoke(functionInput); + // + // assertThat(result) + // .hasExpression("valueBoolean." + parameters.getFunctionName() + "()") + // .isSingular() + // .isElementPath(BooleanCollection.class) + // .selectOrderedResult() + // .hasRows(parameters.getExpectedResult()); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void inputMustNotContainArguments(@Nonnull final TestParameters parameters) { + // final PrimitivePath input = new ElementPathBuilder(spark).build(); + // final StringLiteralPath argument = StringCollection + // .fromLiteral("'some argument'", input); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // final NamedFunction function = NamedFunction.getInstance(parameters.getFunctionName()); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> function.invoke(functionInput)); + // assertEquals( + // "Arguments can not be passed to " + parameters.getFunctionName() + " function", + // error.getMessage()); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void inputMustBeBoolean(@Nonnull final TestParameters parameters) { + // final PrimitivePath input = new ElementPathBuilder(spark) + // .expression("valueString") + // .fhirType(FHIRDefinedType.STRING) + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, + // Collections.emptyList()); + // final NamedFunction function = NamedFunction.getInstance(parameters.getFunctionName()); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> function.invoke(functionInput)); + // assertEquals( + // "Input to " + parameters.getFunctionName() + " function must be Boolean", + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/CountFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/CountFunctionTest.java index cb8e78e2d1..6c5da3a787 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/CountFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/CountFunctionTest.java @@ -17,189 +17,164 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.when; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.IntegerCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.io.source.DataSource; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.builders.ResourceDatasetBuilder; -import au.csiro.pathling.test.builders.ResourcePathBuilder; -import ca.uhn.fhir.context.FhirContext; -import java.util.Collections; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.apache.spark.sql.types.DataTypes; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class CountFunctionTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @MockBean - DataSource dataSource; - - @Test - void countsByResourceIdentity() { - final Dataset patientDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn("gender", DataTypes.StringType) - .withColumn("active", DataTypes.BooleanType) - .withRow("patient-1", "female", true) - .withRow("patient-2", "female", false) - .withRow("patient-3", "male", true) - .build(); - when(dataSource.read(ResourceType.PATIENT)) - .thenReturn(patientDataset); - final ResourceCollection inputPath = ResourceCollection - .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient"); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .idColumn(inputPath.getIdColumn()) - .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) - .inputExpression("Patient") - .build(); - final NamedFunctionInput countInput = new NamedFunctionInput(parserContext, inputPath, - Collections.emptyList()); - final NamedFunction count = NamedFunction.getInstance("count"); - final Collection result = count.invoke(countInput); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.LongType) - .withRow("patient-1", 1L) - .withRow("patient-2", 1L) - .withRow("patient-3", 1L) - .build(); - - assertThat(result) - .hasExpression("count()") - .isSingular() - .isElementPath(IntegerCollection.class) - .hasFhirType(FHIRDefinedType.UNSIGNEDINT) - .selectOrderedResult() - .hasRows(expectedDataset); - } - - @Test - void countsByGrouping() { - final Dataset inputDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn("gender", DataTypes.StringType) - .withColumn("active", DataTypes.BooleanType) - .withRow("patient-1", "female", true) - .withRow("patient-2", "female", false) - .withRow("patient-2", "male", true) - .build(); - when(dataSource.read(ResourceType.PATIENT)).thenReturn(inputDataset); - final ResourceCollection inputPath = new ResourcePathBuilder(spark) - .database(dataSource) - .resourceType(ResourceType.PATIENT) - .expression("Patient") - .build(); - final Column groupingColumn = inputPath.getElementColumn("gender").orElseThrow(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(groupingColumn)) - .inputExpression("Patient") - .build(); - final NamedFunctionInput countInput = new NamedFunctionInput(parserContext, inputPath, - Collections.emptyList()); - final NamedFunction count = NamedFunction.getInstance("count"); - final Collection result = count.invoke(countInput); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withColumn(DataTypes.StringType) - .withColumn(DataTypes.LongType) - .withRow("female", 2L) - .withRow("male", 1L) - .build(); - - assertThat(result) - .hasExpression("count()") - .isSingular() - .isElementPath(IntegerCollection.class) - .hasFhirType(FHIRDefinedType.UNSIGNEDINT) - .selectGroupingResult(Collections.singletonList(groupingColumn)) - .hasRows(expectedDataset); - - } - - @Test - void doesNotCountNullElements() { - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn("gender", DataTypes.StringType) - .withRow("patient-1", "female") - .withRow("patient-2", null) - .withRow("patient-3", "male") - .build(); - final PrimitivePath inputPath = new ElementPathBuilder(spark) - .expression("gender") - .fhirType(FHIRDefinedType.CODE) - .dataset(dataset) - .idAndValueColumns() - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .idColumn(inputPath.getIdColumn()) - .groupingColumns(Collections.emptyList()) - .build(); - final NamedFunctionInput countInput = new NamedFunctionInput(parserContext, inputPath, - Collections.emptyList()); - final NamedFunction count = NamedFunction.getInstance("count"); - final Collection result = count.invoke(countInput); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.LongType) - .withRow("patient-1", 2L) - .build(); - - assertThat(result) - .hasExpression("gender.count()") - .isSingular() - .isElementPath(IntegerCollection.class) - .hasFhirType(FHIRDefinedType.UNSIGNEDINT) - .selectOrderedResult() - .hasRows(expectedDataset); - } - - @Test - void inputMustNotContainArguments() { - final PrimitivePath inputPath = new ElementPathBuilder(spark).build(); - final PrimitivePath argumentPath = new ElementPathBuilder(spark).build(); - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - - final NamedFunctionInput countInput = new NamedFunctionInput(parserContext, inputPath, - Collections.singletonList(argumentPath)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> NamedFunction.getInstance("count").invoke(countInput)); - assertEquals("Arguments can not be passed to count function", error.getMessage()); - } + // TODO: implement with columns + + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @MockBean + // DataSource dataSource; + // + // @Test + // void countsByResourceIdentity() { + // final Dataset patientDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn("gender", DataTypes.StringType) + // .withColumn("active", DataTypes.BooleanType) + // .withRow("patient-1", "female", true) + // .withRow("patient-2", "female", false) + // .withRow("patient-3", "male", true) + // .build(); + // when(dataSource.read(ResourceType.PATIENT)) + // .thenReturn(patientDataset); + // final ResourceCollection inputPath = ResourceCollection + // .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient"); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .idColumn(inputPath.getIdColumn()) + // .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) + // .inputExpression("Patient") + // .build(); + // final NamedFunctionInput countInput = new NamedFunctionInput(parserContext, inputPath, + // Collections.emptyList()); + // final NamedFunction count = NamedFunction.getInstance("count"); + // final Collection result = count.invoke(countInput); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.LongType) + // .withRow("patient-1", 1L) + // .withRow("patient-2", 1L) + // .withRow("patient-3", 1L) + // .build(); + // + // assertThat(result) + // .hasExpression("count()") + // .isSingular() + // .isElementPath(IntegerCollection.class) + // .hasFhirType(FHIRDefinedType.UNSIGNEDINT) + // .selectOrderedResult() + // .hasRows(expectedDataset); + // } + // + // @Test + // void countsByGrouping() { + // final Dataset inputDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn("gender", DataTypes.StringType) + // .withColumn("active", DataTypes.BooleanType) + // .withRow("patient-1", "female", true) + // .withRow("patient-2", "female", false) + // .withRow("patient-2", "male", true) + // .build(); + // when(dataSource.read(ResourceType.PATIENT)).thenReturn(inputDataset); + // final ResourceCollection inputPath = new ResourcePathBuilder(spark) + // .database(dataSource) + // .resourceType(ResourceType.PATIENT) + // .expression("Patient") + // .build(); + // final Column groupingColumn = inputPath.getElementColumn("gender").orElseThrow(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(groupingColumn)) + // .inputExpression("Patient") + // .build(); + // final NamedFunctionInput countInput = new NamedFunctionInput(parserContext, inputPath, + // Collections.emptyList()); + // final NamedFunction count = NamedFunction.getInstance("count"); + // final Collection result = count.invoke(countInput); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withColumn(DataTypes.StringType) + // .withColumn(DataTypes.LongType) + // .withRow("female", 2L) + // .withRow("male", 1L) + // .build(); + // + // assertThat(result) + // .hasExpression("count()") + // .isSingular() + // .isElementPath(IntegerCollection.class) + // .hasFhirType(FHIRDefinedType.UNSIGNEDINT) + // .selectGroupingResult(Collections.singletonList(groupingColumn)) + // .hasRows(expectedDataset); + // + // } + // + // @Test + // void doesNotCountNullElements() { + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn("gender", DataTypes.StringType) + // .withRow("patient-1", "female") + // .withRow("patient-2", null) + // .withRow("patient-3", "male") + // .build(); + // final PrimitivePath inputPath = new ElementPathBuilder(spark) + // .expression("gender") + // .fhirType(FHIRDefinedType.CODE) + // .dataset(dataset) + // .idAndValueColumns() + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .idColumn(inputPath.getIdColumn()) + // .groupingColumns(Collections.emptyList()) + // .build(); + // final NamedFunctionInput countInput = new NamedFunctionInput(parserContext, inputPath, + // Collections.emptyList()); + // final NamedFunction count = NamedFunction.getInstance("count"); + // final Collection result = count.invoke(countInput); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.LongType) + // .withRow("patient-1", 2L) + // .build(); + // + // assertThat(result) + // .hasExpression("gender.count()") + // .isSingular() + // .isElementPath(IntegerCollection.class) + // .hasFhirType(FHIRDefinedType.UNSIGNEDINT) + // .selectOrderedResult() + // .hasRows(expectedDataset); + // } + // + // @Test + // void inputMustNotContainArguments() { + // final PrimitivePath inputPath = new ElementPathBuilder(spark).build(); + // final PrimitivePath argumentPath = new ElementPathBuilder(spark).build(); + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // + // final NamedFunctionInput countInput = new NamedFunctionInput(parserContext, inputPath, + // Collections.singletonList(argumentPath)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> NamedFunction.getInstance("count").invoke(countInput)); + // assertEquals("Arguments can not be passed to count function", error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/EmptyFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/EmptyFunctionTest.java index 10a8313a6a..aec3706543 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/EmptyFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/EmptyFunctionTest.java @@ -17,120 +17,96 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.helpers.SparkHelpers.codeableConceptStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromCodeableConcept; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.BooleanCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.TestHelpers; -import ca.uhn.fhir.context.FhirContext; -import java.util.Collections; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.apache.spark.sql.types.DataTypes; -import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class EmptyFunctionTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @Test - void returnsCorrectResults() { - final Coding coding1 = new Coding(TestHelpers.SNOMED_URL, "840546002", "Exposure to COVID-19"); - final CodeableConcept concept1 = new CodeableConcept(coding1); - final Coding coding2 = new Coding(TestHelpers.SNOMED_URL, "248427009", "Fever symptoms"); - final CodeableConcept concept2 = new CodeableConcept(coding2); - - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(codeableConceptStructType()) - .withRow("observation-1", null) - .withRow("observation-2", null) - .withRow("observation-2", null) - .withRow("observation-3", rowFromCodeableConcept(concept1)) - .withRow("observation-4", rowFromCodeableConcept(concept1)) - .withRow("observation-4", null) - .withRow("observation-5", rowFromCodeableConcept(concept1)) - .withRow("observation-5", rowFromCodeableConcept(concept2)) - .build(); - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .dataset(dataset) - .idAndValueColumns() - .expression("code") - .build(); - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(input.getIdColumn())) - .build(); - - // Set up the function input. - final NamedFunctionInput emptyInput = new NamedFunctionInput(parserContext, input, - Collections.emptyList()); - - // Invoke the function. - final NamedFunction emptyFunction = NamedFunction.getInstance("empty"); - final Collection result = emptyFunction.invoke(emptyInput); - - // Check the result. - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.BooleanType) - .withRow("observation-1", true) - .withRow("observation-2", true) - .withRow("observation-3", false) - .withRow("observation-4", false) - .withRow("observation-5", false) - .build(); - assertThat(result) - .hasExpression("code.empty()") - .isSingular() - .isElementPath(BooleanCollection.class) - .selectOrderedResult() - .hasRows(expectedDataset); - } - - @Test - void inputMustNotContainArguments() { - final PrimitivePath input = new ElementPathBuilder(spark).build(); - final StringLiteralPath argument = StringCollection - .fromLiteral("'some argument'", input); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput emptyInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); + // TODO: implement with columns - final NamedFunction emptyFunction = NamedFunction.getInstance("empty"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> emptyFunction.invoke(emptyInput)); - assertEquals( - "Arguments can not be passed to empty function", - error.getMessage()); - } + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @Test + // void returnsCorrectResults() { + // final Coding coding1 = new Coding(TestHelpers.SNOMED_URL, "840546002", "Exposure to COVID-19"); + // final CodeableConcept concept1 = new CodeableConcept(coding1); + // final Coding coding2 = new Coding(TestHelpers.SNOMED_URL, "248427009", "Fever symptoms"); + // final CodeableConcept concept2 = new CodeableConcept(coding2); + // + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(codeableConceptStructType()) + // .withRow("observation-1", null) + // .withRow("observation-2", null) + // .withRow("observation-2", null) + // .withRow("observation-3", rowFromCodeableConcept(concept1)) + // .withRow("observation-4", rowFromCodeableConcept(concept1)) + // .withRow("observation-4", null) + // .withRow("observation-5", rowFromCodeableConcept(concept1)) + // .withRow("observation-5", rowFromCodeableConcept(concept2)) + // .build(); + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .dataset(dataset) + // .idAndValueColumns() + // .expression("code") + // .build(); + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(input.getIdColumn())) + // .build(); + // + // // Set up the function input. + // final NamedFunctionInput emptyInput = new NamedFunctionInput(parserContext, input, + // Collections.emptyList()); + // + // // Invoke the function. + // final NamedFunction emptyFunction = NamedFunction.getInstance("empty"); + // final Collection result = emptyFunction.invoke(emptyInput); + // + // // Check the result. + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow("observation-1", true) + // .withRow("observation-2", true) + // .withRow("observation-3", false) + // .withRow("observation-4", false) + // .withRow("observation-5", false) + // .build(); + // assertThat(result) + // .hasExpression("code.empty()") + // .isSingular() + // .isElementPath(BooleanCollection.class) + // .selectOrderedResult() + // .hasRows(expectedDataset); + // } + // + // @Test + // void inputMustNotContainArguments() { + // final PrimitivePath input = new ElementPathBuilder(spark).build(); + // final StringLiteralPath argument = StringCollection + // .fromLiteral("'some argument'", input); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput emptyInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final NamedFunction emptyFunction = NamedFunction.getInstance("empty"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> emptyFunction.invoke(emptyInput)); + // assertEquals( + // "Arguments can not be passed to empty function", + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExistsFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExistsFunctionTest.java index 17f2d5d96d..bf468e35dd 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExistsFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExistsFunctionTest.java @@ -17,221 +17,193 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static au.csiro.pathling.test.helpers.SparkHelpers.codeableConceptStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromCodeableConcept; -import static au.csiro.pathling.utilities.Strings.randomAlias; -import static org.apache.spark.sql.functions.not; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.collection.BooleanCollection; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.builders.ResourcePathBuilder; -import au.csiro.pathling.test.helpers.TestHelpers; -import ca.uhn.fhir.context.FhirContext; -import java.util.Collections; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.apache.spark.sql.types.DataTypes; -import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; @SpringBootUnitTest +@NotImplemented public class ExistsFunctionTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @Test - void returnsOppositeResultsToEmpty() { - final Coding coding1 = new Coding(TestHelpers.SNOMED_URL, "840546002", "Exposure to COVID-19"); - final CodeableConcept concept1 = new CodeableConcept(coding1); - final Coding coding2 = new Coding(TestHelpers.SNOMED_URL, "248427009", "Fever symptoms"); - final CodeableConcept concept2 = new CodeableConcept(coding2); - - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(codeableConceptStructType()) - .withRow("observation-1", null) - .withRow("observation-2", null) - .withRow("observation-2", null) - .withRow("observation-3", rowFromCodeableConcept(concept1)) - .withRow("observation-4", rowFromCodeableConcept(concept1)) - .withRow("observation-4", null) - .withRow("observation-5", rowFromCodeableConcept(concept1)) - .withRow("observation-5", rowFromCodeableConcept(concept2)) - .build(); - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .dataset(dataset) - .idAndValueColumns() - .expression("code") - .build(); - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(input.getIdColumn())) - .build(); - - // Set up the function input. - final NamedFunctionInput emptyInput = new NamedFunctionInput(parserContext, input, - Collections.emptyList()); - - // Invoke the empty function. - final NamedFunction emptyFunction = NamedFunction.getInstance("empty"); - final Collection emptyResult = emptyFunction.invoke(emptyInput); - - // Create an expected dataset from the result of the empty function, with an inverted value. - final Dataset emptyResultDataset = emptyResult.getDataset(); - final Column invertedValue = not(emptyResult.getValueColumn()); - final Dataset expectedDataset = emptyResultDataset.select(emptyResult.getIdColumn(), - invertedValue); - - // Invoke the exists function. - final NamedFunction existsFunction = NamedFunction.getInstance("exists"); - final Collection existsResult = existsFunction.invoke(emptyInput); - - // Check the result. - assertThat(existsResult) - .hasExpression("code.exists()") - .isSingular() - .isElementPath(BooleanCollection.class) - .selectOrderedResult() - .hasRows(expectedDataset); - } - - @Test - void returnsSameResultsAsWhereAndExists() { - final String statusColumn = randomAlias(); - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withIdColumn() - .withColumn(statusColumn, DataTypes.StringType) - .withRow("patient-1", makeEid(1), "encounter-1", "in-progress") - .withRow("patient-1", makeEid(0), "encounter-2", "finished") - .withRow("patient-2", makeEid(0), "encounter-3", "in-progress") - .withRow("patient-3", makeEid(1), "encounter-4", "in-progress") - .withRow("patient-3", makeEid(0), "encounter-5", "finished") - .withRow("patient-4", makeEid(1), "encounter-6", "finished") - .withRow("patient-4", makeEid(0), "encounter-7", "finished") - .withRow("patient-5", makeEid(1), "encounter-8", "in-progress") - .withRow("patient-5", makeEid(0), "encounter-9", "in-progress") - .withRow("patient-6", null, null, null) - .build(); - final ResourceCollection inputPath = new ResourcePathBuilder(spark) - .expression("reverseResolve(Encounter.subject)") - .dataset(inputDataset) - .idEidAndValueColumns() - .buildCustom(); - - // Build an expression which represents the argument to the function. We assume that the value - // column from the input dataset is also present within the argument dataset. - - final NonLiteralPath thisPath = inputPath.toThisPath(); - - final Dataset argumentDataset = thisPath.getDataset() - .withColumn("value", - thisPath.getDataset().col(statusColumn).equalTo("in-progress")); - - assertTrue(thisPath.getThisColumn().isPresent()); - final PrimitivePath argumentPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .dataset(argumentDataset) - .idColumn(inputPath.getIdColumn()) - .valueColumn(argumentDataset.col("value")) - .thisColumn(thisPath.getThisColumn().get()) - .singular(true) - .build(); - - // Prepare the input to the where function. - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, - inputPath, Collections.singletonList(argumentPath)); - - // Execute the where function. - final NamedFunction whereFunction = NamedFunction.getInstance("where"); - final Collection whereResult = whereFunction.invoke(whereInput); - - // Prepare the input to the exists function. - final NamedFunctionInput existsInput = new NamedFunctionInput(parserContext, - (NonLiteralPath) whereResult, Collections.emptyList()); - - // Execute the exists function. - final NamedFunction existsFunction = NamedFunction.getInstance("exists"); - final Collection whereExistsResult = existsFunction.invoke(existsInput); - - // Prepare the input to the exists function (with argument). - final NamedFunctionInput existsWithArgumentInput = new NamedFunctionInput(parserContext, - inputPath, Collections.singletonList(argumentPath)); - final Collection existsWithArgumentResult = existsFunction.invoke(existsWithArgumentInput); - - // Check the results. - assertThat(existsWithArgumentResult.getDataset() - .select(existsWithArgumentResult.getIdColumn(), existsWithArgumentResult.getValueColumn())) - .hasRows(whereExistsResult.getDataset() - .select(whereExistsResult.getIdColumn(), whereExistsResult.getValueColumn())); - } - - @Test - void throwsErrorIfArgumentNotBoolean() { - final ResourceCollection input = new ResourcePathBuilder(spark).build(); - final PrimitivePath argument = new ElementPathBuilder(spark) - .expression("$this.gender") - .fhirType(FHIRDefinedType.STRING) - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput existsInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final NamedFunction existsFunction = NamedFunction.getInstance("exists"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> existsFunction.invoke(existsInput)); - assertEquals( - "Argument to exists function must be a singular Boolean: $this.gender", - error.getMessage()); - } - - @Test - void throwsErrorIfArgumentNotSingular() { - final ResourceCollection input = new ResourcePathBuilder(spark).build(); - final PrimitivePath argument = new ElementPathBuilder(spark) - .expression("$this.communication.preferred") - .fhirType(FHIRDefinedType.BOOLEAN) - .singular(false) - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput existsInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final NamedFunction existsFunction = NamedFunction.getInstance("exists"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> existsFunction.invoke(existsInput)); - assertEquals( - "Argument to exists function must be a singular Boolean: " - + "$this.communication.preferred", - error.getMessage()); - } + // TODO: implement with columns + + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @Test + // void returnsOppositeResultsToEmpty() { + // final Coding coding1 = new Coding(TestHelpers.SNOMED_URL, "840546002", "Exposure to COVID-19"); + // final CodeableConcept concept1 = new CodeableConcept(coding1); + // final Coding coding2 = new Coding(TestHelpers.SNOMED_URL, "248427009", "Fever symptoms"); + // final CodeableConcept concept2 = new CodeableConcept(coding2); + // + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(codeableConceptStructType()) + // .withRow("observation-1", null) + // .withRow("observation-2", null) + // .withRow("observation-2", null) + // .withRow("observation-3", rowFromCodeableConcept(concept1)) + // .withRow("observation-4", rowFromCodeableConcept(concept1)) + // .withRow("observation-4", null) + // .withRow("observation-5", rowFromCodeableConcept(concept1)) + // .withRow("observation-5", rowFromCodeableConcept(concept2)) + // .build(); + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .dataset(dataset) + // .idAndValueColumns() + // .expression("code") + // .build(); + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(input.getIdColumn())) + // .build(); + // + // // Set up the function input. + // final NamedFunctionInput emptyInput = new NamedFunctionInput(parserContext, input, + // Collections.emptyList()); + // + // // Invoke the empty function. + // final NamedFunction emptyFunction = NamedFunction.getInstance("empty"); + // final Collection emptyResult = emptyFunction.invoke(emptyInput); + // + // // Create an expected dataset from the result of the empty function, with an inverted value. + // final Dataset emptyResultDataset = emptyResult.getDataset(); + // final Column invertedValue = not(emptyResult.getValueColumn()); + // final Dataset expectedDataset = emptyResultDataset.select(emptyResult.getIdColumn(), + // invertedValue); + // + // // Invoke the exists function. + // final NamedFunction existsFunction = NamedFunction.getInstance("exists"); + // final Collection existsResult = existsFunction.invoke(emptyInput); + // + // // Check the result. + // assertThat(existsResult) + // .hasExpression("code.exists()") + // .isSingular() + // .isElementPath(BooleanCollection.class) + // .selectOrderedResult() + // .hasRows(expectedDataset); + // } + // + // @Test + // void returnsSameResultsAsWhereAndExists() { + // final String statusColumn = randomAlias(); + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withIdColumn() + // .withColumn(statusColumn, DataTypes.StringType) + // .withRow("patient-1", makeEid(1), "encounter-1", "in-progress") + // .withRow("patient-1", makeEid(0), "encounter-2", "finished") + // .withRow("patient-2", makeEid(0), "encounter-3", "in-progress") + // .withRow("patient-3", makeEid(1), "encounter-4", "in-progress") + // .withRow("patient-3", makeEid(0), "encounter-5", "finished") + // .withRow("patient-4", makeEid(1), "encounter-6", "finished") + // .withRow("patient-4", makeEid(0), "encounter-7", "finished") + // .withRow("patient-5", makeEid(1), "encounter-8", "in-progress") + // .withRow("patient-5", makeEid(0), "encounter-9", "in-progress") + // .withRow("patient-6", null, null, null) + // .build(); + // final ResourceCollection inputPath = new ResourcePathBuilder(spark) + // .expression("reverseResolve(Encounter.subject)") + // .dataset(inputDataset) + // .idEidAndValueColumns() + // .buildCustom(); + // + // // Build an expression which represents the argument to the function. We assume that the value + // // column from the input dataset is also present within the argument dataset. + // + // final NonLiteralPath thisPath = inputPath.toThisPath(); + // + // final Dataset argumentDataset = thisPath.getDataset() + // .withColumn("value", + // thisPath.getDataset().col(statusColumn).equalTo("in-progress")); + // + // assertTrue(thisPath.getThisColumn().isPresent()); + // final PrimitivePath argumentPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .dataset(argumentDataset) + // .idColumn(inputPath.getIdColumn()) + // .valueColumn(argumentDataset.col("value")) + // .thisColumn(thisPath.getThisColumn().get()) + // .singular(true) + // .build(); + // + // // Prepare the input to the where function. + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, + // inputPath, Collections.singletonList(argumentPath)); + // + // // Execute the where function. + // final NamedFunction whereFunction = NamedFunction.getInstance("where"); + // final Collection whereResult = whereFunction.invoke(whereInput); + // + // // Prepare the input to the exists function. + // final NamedFunctionInput existsInput = new NamedFunctionInput(parserContext, + // (NonLiteralPath) whereResult, Collections.emptyList()); + // + // // Execute the exists function. + // final NamedFunction existsFunction = NamedFunction.getInstance("exists"); + // final Collection whereExistsResult = existsFunction.invoke(existsInput); + // + // // Prepare the input to the exists function (with argument). + // final NamedFunctionInput existsWithArgumentInput = new NamedFunctionInput(parserContext, + // inputPath, Collections.singletonList(argumentPath)); + // final Collection existsWithArgumentResult = existsFunction.invoke(existsWithArgumentInput); + // + // // Check the results. + // assertThat(existsWithArgumentResult.getDataset() + // .select(existsWithArgumentResult.getIdColumn(), existsWithArgumentResult.getValueColumn())) + // .hasRows(whereExistsResult.getDataset() + // .select(whereExistsResult.getIdColumn(), whereExistsResult.getValueColumn())); + // } + // + // @Test + // void throwsErrorIfArgumentNotBoolean() { + // final ResourceCollection input = new ResourcePathBuilder(spark).build(); + // final PrimitivePath argument = new ElementPathBuilder(spark) + // .expression("$this.gender") + // .fhirType(FHIRDefinedType.STRING) + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput existsInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final NamedFunction existsFunction = NamedFunction.getInstance("exists"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> existsFunction.invoke(existsInput)); + // assertEquals( + // "Argument to exists function must be a singular Boolean: $this.gender", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfArgumentNotSingular() { + // final ResourceCollection input = new ResourcePathBuilder(spark).build(); + // final PrimitivePath argument = new ElementPathBuilder(spark) + // .expression("$this.communication.preferred") + // .fhirType(FHIRDefinedType.BOOLEAN) + // .singular(false) + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput existsInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final NamedFunction existsFunction = NamedFunction.getInstance("exists"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> existsFunction.invoke(existsInput)); + // assertEquals( + // "Argument to exists function must be a singular Boolean: " + // + "$this.communication.preferred", + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExtensionFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExtensionFunctionTest.java index 5bd6543e98..044ce27703 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExtensionFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ExtensionFunctionTest.java @@ -17,249 +17,209 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static au.csiro.pathling.test.fixtures.ExtensionFixture.MANY_EXT_ROW_1; -import static au.csiro.pathling.test.fixtures.ExtensionFixture.MANY_EXT_ROW_2; -import static au.csiro.pathling.test.fixtures.ExtensionFixture.MANY_MY_EXTENSIONS; -import static au.csiro.pathling.test.fixtures.ExtensionFixture.NO_MY_EXTENSIONS; -import static au.csiro.pathling.test.fixtures.ExtensionFixture.ONE_EXT_ROW_1; -import static au.csiro.pathling.test.fixtures.ExtensionFixture.ONE_MY_EXTENSION; -import static au.csiro.pathling.test.fixtures.ExtensionFixture.oneEntryMap; -import static au.csiro.pathling.test.fixtures.ExtensionFixture.toElementDataset; -import static au.csiro.pathling.utilities.Preconditions.checkPresent; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.io.source.DataSource; -import au.csiro.pathling.terminology.TerminologyServiceFactory; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.builders.ResourceDatasetBuilder; -import au.csiro.pathling.test.helpers.FhirHelpers; -import ca.uhn.fhir.context.FhirContext; -import java.util.Arrays; -import java.util.Collections; -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.hl7.fhir.r4.model.Enumerations.ResourceType; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class ExtensionFunctionTest { - @Autowired - private SparkSession spark; - - @Autowired - private FhirContext fhirContext; - - @MockBean - private DataSource dataSource; - - @Test - public void testExtensionOnResources() { - - final Dataset patientDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn("gender", DataTypes.StringType) - .withColumn("active", DataTypes.BooleanType) - .withFidColumn() - .withExtensionColumn() - .withRow("patient-1", "female", true, 1, oneEntryMap(1, MANY_MY_EXTENSIONS)) - .withRow("patient-2", "female", false, 1, oneEntryMap(1, ONE_MY_EXTENSION)) - .withRow("patient-3", "male", false, 1, oneEntryMap(1, NO_MY_EXTENSIONS)) - .withRow("patient-4", "male", false, 1, oneEntryMap(2, ONE_MY_EXTENSION)) - .withRow("patient-5", "male", true, 1, null) - .build(); - when(dataSource.read(ResourceType.PATIENT)) - .thenReturn(patientDataset); - final ResourceCollection inputPath = ResourceCollection - .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient"); - - final StringLiteralPath argumentExpression = StringCollection - .fromLiteral("'" + "uuid:myExtension" + "'", inputPath); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - - final NamedFunctionInput extensionInput = new NamedFunctionInput(parserContext, inputPath, - Collections.singletonList(argumentExpression)); - - final NamedFunction extension = NamedFunction.getInstance("extension"); - final Collection result = extension.invoke(extensionInput); - - assertThat(result) - .hasExpression("Patient.extension('uuid:myExtension')") - .isNotSingular() - .isElementPath(PrimitivePath.class) - .hasFhirType(FHIRDefinedType.EXTENSION) - .selectOrderedResult() - .hasRows( - // Multiple extensions of required type present on the resource - RowFactory.create("patient-1", null), - RowFactory.create("patient-1", null), - RowFactory.create("patient-1", MANY_EXT_ROW_1), - RowFactory.create("patient-1", MANY_EXT_ROW_2), - // A single extension of the required type present on the resource - RowFactory.create("patient-2", null), - RowFactory.create("patient-2", ONE_EXT_ROW_1), - // Not extensions of required type present - RowFactory.create("patient-3", null), - RowFactory.create("patient-3", null), - // Extension of required type present but not on the resource - RowFactory.create("patient-4", null), - // No extensions present all together - RowFactory.create("patient-5", null) - ); - } - - @Test - public void testExtensionOnElements() { - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - - // Construct element dataset from the resource dataset so that the resource path - // can be used as the current resource for this element path - // Note: this resource path is not singular as this will be a base for elements. - - final Dataset resourceLikeDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructColumn("name", DataTypes.StringType) - .withStructColumn("_fid", DataTypes.IntegerType) - .withStructValueColumn() - .withExtensionColumn() - .withRow("observation-1", makeEid(0), RowFactory.create("name1", 0), - oneEntryMap(0, MANY_MY_EXTENSIONS)) - .withRow("observation-2", makeEid(0), RowFactory.create("name2", 1), - oneEntryMap(1, ONE_MY_EXTENSION)) - .withRow("observation-3", makeEid(0), RowFactory.create("name3", 2), - oneEntryMap(2, NO_MY_EXTENSIONS)) - .withRow("observation-4", makeEid(0), RowFactory.create("name4", 3), - oneEntryMap(3, ONE_MY_EXTENSION)) - .withRow("observation-4", makeEid(1), RowFactory.create("name5", 4), - oneEntryMap(3, ONE_MY_EXTENSION)) - .withRow("observation-5", makeEid(0), null, null) - .withRow("observation-5", makeEid(1), null, null) - .build(); - - when(dataSource.read(ResourceType.OBSERVATION)) - .thenReturn(resourceLikeDataset); - final ResourceCollection baseResourceCollection = ResourceCollection - .build(fhirContext, dataSource, ResourceType.OBSERVATION, "Observation"); - - final Dataset elementDataset = toElementDataset(resourceLikeDataset, - baseResourceCollection); - - final ElementDefinition codeDefinition = checkPresent(FhirHelpers - .getChildOfResource(fhirContext, "Observation", "code")); - - final PrimitivePath inputPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .definition(codeDefinition) - .dataset(elementDataset) - .idAndEidAndValueColumns() - .expression("code") - .singular(false) - .currentResource(baseResourceCollection) - .buildDefined(); - - final StringLiteralPath argumentExpression = StringCollection - .fromLiteral("'" + "uuid:myExtension" + "'", inputPath); - - final NamedFunctionInput extensionInput = new NamedFunctionInput(parserContext, inputPath, - Collections.singletonList(argumentExpression)); - - final NamedFunction extension = NamedFunction.getInstance("extension"); - final Collection result = extension.invoke(extensionInput); - - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(DatasetBuilder.SIMPLE_EXTENSION_TYPE) - .withRow("observation-1", makeEid(0, 0), null) - .withRow("observation-1", makeEid(0, 1), null) - .withRow("observation-1", makeEid(0, 2), MANY_EXT_ROW_1) - .withRow("observation-1", makeEid(0, 3), MANY_EXT_ROW_2) - .withRow("observation-2", makeEid(0, 0), null) - .withRow("observation-2", makeEid(0, 1), ONE_EXT_ROW_1) - .withRow("observation-3", makeEid(0, 0), null) - .withRow("observation-3", makeEid(0, 1), null) - .withRow("observation-4", makeEid(0, 0), null) - .withRow("observation-4", makeEid(0, 1), ONE_EXT_ROW_1) - .withRow("observation-4", makeEid(1, 0), null) - .withRow("observation-5", makeEid(0, 0), null) - .withRow("observation-5", makeEid(1, 0), null) - .buildWithStructValue(); - - assertThat(result) - .hasExpression("code.extension('uuid:myExtension')") - .isNotSingular() - .isElementPath(PrimitivePath.class) - .hasFhirType(FHIRDefinedType.EXTENSION) - .selectOrderedResultWithEid() - .hasRows(expectedResult); - } - - @Test - public void throwsErrorIfArgumentIsNotString() { - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .build(); - final IntegerLiteralPath argument = IntegerLiteralPath.fromString("4", input); - - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .build(); - - final NamedFunctionInput extensionInput = new NamedFunctionInput(context, input, - Collections.singletonList(argument)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new ExtensionFunction().invoke(extensionInput)); - assertEquals("extension function must have argument of type String literal: .extension(4)", - error.getMessage()); - } - - @Test - public void throwsErrorIfMoreThanOneArgument() { - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .build(); - final StringLiteralPath argument1 = StringCollection.fromLiteral("'foo'", input), - argument2 = StringCollection.fromLiteral("'bar'", input); - - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(mock(TerminologyServiceFactory.class)) - .build(); - - final NamedFunctionInput extensionInput = new NamedFunctionInput(context, input, - Arrays.asList(argument1, argument2)); - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new ExtensionFunction().invoke(extensionInput)); - assertEquals("extension function must have one argument: .extension('foo', 'bar')", - error.getMessage()); - } + // TODO: implement with columns + + // @Autowired + // private SparkSession spark; + // + // @Autowired + // private FhirContext fhirContext; + // + // @MockBean + // private DataSource dataSource; + // + // @Test + // public void testExtensionOnResources() { + // + // final Dataset patientDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn("gender", DataTypes.StringType) + // .withColumn("active", DataTypes.BooleanType) + // .withFidColumn() + // .withExtensionColumn() + // .withRow("patient-1", "female", true, 1, oneEntryMap(1, MANY_MY_EXTENSIONS)) + // .withRow("patient-2", "female", false, 1, oneEntryMap(1, ONE_MY_EXTENSION)) + // .withRow("patient-3", "male", false, 1, oneEntryMap(1, NO_MY_EXTENSIONS)) + // .withRow("patient-4", "male", false, 1, oneEntryMap(2, ONE_MY_EXTENSION)) + // .withRow("patient-5", "male", true, 1, null) + // .build(); + // when(dataSource.read(ResourceType.PATIENT)) + // .thenReturn(patientDataset); + // final ResourceCollection inputPath = ResourceCollection + // .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient"); + // + // final StringLiteralPath argumentExpression = StringCollection + // .fromLiteral("'" + "uuid:myExtension" + "'", inputPath); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // + // final NamedFunctionInput extensionInput = new NamedFunctionInput(parserContext, inputPath, + // Collections.singletonList(argumentExpression)); + // + // final NamedFunction extension = NamedFunction.getInstance("extension"); + // final Collection result = extension.invoke(extensionInput); + // + // assertThat(result) + // .hasExpression("Patient.extension('uuid:myExtension')") + // .isNotSingular() + // .isElementPath(PrimitivePath.class) + // .hasFhirType(FHIRDefinedType.EXTENSION) + // .selectOrderedResult() + // .hasRows( + // // Multiple extensions of required type present on the resource + // RowFactory.create("patient-1", null), + // RowFactory.create("patient-1", null), + // RowFactory.create("patient-1", MANY_EXT_ROW_1), + // RowFactory.create("patient-1", MANY_EXT_ROW_2), + // // A single extension of the required type present on the resource + // RowFactory.create("patient-2", null), + // RowFactory.create("patient-2", ONE_EXT_ROW_1), + // // Not extensions of required type present + // RowFactory.create("patient-3", null), + // RowFactory.create("patient-3", null), + // // Extension of required type present but not on the resource + // RowFactory.create("patient-4", null), + // // No extensions present all together + // RowFactory.create("patient-5", null) + // ); + // } + // + // @Test + // public void testExtensionOnElements() { + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // + // // Construct element dataset from the resource dataset so that the resource path + // // can be used as the current resource for this element path + // // Note: this resource path is not singular as this will be a base for elements. + // + // final Dataset resourceLikeDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructColumn("name", DataTypes.StringType) + // .withStructColumn("_fid", DataTypes.IntegerType) + // .withStructValueColumn() + // .withExtensionColumn() + // .withRow("observation-1", makeEid(0), RowFactory.create("name1", 0), + // oneEntryMap(0, MANY_MY_EXTENSIONS)) + // .withRow("observation-2", makeEid(0), RowFactory.create("name2", 1), + // oneEntryMap(1, ONE_MY_EXTENSION)) + // .withRow("observation-3", makeEid(0), RowFactory.create("name3", 2), + // oneEntryMap(2, NO_MY_EXTENSIONS)) + // .withRow("observation-4", makeEid(0), RowFactory.create("name4", 3), + // oneEntryMap(3, ONE_MY_EXTENSION)) + // .withRow("observation-4", makeEid(1), RowFactory.create("name5", 4), + // oneEntryMap(3, ONE_MY_EXTENSION)) + // .withRow("observation-5", makeEid(0), null, null) + // .withRow("observation-5", makeEid(1), null, null) + // .build(); + // + // when(dataSource.read(ResourceType.OBSERVATION)) + // .thenReturn(resourceLikeDataset); + // final ResourceCollection baseResourceCollection = ResourceCollection + // .build(fhirContext, dataSource, ResourceType.OBSERVATION, "Observation"); + // + // final Dataset elementDataset = toElementDataset(resourceLikeDataset, + // baseResourceCollection); + // + // final ElementDefinition codeDefinition = checkPresent(FhirHelpers + // .getChildOfResource(fhirContext, "Observation", "code")); + // + // final PrimitivePath inputPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .definition(codeDefinition) + // .dataset(elementDataset) + // .idAndEidAndValueColumns() + // .expression("code") + // .singular(false) + // .currentResource(baseResourceCollection) + // .buildDefined(); + // + // final StringLiteralPath argumentExpression = StringCollection + // .fromLiteral("'" + "uuid:myExtension" + "'", inputPath); + // + // final NamedFunctionInput extensionInput = new NamedFunctionInput(parserContext, inputPath, + // Collections.singletonList(argumentExpression)); + // + // final NamedFunction extension = NamedFunction.getInstance("extension"); + // final Collection result = extension.invoke(extensionInput); + // + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(DatasetBuilder.SIMPLE_EXTENSION_TYPE) + // .withRow("observation-1", makeEid(0, 0), null) + // .withRow("observation-1", makeEid(0, 1), null) + // .withRow("observation-1", makeEid(0, 2), MANY_EXT_ROW_1) + // .withRow("observation-1", makeEid(0, 3), MANY_EXT_ROW_2) + // .withRow("observation-2", makeEid(0, 0), null) + // .withRow("observation-2", makeEid(0, 1), ONE_EXT_ROW_1) + // .withRow("observation-3", makeEid(0, 0), null) + // .withRow("observation-3", makeEid(0, 1), null) + // .withRow("observation-4", makeEid(0, 0), null) + // .withRow("observation-4", makeEid(0, 1), ONE_EXT_ROW_1) + // .withRow("observation-4", makeEid(1, 0), null) + // .withRow("observation-5", makeEid(0, 0), null) + // .withRow("observation-5", makeEid(1, 0), null) + // .buildWithStructValue(); + // + // assertThat(result) + // .hasExpression("code.extension('uuid:myExtension')") + // .isNotSingular() + // .isElementPath(PrimitivePath.class) + // .hasFhirType(FHIRDefinedType.EXTENSION) + // .selectOrderedResultWithEid() + // .hasRows(expectedResult); + // } + // + // @Test + // public void throwsErrorIfArgumentIsNotString() { + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .build(); + // final IntegerLiteralPath argument = IntegerLiteralPath.fromString("4", input); + // + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .build(); + // + // final NamedFunctionInput extensionInput = new NamedFunctionInput(context, input, + // Collections.singletonList(argument)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new ExtensionFunction().invoke(extensionInput)); + // assertEquals("extension function must have argument of type String literal: .extension(4)", + // error.getMessage()); + // } + // + // @Test + // public void throwsErrorIfMoreThanOneArgument() { + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .build(); + // final StringLiteralPath argument1 = StringCollection.fromLiteral("'foo'", input), + // argument2 = StringCollection.fromLiteral("'bar'", input); + // + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .terminologyClientFactory(mock(TerminologyServiceFactory.class)) + // .build(); + // + // final NamedFunctionInput extensionInput = new NamedFunctionInput(context, input, + // Arrays.asList(argument1, argument2)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new ExtensionFunction().invoke(extensionInput)); + // assertEquals("extension function must have one argument: .extension('foo', 'bar')", + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/FirstFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/FirstFunctionTest.java index a8d39583b7..695157e5fc 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/FirstFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/FirstFunctionTest.java @@ -17,263 +17,235 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static au.csiro.pathling.utilities.Strings.randomAlias; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.when; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.io.source.DataSource; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.builders.ResourceDatasetBuilder; -import au.csiro.pathling.test.builders.ResourcePathBuilder; -import ca.uhn.fhir.context.FhirContext; -import java.util.Collections; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.apache.spark.sql.types.DataTypes; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; /** * @author Piotr Szul */ @SpringBootUnitTest +@NotImplemented class FirstFunctionTest { - - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @MockBean - DataSource dataSource; - - @Test - void firstOfRootResources() { - - final Dataset patientDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn("gender", DataTypes.StringType) - .withColumn("active", DataTypes.BooleanType) - .withRow("patient-1", "female", true) - .withRow("patient-2", "female", false) - .withRow("patient-3", "male", true) - .build(); - when(dataSource.read(ResourceType.PATIENT)) - .thenReturn(patientDataset); - final ResourceCollection inputPath = ResourceCollection - .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient"); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) - .build(); - - final NamedFunctionInput firstInput = new NamedFunctionInput(parserContext, inputPath, - Collections.emptyList()); - final NamedFunction firstFunction = NamedFunction.getInstance("first"); - final Collection result = firstFunction.invoke(firstInput); - - assertTrue(result instanceof ResourceCollection); - assertThat((ResourceCollection) result) - .hasExpression("Patient.first()") - .isSingular() - .hasResourceType(ResourceType.PATIENT); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", "patient-1") - .withRow("patient-2", "patient-2") - .withRow("patient-3", "patient-3") - .build(); - - assertThat(result) - .selectOrderedResult() - .hasRows(expectedDataset); - } - - @Test - void firstOfUngroupedSubResources() { - - final String subresourceId = randomAlias(); - final String statusColumn = randomAlias(); - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(subresourceId, DataTypes.StringType) - .withColumn(statusColumn, DataTypes.StringType) - .withRow("patient-1", makeEid(2), "Encounter/5", "in-progress") - .withRow("patient-1", makeEid(1), "Encounter/1", "in-progress") - .withRow("patient-1", makeEid(0), "Encounter/2", "finished") - .withRow("patient-2", makeEid(0), "Encounter/3", "in-progress") - .withRow("patient-3", null, null, null) - .build(); - final ResourceCollection inputPath = new ResourcePathBuilder(spark) - .expression("reverseResolve(Encounter.subject)") - .dataset(inputDataset) - .idEidAndValueColumns() - .valueColumn(inputDataset.col(subresourceId)) - .resourceType(ResourceType.ENCOUNTER) - .buildCustom(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) - .build(); - - final NamedFunctionInput firstInput = new NamedFunctionInput(parserContext, inputPath, - Collections.emptyList()); - final NamedFunction firstFunction = NamedFunction.getInstance("first"); - final Collection result = firstFunction.invoke(firstInput); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withIdColumn() - .withRow("patient-1", null, "Encounter/2") - .withRow("patient-2", null, "Encounter/3") - .withRow("patient-3", null, null) - .build(); - - assertThat(result) - .isResourcePath() - .hasExpression("reverseResolve(Encounter.subject).first()") - .isSingular() - .hasResourceType(ResourceType.ENCOUNTER) - .selectOrderedResultWithEid() - .hasRows(expectedDataset); - } - - @Test - void firstOfUngroupedElements() { - - // Check the result. - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", makeEid(0, 3), "Jude") // when: "two values" expect: "Jude" - .withRow("patient-1", makeEid(0, 2), "Mark") - .withRow("patient-1", makeEid(0, 1), "Mark") - .withRow("patient-1", makeEid(0, 0), "Zaak") - .withRow("patient-2", makeEid(0, 0), "Samuel") // when: "single value" expect: "Samuel" - .withRow("patient-3", makeEid(0, 1), "Adam") // when: "leading null" expect: "Adam" - .withRow("patient-3", makeEid(0, 0), null) - .withRow("patient-4", makeEid(0, 1), null) // when: "trailing null" expect: "John - .withRow("patient-4", makeEid(0, 0), "John") - .withRow("patient-5", null, null) // when: "single null" expect: null - .withRow("patient-6", null, null) // when: "many nulls" expect: null - .withRow("patient-6", null, null) - .build(); - - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("name") - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(input.getIdColumn())) - .build(); - - final NamedFunctionInput firstInput = new NamedFunctionInput(parserContext, input, - Collections.emptyList()); - - final NamedFunction firstFunction = NamedFunction.getInstance("first"); - final Collection result = firstFunction.invoke(firstInput); - - assertTrue(result instanceof StringCollection); - assertThat((PrimitivePath) result) - .hasExpression("name.first()") - .isSingular() - .hasFhirType(FHIRDefinedType.STRING); - - // expected result dataset - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", null, "Zaak") - .withRow("patient-2", null, "Samuel") - .withRow("patient-3", null, "Adam") - .withRow("patient-4", null, "John") - .withRow("patient-5", null, null) - .withRow("patient-6", null, null) - .build(); - - assertThat(result) - .selectOrderedResultWithEid() - .hasRows(expectedDataset); - } - - @Test - void illegalToCallFirstOnGrouping() { - final Dataset inputDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn("gender", DataTypes.StringType) - .withColumn("active", DataTypes.BooleanType) - .withRow("patient-1", "female", true) - .withRow("patient-2", "female", false) - .withRow("patient-2", "male", true) - .build(); - when(dataSource.read(ResourceType.PATIENT)).thenReturn(inputDataset); - final ResourceCollection inputPath = new ResourcePathBuilder(spark) - .database(dataSource) - .resourceType(ResourceType.PATIENT) - .expression("Patient") - .build(); - - final Column groupingColumn = inputPath.getElementColumn("gender").orElseThrow(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(groupingColumn)) - .inputExpression("Patient") - .build(); - final NamedFunctionInput firstInput = new NamedFunctionInput(parserContext, inputPath, - Collections.emptyList()); - final NamedFunction firstFunction = NamedFunction.getInstance("first"); - - final IllegalStateException error = assertThrows( - IllegalStateException.class, - () -> firstFunction.invoke(firstInput)); - assertEquals( - "Orderable path expected", - error.getMessage()); - } - - @Test - void inputMustNotContainArguments() { - final PrimitivePath inputPath = new ElementPathBuilder(spark).build(); - final PrimitivePath argumentPath = new ElementPathBuilder(spark).build(); - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - - final NamedFunctionInput firstInput = new NamedFunctionInput(parserContext, inputPath, - Collections.singletonList(argumentPath)); - - final NamedFunction firstFunction = NamedFunction.getInstance("first"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> firstFunction.invoke(firstInput)); - assertEquals( - "Arguments can not be passed to first function", - error.getMessage()); - } + + // TODO: implement with columns + + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @MockBean + // DataSource dataSource; + // + // @Test + // void firstOfRootResources() { + // + // final Dataset patientDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn("gender", DataTypes.StringType) + // .withColumn("active", DataTypes.BooleanType) + // .withRow("patient-1", "female", true) + // .withRow("patient-2", "female", false) + // .withRow("patient-3", "male", true) + // .build(); + // when(dataSource.read(ResourceType.PATIENT)) + // .thenReturn(patientDataset); + // final ResourceCollection inputPath = ResourceCollection + // .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient"); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) + // .build(); + // + // final NamedFunctionInput firstInput = new NamedFunctionInput(parserContext, inputPath, + // Collections.emptyList()); + // final NamedFunction firstFunction = NamedFunction.getInstance("first"); + // final Collection result = firstFunction.invoke(firstInput); + // + // assertTrue(result instanceof ResourceCollection); + // assertThat((ResourceCollection) result) + // .hasExpression("Patient.first()") + // .isSingular() + // .hasResourceType(ResourceType.PATIENT); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "patient-1") + // .withRow("patient-2", "patient-2") + // .withRow("patient-3", "patient-3") + // .build(); + // + // assertThat(result) + // .selectOrderedResult() + // .hasRows(expectedDataset); + // } + // + // @Test + // void firstOfUngroupedSubResources() { + // + // final String subresourceId = randomAlias(); + // final String statusColumn = randomAlias(); + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(subresourceId, DataTypes.StringType) + // .withColumn(statusColumn, DataTypes.StringType) + // .withRow("patient-1", makeEid(2), "Encounter/5", "in-progress") + // .withRow("patient-1", makeEid(1), "Encounter/1", "in-progress") + // .withRow("patient-1", makeEid(0), "Encounter/2", "finished") + // .withRow("patient-2", makeEid(0), "Encounter/3", "in-progress") + // .withRow("patient-3", null, null, null) + // .build(); + // final ResourceCollection inputPath = new ResourcePathBuilder(spark) + // .expression("reverseResolve(Encounter.subject)") + // .dataset(inputDataset) + // .idEidAndValueColumns() + // .valueColumn(inputDataset.col(subresourceId)) + // .resourceType(ResourceType.ENCOUNTER) + // .buildCustom(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) + // .build(); + // + // final NamedFunctionInput firstInput = new NamedFunctionInput(parserContext, inputPath, + // Collections.emptyList()); + // final NamedFunction firstFunction = NamedFunction.getInstance("first"); + // final Collection result = firstFunction.invoke(firstInput); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withIdColumn() + // .withRow("patient-1", null, "Encounter/2") + // .withRow("patient-2", null, "Encounter/3") + // .withRow("patient-3", null, null) + // .build(); + // + // assertThat(result) + // .isResourcePath() + // .hasExpression("reverseResolve(Encounter.subject).first()") + // .isSingular() + // .hasResourceType(ResourceType.ENCOUNTER) + // .selectOrderedResultWithEid() + // .hasRows(expectedDataset); + // } + // + // @Test + // void firstOfUngroupedElements() { + // + // // Check the result. + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", makeEid(0, 3), "Jude") // when: "two values" expect: "Jude" + // .withRow("patient-1", makeEid(0, 2), "Mark") + // .withRow("patient-1", makeEid(0, 1), "Mark") + // .withRow("patient-1", makeEid(0, 0), "Zaak") + // .withRow("patient-2", makeEid(0, 0), "Samuel") // when: "single value" expect: "Samuel" + // .withRow("patient-3", makeEid(0, 1), "Adam") // when: "leading null" expect: "Adam" + // .withRow("patient-3", makeEid(0, 0), null) + // .withRow("patient-4", makeEid(0, 1), null) // when: "trailing null" expect: "John + // .withRow("patient-4", makeEid(0, 0), "John") + // .withRow("patient-5", null, null) // when: "single null" expect: null + // .withRow("patient-6", null, null) // when: "many nulls" expect: null + // .withRow("patient-6", null, null) + // .build(); + // + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("name") + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(input.getIdColumn())) + // .build(); + // + // final NamedFunctionInput firstInput = new NamedFunctionInput(parserContext, input, + // Collections.emptyList()); + // + // final NamedFunction firstFunction = NamedFunction.getInstance("first"); + // final Collection result = firstFunction.invoke(firstInput); + // + // assertTrue(result instanceof StringCollection); + // assertThat((PrimitivePath) result) + // .hasExpression("name.first()") + // .isSingular() + // .hasFhirType(FHIRDefinedType.STRING); + // + // // expected result dataset + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", null, "Zaak") + // .withRow("patient-2", null, "Samuel") + // .withRow("patient-3", null, "Adam") + // .withRow("patient-4", null, "John") + // .withRow("patient-5", null, null) + // .withRow("patient-6", null, null) + // .build(); + // + // assertThat(result) + // .selectOrderedResultWithEid() + // .hasRows(expectedDataset); + // } + // + // @Test + // void illegalToCallFirstOnGrouping() { + // final Dataset inputDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn("gender", DataTypes.StringType) + // .withColumn("active", DataTypes.BooleanType) + // .withRow("patient-1", "female", true) + // .withRow("patient-2", "female", false) + // .withRow("patient-2", "male", true) + // .build(); + // when(dataSource.read(ResourceType.PATIENT)).thenReturn(inputDataset); + // final ResourceCollection inputPath = new ResourcePathBuilder(spark) + // .database(dataSource) + // .resourceType(ResourceType.PATIENT) + // .expression("Patient") + // .build(); + // + // final Column groupingColumn = inputPath.getElementColumn("gender").orElseThrow(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(groupingColumn)) + // .inputExpression("Patient") + // .build(); + // final NamedFunctionInput firstInput = new NamedFunctionInput(parserContext, inputPath, + // Collections.emptyList()); + // final NamedFunction firstFunction = NamedFunction.getInstance("first"); + // + // final IllegalStateException error = assertThrows( + // IllegalStateException.class, + // () -> firstFunction.invoke(firstInput)); + // assertEquals( + // "Orderable path expected", + // error.getMessage()); + // } + // + // @Test + // void inputMustNotContainArguments() { + // final PrimitivePath inputPath = new ElementPathBuilder(spark).build(); + // final PrimitivePath argumentPath = new ElementPathBuilder(spark).build(); + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // + // final NamedFunctionInput firstInput = new NamedFunctionInput(parserContext, inputPath, + // Collections.singletonList(argumentPath)); + // + // final NamedFunction firstFunction = NamedFunction.getInstance("first"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> firstFunction.invoke(firstInput)); + // assertEquals( + // "Arguments can not be passed to first function", + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/IifFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/IifFunctionTest.java index 649b23daa8..557ebafb37 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/IifFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/IifFunctionTest.java @@ -17,558 +17,526 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.when; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.collection.UntypedResourcePath; -import au.csiro.pathling.fhirpath.collection.IntegerCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.io.source.DataSource; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.builders.ResourceDatasetBuilder; -import au.csiro.pathling.test.builders.ResourcePathBuilder; -import au.csiro.pathling.test.builders.UntypedResourcePathBuilder; -import ca.uhn.fhir.context.FhirContext; -import java.util.Arrays; -import java.util.Collections; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.apache.spark.sql.types.DataTypes; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class IifFunctionTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @MockBean - DataSource dataSource; - - - static final String ID_ALIAS = "_abc123"; - - ParserContext parserContext; - - @BeforeEach - void setUp() { - parserContext = new ParserContextBuilder(spark, fhirContext).build(); - } - - @Test - void returnsCorrectResultsForTwoLiterals() { - final Dataset inputContextDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withRow("observation-1") - .withRow("observation-2") - .withRow("observation-3") - .withRow("observation-4") - .withRow("observation-5") - .build(); - when(dataSource.read(ResourceType.OBSERVATION)).thenReturn(inputContextDataset); - final ResourceCollection inputContext = new ResourcePathBuilder(spark) - .expression("Observation") - .resourceType(ResourceType.OBSERVATION) - .database(dataSource) - .singular(true) - .build(); - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withEidColumn() - .withColumn(DataTypes.BooleanType) - .withRow("observation-1", makeEid(0), true) - .withRow("observation-2", makeEid(0), false) - .withRow("observation-3", makeEid(0), null) - .withRow("observation-4", makeEid(0), true) - .withRow("observation-4", makeEid(1), false) - .withRow("observation-5", makeEid(0), null) - .withRow("observation-5", makeEid(1), null) - .build(); - final PrimitivePath inputPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("valueBoolean") - .singular(false) - .build(); - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) - .build(); - final NonLiteralPath condition = inputPath.toThisPath(); - final StringLiteralPath ifTrue = StringCollection.fromLiteral("foo", inputContext); - final StringLiteralPath otherwise = StringCollection.fromLiteral("bar", inputContext); - - final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, inputPath, - Arrays.asList(condition, ifTrue, otherwise)); - final Collection result = NamedFunction.getInstance("iif").invoke(iifInput); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("observation-1", makeEid(0), "foo") - .withRow("observation-2", makeEid(0), "bar") - .withRow("observation-3", makeEid(0), "bar") - .withRow("observation-4", makeEid(0), "foo") - .withRow("observation-4", makeEid(1), "bar") - .withRow("observation-5", makeEid(0), "bar") - .withRow("observation-5", makeEid(1), "bar") - .build(); - assertThat(result) - .hasExpression("valueBoolean.iif($this, 'foo', 'bar')") - .isNotSingular() - .isElementPath(StringCollection.class) - .selectOrderedResultWithEid() - .hasRowsUnordered(expectedDataset); - } - - @Test - void returnsCorrectResultsForTwoNonLiterals() { - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withEidColumn() - .withColumn(DataTypes.BooleanType) - .withRow("observation-1", makeEid(0), false) - .withRow("observation-2", makeEid(0), true) - .withRow("observation-3", makeEid(0), null) - .withRow("observation-4", makeEid(0), true) - .withRow("observation-4", makeEid(1), false) - .withRow("observation-5", makeEid(0), null) - .withRow("observation-5", makeEid(1), null) - .build(); - final PrimitivePath inputPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("valueBoolean") - .singular(false) - .build(); - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) - .build(); - final NonLiteralPath condition = inputPath.toThisPath(); - - final Dataset ifTrueDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withEidColumn() - .withColumn(DataTypes.IntegerType) - .withRow("observation-1", makeEid(0), 1) - .withRow("observation-2", makeEid(0), 2) - .withRow("observation-3", makeEid(0), 3) - .withRow("observation-4", makeEid(0), 4) - .withRow("observation-5", makeEid(0), 5) - .build(); - final PrimitivePath ifTrue = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .dataset(ifTrueDataset) - .idAndEidAndValueColumns() - .expression("someInteger") - .singular(true) - .build(); - - final Dataset otherwiseDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withEidColumn() - .withColumn(DataTypes.IntegerType) - .withRow("observation-1", makeEid(0), 11) - .withRow("observation-1", makeEid(0), 16) - .withRow("observation-2", makeEid(0), 12) - .withRow("observation-3", makeEid(0), 13) - .withRow("observation-4", makeEid(0), 14) - .withRow("observation-5", makeEid(0), 15) - .build(); - final PrimitivePath otherwise = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .dataset(otherwiseDataset) - .idAndEidAndValueColumns() - .expression("anotherInteger") - .singular(true) - .build(); - - final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, inputPath, - Arrays.asList(condition, ifTrue, otherwise)); - final Collection result = NamedFunction.getInstance("iif").invoke(iifInput); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withEidColumn() - .withColumn(DataTypes.IntegerType) - .withRow("observation-1", makeEid(0), 11) - .withRow("observation-1", makeEid(0), 16) - .withRow("observation-2", makeEid(0), 2) - .withRow("observation-3", makeEid(0), 13) - .withRow("observation-4", makeEid(0), 4) - .withRow("observation-4", makeEid(1), 14) - .withRow("observation-5", makeEid(0), 15) - .withRow("observation-5", makeEid(1), 15) - .build(); - assertThat(result) - .hasExpression("valueBoolean.iif($this, someInteger, anotherInteger)") - .isNotSingular() - .isElementPath(IntegerCollection.class) - .selectOrderedResultWithEid() - .hasRowsUnordered(expectedDataset); - } - - @Test - void returnsCorrectResultsForLiteralAndNonLiteral() { - final Dataset inputContextDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withRow("observation-1") - .withRow("observation-2") - .withRow("observation-3") - .withRow("observation-4") - .withRow("observation-5") - .build(); - when(dataSource.read(ResourceType.OBSERVATION)).thenReturn(inputContextDataset); - final ResourceCollection inputContext = new ResourcePathBuilder(spark) - .expression("Observation") - .resourceType(ResourceType.OBSERVATION) - .database(dataSource) - .singular(true) - .build(); - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withEidColumn() - .withColumn(DataTypes.BooleanType) - .withRow("observation-1", makeEid(0), false) - .withRow("observation-2", makeEid(0), true) - .withRow("observation-3", makeEid(0), null) - .withRow("observation-4", makeEid(0), true) - .withRow("observation-4", makeEid(1), false) - .withRow("observation-5", makeEid(0), null) - .withRow("observation-5", makeEid(1), null) - .build(); - final PrimitivePath inputPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("valueBoolean") - .singular(false) - .build(); - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) - .build(); - final NonLiteralPath condition = inputPath.toThisPath(); - - final Dataset ifTrueDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withEidColumn() - .withColumn(DataTypes.IntegerType) - .withRow("observation-1", makeEid(0), 1) - .withRow("observation-2", makeEid(0), 2) - .withRow("observation-3", makeEid(0), 3) - .withRow("observation-4", makeEid(0), 4) - .withRow("observation-5", makeEid(0), 5) - .build(); - final PrimitivePath ifTrue = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .dataset(ifTrueDataset) - .idAndEidAndValueColumns() - .expression("someInteger") - .singular(true) - .build(); - - final IntegerLiteralPath otherwise = IntegerLiteralPath.fromString("99", inputContext); - - final NamedFunctionInput iifInput1 = new NamedFunctionInput(parserContext, inputPath, - Arrays.asList(condition, ifTrue, otherwise)); - final Collection result1 = NamedFunction.getInstance("iif").invoke(iifInput1); - - final Dataset expectedDataset1 = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withEidColumn() - .withColumn(DataTypes.IntegerType) - .withRow("observation-1", makeEid(0), 99) - .withRow("observation-2", makeEid(0), 2) - .withRow("observation-3", makeEid(0), 99) - .withRow("observation-4", makeEid(0), 4) - .withRow("observation-4", makeEid(1), 99) - .withRow("observation-5", makeEid(0), 99) - .withRow("observation-5", makeEid(1), 99) - .build(); - assertThat(result1) - .hasExpression("valueBoolean.iif($this, someInteger, 99)") - .isNotSingular() - .isElementPath(IntegerCollection.class) - .selectOrderedResultWithEid() - .hasRows(expectedDataset1); - - final NamedFunctionInput iifInput2 = new NamedFunctionInput(parserContext, inputPath, - Arrays.asList(condition, otherwise, ifTrue)); - final Collection result2 = NamedFunction.getInstance("iif").invoke(iifInput2); - - final Dataset expectedDataset2 = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withEidColumn() - .withColumn(DataTypes.IntegerType) - .withRow("observation-1", makeEid(0), 1) - .withRow("observation-2", makeEid(0), 99) - .withRow("observation-3", makeEid(0), 3) - .withRow("observation-4", makeEid(0), 99) - .withRow("observation-4", makeEid(1), 4) - .withRow("observation-5", makeEid(0), 5) - .withRow("observation-5", makeEid(1), 5) - .build(); - assertThat(result2) - .hasExpression("valueBoolean.iif($this, 99, someInteger)") - .isNotSingular() - .isElementPath(IntegerCollection.class) - .selectOrderedResultWithEid() - .hasRows(expectedDataset2); - } - - @Test - void throwsErrorIfConditionNotBoolean() { - final PrimitivePath condition = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .expression("valueInteger") - .singular(true) - .build(); - final StringLiteralPath ifTrue = StringCollection.fromLiteral("foo", condition); - final StringLiteralPath otherwise = StringCollection.fromLiteral("bar", condition); - - final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, - Arrays.asList(condition, ifTrue, otherwise)); - - final NamedFunction notFunction = NamedFunction.getInstance("iif"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> notFunction.invoke(iifInput)); - assertEquals( - "Condition argument to iif must be Boolean: valueInteger", - error.getMessage()); - } - - @Test - void throwsErrorIfConditionNotSingular() { - final PrimitivePath condition = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .expression("valueBoolean") - .singular(false) - .build(); - final StringLiteralPath ifTrue = StringCollection.fromLiteral("foo", condition); - final StringLiteralPath otherwise = StringCollection.fromLiteral("bar", condition); - - final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, - Arrays.asList(condition, ifTrue, otherwise)); - - final NamedFunction notFunction = NamedFunction.getInstance("iif"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> notFunction.invoke(iifInput)); - assertEquals( - "Condition argument to iif must be singular: valueBoolean", - error.getMessage()); - } - - @Test - void throwsErrorIfNoThisInCondition() { - final PrimitivePath condition = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .expression("valueBoolean") - .singular(true) - .build(); - final StringLiteralPath ifTrue = StringCollection.fromLiteral("foo", condition); - final StringLiteralPath otherwise = StringCollection.fromLiteral("bar", condition); - - final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, - Arrays.asList(condition, ifTrue, otherwise)); - - final NamedFunction notFunction = NamedFunction.getInstance("iif"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> notFunction.invoke(iifInput)); - assertEquals( - "Condition argument to iif function must be navigable from collection item (use $this): valueBoolean", - error.getMessage()); - } - - @Test - void throwsErrorIfResultArgumentsDifferentTypes() { - final NonLiteralPath condition = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .expression("valueBoolean") - .singular(true) - .build() - .toThisPath(); - final StringLiteralPath ifTrue = StringCollection.fromLiteral("foo", condition); - final IntegerLiteralPath otherwise = IntegerLiteralPath.fromString("99", condition); - - final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, - Arrays.asList(condition, ifTrue, otherwise)); - - final NamedFunction notFunction = NamedFunction.getInstance("iif"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> notFunction.invoke(iifInput)); - assertEquals( - "Paths cannot be merged into a collection together: 'foo', 99", - error.getMessage()); - } - - @Test - void throwsErrorIfResultArgumentsAreBackboneElements() { - final NonLiteralPath condition = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .expression("valueBoolean") - .singular(true) - .build() - .toThisPath(); - final PrimitivePath ifTrue = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BACKBONEELEMENT) - .expression("someBackboneElement") - .build(); - final PrimitivePath otherwise = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BACKBONEELEMENT) - .expression("anotherBackboneElement") - .build(); - - final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, - Arrays.asList(condition, ifTrue, otherwise)); - - final NamedFunction notFunction = NamedFunction.getInstance("iif"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> notFunction.invoke(iifInput)); - assertEquals( - "Paths cannot be merged into a collection together: someBackboneElement, " - + "anotherBackboneElement", error.getMessage()); - } - - @Test - void throwsErrorWithIncompatibleResourceResults() { - final NonLiteralPath condition = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .expression("valueBoolean") - .singular(true) - .build() - .toThisPath(); - final ResourceCollection ifTrue = new ResourcePathBuilder(spark) - .expression("someResource") - .resourceType(ResourceType.PATIENT) - .build(); - final ResourceCollection otherwise = new ResourcePathBuilder(spark) - .expression("anotherResource") - .resourceType(ResourceType.CONDITION) - .build(); - - final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, - Arrays.asList(condition, ifTrue, otherwise)); - - final NamedFunction notFunction = NamedFunction.getInstance("iif"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> notFunction.invoke(iifInput)); - assertEquals( - "Paths cannot be merged into a collection together: someResource, anotherResource", - error.getMessage()); - } - - @Test - void throwsErrorWithResourceAndLiteralResults() { - final NonLiteralPath condition = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .expression("valueBoolean") - .singular(true) - .build() - .toThisPath(); - final ResourceCollection ifTrue = new ResourcePathBuilder(spark) - .expression("someResource") - .resourceType(ResourceType.PATIENT) - .build(); - final StringLiteralPath otherwise = StringCollection.fromLiteral("foo", condition); - - final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, - Arrays.asList(condition, ifTrue, otherwise)); - - final NamedFunction notFunction = NamedFunction.getInstance("iif"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> notFunction.invoke(iifInput)); - assertEquals( - "Paths cannot be merged into a collection together: someResource, 'foo'", - error.getMessage()); - } - - @Test - void throwsErrorWithUntypedResourceAndLiteralResults() { - final NonLiteralPath condition = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .expression("valueBoolean") - .singular(true) - .build() - .toThisPath(); - final UntypedResourcePath ifTrue = new UntypedResourcePathBuilder(spark) - .expression("someUntypedResource") - .build(); - final StringLiteralPath otherwise = StringCollection.fromLiteral("foo", condition); - - final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, - Arrays.asList(condition, ifTrue, otherwise)); - - final NamedFunction notFunction = NamedFunction.getInstance("iif"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> notFunction.invoke(iifInput)); - assertEquals( - "Paths cannot be merged into a collection together: someUntypedResource, 'foo'", - error.getMessage()); - } - - @Test - void throwsErrorWithElementAndResourceResults() { - final NonLiteralPath condition = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .expression("valueBoolean") - .singular(true) - .build() - .toThisPath(); - final PrimitivePath ifTrue = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .expression("someString") - .build(); - final ResourceCollection otherwise = new ResourcePathBuilder(spark) - .expression("someResource") - .resourceType(ResourceType.CONDITION) - .build(); - - final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, - Arrays.asList(condition, ifTrue, otherwise)); - - final NamedFunction notFunction = NamedFunction.getInstance("iif"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> notFunction.invoke(iifInput)); - assertEquals( - "Paths cannot be merged into a collection together: someString, someResource", - error.getMessage()); - } + // TODO: implement with columns + + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @MockBean + // DataSource dataSource; + // + // + // static final String ID_ALIAS = "_abc123"; + // + // ParserContext parserContext; + // + // @BeforeEach + // void setUp() { + // parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // } + // + // @Test + // void returnsCorrectResultsForTwoLiterals() { + // final Dataset inputContextDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withRow("observation-1") + // .withRow("observation-2") + // .withRow("observation-3") + // .withRow("observation-4") + // .withRow("observation-5") + // .build(); + // when(dataSource.read(ResourceType.OBSERVATION)).thenReturn(inputContextDataset); + // final ResourceCollection inputContext = new ResourcePathBuilder(spark) + // .expression("Observation") + // .resourceType(ResourceType.OBSERVATION) + // .database(dataSource) + // .singular(true) + // .build(); + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withEidColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow("observation-1", makeEid(0), true) + // .withRow("observation-2", makeEid(0), false) + // .withRow("observation-3", makeEid(0), null) + // .withRow("observation-4", makeEid(0), true) + // .withRow("observation-4", makeEid(1), false) + // .withRow("observation-5", makeEid(0), null) + // .withRow("observation-5", makeEid(1), null) + // .build(); + // final PrimitivePath inputPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("valueBoolean") + // .singular(false) + // .build(); + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) + // .build(); + // final NonLiteralPath condition = inputPath.toThisPath(); + // final StringLiteralPath ifTrue = StringCollection.fromLiteral("foo", inputContext); + // final StringLiteralPath otherwise = StringCollection.fromLiteral("bar", inputContext); + // + // final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, inputPath, + // Arrays.asList(condition, ifTrue, otherwise)); + // final Collection result = NamedFunction.getInstance("iif").invoke(iifInput); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("observation-1", makeEid(0), "foo") + // .withRow("observation-2", makeEid(0), "bar") + // .withRow("observation-3", makeEid(0), "bar") + // .withRow("observation-4", makeEid(0), "foo") + // .withRow("observation-4", makeEid(1), "bar") + // .withRow("observation-5", makeEid(0), "bar") + // .withRow("observation-5", makeEid(1), "bar") + // .build(); + // assertThat(result) + // .hasExpression("valueBoolean.iif($this, 'foo', 'bar')") + // .isNotSingular() + // .isElementPath(StringCollection.class) + // .selectOrderedResultWithEid() + // .hasRowsUnordered(expectedDataset); + // } + // + // @Test + // void returnsCorrectResultsForTwoNonLiterals() { + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withEidColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow("observation-1", makeEid(0), false) + // .withRow("observation-2", makeEid(0), true) + // .withRow("observation-3", makeEid(0), null) + // .withRow("observation-4", makeEid(0), true) + // .withRow("observation-4", makeEid(1), false) + // .withRow("observation-5", makeEid(0), null) + // .withRow("observation-5", makeEid(1), null) + // .build(); + // final PrimitivePath inputPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("valueBoolean") + // .singular(false) + // .build(); + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) + // .build(); + // final NonLiteralPath condition = inputPath.toThisPath(); + // + // final Dataset ifTrueDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withEidColumn() + // .withColumn(DataTypes.IntegerType) + // .withRow("observation-1", makeEid(0), 1) + // .withRow("observation-2", makeEid(0), 2) + // .withRow("observation-3", makeEid(0), 3) + // .withRow("observation-4", makeEid(0), 4) + // .withRow("observation-5", makeEid(0), 5) + // .build(); + // final PrimitivePath ifTrue = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .dataset(ifTrueDataset) + // .idAndEidAndValueColumns() + // .expression("someInteger") + // .singular(true) + // .build(); + // + // final Dataset otherwiseDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withEidColumn() + // .withColumn(DataTypes.IntegerType) + // .withRow("observation-1", makeEid(0), 11) + // .withRow("observation-1", makeEid(0), 16) + // .withRow("observation-2", makeEid(0), 12) + // .withRow("observation-3", makeEid(0), 13) + // .withRow("observation-4", makeEid(0), 14) + // .withRow("observation-5", makeEid(0), 15) + // .build(); + // final PrimitivePath otherwise = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .dataset(otherwiseDataset) + // .idAndEidAndValueColumns() + // .expression("anotherInteger") + // .singular(true) + // .build(); + // + // final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, inputPath, + // Arrays.asList(condition, ifTrue, otherwise)); + // final Collection result = NamedFunction.getInstance("iif").invoke(iifInput); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withEidColumn() + // .withColumn(DataTypes.IntegerType) + // .withRow("observation-1", makeEid(0), 11) + // .withRow("observation-1", makeEid(0), 16) + // .withRow("observation-2", makeEid(0), 2) + // .withRow("observation-3", makeEid(0), 13) + // .withRow("observation-4", makeEid(0), 4) + // .withRow("observation-4", makeEid(1), 14) + // .withRow("observation-5", makeEid(0), 15) + // .withRow("observation-5", makeEid(1), 15) + // .build(); + // assertThat(result) + // .hasExpression("valueBoolean.iif($this, someInteger, anotherInteger)") + // .isNotSingular() + // .isElementPath(IntegerCollection.class) + // .selectOrderedResultWithEid() + // .hasRowsUnordered(expectedDataset); + // } + // + // @Test + // void returnsCorrectResultsForLiteralAndNonLiteral() { + // final Dataset inputContextDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withRow("observation-1") + // .withRow("observation-2") + // .withRow("observation-3") + // .withRow("observation-4") + // .withRow("observation-5") + // .build(); + // when(dataSource.read(ResourceType.OBSERVATION)).thenReturn(inputContextDataset); + // final ResourceCollection inputContext = new ResourcePathBuilder(spark) + // .expression("Observation") + // .resourceType(ResourceType.OBSERVATION) + // .database(dataSource) + // .singular(true) + // .build(); + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withEidColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow("observation-1", makeEid(0), false) + // .withRow("observation-2", makeEid(0), true) + // .withRow("observation-3", makeEid(0), null) + // .withRow("observation-4", makeEid(0), true) + // .withRow("observation-4", makeEid(1), false) + // .withRow("observation-5", makeEid(0), null) + // .withRow("observation-5", makeEid(1), null) + // .build(); + // final PrimitivePath inputPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("valueBoolean") + // .singular(false) + // .build(); + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) + // .build(); + // final NonLiteralPath condition = inputPath.toThisPath(); + // + // final Dataset ifTrueDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withEidColumn() + // .withColumn(DataTypes.IntegerType) + // .withRow("observation-1", makeEid(0), 1) + // .withRow("observation-2", makeEid(0), 2) + // .withRow("observation-3", makeEid(0), 3) + // .withRow("observation-4", makeEid(0), 4) + // .withRow("observation-5", makeEid(0), 5) + // .build(); + // final PrimitivePath ifTrue = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .dataset(ifTrueDataset) + // .idAndEidAndValueColumns() + // .expression("someInteger") + // .singular(true) + // .build(); + // + // final IntegerLiteralPath otherwise = IntegerLiteralPath.fromString("99", inputContext); + // + // final NamedFunctionInput iifInput1 = new NamedFunctionInput(parserContext, inputPath, + // Arrays.asList(condition, ifTrue, otherwise)); + // final Collection result1 = NamedFunction.getInstance("iif").invoke(iifInput1); + // + // final Dataset expectedDataset1 = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withEidColumn() + // .withColumn(DataTypes.IntegerType) + // .withRow("observation-1", makeEid(0), 99) + // .withRow("observation-2", makeEid(0), 2) + // .withRow("observation-3", makeEid(0), 99) + // .withRow("observation-4", makeEid(0), 4) + // .withRow("observation-4", makeEid(1), 99) + // .withRow("observation-5", makeEid(0), 99) + // .withRow("observation-5", makeEid(1), 99) + // .build(); + // assertThat(result1) + // .hasExpression("valueBoolean.iif($this, someInteger, 99)") + // .isNotSingular() + // .isElementPath(IntegerCollection.class) + // .selectOrderedResultWithEid() + // .hasRows(expectedDataset1); + // + // final NamedFunctionInput iifInput2 = new NamedFunctionInput(parserContext, inputPath, + // Arrays.asList(condition, otherwise, ifTrue)); + // final Collection result2 = NamedFunction.getInstance("iif").invoke(iifInput2); + // + // final Dataset expectedDataset2 = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withEidColumn() + // .withColumn(DataTypes.IntegerType) + // .withRow("observation-1", makeEid(0), 1) + // .withRow("observation-2", makeEid(0), 99) + // .withRow("observation-3", makeEid(0), 3) + // .withRow("observation-4", makeEid(0), 99) + // .withRow("observation-4", makeEid(1), 4) + // .withRow("observation-5", makeEid(0), 5) + // .withRow("observation-5", makeEid(1), 5) + // .build(); + // assertThat(result2) + // .hasExpression("valueBoolean.iif($this, 99, someInteger)") + // .isNotSingular() + // .isElementPath(IntegerCollection.class) + // .selectOrderedResultWithEid() + // .hasRows(expectedDataset2); + // } + // + // @Test + // void throwsErrorIfConditionNotBoolean() { + // final PrimitivePath condition = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .expression("valueInteger") + // .singular(true) + // .build(); + // final StringLiteralPath ifTrue = StringCollection.fromLiteral("foo", condition); + // final StringLiteralPath otherwise = StringCollection.fromLiteral("bar", condition); + // + // final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, + // Arrays.asList(condition, ifTrue, otherwise)); + // + // final NamedFunction notFunction = NamedFunction.getInstance("iif"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> notFunction.invoke(iifInput)); + // assertEquals( + // "Condition argument to iif must be Boolean: valueInteger", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfConditionNotSingular() { + // final PrimitivePath condition = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .expression("valueBoolean") + // .singular(false) + // .build(); + // final StringLiteralPath ifTrue = StringCollection.fromLiteral("foo", condition); + // final StringLiteralPath otherwise = StringCollection.fromLiteral("bar", condition); + // + // final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, + // Arrays.asList(condition, ifTrue, otherwise)); + // + // final NamedFunction notFunction = NamedFunction.getInstance("iif"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> notFunction.invoke(iifInput)); + // assertEquals( + // "Condition argument to iif must be singular: valueBoolean", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfNoThisInCondition() { + // final PrimitivePath condition = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .expression("valueBoolean") + // .singular(true) + // .build(); + // final StringLiteralPath ifTrue = StringCollection.fromLiteral("foo", condition); + // final StringLiteralPath otherwise = StringCollection.fromLiteral("bar", condition); + // + // final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, + // Arrays.asList(condition, ifTrue, otherwise)); + // + // final NamedFunction notFunction = NamedFunction.getInstance("iif"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> notFunction.invoke(iifInput)); + // assertEquals( + // "Condition argument to iif function must be navigable from collection item (use $this): valueBoolean", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfResultArgumentsDifferentTypes() { + // final NonLiteralPath condition = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .expression("valueBoolean") + // .singular(true) + // .build() + // .toThisPath(); + // final StringLiteralPath ifTrue = StringCollection.fromLiteral("foo", condition); + // final IntegerLiteralPath otherwise = IntegerLiteralPath.fromString("99", condition); + // + // final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, + // Arrays.asList(condition, ifTrue, otherwise)); + // + // final NamedFunction notFunction = NamedFunction.getInstance("iif"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> notFunction.invoke(iifInput)); + // assertEquals( + // "Paths cannot be merged into a collection together: 'foo', 99", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfResultArgumentsAreBackboneElements() { + // final NonLiteralPath condition = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .expression("valueBoolean") + // .singular(true) + // .build() + // .toThisPath(); + // final PrimitivePath ifTrue = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BACKBONEELEMENT) + // .expression("someBackboneElement") + // .build(); + // final PrimitivePath otherwise = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BACKBONEELEMENT) + // .expression("anotherBackboneElement") + // .build(); + // + // final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, + // Arrays.asList(condition, ifTrue, otherwise)); + // + // final NamedFunction notFunction = NamedFunction.getInstance("iif"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> notFunction.invoke(iifInput)); + // assertEquals( + // "Paths cannot be merged into a collection together: someBackboneElement, " + // + "anotherBackboneElement", error.getMessage()); + // } + // + // @Test + // void throwsErrorWithIncompatibleResourceResults() { + // final NonLiteralPath condition = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .expression("valueBoolean") + // .singular(true) + // .build() + // .toThisPath(); + // final ResourceCollection ifTrue = new ResourcePathBuilder(spark) + // .expression("someResource") + // .resourceType(ResourceType.PATIENT) + // .build(); + // final ResourceCollection otherwise = new ResourcePathBuilder(spark) + // .expression("anotherResource") + // .resourceType(ResourceType.CONDITION) + // .build(); + // + // final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, + // Arrays.asList(condition, ifTrue, otherwise)); + // + // final NamedFunction notFunction = NamedFunction.getInstance("iif"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> notFunction.invoke(iifInput)); + // assertEquals( + // "Paths cannot be merged into a collection together: someResource, anotherResource", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorWithResourceAndLiteralResults() { + // final NonLiteralPath condition = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .expression("valueBoolean") + // .singular(true) + // .build() + // .toThisPath(); + // final ResourceCollection ifTrue = new ResourcePathBuilder(spark) + // .expression("someResource") + // .resourceType(ResourceType.PATIENT) + // .build(); + // final StringLiteralPath otherwise = StringCollection.fromLiteral("foo", condition); + // + // final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, + // Arrays.asList(condition, ifTrue, otherwise)); + // + // final NamedFunction notFunction = NamedFunction.getInstance("iif"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> notFunction.invoke(iifInput)); + // assertEquals( + // "Paths cannot be merged into a collection together: someResource, 'foo'", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorWithUntypedResourceAndLiteralResults() { + // final NonLiteralPath condition = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .expression("valueBoolean") + // .singular(true) + // .build() + // .toThisPath(); + // final UntypedResourcePath ifTrue = new UntypedResourcePathBuilder(spark) + // .expression("someUntypedResource") + // .build(); + // final StringLiteralPath otherwise = StringCollection.fromLiteral("foo", condition); + // + // final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, + // Arrays.asList(condition, ifTrue, otherwise)); + // + // final NamedFunction notFunction = NamedFunction.getInstance("iif"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> notFunction.invoke(iifInput)); + // assertEquals( + // "Paths cannot be merged into a collection together: someUntypedResource, 'foo'", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorWithElementAndResourceResults() { + // final NonLiteralPath condition = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .expression("valueBoolean") + // .singular(true) + // .build() + // .toThisPath(); + // final PrimitivePath ifTrue = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .expression("someString") + // .build(); + // final ResourceCollection otherwise = new ResourcePathBuilder(spark) + // .expression("someResource") + // .resourceType(ResourceType.CONDITION) + // .build(); + // + // final NamedFunctionInput iifInput = new NamedFunctionInput(parserContext, condition, + // Arrays.asList(condition, ifTrue, otherwise)); + // + // final NamedFunction notFunction = NamedFunction.getInstance("iif"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> notFunction.invoke(iifInput)); + // assertEquals( + // "Paths cannot be merged into a collection together: someString, someResource", + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/NotFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/NotFunctionTest.java index 9ef49d9447..71e2d14ff5 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/NotFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/NotFunctionTest.java @@ -17,128 +17,109 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.fhirpath.collection.BooleanCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.StringCollection; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.DatasetBuilder; -import au.csiro.pathling.test.builders.ElementPathBuilder; -import au.csiro.pathling.test.builders.ParserContextBuilder; -import ca.uhn.fhir.context.FhirContext; -import java.util.Collections; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -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.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class NotFunctionTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @Test - void returnsCorrectResults() { - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.BooleanType) - .withRow("observation-1", makeEid(0), true) - .withRow("observation-2", makeEid(0), false) - .withRow("observation-3", makeEid(0), null) - .withRow("observation-4", makeEid(0), true) - .withRow("observation-4", makeEid(1), false) - .withRow("observation-5", makeEid(0), null) - .withRow("observation-5", makeEid(1), null) - .build(); - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .dataset(dataset) - .idAndEidAndValueColumns() - .expression("valueBoolean") - .singular(false) - .build(); - - final NamedFunctionInput notInput = new NamedFunctionInput(parserContext, input, - Collections.emptyList()); - final NamedFunction notFunction = NamedFunction.getInstance("not"); - final Collection result = notFunction.invoke(notInput); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.BooleanType) - .withRow("observation-1", false) - .withRow("observation-2", true) - .withRow("observation-3", null) - .withRow("observation-4", false) - .withRow("observation-4", true) - .withRow("observation-5", null) - .withRow("observation-5", null) - .build(); - assertThat(result) - .hasExpression("valueBoolean.not()") - .isNotSingular() - .isElementPath(BooleanCollection.class) - .selectOrderedResult() - .hasRows(expectedDataset); - } - - @Test - void inputMustNotContainArguments() { - final PrimitivePath input = new ElementPathBuilder(spark).build(); - final StringLiteralPath argument = StringCollection - .fromLiteral("'some argument'", input); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput notInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final NamedFunction notFunction = NamedFunction.getInstance("not"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> notFunction.invoke(notInput)); - assertEquals( - "Arguments can not be passed to not function", - error.getMessage()); - } - - @Test - void throwsErrorIfInputNotBoolean() { - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .expression("valueInteger") - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput notInput = new NamedFunctionInput(parserContext, input, - Collections.emptyList()); - - final NamedFunction notFunction = NamedFunction.getInstance("not"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> notFunction.invoke(notInput)); - assertEquals( - "Input to not function must be Boolean: valueInteger", - error.getMessage()); - } - + // TODO: implement with columns + + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @Test + // void returnsCorrectResults() { + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow("observation-1", makeEid(0), true) + // .withRow("observation-2", makeEid(0), false) + // .withRow("observation-3", makeEid(0), null) + // .withRow("observation-4", makeEid(0), true) + // .withRow("observation-4", makeEid(1), false) + // .withRow("observation-5", makeEid(0), null) + // .withRow("observation-5", makeEid(1), null) + // .build(); + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .dataset(dataset) + // .idAndEidAndValueColumns() + // .expression("valueBoolean") + // .singular(false) + // .build(); + // + // final NamedFunctionInput notInput = new NamedFunctionInput(parserContext, input, + // Collections.emptyList()); + // final NamedFunction notFunction = NamedFunction.getInstance("not"); + // final Collection result = notFunction.invoke(notInput); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow("observation-1", false) + // .withRow("observation-2", true) + // .withRow("observation-3", null) + // .withRow("observation-4", false) + // .withRow("observation-4", true) + // .withRow("observation-5", null) + // .withRow("observation-5", null) + // .build(); + // assertThat(result) + // .hasExpression("valueBoolean.not()") + // .isNotSingular() + // .isElementPath(BooleanCollection.class) + // .selectOrderedResult() + // .hasRows(expectedDataset); + // } + // + // @Test + // void inputMustNotContainArguments() { + // final PrimitivePath input = new ElementPathBuilder(spark).build(); + // final StringLiteralPath argument = StringCollection + // .fromLiteral("'some argument'", input); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput notInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final NamedFunction notFunction = NamedFunction.getInstance("not"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> notFunction.invoke(notInput)); + // assertEquals( + // "Arguments can not be passed to not function", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfInputNotBoolean() { + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .expression("valueInteger") + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput notInput = new NamedFunctionInput(parserContext, input, + // Collections.emptyList()); + // + // final NamedFunction notFunction = NamedFunction.getInstance("not"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> notFunction.invoke(notInput)); + // assertEquals( + // "Input to not function must be Boolean: valueInteger", + // error.getMessage()); + // } + // } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/OfTypeFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/OfTypeFunctionTest.java index 69131f18a2..876dd81e68 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/OfTypeFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/OfTypeFunctionTest.java @@ -17,191 +17,162 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static au.csiro.pathling.test.helpers.SparkHelpers.referenceStructType; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.collection.UntypedResourcePath; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.io.source.DataSource; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.DatasetBuilder; -import au.csiro.pathling.test.builders.ParserContextBuilder; -import au.csiro.pathling.test.builders.ResourceDatasetBuilder; -import au.csiro.pathling.test.builders.ResourcePathBuilder; -import au.csiro.pathling.test.builders.UntypedResourcePathBuilder; -import ca.uhn.fhir.context.FhirContext; -import java.util.Arrays; -import java.util.Collections; -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.ResourceType; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class OfTypeFunctionTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @MockBean - DataSource database; - - @Test - void resolvesPolymorphicReference() { - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(referenceStructType()) - .withRow("encounter-1", makeEid(1), RowFactory.create(null, "Patient/patient-1", null)) - .withRow("encounter-1", makeEid(0), RowFactory.create(null, "Patient/patient-2", null)) - .withRow("encounter-2", makeEid(0), RowFactory.create(null, "Patient/patient-3", null)) - .withRow("encounter-2", makeEid(1), RowFactory.create(null, "Group/group-1", null)) - .withRow("encounter-3", makeEid(0), RowFactory.create(null, "Patient/patient-2", null)) - .withRow("encounter-4", makeEid(0), RowFactory.create(null, "Patient/patient-2", null)) - .withRow("encounter-5", makeEid(0), RowFactory.create(null, "Group/group-1", null)) - .withRow("encounter-6", null, null) - .buildWithStructValue(); - final UntypedResourcePath inputPath = new UntypedResourcePathBuilder(spark) - .expression("subject.resolve()") - .dataset(inputDataset) - .idEidAndValueColumns() - .singular(false) - .build(); - - final Dataset argumentDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.StringType) - .withColumn(DataTypes.BooleanType) - .withRow("patient-1", "female", true) - .withRow("patient-2", "female", false) - .withRow("patient-3", "male", true) - .build(); - when(database.read(eq(ResourceType.PATIENT))) - .thenReturn(argumentDataset); - final ResourceCollection argumentPath = new ResourcePathBuilder(spark) - .database(database) - .resourceType(ResourceType.PATIENT) - .expression("Patient") - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .idColumn(inputPath.getIdColumn()) - .database(database) - .build(); - final NamedFunctionInput ofTypeInput = new NamedFunctionInput(parserContext, inputPath, - Collections.singletonList(argumentPath)); - final NamedFunction ofType = NamedFunction.getInstance("ofType"); - final Collection result = ofType.invoke(ofTypeInput); - - assertTrue(result instanceof ResourceCollection); - assertThat((ResourceCollection) result) - .hasExpression("subject.resolve().ofType(Patient)") - .isNotSingular() - .hasResourceType(ResourceType.PATIENT); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withIdColumn() // this represents value for a resource - .withRow("encounter-1", makeEid(0), "patient-2") - .withRow("encounter-1", makeEid(1), "patient-1") - .withRow("encounter-2", makeEid(0), "patient-3") - .withRow("encounter-2", makeEid(1), null) - .withRow("encounter-3", makeEid(0), "patient-2") - .withRow("encounter-4", makeEid(0), "patient-2") - .withRow("encounter-5", makeEid(0), null) - .withRow("encounter-6", null, null) - .build(); - assertThat(result) - .selectOrderedResultWithEid() - .hasRows(expectedDataset); - } - - @Test - void throwsErrorIfInputNotPolymorphic() { - final ResourceCollection input = new ResourcePathBuilder(spark) - .expression("Patient") - .build(); - final ResourceCollection argument = new ResourcePathBuilder(spark).build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput ofTypeInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final NamedFunction ofTypeFunction = NamedFunction.getInstance("ofType"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> ofTypeFunction.invoke(ofTypeInput)); - assertEquals( - "Input to ofType function must be a polymorphic resource type: Patient", - error.getMessage()); - } - - @Test - void throwsErrorIfMoreThanOneArgument() { - final UntypedResourcePath input = new UntypedResourcePathBuilder(spark) - .expression("subject") - .build(); - final ResourceCollection argument1 = new ResourcePathBuilder(spark) - .expression("Patient") - .build(); - final ResourceCollection argument2 = new ResourcePathBuilder(spark) - .expression("Condition") - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput ofTypeInput = new NamedFunctionInput(parserContext, input, - Arrays.asList(argument1, argument2)); - - final NamedFunction ofTypeFunction = NamedFunction.getInstance("ofType"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> ofTypeFunction.invoke(ofTypeInput)); - assertEquals( - "ofType function must have one argument: subject.ofType(Patient, Condition)", - error.getMessage()); - } - - @Test - void throwsErrorIfArgumentNotResource() { - final UntypedResourcePath input = new UntypedResourcePathBuilder(spark) - .expression("subject") - .build(); - final StringLiteralPath argument = StringCollection - .fromLiteral("'some string'", input); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput ofTypeInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final NamedFunction ofTypeFunction = NamedFunction.getInstance("ofType"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> ofTypeFunction.invoke(ofTypeInput)); - assertEquals("Argument to ofType function must be a resource type: 'some string'", - error.getMessage()); - } - + // TODO: implement with columns + + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @MockBean + // DataSource database; + // + // @Test + // void resolvesPolymorphicReference() { + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(referenceStructType()) + // .withRow("encounter-1", makeEid(1), RowFactory.create(null, "Patient/patient-1", null)) + // .withRow("encounter-1", makeEid(0), RowFactory.create(null, "Patient/patient-2", null)) + // .withRow("encounter-2", makeEid(0), RowFactory.create(null, "Patient/patient-3", null)) + // .withRow("encounter-2", makeEid(1), RowFactory.create(null, "Group/group-1", null)) + // .withRow("encounter-3", makeEid(0), RowFactory.create(null, "Patient/patient-2", null)) + // .withRow("encounter-4", makeEid(0), RowFactory.create(null, "Patient/patient-2", null)) + // .withRow("encounter-5", makeEid(0), RowFactory.create(null, "Group/group-1", null)) + // .withRow("encounter-6", null, null) + // .buildWithStructValue(); + // final UntypedResourcePath inputPath = new UntypedResourcePathBuilder(spark) + // .expression("subject.resolve()") + // .dataset(inputDataset) + // .idEidAndValueColumns() + // .singular(false) + // .build(); + // + // final Dataset argumentDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.StringType) + // .withColumn(DataTypes.BooleanType) + // .withRow("patient-1", "female", true) + // .withRow("patient-2", "female", false) + // .withRow("patient-3", "male", true) + // .build(); + // when(database.read(eq(ResourceType.PATIENT))) + // .thenReturn(argumentDataset); + // final ResourceCollection argumentPath = new ResourcePathBuilder(spark) + // .database(database) + // .resourceType(ResourceType.PATIENT) + // .expression("Patient") + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .idColumn(inputPath.getIdColumn()) + // .database(database) + // .build(); + // final NamedFunctionInput ofTypeInput = new NamedFunctionInput(parserContext, inputPath, + // Collections.singletonList(argumentPath)); + // final NamedFunction ofType = NamedFunction.getInstance("ofType"); + // final Collection result = ofType.invoke(ofTypeInput); + // + // assertTrue(result instanceof ResourceCollection); + // assertThat((ResourceCollection) result) + // .hasExpression("subject.resolve().ofType(Patient)") + // .isNotSingular() + // .hasResourceType(ResourceType.PATIENT); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withIdColumn() // this represents value for a resource + // .withRow("encounter-1", makeEid(0), "patient-2") + // .withRow("encounter-1", makeEid(1), "patient-1") + // .withRow("encounter-2", makeEid(0), "patient-3") + // .withRow("encounter-2", makeEid(1), null) + // .withRow("encounter-3", makeEid(0), "patient-2") + // .withRow("encounter-4", makeEid(0), "patient-2") + // .withRow("encounter-5", makeEid(0), null) + // .withRow("encounter-6", null, null) + // .build(); + // assertThat(result) + // .selectOrderedResultWithEid() + // .hasRows(expectedDataset); + // } + // + // @Test + // void throwsErrorIfInputNotPolymorphic() { + // final ResourceCollection input = new ResourcePathBuilder(spark) + // .expression("Patient") + // .build(); + // final ResourceCollection argument = new ResourcePathBuilder(spark).build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput ofTypeInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final NamedFunction ofTypeFunction = NamedFunction.getInstance("ofType"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> ofTypeFunction.invoke(ofTypeInput)); + // assertEquals( + // "Input to ofType function must be a polymorphic resource type: Patient", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfMoreThanOneArgument() { + // final UntypedResourcePath input = new UntypedResourcePathBuilder(spark) + // .expression("subject") + // .build(); + // final ResourceCollection argument1 = new ResourcePathBuilder(spark) + // .expression("Patient") + // .build(); + // final ResourceCollection argument2 = new ResourcePathBuilder(spark) + // .expression("Condition") + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput ofTypeInput = new NamedFunctionInput(parserContext, input, + // Arrays.asList(argument1, argument2)); + // + // final NamedFunction ofTypeFunction = NamedFunction.getInstance("ofType"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> ofTypeFunction.invoke(ofTypeInput)); + // assertEquals( + // "ofType function must have one argument: subject.ofType(Patient, Condition)", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfArgumentNotResource() { + // final UntypedResourcePath input = new UntypedResourcePathBuilder(spark) + // .expression("subject") + // .build(); + // final StringLiteralPath argument = StringCollection + // .fromLiteral("'some string'", input); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput ofTypeInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final NamedFunction ofTypeFunction = NamedFunction.getInstance("ofType"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> ofTypeFunction.invoke(ofTypeInput)); + // assertEquals("Argument to ofType function must be a resource type: 'some string'", + // error.getMessage()); + // } + // } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ResolveFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ResolveFunctionTest.java index c138c6a85b..1f9aadb8fb 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ResolveFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ResolveFunctionTest.java @@ -17,303 +17,269 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static au.csiro.pathling.test.helpers.SparkHelpers.referenceStructType; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.when; - -import au.csiro.pathling.encoders.FhirEncoders; -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.collection.UntypedResourcePath; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.io.source.DataSource; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.builders.ResourceDatasetBuilder; -import au.csiro.pathling.test.helpers.FhirHelpers; -import au.csiro.pathling.test.helpers.TestHelpers; -import ca.uhn.fhir.context.FhirContext; -import java.util.Collections; -import java.util.Optional; -import javax.annotation.Nonnull; -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.hl7.fhir.r4.model.Enumerations.ResourceType; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class ResolveFunctionTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @Autowired - FhirEncoders fhirEncoders; - - @MockBean - DataSource dataSource; - - @Test - void simpleResolve() { - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Encounter", "episodeOfCare"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - - final Dataset referenceDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(referenceStructType()) - .withRow("encounter-1", makeEid(0), - RowFactory.create(null, "EpisodeOfCare/episodeofcare-1", null)) - .withRow("encounter-2", makeEid(0), - RowFactory.create(null, "EpisodeOfCare/episodeofcare-3", null)) - .withRow("encounter-3", makeEid(0), - RowFactory.create(null, "EpisodeOfCare/episodeofcare-2", null)) - .withRow("encounter-4", makeEid(0), - RowFactory.create(null, "EpisodeOfCare/episodeofcare-2", null)) - .buildWithStructValue(); - final PrimitivePath referencePath = new ElementPathBuilder(spark) - .expression("Encounter.episodeOfCare") - .dataset(referenceDataset) - .idAndEidAndValueColumns() - .singular(false) - .definition(definition) - .buildDefined(); - - final Dataset episodeOfCareDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.StringType) - .withRow("episodeofcare-1", "planned") - .withRow("episodeofcare-2", "waitlist") - .withRow("episodeofcare-3", "active") - .build(); - when(dataSource.read(ResourceType.EPISODEOFCARE)).thenReturn(episodeOfCareDataset); - - final NamedFunctionInput resolveInput = buildFunctionInput(referencePath); - final Collection result = invokeResolve(resolveInput); - - assertTrue(result instanceof ResourceCollection); - assertThat((ResourceCollection) result) - .hasExpression("Encounter.episodeOfCare.resolve()") - .isNotSingular() - .hasResourceType(ResourceType.EPISODEOFCARE); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.StringType) - .withRow("encounter-1", "episodeofcare-1") - .withRow("encounter-2", "episodeofcare-3") - .withRow("encounter-3", "episodeofcare-2") - .withRow("encounter-4", "episodeofcare-2") - .build(); - assertThat(result) - .selectOrderedResult() - .hasRows(expectedDataset); - } - - @Test - void polymorphicResolve() { - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Encounter", "subject"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - - final Dataset referenceDataset = new DatasetBuilder(spark) - .withIdColumn() - .withStructTypeColumns(referenceStructType()) - .withRow("encounter-1", RowFactory.create(null, "Patient/patient-1", null)) - .withRow("encounter-2", RowFactory.create(null, "Patient/patient-3", null)) - .withRow("encounter-3", RowFactory.create(null, "Patient/patient-2", null)) - .withRow("encounter-4", RowFactory.create(null, "Patient/patient-2", null)) - .withRow("encounter-5", RowFactory.create(null, "Group/group-1", null)) - .buildWithStructValue(); - final PrimitivePath referencePath = new ElementPathBuilder(spark) - .expression("Encounter.subject") - .dataset(referenceDataset) - .idAndValueColumns() - .singular(true) - .definition(definition) - .buildDefined(); - - final Dataset patientDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.StringType) - .withColumn(DataTypes.BooleanType) - .withRow("patient-1", "female", true) - .withRow("patient-2", "female", false) - .withRow("patient-3", "male", true) - .build(); - when(dataSource.read(ResourceType.PATIENT)) - .thenReturn(patientDataset); - - final Dataset groupDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.StringType) - .withColumn(DataTypes.BooleanType) - .withRow("group-1", "Some group", true) - .build(); - when(dataSource.read(ResourceType.GROUP)) - .thenReturn(groupDataset); - - final NamedFunctionInput resolveInput = buildFunctionInput(referencePath); - final Collection result = invokeResolve(resolveInput); - - assertTrue(result instanceof UntypedResourcePath); - assertThat(result) - .hasExpression("Encounter.subject.resolve()") - .isSingular(); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withStructTypeColumns(referenceStructType()) - .withRow("encounter-1", RowFactory.create(null, "Patient/patient-1", null)) - .withRow("encounter-2", RowFactory.create(null, "Patient/patient-3", null)) - .withRow("encounter-3", RowFactory.create(null, "Patient/patient-2", null)) - .withRow("encounter-4", RowFactory.create(null, "Patient/patient-2", null)) - .withRow("encounter-5", RowFactory.create(null, "Group/group-1", null)) - .buildWithStructValue(); - assertThat(result) - .selectResult() - .hasRows(expectedDataset); - } - - @Test - void polymorphicResolveAnyType() { - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Condition", "evidence") - .flatMap(child -> child.getChildElement("detail")); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - TestHelpers.mockAllEmptyResources(dataSource, spark, fhirEncoders); - - final Dataset referenceDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(referenceStructType()) - .withRow("condition-1", makeEid(0), - RowFactory.create(null, "Observation/observation-1", null)) - .withRow("condition-2", makeEid(0), - RowFactory.create(null, "ClinicalImpression/clinicalimpression-1", null)) - .buildWithStructValue(); - final PrimitivePath referencePath = new ElementPathBuilder(spark) - .expression("Condition.evidence.detail") - .dataset(referenceDataset) - .idAndEidAndValueColumns() - .singular(false) - .definition(definition) - .buildDefined(); - - final Dataset observationDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.StringType) - .withRow("observation-1", "registered") - .build(); - when(dataSource.read(ResourceType.OBSERVATION)) - .thenReturn(observationDataset); - - final Dataset clinicalImpressionDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.StringType) - .withRow("clinicalimpression-1", "in-progress") - .build(); - when(dataSource.read(ResourceType.CLINICALIMPRESSION)) - .thenReturn(clinicalImpressionDataset); - - final NamedFunctionInput resolveInput = buildFunctionInput(referencePath); - final Collection result = invokeResolve(resolveInput); - - assertTrue(result instanceof UntypedResourcePath); - assertThat(result) - .hasExpression("Condition.evidence.detail.resolve()") - .isNotSingular(); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withStructTypeColumns(referenceStructType()) - .withRow("condition-1", RowFactory.create(null, "Observation/observation-1", null)) - .withRow("condition-2", - RowFactory.create(null, "ClinicalImpression/clinicalimpression-1", null)) - .buildWithStructValue(); - assertThat(result) - .selectResult() - .hasRows(expectedDataset); - } - - - @Test - void throwExceptionWhenInputNotReference() { - final Dataset patientDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.StringType) - .build(); - final PrimitivePath genderPath = new ElementPathBuilder(spark) - .expression("Patient.gender") - .dataset(patientDataset) - .idAndValueColumns() - .singular(true) - .fhirType(FHIRDefinedType.CODE) - .build(); - - final NamedFunctionInput resolveInput = buildFunctionInput(genderPath); - - assertThrows(InvalidUserInputError.class, () -> invokeResolve(resolveInput), - "Input to resolve function must be a Reference: gender"); - } - - @Test - void throwExceptionWhenArgumentSupplied() { - final PrimitivePath referencePath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.REFERENCE) - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .build(); - final StringLiteralPath stringLiteralPath = StringCollection - .fromLiteral("'foo'", parserContext.getInputContext()); - final NamedFunctionInput resolveInput = new NamedFunctionInput(parserContext, referencePath, - Collections.singletonList(stringLiteralPath)); - - assertThrows(InvalidUserInputError.class, () -> invokeResolve(resolveInput), - "resolve function does not accept arguments"); - } - - @Nonnull - NamedFunctionInput buildFunctionInput(@Nonnull final NonLiteralPath inputPath) { - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .idColumn(inputPath.getIdColumn()) - .database(dataSource) - .build(); - return new NamedFunctionInput(parserContext, inputPath, Collections.emptyList()); - } - - @Nonnull - Collection invokeResolve(@Nonnull final NamedFunctionInput resolveInput) { - final NamedFunction resolve = NamedFunction.getInstance("resolve"); - return resolve.invoke(resolveInput); - } - + // TODO: implement with columns + + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @Autowired + // FhirEncoders fhirEncoders; + // + // @MockBean + // DataSource dataSource; + // + // @Test + // void simpleResolve() { + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Encounter", "episodeOfCare"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // + // final Dataset referenceDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(referenceStructType()) + // .withRow("encounter-1", makeEid(0), + // RowFactory.create(null, "EpisodeOfCare/episodeofcare-1", null)) + // .withRow("encounter-2", makeEid(0), + // RowFactory.create(null, "EpisodeOfCare/episodeofcare-3", null)) + // .withRow("encounter-3", makeEid(0), + // RowFactory.create(null, "EpisodeOfCare/episodeofcare-2", null)) + // .withRow("encounter-4", makeEid(0), + // RowFactory.create(null, "EpisodeOfCare/episodeofcare-2", null)) + // .buildWithStructValue(); + // final PrimitivePath referencePath = new ElementPathBuilder(spark) + // .expression("Encounter.episodeOfCare") + // .dataset(referenceDataset) + // .idAndEidAndValueColumns() + // .singular(false) + // .definition(definition) + // .buildDefined(); + // + // final Dataset episodeOfCareDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.StringType) + // .withRow("episodeofcare-1", "planned") + // .withRow("episodeofcare-2", "waitlist") + // .withRow("episodeofcare-3", "active") + // .build(); + // when(dataSource.read(ResourceType.EPISODEOFCARE)).thenReturn(episodeOfCareDataset); + // + // final NamedFunctionInput resolveInput = buildFunctionInput(referencePath); + // final Collection result = invokeResolve(resolveInput); + // + // assertTrue(result instanceof ResourceCollection); + // assertThat((ResourceCollection) result) + // .hasExpression("Encounter.episodeOfCare.resolve()") + // .isNotSingular() + // .hasResourceType(ResourceType.EPISODEOFCARE); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.StringType) + // .withRow("encounter-1", "episodeofcare-1") + // .withRow("encounter-2", "episodeofcare-3") + // .withRow("encounter-3", "episodeofcare-2") + // .withRow("encounter-4", "episodeofcare-2") + // .build(); + // assertThat(result) + // .selectOrderedResult() + // .hasRows(expectedDataset); + // } + // + // @Test + // void polymorphicResolve() { + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Encounter", "subject"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // + // final Dataset referenceDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withStructTypeColumns(referenceStructType()) + // .withRow("encounter-1", RowFactory.create(null, "Patient/patient-1", null)) + // .withRow("encounter-2", RowFactory.create(null, "Patient/patient-3", null)) + // .withRow("encounter-3", RowFactory.create(null, "Patient/patient-2", null)) + // .withRow("encounter-4", RowFactory.create(null, "Patient/patient-2", null)) + // .withRow("encounter-5", RowFactory.create(null, "Group/group-1", null)) + // .buildWithStructValue(); + // final PrimitivePath referencePath = new ElementPathBuilder(spark) + // .expression("Encounter.subject") + // .dataset(referenceDataset) + // .idAndValueColumns() + // .singular(true) + // .definition(definition) + // .buildDefined(); + // + // final Dataset patientDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.StringType) + // .withColumn(DataTypes.BooleanType) + // .withRow("patient-1", "female", true) + // .withRow("patient-2", "female", false) + // .withRow("patient-3", "male", true) + // .build(); + // when(dataSource.read(ResourceType.PATIENT)) + // .thenReturn(patientDataset); + // + // final Dataset groupDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.StringType) + // .withColumn(DataTypes.BooleanType) + // .withRow("group-1", "Some group", true) + // .build(); + // when(dataSource.read(ResourceType.GROUP)) + // .thenReturn(groupDataset); + // + // final NamedFunctionInput resolveInput = buildFunctionInput(referencePath); + // final Collection result = invokeResolve(resolveInput); + // + // assertTrue(result instanceof UntypedResourcePath); + // assertThat(result) + // .hasExpression("Encounter.subject.resolve()") + // .isSingular(); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withStructTypeColumns(referenceStructType()) + // .withRow("encounter-1", RowFactory.create(null, "Patient/patient-1", null)) + // .withRow("encounter-2", RowFactory.create(null, "Patient/patient-3", null)) + // .withRow("encounter-3", RowFactory.create(null, "Patient/patient-2", null)) + // .withRow("encounter-4", RowFactory.create(null, "Patient/patient-2", null)) + // .withRow("encounter-5", RowFactory.create(null, "Group/group-1", null)) + // .buildWithStructValue(); + // assertThat(result) + // .selectResult() + // .hasRows(expectedDataset); + // } + // + // @Test + // void polymorphicResolveAnyType() { + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Condition", "evidence") + // .flatMap(child -> child.getChildElement("detail")); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // TestHelpers.mockAllEmptyResources(dataSource, spark, fhirEncoders); + // + // final Dataset referenceDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(referenceStructType()) + // .withRow("condition-1", makeEid(0), + // RowFactory.create(null, "Observation/observation-1", null)) + // .withRow("condition-2", makeEid(0), + // RowFactory.create(null, "ClinicalImpression/clinicalimpression-1", null)) + // .buildWithStructValue(); + // final PrimitivePath referencePath = new ElementPathBuilder(spark) + // .expression("Condition.evidence.detail") + // .dataset(referenceDataset) + // .idAndEidAndValueColumns() + // .singular(false) + // .definition(definition) + // .buildDefined(); + // + // final Dataset observationDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.StringType) + // .withRow("observation-1", "registered") + // .build(); + // when(dataSource.read(ResourceType.OBSERVATION)) + // .thenReturn(observationDataset); + // + // final Dataset clinicalImpressionDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.StringType) + // .withRow("clinicalimpression-1", "in-progress") + // .build(); + // when(dataSource.read(ResourceType.CLINICALIMPRESSION)) + // .thenReturn(clinicalImpressionDataset); + // + // final NamedFunctionInput resolveInput = buildFunctionInput(referencePath); + // final Collection result = invokeResolve(resolveInput); + // + // assertTrue(result instanceof UntypedResourcePath); + // assertThat(result) + // .hasExpression("Condition.evidence.detail.resolve()") + // .isNotSingular(); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withStructTypeColumns(referenceStructType()) + // .withRow("condition-1", RowFactory.create(null, "Observation/observation-1", null)) + // .withRow("condition-2", + // RowFactory.create(null, "ClinicalImpression/clinicalimpression-1", null)) + // .buildWithStructValue(); + // assertThat(result) + // .selectResult() + // .hasRows(expectedDataset); + // } + // + // + // @Test + // void throwExceptionWhenInputNotReference() { + // final Dataset patientDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.StringType) + // .build(); + // final PrimitivePath genderPath = new ElementPathBuilder(spark) + // .expression("Patient.gender") + // .dataset(patientDataset) + // .idAndValueColumns() + // .singular(true) + // .fhirType(FHIRDefinedType.CODE) + // .build(); + // + // final NamedFunctionInput resolveInput = buildFunctionInput(genderPath); + // + // assertThrows(InvalidUserInputError.class, () -> invokeResolve(resolveInput), + // "Input to resolve function must be a Reference: gender"); + // } + // + // @Test + // void throwExceptionWhenArgumentSupplied() { + // final PrimitivePath referencePath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.REFERENCE) + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .build(); + // final StringLiteralPath stringLiteralPath = StringCollection + // .fromLiteral("'foo'", parserContext.getInputContext()); + // final NamedFunctionInput resolveInput = new NamedFunctionInput(parserContext, referencePath, + // Collections.singletonList(stringLiteralPath)); + // + // assertThrows(InvalidUserInputError.class, () -> invokeResolve(resolveInput), + // "resolve function does not accept arguments"); + // } + // + // @Nonnull + // NamedFunctionInput buildFunctionInput(@Nonnull final NonLiteralPath inputPath) { + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .idColumn(inputPath.getIdColumn()) + // .database(dataSource) + // .build(); + // return new NamedFunctionInput(parserContext, inputPath, Collections.emptyList()); + // } + // + // @Nonnull + // Collection invokeResolve(@Nonnull final NamedFunctionInput resolveInput) { + // final NamedFunction resolve = NamedFunction.getInstance("resolve"); + // return resolve.invoke(resolveInput); + // } + // } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java index 283b2cca33..66ce78f9b2 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunctionTest.java @@ -17,255 +17,220 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.helpers.SparkHelpers.getIdAndValueColumns; -import static au.csiro.pathling.test.helpers.SparkHelpers.referenceStructType; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.when; - -import au.csiro.pathling.QueryHelpers.JoinType; -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.io.source.DataSource; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.builders.ResourceDatasetBuilder; -import au.csiro.pathling.test.builders.ResourcePathBuilder; -import au.csiro.pathling.test.helpers.FhirHelpers; -import au.csiro.pathling.test.helpers.SparkHelpers.IdAndValueColumns; -import ca.uhn.fhir.context.BaseRuntimeChildDefinition; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import java.util.Arrays; -import java.util.Collections; -import java.util.Optional; -import org.apache.spark.sql.Column; -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.hl7.fhir.r4.model.Enumerations.ResourceType; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class ReverseResolveFunctionTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @MockBean - DataSource database; - - @Test - void reverseResolve() { - final Dataset patientDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn("gender", DataTypes.StringType) - .withColumn("active", DataTypes.BooleanType) - .withRow("patient-1", "female", true) - .withRow("patient-2", "female", false) - .withRow("patient-3", "male", true) - .withRow("patient-4", "male", true) - .build(); - when(database.read(ResourceType.PATIENT)) - .thenReturn(patientDataset); - final ResourceCollection inputPath = ResourceCollection - .build(fhirContext, database, ResourceType.PATIENT, "Patient"); - - final DatasetBuilder encounterDatasetBuilder = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn("status", DataTypes.StringType) - .withRow("encounter-1", "planned") - .withRow("encounter-2", "arrived") - .withRow("encounter-3", "triaged") - .withRow("encounter-4", "in-progress") - .withRow("encounter-5", "onleave"); - final Dataset encounterDataset = encounterDatasetBuilder.build(); - when(database.read(ResourceType.ENCOUNTER)).thenReturn(encounterDataset); - final ResourceCollection originPath = ResourceCollection - .build(fhirContext, database, ResourceType.ENCOUNTER, "Encounter"); - - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Encounter", "subject"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - - final Dataset argumentDatasetPreJoin = new DatasetBuilder(spark) - .withIdColumn() - .withStructTypeColumns(referenceStructType()) - .withRow("encounter-1", RowFactory.create(null, "Patient/patient-1", null)) - .withRow("encounter-2", RowFactory.create(null, "Patient/patient-3", null)) - .withRow("encounter-3", RowFactory.create(null, "Patient/patient-2", null)) - .withRow("encounter-4", RowFactory.create(null, "Patient/patient-2", null)) - .withRow("encounter-5", RowFactory.create(null, "Group/group-1", null)) - .buildWithStructValue(); - final IdAndValueColumns idAndValueColumns = getIdAndValueColumns(argumentDatasetPreJoin); - final Column idColumn = idAndValueColumns.getId(); - final Column valueColumn = idAndValueColumns.getValues().get(0); - - final Dataset argumentDataset = join(originPath.getDataset(), - originPath.getIdColumn(), argumentDatasetPreJoin, idColumn, JoinType.LEFT_OUTER); - final Collection argumentPath = new ElementPathBuilder(spark) - .dataset(argumentDataset) - .idColumn(originPath.getIdColumn()) - .valueColumn(valueColumn) - .expression("Encounter.subject") - .singular(false) - .currentResource(originPath) - .definition(definition) - .buildDefined(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .idColumn(inputPath.getIdColumn()) - .database(database) - .inputExpression("Patient") - .build(); - final NamedFunctionInput reverseResolveInput = new NamedFunctionInput(parserContext, inputPath, - Collections.singletonList(argumentPath)); - final NamedFunction reverseResolve = NamedFunction.getInstance("reverseResolve"); - final Collection result = reverseResolve.invoke(reverseResolveInput); - - assertTrue(result instanceof ResourceCollection); - assertThat((ResourceCollection) result) - .hasExpression("reverseResolve(Encounter.subject)") - .isNotSingular() - .hasResourceType(ResourceType.ENCOUNTER); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withIdColumn() - .withRow("patient-1", "encounter-1") - .withRow("patient-2", "encounter-3") - .withRow("patient-2", "encounter-4") - .withRow("patient-3", "encounter-2") - .withRow("patient-4", null) - .build(); - assertThat(result) - .selectOrderedResult() - .hasRows(expectedDataset); - } - - @Test - void throwsErrorIfInputNotResource() { - final PrimitivePath input = new ElementPathBuilder(spark) - .expression("gender") - .fhirType(FHIRDefinedType.CODE) - .build(); - final PrimitivePath argument = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.REFERENCE) - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput reverseResolveInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final NamedFunction reverseResolveFunction = NamedFunction.getInstance("reverseResolve"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> reverseResolveFunction.invoke(reverseResolveInput)); - assertEquals( - "Input to reverseResolve function must be a resource: gender", - error.getMessage()); - } - - @Test - void throwsErrorIfArgumentIsNotReference() { - final ResourceCollection input = new ResourcePathBuilder(spark).build(); - final PrimitivePath argument = new ElementPathBuilder(spark) - .expression("gender") - .fhirType(FHIRDefinedType.CODE) - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput reverseResolveInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final NamedFunction reverseResolveFunction = NamedFunction.getInstance("reverseResolve"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> reverseResolveFunction.invoke(reverseResolveInput)); - assertEquals( - "Argument to reverseResolve function must be a Reference: gender", - error.getMessage()); - } - - @Test - void throwsErrorIfMoreThanOneArgument() { - final ResourceCollection input = new ResourcePathBuilder(spark) - .expression("Patient") - .build(); - final PrimitivePath argument1 = new ElementPathBuilder(spark) - .expression("Encounter.subject") - .fhirType(FHIRDefinedType.REFERENCE) - .build(); - final PrimitivePath argument2 = new ElementPathBuilder(spark) - .expression("Encounter.participant.individual") - .fhirType(FHIRDefinedType.REFERENCE) - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .inputExpression("Patient") - .build(); - final NamedFunctionInput reverseResolveInput = new NamedFunctionInput(parserContext, input, - Arrays.asList(argument1, argument2)); - - final NamedFunction reverseResolveFunction = NamedFunction.getInstance("reverseResolve"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> reverseResolveFunction.invoke(reverseResolveInput)); - assertEquals( - "reverseResolve function accepts a single argument: reverseResolve(Encounter.subject, Encounter.participant.individual)", - error.getMessage()); - } - - @Test - void throwsErrorIfArgumentTypeDoesNotMatchInput() { - final ResourceCollection input = new ResourcePathBuilder(spark) - .resourceType(ResourceType.PATIENT) - .expression("Patient") - .build(); - final RuntimeResourceDefinition hapiResourceDef = fhirContext.getResourceDefinition( - "Encounter"); - final BaseRuntimeChildDefinition childDefinition = hapiResourceDef.getChildByName( - "episodeOfCare"); - final ElementDefinition definition = ElementDefinition.build(childDefinition); - final PrimitivePath argument = new ElementPathBuilder(spark) - .expression("Encounter.episodeOfCare") - .fhirType(FHIRDefinedType.REFERENCE) - .definition(definition) - .buildDefined(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .inputExpression("Patient") - .build(); - final NamedFunctionInput reverseResolveInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final NamedFunction reverseResolveFunction = NamedFunction.getInstance("reverseResolve"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> reverseResolveFunction.invoke(reverseResolveInput)); - assertEquals( - "Reference in argument to reverseResolve does not support input resource type: reverseResolve(Encounter.episodeOfCare)", - error.getMessage()); - } - + // TODO: implement with columns + + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @MockBean + // DataSource database; + // + // @Test + // void reverseResolve() { + // final Dataset patientDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn("gender", DataTypes.StringType) + // .withColumn("active", DataTypes.BooleanType) + // .withRow("patient-1", "female", true) + // .withRow("patient-2", "female", false) + // .withRow("patient-3", "male", true) + // .withRow("patient-4", "male", true) + // .build(); + // when(database.read(ResourceType.PATIENT)) + // .thenReturn(patientDataset); + // final ResourceCollection inputPath = ResourceCollection + // .build(fhirContext, database, ResourceType.PATIENT, "Patient"); + // + // final DatasetBuilder encounterDatasetBuilder = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn("status", DataTypes.StringType) + // .withRow("encounter-1", "planned") + // .withRow("encounter-2", "arrived") + // .withRow("encounter-3", "triaged") + // .withRow("encounter-4", "in-progress") + // .withRow("encounter-5", "onleave"); + // final Dataset encounterDataset = encounterDatasetBuilder.build(); + // when(database.read(ResourceType.ENCOUNTER)).thenReturn(encounterDataset); + // final ResourceCollection originPath = ResourceCollection + // .build(fhirContext, database, ResourceType.ENCOUNTER, "Encounter"); + // + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Encounter", "subject"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // + // final Dataset argumentDatasetPreJoin = new DatasetBuilder(spark) + // .withIdColumn() + // .withStructTypeColumns(referenceStructType()) + // .withRow("encounter-1", RowFactory.create(null, "Patient/patient-1", null)) + // .withRow("encounter-2", RowFactory.create(null, "Patient/patient-3", null)) + // .withRow("encounter-3", RowFactory.create(null, "Patient/patient-2", null)) + // .withRow("encounter-4", RowFactory.create(null, "Patient/patient-2", null)) + // .withRow("encounter-5", RowFactory.create(null, "Group/group-1", null)) + // .buildWithStructValue(); + // final IdAndValueColumns idAndValueColumns = getIdAndValueColumns(argumentDatasetPreJoin); + // final Column idColumn = idAndValueColumns.getId(); + // final Column valueColumn = idAndValueColumns.getValues().get(0); + // + // final Dataset argumentDataset = join(originPath.getDataset(), + // originPath.getIdColumn(), argumentDatasetPreJoin, idColumn, JoinType.LEFT_OUTER); + // final Collection argumentPath = new ElementPathBuilder(spark) + // .dataset(argumentDataset) + // .idColumn(originPath.getIdColumn()) + // .valueColumn(valueColumn) + // .expression("Encounter.subject") + // .singular(false) + // .currentResource(originPath) + // .definition(definition) + // .buildDefined(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .idColumn(inputPath.getIdColumn()) + // .database(database) + // .inputExpression("Patient") + // .build(); + // final NamedFunctionInput reverseResolveInput = new NamedFunctionInput(parserContext, inputPath, + // Collections.singletonList(argumentPath)); + // final NamedFunction reverseResolve = NamedFunction.getInstance("reverseResolve"); + // final Collection result = reverseResolve.invoke(reverseResolveInput); + // + // assertTrue(result instanceof ResourceCollection); + // assertThat((ResourceCollection) result) + // .hasExpression("reverseResolve(Encounter.subject)") + // .isNotSingular() + // .hasResourceType(ResourceType.ENCOUNTER); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withIdColumn() + // .withRow("patient-1", "encounter-1") + // .withRow("patient-2", "encounter-3") + // .withRow("patient-2", "encounter-4") + // .withRow("patient-3", "encounter-2") + // .withRow("patient-4", null) + // .build(); + // assertThat(result) + // .selectOrderedResult() + // .hasRows(expectedDataset); + // } + // + // @Test + // void throwsErrorIfInputNotResource() { + // final PrimitivePath input = new ElementPathBuilder(spark) + // .expression("gender") + // .fhirType(FHIRDefinedType.CODE) + // .build(); + // final PrimitivePath argument = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.REFERENCE) + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput reverseResolveInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final NamedFunction reverseResolveFunction = NamedFunction.getInstance("reverseResolve"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> reverseResolveFunction.invoke(reverseResolveInput)); + // assertEquals( + // "Input to reverseResolve function must be a resource: gender", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfArgumentIsNotReference() { + // final ResourceCollection input = new ResourcePathBuilder(spark).build(); + // final PrimitivePath argument = new ElementPathBuilder(spark) + // .expression("gender") + // .fhirType(FHIRDefinedType.CODE) + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput reverseResolveInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final NamedFunction reverseResolveFunction = NamedFunction.getInstance("reverseResolve"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> reverseResolveFunction.invoke(reverseResolveInput)); + // assertEquals( + // "Argument to reverseResolve function must be a Reference: gender", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfMoreThanOneArgument() { + // final ResourceCollection input = new ResourcePathBuilder(spark) + // .expression("Patient") + // .build(); + // final PrimitivePath argument1 = new ElementPathBuilder(spark) + // .expression("Encounter.subject") + // .fhirType(FHIRDefinedType.REFERENCE) + // .build(); + // final PrimitivePath argument2 = new ElementPathBuilder(spark) + // .expression("Encounter.participant.individual") + // .fhirType(FHIRDefinedType.REFERENCE) + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .inputExpression("Patient") + // .build(); + // final NamedFunctionInput reverseResolveInput = new NamedFunctionInput(parserContext, input, + // Arrays.asList(argument1, argument2)); + // + // final NamedFunction reverseResolveFunction = NamedFunction.getInstance("reverseResolve"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> reverseResolveFunction.invoke(reverseResolveInput)); + // assertEquals( + // "reverseResolve function accepts a single argument: reverseResolve(Encounter.subject, Encounter.participant.individual)", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfArgumentTypeDoesNotMatchInput() { + // final ResourceCollection input = new ResourcePathBuilder(spark) + // .resourceType(ResourceType.PATIENT) + // .expression("Patient") + // .build(); + // final RuntimeResourceDefinition hapiResourceDef = fhirContext.getResourceDefinition( + // "Encounter"); + // final BaseRuntimeChildDefinition childDefinition = hapiResourceDef.getChildByName( + // "episodeOfCare"); + // final ElementDefinition definition = ElementDefinition.build(childDefinition); + // final PrimitivePath argument = new ElementPathBuilder(spark) + // .expression("Encounter.episodeOfCare") + // .fhirType(FHIRDefinedType.REFERENCE) + // .definition(definition) + // .buildDefined(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .inputExpression("Patient") + // .build(); + // final NamedFunctionInput reverseResolveInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final NamedFunction reverseResolveFunction = NamedFunction.getInstance("reverseResolve"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> reverseResolveFunction.invoke(reverseResolveInput)); + // assertEquals( + // "Reference in argument to reverseResolve does not support input resource type: reverseResolve(Encounter.episodeOfCare)", + // error.getMessage()); + // } + // } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/SumFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/SumFunctionTest.java index 865c6d85c3..b184203e65 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/SumFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/SumFunctionTest.java @@ -17,148 +17,129 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.DecimalCollection; -import au.csiro.pathling.fhirpath.collection.IntegerCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.DatasetBuilder; -import au.csiro.pathling.test.builders.ElementPathBuilder; -import au.csiro.pathling.test.builders.ParserContextBuilder; -import ca.uhn.fhir.context.FhirContext; -import java.math.BigDecimal; -import java.util.Collections; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -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.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class SumFunctionTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - ParserContext parserContext; - - @Test - void returnsCorrectIntegerResult() { - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.IntegerType) - .withRow("observation-1", makeEid(0), 3) - .withRow("observation-1", makeEid(1), 5) - .withRow("observation-1", makeEid(2), 7) - .withRow("observation-2", null, null) - .withRow("observation-3", makeEid(0), -1) - .withRow("observation-3", makeEid(1), null) - .build(); - final PrimitivePath inputPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("valueInteger") - .singular(false) - .build(); - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) - .build(); - - final NamedFunctionInput sumInput = new NamedFunctionInput(parserContext, inputPath, - Collections.emptyList()); - final Collection result = NamedFunction.getInstance("sum").invoke(sumInput); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.IntegerType) - .withRow("observation-1", 15) - .withRow("observation-2", null) - .withRow("observation-3", -1) - .build(); - assertThat(result) - .hasExpression("valueInteger.sum()") - .isSingular() - .isElementPath(IntegerCollection.class) - .selectResult() - .hasRows(expectedDataset); - } - - @Test - void returnsCorrectDecimalResult() { - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.createDecimalType()) - .withRow("observation-1", makeEid(0), new BigDecimal("3.0")) - .withRow("observation-1", makeEid(1), new BigDecimal("5.5")) - .withRow("observation-1", makeEid(2), new BigDecimal("7")) - .withRow("observation-2", null, null) - .withRow("observation-3", makeEid(0), new BigDecimal("-2.50")) - .build(); - final PrimitivePath inputPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DECIMAL) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("valueDecimal") - .singular(false) - .build(); - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) - .build(); - - final NamedFunctionInput sumInput = new NamedFunctionInput(parserContext, inputPath, - Collections.emptyList()); - final Collection result = NamedFunction.getInstance("sum").invoke(sumInput); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.createDecimalType()) - .withRow("observation-1", new BigDecimal("15.5")) - .withRow("observation-2", null) - .withRow("observation-3", new BigDecimal("-2.5")) - .build(); - assertThat(result) - .hasExpression("valueDecimal.sum()") - .isSingular() - .isElementPath(DecimalCollection.class) - .selectResult() - .hasRows(expectedDataset); - } - - @Test - void throwsErrorIfInputNotNumeric() { - final PrimitivePath inputPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .expression("valueString") - .build(); - - final NamedFunctionInput sumInput = new NamedFunctionInput(parserContext, inputPath, - Collections.emptyList()); - final NamedFunction sumFunction = NamedFunction.getInstance("sum"); - - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> sumFunction.invoke(sumInput)); - assertEquals( - "Input to sum function must be numeric: valueString", - error.getMessage()); - } - + // TODO: implement with columns + + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // ParserContext parserContext; + // + // @Test + // void returnsCorrectIntegerResult() { + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.IntegerType) + // .withRow("observation-1", makeEid(0), 3) + // .withRow("observation-1", makeEid(1), 5) + // .withRow("observation-1", makeEid(2), 7) + // .withRow("observation-2", null, null) + // .withRow("observation-3", makeEid(0), -1) + // .withRow("observation-3", makeEid(1), null) + // .build(); + // final PrimitivePath inputPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("valueInteger") + // .singular(false) + // .build(); + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) + // .build(); + // + // final NamedFunctionInput sumInput = new NamedFunctionInput(parserContext, inputPath, + // Collections.emptyList()); + // final Collection result = NamedFunction.getInstance("sum").invoke(sumInput); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.IntegerType) + // .withRow("observation-1", 15) + // .withRow("observation-2", null) + // .withRow("observation-3", -1) + // .build(); + // assertThat(result) + // .hasExpression("valueInteger.sum()") + // .isSingular() + // .isElementPath(IntegerCollection.class) + // .selectResult() + // .hasRows(expectedDataset); + // } + // + // @Test + // void returnsCorrectDecimalResult() { + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.createDecimalType()) + // .withRow("observation-1", makeEid(0), new BigDecimal("3.0")) + // .withRow("observation-1", makeEid(1), new BigDecimal("5.5")) + // .withRow("observation-1", makeEid(2), new BigDecimal("7")) + // .withRow("observation-2", null, null) + // .withRow("observation-3", makeEid(0), new BigDecimal("-2.50")) + // .build(); + // final PrimitivePath inputPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DECIMAL) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("valueDecimal") + // .singular(false) + // .build(); + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(inputPath.getIdColumn())) + // .build(); + // + // final NamedFunctionInput sumInput = new NamedFunctionInput(parserContext, inputPath, + // Collections.emptyList()); + // final Collection result = NamedFunction.getInstance("sum").invoke(sumInput); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.createDecimalType()) + // .withRow("observation-1", new BigDecimal("15.5")) + // .withRow("observation-2", null) + // .withRow("observation-3", new BigDecimal("-2.5")) + // .build(); + // assertThat(result) + // .hasExpression("valueDecimal.sum()") + // .isSingular() + // .isElementPath(DecimalCollection.class) + // .selectResult() + // .hasRows(expectedDataset); + // } + // + // @Test + // void throwsErrorIfInputNotNumeric() { + // final PrimitivePath inputPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .expression("valueString") + // .build(); + // + // final NamedFunctionInput sumInput = new NamedFunctionInput(parserContext, inputPath, + // Collections.emptyList()); + // final NamedFunction sumFunction = NamedFunction.getInstance("sum"); + // + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> sumFunction.invoke(sumInput)); + // assertEquals( + // "Input to sum function must be numeric: valueString", + // error.getMessage()); + // } + // } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/UntilFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/UntilFunctionTest.java index 46dd7c159a..b1e8f806c9 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/UntilFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/UntilFunctionTest.java @@ -17,378 +17,340 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.collection.DateCollection; -import au.csiro.pathling.fhirpath.collection.DateTimeCollection; -import au.csiro.pathling.fhirpath.collection.IntegerCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.ReferencePath; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.literal.DateLiteralPath; -import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.DatasetBuilder; -import au.csiro.pathling.test.builders.ElementPathBuilder; -import au.csiro.pathling.test.builders.ParserContextBuilder; -import ca.uhn.fhir.context.FhirContext; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMap.Builder; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import lombok.Value; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -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.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest @TestInstance(Lifecycle.PER_CLASS) +@NotImplemented class UntilFunctionTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - static final String ID_ALIAS = "_abc123"; - static final String[] IDS = {"patient-1", "patient-2", "patient-3", "patient-4", "patient-5", - "patient-6"}; - - Dataset leftDataset() { - return new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-1", "2020-01-01T00:00:00Z") - .withRow("patient-2", "2020-01-01T00:00:00Z") - .withRow("patient-3", "2020-01-01") - .withRow("patient-4", null) - .withRow("patient-5", "2020-01-01T00:00:00Z") - .withRow("patient-6", null) - .build(); - } - - Dataset rightDataset() { - return new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-1", "2021-01-01T00:00:00Z") - .withRow("patient-2", "2021-01-01") - .withRow("patient-3", "2021-01-01T00:00:00Z") - .withRow("patient-4", "2021-01-01T00:00:00Z") - .withRow("patient-5", null) - .withRow("patient-6", null) - .build(); - } - - static final Object[] YEARS_RESULT = {1, 1, 1, null, null, null}; - static final Object[] MONTHS_RESULT = {12, 12, 12, null, null, null}; - static final Object[] DAYS_RESULT = {366, 366, 366, null, null, null}; - static final Object[] HOURS_RESULT = {8784, 8784, 8784, null, null, null}; - static final Object[] MINUTES_RESULT = {527040, 527040, 527040, null, null, null}; - static final Object[] SECONDS_RESULT = {31622400, 31622400, 31622400, null, null, null}; - private static final ImmutableMap CALENDAR_DURATION_TO_RESULT = new Builder() - .put("years", YEARS_RESULT) - .put("months", MONTHS_RESULT) - .put("days", DAYS_RESULT) - .put("hours", HOURS_RESULT) - .put("minutes", MINUTES_RESULT) - .put("seconds", SECONDS_RESULT) - .put("year", YEARS_RESULT) - .put("month", MONTHS_RESULT) - .put("day", DAYS_RESULT) - .put("hour", HOURS_RESULT) - .put("minute", MINUTES_RESULT) - .put("second", SECONDS_RESULT) - .build(); - - @Value - static class TestParameters { - - @Nonnull - String name; - - @Nonnull - NonLiteralPath input; - - @Nonnull - List arguments; - - @Nonnull - ParserContext context; - - @Nonnull - Dataset expectedResult; - - @Override - public String toString() { - return name; - } - - } - - @Nonnull - Stream parameters() { - final java.util.Collection parameters = new ArrayList<>(); - for (final String calendarDuration : CALENDAR_DURATION_TO_RESULT.keySet()) { - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DATETIME) - .dataset(leftDataset()) - .idAndValueColumns() - .singular(true) - .build(); - final PrimitivePath argument = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DATETIME) - .dataset(rightDataset()) - .idAndValueColumns() - .singular(true) - .build(); - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(input.getIdColumn())) - .build(); - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.IntegerType) - .withIdValueRows(Arrays.asList(IDS), id -> { - final int index = Integer.parseInt(id.split("-")[1]) - 1; - final Object[] results = CALENDAR_DURATION_TO_RESULT.get(calendarDuration); - assertNotNull(results); - return results[index]; - }).build(); - final List arguments = List.of(argument, - StringCollection.fromLiteral(calendarDuration, input)); - parameters.add( - new TestParameters(calendarDuration, input, arguments, context, expectedResult)); - } - return parameters.stream(); - } - - @ParameterizedTest - @MethodSource("parameters") - void test(@Nonnull final TestParameters parameters) { - final NamedFunctionInput input = new NamedFunctionInput(parameters.getContext(), - parameters.getInput(), parameters.getArguments()); - final Collection result = NamedFunction.getInstance("until").invoke(input); - assertThat(result) - .isElementPath(IntegerCollection.class) - .selectResult() - .hasRows(parameters.getExpectedResult()); - } - - @Test - void milliseconds() { - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-1", "2020-01-01") - .build(); - final Dataset rightDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-1", "2020-01-02") - .build(); - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DATE) - .dataset(leftDataset) - .idAndValueColumns() - .singular(true) - .build(); - final PrimitivePath argument = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DATE) - .dataset(rightDataset) - .idAndValueColumns() - .singular(true) - .build(); - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(input.getIdColumn())) - .build(); - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.IntegerType) - .withRow("patient-1", 86400000) - .build(); - final List arguments1 = List.of(argument, - StringCollection.fromLiteral("millisecond", input)); - final List arguments2 = List.of(argument, - StringCollection.fromLiteral("millisecond", input)); - for (final List arguments : List.of(arguments1, arguments2)) { - final NamedFunctionInput functionInput = new NamedFunctionInput(context, input, arguments); - final Collection result = NamedFunction.getInstance("until").invoke(functionInput); - assertThat(result) - .isElementPath(IntegerCollection.class) - .selectResult() - .hasRows(expectedResult); - } - } - - @Test - void dateLiteralArgument() throws ParseException { - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-1", "2020-01-01") - .build(); - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DATE) - .dataset(leftDataset) - .idAndValueColumns() - .singular(true) - .build(); - final DateLiteralPath argument = DateCollection.fromLiteral("2020-01-02", input); - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(input.getIdColumn())) - .build(); - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.IntegerType) - .withRow("patient-1", 86400000) - .build(); - final List arguments = List.of(argument, - StringCollection.fromLiteral("millisecond", input)); - final NamedFunctionInput functionInput = new NamedFunctionInput(context, input, arguments); - final Collection result = NamedFunction.getInstance("until").invoke(functionInput); - assertThat(result) - .isElementPath(IntegerCollection.class) - .selectResult() - .hasRows(expectedResult); - } - - @Test - void dateTimeLiteralArgument() throws ParseException { - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-1", "2020-01-01") - .build(); - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DATE) - .dataset(leftDataset) - .idAndValueColumns() - .singular(true) - .build(); - final DateTimeLiteralPath argument = DateTimeLiteralPath.fromString("2020-01-02T00:00:00Z", - input); - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(input.getIdColumn())) - .build(); - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.IntegerType) - .withRow("patient-1", 86400000) - .build(); - final List arguments = List.of(argument, - StringCollection.fromLiteral("millisecond", input)); - final NamedFunctionInput functionInput = new NamedFunctionInput(context, input, arguments); - final Collection result = NamedFunction.getInstance("until").invoke(functionInput); - assertThat(result) - .isElementPath(IntegerCollection.class) - .selectResult() - .hasRows(expectedResult); - } - - @Test - void invalidCalendarDuration() { - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-1", "2020-01-01") - .build(); - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DATE) - .dataset(leftDataset) - .idAndValueColumns() - .singular(true) - .build(); - - final DateTimeCollection argument = mock(DateTimeCollection.class); - when(argument.isSingular()).thenReturn(true); - - final NamedFunctionInput functionInput = new NamedFunctionInput(mock(ParserContext.class), - input, - List.of(argument, StringCollection.fromLiteral("nanosecond", input))); - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> NamedFunction.getInstance("until").invoke(functionInput)); - assertEquals("Invalid calendar duration: nanosecond", error.getMessage()); - } - - @Test - void wrongNumberOfArguments() { - final NamedFunctionInput input = new NamedFunctionInput(mock(ParserContext.class), - mock(DateTimeCollection.class), List.of(mock(DateTimeCollection.class))); - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> NamedFunction.getInstance("until").invoke(input)); - assertEquals("until function must have two arguments", error.getMessage()); - } - - @Test - void wrongInputType() { - final NamedFunctionInput input = new NamedFunctionInput(mock(ParserContext.class), - mock(StringCollection.class), - List.of(mock(DateTimeCollection.class), mock(StringLiteralPath.class))); - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> NamedFunction.getInstance("until").invoke(input)); - assertEquals("until function must be invoked on a DateTime or Date", error.getMessage()); - } - - @Test - void wrongArgumentType() { - final NamedFunctionInput input = new NamedFunctionInput(mock(ParserContext.class), - mock(DateCollection.class), - List.of(mock(ReferencePath.class), mock(StringLiteralPath.class))); - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> NamedFunction.getInstance("until").invoke(input)); - assertEquals("until function must have a DateTime or Date as the first argument", - error.getMessage()); - } - - @Test - void inputNotSingular() { - final DateTimeCollection input = mock(DateTimeCollection.class); - final NamedFunctionInput functionInput = new NamedFunctionInput(mock(ParserContext.class), - input, List.of(mock(DateTimeLiteralPath.class), - StringCollection.fromLiteral("nanosecond", input))); - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> NamedFunction.getInstance("until").invoke(functionInput)); - assertEquals("until function must be invoked on a singular path", error.getMessage()); - } - - @Test - void argumentNotSingular() { - final DateTimeCollection input = mock(DateTimeCollection.class); - when(input.isSingular()).thenReturn(true); - final NamedFunctionInput functionInput = new NamedFunctionInput(mock(ParserContext.class), - input, - List.of(mock(DateTimeLiteralPath.class), - StringCollection.fromLiteral("nanosecond", input))); - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> NamedFunction.getInstance("until").invoke(functionInput)); - assertEquals("until function must have the singular path as its first argument", - error.getMessage()); - } + // TODO: implement with columns + // + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // static final String ID_ALIAS = "_abc123"; + // static final String[] IDS = {"patient-1", "patient-2", "patient-3", "patient-4", "patient-5", + // "patient-6"}; + // + // Dataset leftDataset() { + // return new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "2020-01-01T00:00:00Z") + // .withRow("patient-2", "2020-01-01T00:00:00Z") + // .withRow("patient-3", "2020-01-01") + // .withRow("patient-4", null) + // .withRow("patient-5", "2020-01-01T00:00:00Z") + // .withRow("patient-6", null) + // .build(); + // } + // + // Dataset rightDataset() { + // return new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "2021-01-01T00:00:00Z") + // .withRow("patient-2", "2021-01-01") + // .withRow("patient-3", "2021-01-01T00:00:00Z") + // .withRow("patient-4", "2021-01-01T00:00:00Z") + // .withRow("patient-5", null) + // .withRow("patient-6", null) + // .build(); + // } + // + // static final Object[] YEARS_RESULT = {1, 1, 1, null, null, null}; + // static final Object[] MONTHS_RESULT = {12, 12, 12, null, null, null}; + // static final Object[] DAYS_RESULT = {366, 366, 366, null, null, null}; + // static final Object[] HOURS_RESULT = {8784, 8784, 8784, null, null, null}; + // static final Object[] MINUTES_RESULT = {527040, 527040, 527040, null, null, null}; + // static final Object[] SECONDS_RESULT = {31622400, 31622400, 31622400, null, null, null}; + // private static final ImmutableMap CALENDAR_DURATION_TO_RESULT = new Builder() + // .put("years", YEARS_RESULT) + // .put("months", MONTHS_RESULT) + // .put("days", DAYS_RESULT) + // .put("hours", HOURS_RESULT) + // .put("minutes", MINUTES_RESULT) + // .put("seconds", SECONDS_RESULT) + // .put("year", YEARS_RESULT) + // .put("month", MONTHS_RESULT) + // .put("day", DAYS_RESULT) + // .put("hour", HOURS_RESULT) + // .put("minute", MINUTES_RESULT) + // .put("second", SECONDS_RESULT) + // .build(); + // + // @Value + // static class TestParameters { + // + // @Nonnull + // String name; + // + // @Nonnull + // NonLiteralPath input; + // + // @Nonnull + // List arguments; + // + // @Nonnull + // ParserContext context; + // + // @Nonnull + // Dataset expectedResult; + // + // @Override + // public String toString() { + // return name; + // } + // + // } + // + // @Nonnull + // Stream parameters() { + // final java.util.Collection parameters = new ArrayList<>(); + // for (final String calendarDuration : CALENDAR_DURATION_TO_RESULT.keySet()) { + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DATETIME) + // .dataset(leftDataset()) + // .idAndValueColumns() + // .singular(true) + // .build(); + // final PrimitivePath argument = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DATETIME) + // .dataset(rightDataset()) + // .idAndValueColumns() + // .singular(true) + // .build(); + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(input.getIdColumn())) + // .build(); + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.IntegerType) + // .withIdValueRows(Arrays.asList(IDS), id -> { + // final int index = Integer.parseInt(id.split("-")[1]) - 1; + // final Object[] results = CALENDAR_DURATION_TO_RESULT.get(calendarDuration); + // assertNotNull(results); + // return results[index]; + // }).build(); + // final List arguments = List.of(argument, + // StringCollection.fromLiteral(calendarDuration, input)); + // parameters.add( + // new TestParameters(calendarDuration, input, arguments, context, expectedResult)); + // } + // return parameters.stream(); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void test(@Nonnull final TestParameters parameters) { + // final NamedFunctionInput input = new NamedFunctionInput(parameters.getContext(), + // parameters.getInput(), parameters.getArguments()); + // final Collection result = NamedFunction.getInstance("until").invoke(input); + // assertThat(result) + // .isElementPath(IntegerCollection.class) + // .selectResult() + // .hasRows(parameters.getExpectedResult()); + // } + // + // @Test + // void milliseconds() { + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "2020-01-01") + // .build(); + // final Dataset rightDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "2020-01-02") + // .build(); + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DATE) + // .dataset(leftDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // final PrimitivePath argument = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DATE) + // .dataset(rightDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(input.getIdColumn())) + // .build(); + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.IntegerType) + // .withRow("patient-1", 86400000) + // .build(); + // final List arguments1 = List.of(argument, + // StringCollection.fromLiteral("millisecond", input)); + // final List arguments2 = List.of(argument, + // StringCollection.fromLiteral("millisecond", input)); + // for (final List arguments : List.of(arguments1, arguments2)) { + // final NamedFunctionInput functionInput = new NamedFunctionInput(context, input, arguments); + // final Collection result = NamedFunction.getInstance("until").invoke(functionInput); + // assertThat(result) + // .isElementPath(IntegerCollection.class) + // .selectResult() + // .hasRows(expectedResult); + // } + // } + // + // @Test + // void dateLiteralArgument() throws ParseException { + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "2020-01-01") + // .build(); + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DATE) + // .dataset(leftDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // final DateLiteralPath argument = DateCollection.fromLiteral("2020-01-02", input); + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(input.getIdColumn())) + // .build(); + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.IntegerType) + // .withRow("patient-1", 86400000) + // .build(); + // final List arguments = List.of(argument, + // StringCollection.fromLiteral("millisecond", input)); + // final NamedFunctionInput functionInput = new NamedFunctionInput(context, input, arguments); + // final Collection result = NamedFunction.getInstance("until").invoke(functionInput); + // assertThat(result) + // .isElementPath(IntegerCollection.class) + // .selectResult() + // .hasRows(expectedResult); + // } + // + // @Test + // void dateTimeLiteralArgument() throws ParseException { + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "2020-01-01") + // .build(); + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DATE) + // .dataset(leftDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // final DateTimeLiteralPath argument = DateTimeLiteralPath.fromString("2020-01-02T00:00:00Z", + // input); + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(input.getIdColumn())) + // .build(); + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.IntegerType) + // .withRow("patient-1", 86400000) + // .build(); + // final List arguments = List.of(argument, + // StringCollection.fromLiteral("millisecond", input)); + // final NamedFunctionInput functionInput = new NamedFunctionInput(context, input, arguments); + // final Collection result = NamedFunction.getInstance("until").invoke(functionInput); + // assertThat(result) + // .isElementPath(IntegerCollection.class) + // .selectResult() + // .hasRows(expectedResult); + // } + // + // @Test + // void invalidCalendarDuration() { + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "2020-01-01") + // .build(); + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DATE) + // .dataset(leftDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // + // final DateTimeCollection argument = mock(DateTimeCollection.class); + // when(argument.isSingular()).thenReturn(true); + // + // final NamedFunctionInput functionInput = new NamedFunctionInput(mock(ParserContext.class), + // input, + // List.of(argument, StringCollection.fromLiteral("nanosecond", input))); + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> NamedFunction.getInstance("until").invoke(functionInput)); + // assertEquals("Invalid calendar duration: nanosecond", error.getMessage()); + // } + // + // @Test + // void wrongNumberOfArguments() { + // final NamedFunctionInput input = new NamedFunctionInput(mock(ParserContext.class), + // mock(DateTimeCollection.class), List.of(mock(DateTimeCollection.class))); + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> NamedFunction.getInstance("until").invoke(input)); + // assertEquals("until function must have two arguments", error.getMessage()); + // } + // + // @Test + // void wrongInputType() { + // final NamedFunctionInput input = new NamedFunctionInput(mock(ParserContext.class), + // mock(StringCollection.class), + // List.of(mock(DateTimeCollection.class), mock(StringLiteralPath.class))); + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> NamedFunction.getInstance("until").invoke(input)); + // assertEquals("until function must be invoked on a DateTime or Date", error.getMessage()); + // } + // + // @Test + // void wrongArgumentType() { + // final NamedFunctionInput input = new NamedFunctionInput(mock(ParserContext.class), + // mock(DateCollection.class), + // List.of(mock(ReferencePath.class), mock(StringLiteralPath.class))); + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> NamedFunction.getInstance("until").invoke(input)); + // assertEquals("until function must have a DateTime or Date as the first argument", + // error.getMessage()); + // } + // + // @Test + // void inputNotSingular() { + // final DateTimeCollection input = mock(DateTimeCollection.class); + // final NamedFunctionInput functionInput = new NamedFunctionInput(mock(ParserContext.class), + // input, List.of(mock(DateTimeLiteralPath.class), + // StringCollection.fromLiteral("nanosecond", input))); + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> NamedFunction.getInstance("until").invoke(functionInput)); + // assertEquals("until function must be invoked on a singular path", error.getMessage()); + // } + // + // @Test + // void argumentNotSingular() { + // final DateTimeCollection input = mock(DateTimeCollection.class); + // when(input.isSingular()).thenReturn(true); + // final NamedFunctionInput functionInput = new NamedFunctionInput(mock(ParserContext.class), + // input, + // List.of(mock(DateTimeLiteralPath.class), + // StringCollection.fromLiteral("nanosecond", input))); + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> NamedFunction.getInstance("until").invoke(functionInput)); + // assertEquals("until function must have the singular path as its first argument", + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/WhereFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/WhereFunctionTest.java index a84aacf01b..e740d11f31 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/WhereFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/WhereFunctionTest.java @@ -17,346 +17,322 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static au.csiro.pathling.utilities.Strings.randomAlias; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.builders.ResourcePathBuilder; -import ca.uhn.fhir.context.FhirContext; -import java.util.Arrays; -import java.util.Collections; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.apache.spark.sql.functions; -import org.apache.spark.sql.types.DataTypes; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest @TestInstance(Lifecycle.PER_METHOD) +@NotImplemented class WhereFunctionTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - // This test simulates the execution of the where function on the path - // `Patient.reverseResolve(Encounter.subject).where($this.status = 'in-progress')`. - @Test - void whereOnResource() { - final String statusColumn = randomAlias(); - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withIdColumn() - .withColumn(statusColumn, DataTypes.StringType) - .withRow("patient-1", makeEid(1), "encounter-1", "in-progress") - .withRow("patient-1", makeEid(0), "encounter-2", "finished") - .withRow("patient-2", makeEid(0), "encounter-3", "in-progress") - .withRow("patient-3", makeEid(1), "encounter-4", "in-progress") - .withRow("patient-3", makeEid(0), "encounter-5", "finished") - .withRow("patient-4", makeEid(1), "encounter-6", "finished") - .withRow("patient-4", makeEid(0), "encounter-7", "finished") - .withRow("patient-5", makeEid(1), "encounter-8", "in-progress") - .withRow("patient-5", makeEid(0), "encounter-9", "in-progress") - .withRow("patient-6", null, null, null) - .build(); - final ResourceCollection inputPath = new ResourcePathBuilder(spark) - .expression("reverseResolve(Encounter.subject)") - .dataset(inputDataset) - .idEidAndValueColumns() - .buildCustom(); - - // Build an expression which represents the argument to the function. We assume that the value - // column from the input dataset is also present within the argument dataset. - - final NonLiteralPath thisPath = inputPath.toThisPath(); - - final Dataset argumentDataset = thisPath.getDataset() - .withColumn("value", - thisPath.getDataset().col(statusColumn).equalTo("in-progress")); - - assertTrue(thisPath.getThisColumn().isPresent()); - final PrimitivePath argumentPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .dataset(argumentDataset) - .idColumn(inputPath.getIdColumn()) - .valueColumn(argumentDataset.col("value")) - .thisColumn(thisPath.getThisColumn().get()) - .singular(true) - .build(); - - // Prepare the input to the function. - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, - inputPath, Collections.singletonList(argumentPath)); - - // Execute the function. - final NamedFunction whereFunction = NamedFunction.getInstance("where"); - final Collection result = whereFunction.invoke(whereInput); - - // Check the result dataset. - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withIdColumn() - .withRow("patient-1", makeEid(0), null) - .withRow("patient-1", makeEid(1), "patient-1") - .withRow("patient-2", makeEid(0), "patient-2") - .withRow("patient-3", makeEid(0), null) - .withRow("patient-3", makeEid(1), "patient-3") - .withRow("patient-4", makeEid(0), null) - .withRow("patient-4", makeEid(1), null) - .withRow("patient-5", makeEid(0), "patient-5") - .withRow("patient-5", makeEid(1), "patient-5") - .withRow("patient-6", null, null) - .build(); - - assertThat(result) - .selectOrderedResultWithEid() - .hasRows(expectedDataset); - } - - @Test - void whereOnElement() { - // Build an expression which represents the input to the function. - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", makeEid(1), "en") - .withRow("patient-1", makeEid(0), "es") - .withRow("patient-2", makeEid(0), "de") - .withRow("patient-3", makeEid(2), "en") - .withRow("patient-3", makeEid(1), "en") - .withRow("patient-3", makeEid(0), "zh") - .withRow("patient-4", makeEid(1), "fr") - .withRow("patient-4", makeEid(0), "fr") - .withRow("patient-5", null, null) - .build(); - final PrimitivePath inputPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(dataset) - .idAndEidAndValueColumns() - .singular(false) - .build(); - - final NonLiteralPath thisPath = inputPath.toThisPath(); - - // Build an expression which represents the argument to the function. - final Dataset argumentDataset = thisPath.getDataset() - .withColumn("value", inputPath.getValueColumn().equalTo("en")); - - assertTrue(thisPath.getThisColumn().isPresent()); - final PrimitivePath argumentExpression = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .dataset(argumentDataset) - .idColumn(inputPath.getIdColumn()) - .valueColumn(argumentDataset.col("value")) - .thisColumn(thisPath.getThisColumn().get()) - .singular(true) - .build(); - - // Prepare the input to the function. - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, - inputPath, - Collections.singletonList(argumentExpression)); - - // Execute the function. - final NamedFunction whereFunction = NamedFunction.getInstance("where"); - final Collection result = whereFunction.invoke(whereInput); - - // Check the result dataset. - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", makeEid(0), null) - .withRow("patient-1", makeEid(1), "en") - .withRow("patient-2", makeEid(0), null) - .withRow("patient-3", makeEid(0), null) - .withRow("patient-3", makeEid(1), "en") - .withRow("patient-3", makeEid(2), "en") - .withRow("patient-4", makeEid(0), null) - .withRow("patient-4", makeEid(1), null) - .withRow("patient-5", null, null) - .build(); - assertThat(result) - .selectOrderedResultWithEid() - .hasRows(expectedDataset); - } - - @Test - void nullValuesAreNull() { - // Build an expression which represents the input to the function. - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", makeEid(0), "en") - .withRow("patient-1", makeEid(1), "es") - .withRow("patient-2", makeEid(0), "de") - .withRow("patient-3", makeEid(0), "en") - .withRow("patient-3", makeEid(1), "en") - .withRow("patient-3", makeEid(2), "zh") - .withRow("patient-4", makeEid(0), "ar") - .build(); - final PrimitivePath inputPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(dataset) - .idAndEidAndValueColumns() - .singular(false) - .build(); - - // Build an expression which represents the argument to the function. - final NonLiteralPath thisPath = inputPath.toThisPath(); - - final Dataset argumentDataset = thisPath.getDataset() - .withColumn("value", - functions.when(inputPath.getValueColumn().equalTo("en"), null).otherwise(true)); - assertTrue(thisPath.getThisColumn().isPresent()); - final PrimitivePath argumentPath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .dataset(argumentDataset) - .idColumn(inputPath.getIdColumn()) - .valueColumn(argumentDataset.col("value")) - .thisColumn(thisPath.getThisColumn().get()) - .singular(true) - .build(); - - // Prepare the input to the function. - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, - inputPath, Collections.singletonList(argumentPath)); - - // Execute the function. - final NamedFunction whereFunction = NamedFunction.getInstance("where"); - final Collection result = whereFunction.invoke(whereInput); - - // Check the result dataset. - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", makeEid(0), null) - .withRow("patient-1", makeEid(1), "es") - .withRow("patient-2", makeEid(0), "de") - .withRow("patient-3", makeEid(0), null) - .withRow("patient-3", makeEid(1), null) - .withRow("patient-3", makeEid(2), "zh") - .withRow("patient-4", makeEid(0), "ar") - .build(); - assertThat(result) - .selectOrderedResultWithEid() - .hasRows(expectedDataset); - } - - @Test - void throwsErrorIfMoreThanOneArgument() { - final ResourceCollection input = new ResourcePathBuilder(spark).build(); - final PrimitivePath argument1 = new ElementPathBuilder(spark) - .expression("$this.gender = 'female'") - .fhirType(FHIRDefinedType.BOOLEAN) - .build(); - final PrimitivePath argument2 = new ElementPathBuilder(spark) - .expression("$this.gender != 'male'") - .fhirType(FHIRDefinedType.BOOLEAN) - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, input, - Arrays.asList(argument1, argument2)); - - final NamedFunction whereFunction = NamedFunction.getInstance("where"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> whereFunction.invoke(whereInput)); - assertEquals("where function accepts one argument", error.getMessage()); - } - - @Test - void throwsErrorIfArgumentNotBoolean() { - final ResourceCollection input = new ResourcePathBuilder(spark).build(); - final PrimitivePath argument = new ElementPathBuilder(spark) - .expression("$this.gender") - .fhirType(FHIRDefinedType.STRING) - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final NamedFunction whereFunction = NamedFunction.getInstance("where"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> whereFunction.invoke(whereInput)); - assertEquals( - "Argument to where function must be a singular Boolean: $this.gender", - error.getMessage()); - } - - @Test - void throwsErrorIfArgumentNotSingular() { - final ResourceCollection input = new ResourcePathBuilder(spark).build(); - final PrimitivePath argument = new ElementPathBuilder(spark) - .expression("$this.communication.preferred") - .fhirType(FHIRDefinedType.BOOLEAN) - .singular(false) - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final NamedFunction whereFunction = NamedFunction.getInstance("where"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> whereFunction.invoke(whereInput)); - assertEquals( - "Argument to where function must be a singular Boolean: $this.communication.preferred", - error.getMessage()); - } - - @Test - void throwsErrorIfArgumentIsLiteral() { - final ResourceCollection input = new ResourcePathBuilder(spark).build(); - final BooleanLiteralPath argument = BooleanLiteralPath - .fromString("true", input); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final NamedFunction whereFunction = NamedFunction.getInstance("where"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> whereFunction.invoke(whereInput)); - assertEquals( - "Argument to where function cannot be a literal: true", - error.getMessage()); - } - + // TODO: implement with columns + // + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // // This test simulates the execution of the where function on the path + // // `Patient.reverseResolve(Encounter.subject).where($this.status = 'in-progress')`. + // @Test + // void whereOnResource() { + // final String statusColumn = randomAlias(); + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withIdColumn() + // .withColumn(statusColumn, DataTypes.StringType) + // .withRow("patient-1", makeEid(1), "encounter-1", "in-progress") + // .withRow("patient-1", makeEid(0), "encounter-2", "finished") + // .withRow("patient-2", makeEid(0), "encounter-3", "in-progress") + // .withRow("patient-3", makeEid(1), "encounter-4", "in-progress") + // .withRow("patient-3", makeEid(0), "encounter-5", "finished") + // .withRow("patient-4", makeEid(1), "encounter-6", "finished") + // .withRow("patient-4", makeEid(0), "encounter-7", "finished") + // .withRow("patient-5", makeEid(1), "encounter-8", "in-progress") + // .withRow("patient-5", makeEid(0), "encounter-9", "in-progress") + // .withRow("patient-6", null, null, null) + // .build(); + // final ResourceCollection inputPath = new ResourcePathBuilder(spark) + // .expression("reverseResolve(Encounter.subject)") + // .dataset(inputDataset) + // .idEidAndValueColumns() + // .buildCustom(); + // + // // Build an expression which represents the argument to the function. We assume that the value + // // column from the input dataset is also present within the argument dataset. + // + // final NonLiteralPath thisPath = inputPath.toThisPath(); + // + // final Dataset argumentDataset = thisPath.getDataset() + // .withColumn("value", + // thisPath.getDataset().col(statusColumn).equalTo("in-progress")); + // + // assertTrue(thisPath.getThisColumn().isPresent()); + // final PrimitivePath argumentPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .dataset(argumentDataset) + // .idColumn(inputPath.getIdColumn()) + // .valueColumn(argumentDataset.col("value")) + // .thisColumn(thisPath.getThisColumn().get()) + // .singular(true) + // .build(); + // + // // Prepare the input to the function. + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, + // inputPath, Collections.singletonList(argumentPath)); + // + // // Execute the function. + // final NamedFunction whereFunction = NamedFunction.getInstance("where"); + // final Collection result = whereFunction.invoke(whereInput); + // + // // Check the result dataset. + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withIdColumn() + // .withRow("patient-1", makeEid(0), null) + // .withRow("patient-1", makeEid(1), "patient-1") + // .withRow("patient-2", makeEid(0), "patient-2") + // .withRow("patient-3", makeEid(0), null) + // .withRow("patient-3", makeEid(1), "patient-3") + // .withRow("patient-4", makeEid(0), null) + // .withRow("patient-4", makeEid(1), null) + // .withRow("patient-5", makeEid(0), "patient-5") + // .withRow("patient-5", makeEid(1), "patient-5") + // .withRow("patient-6", null, null) + // .build(); + // + // assertThat(result) + // .selectOrderedResultWithEid() + // .hasRows(expectedDataset); + // } + // + // @Test + // void whereOnElement() { + // // Build an expression which represents the input to the function. + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", makeEid(1), "en") + // .withRow("patient-1", makeEid(0), "es") + // .withRow("patient-2", makeEid(0), "de") + // .withRow("patient-3", makeEid(2), "en") + // .withRow("patient-3", makeEid(1), "en") + // .withRow("patient-3", makeEid(0), "zh") + // .withRow("patient-4", makeEid(1), "fr") + // .withRow("patient-4", makeEid(0), "fr") + // .withRow("patient-5", null, null) + // .build(); + // final PrimitivePath inputPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(dataset) + // .idAndEidAndValueColumns() + // .singular(false) + // .build(); + // + // final NonLiteralPath thisPath = inputPath.toThisPath(); + // + // // Build an expression which represents the argument to the function. + // final Dataset argumentDataset = thisPath.getDataset() + // .withColumn("value", inputPath.getValueColumn().equalTo("en")); + // + // assertTrue(thisPath.getThisColumn().isPresent()); + // final PrimitivePath argumentExpression = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .dataset(argumentDataset) + // .idColumn(inputPath.getIdColumn()) + // .valueColumn(argumentDataset.col("value")) + // .thisColumn(thisPath.getThisColumn().get()) + // .singular(true) + // .build(); + // + // // Prepare the input to the function. + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, + // inputPath, + // Collections.singletonList(argumentExpression)); + // + // // Execute the function. + // final NamedFunction whereFunction = NamedFunction.getInstance("where"); + // final Collection result = whereFunction.invoke(whereInput); + // + // // Check the result dataset. + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", makeEid(0), null) + // .withRow("patient-1", makeEid(1), "en") + // .withRow("patient-2", makeEid(0), null) + // .withRow("patient-3", makeEid(0), null) + // .withRow("patient-3", makeEid(1), "en") + // .withRow("patient-3", makeEid(2), "en") + // .withRow("patient-4", makeEid(0), null) + // .withRow("patient-4", makeEid(1), null) + // .withRow("patient-5", null, null) + // .build(); + // assertThat(result) + // .selectOrderedResultWithEid() + // .hasRows(expectedDataset); + // } + // + // @Test + // void nullValuesAreNull() { + // // Build an expression which represents the input to the function. + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", makeEid(0), "en") + // .withRow("patient-1", makeEid(1), "es") + // .withRow("patient-2", makeEid(0), "de") + // .withRow("patient-3", makeEid(0), "en") + // .withRow("patient-3", makeEid(1), "en") + // .withRow("patient-3", makeEid(2), "zh") + // .withRow("patient-4", makeEid(0), "ar") + // .build(); + // final PrimitivePath inputPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(dataset) + // .idAndEidAndValueColumns() + // .singular(false) + // .build(); + // + // // Build an expression which represents the argument to the function. + // final NonLiteralPath thisPath = inputPath.toThisPath(); + // + // final Dataset argumentDataset = thisPath.getDataset() + // .withColumn("value", + // functions.when(inputPath.getValueColumn().equalTo("en"), null).otherwise(true)); + // assertTrue(thisPath.getThisColumn().isPresent()); + // final PrimitivePath argumentPath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .dataset(argumentDataset) + // .idColumn(inputPath.getIdColumn()) + // .valueColumn(argumentDataset.col("value")) + // .thisColumn(thisPath.getThisColumn().get()) + // .singular(true) + // .build(); + // + // // Prepare the input to the function. + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, + // inputPath, Collections.singletonList(argumentPath)); + // + // // Execute the function. + // final NamedFunction whereFunction = NamedFunction.getInstance("where"); + // final Collection result = whereFunction.invoke(whereInput); + // + // // Check the result dataset. + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", makeEid(0), null) + // .withRow("patient-1", makeEid(1), "es") + // .withRow("patient-2", makeEid(0), "de") + // .withRow("patient-3", makeEid(0), null) + // .withRow("patient-3", makeEid(1), null) + // .withRow("patient-3", makeEid(2), "zh") + // .withRow("patient-4", makeEid(0), "ar") + // .build(); + // assertThat(result) + // .selectOrderedResultWithEid() + // .hasRows(expectedDataset); + // } + // + // @Test + // void throwsErrorIfMoreThanOneArgument() { + // final ResourceCollection input = new ResourcePathBuilder(spark).build(); + // final PrimitivePath argument1 = new ElementPathBuilder(spark) + // .expression("$this.gender = 'female'") + // .fhirType(FHIRDefinedType.BOOLEAN) + // .build(); + // final PrimitivePath argument2 = new ElementPathBuilder(spark) + // .expression("$this.gender != 'male'") + // .fhirType(FHIRDefinedType.BOOLEAN) + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, input, + // Arrays.asList(argument1, argument2)); + // + // final NamedFunction whereFunction = NamedFunction.getInstance("where"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> whereFunction.invoke(whereInput)); + // assertEquals("where function accepts one argument", error.getMessage()); + // } + // + // @Test + // void throwsErrorIfArgumentNotBoolean() { + // final ResourceCollection input = new ResourcePathBuilder(spark).build(); + // final PrimitivePath argument = new ElementPathBuilder(spark) + // .expression("$this.gender") + // .fhirType(FHIRDefinedType.STRING) + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final NamedFunction whereFunction = NamedFunction.getInstance("where"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> whereFunction.invoke(whereInput)); + // assertEquals( + // "Argument to where function must be a singular Boolean: $this.gender", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfArgumentNotSingular() { + // final ResourceCollection input = new ResourcePathBuilder(spark).build(); + // final PrimitivePath argument = new ElementPathBuilder(spark) + // .expression("$this.communication.preferred") + // .fhirType(FHIRDefinedType.BOOLEAN) + // .singular(false) + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final NamedFunction whereFunction = NamedFunction.getInstance("where"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> whereFunction.invoke(whereInput)); + // assertEquals( + // "Argument to where function must be a singular Boolean: $this.communication.preferred", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfArgumentIsLiteral() { + // final ResourceCollection input = new ResourcePathBuilder(spark).build(); + // final BooleanLiteralPath argument = BooleanLiteralPath + // .fromString("true", input); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput whereInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final NamedFunction whereFunction = NamedFunction.getInstance("where"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> whereFunction.invoke(whereInput)); + // assertEquals( + // "Argument to where function cannot be a literal: true", + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunctionTest.java index be7005e2f1..eea4c9f260 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DesignationFunctionTest.java @@ -17,316 +17,274 @@ package au.csiro.pathling.fhirpath.function.terminology; -import static au.csiro.pathling.fhirpath.encoding.CodingEncoding.codingStructType; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromCoding; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.collection.CodingCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.literal.CodingLiteral; -import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.terminology.TerminologyService; -import au.csiro.pathling.terminology.TerminologyServiceFactory; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.AbstractTerminologyTestBase; import au.csiro.pathling.test.SpringBootUnitTest; -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 au.csiro.pathling.test.helpers.TerminologyServiceHelpers; -import ca.uhn.fhir.context.FhirContext; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.apache.spark.sql.types.DataTypes; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author Piotr Szul */ @SpringBootUnitTest +@NotImplemented class DesignationFunctionTest extends AbstractTerminologyTestBase { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @Autowired - TerminologyServiceFactory terminologyServiceFactory; - - @Autowired - TerminologyService terminologyService; - - @BeforeEach - void setUp() { - reset(terminologyService); - } - - - private void checkDesignationsOfCoding( - @Nonnull final Optional maybeUse, - @Nonnull final Optional maybeLanguage, - @Nonnull final Dataset expectedResult) { - - assertTrue(maybeLanguage.isEmpty() || maybeUse.isPresent(), - "'use' is required when 'language' is provided."); - - TerminologyServiceHelpers.setupLookup(terminologyService) - .withDesignation(CODING_A, CODING_C, "en", "A_C_en") - .withDesignation(CODING_A, CODING_D, "en", "A_D_en") - .withDesignation(CODING_A, null, null, "A_?_??") - .withDesignation(CODING_B, CODING_D, "en", "B_D_en") - .withDesignation(CODING_B, CODING_D, "fr", "B_D_fr.0", "B_D_fr.1") - .done(); - - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Encounter", "class"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(codingStructType()) - .withRow("encounter-1", makeEid(0), rowFromCoding(CODING_A)) - .withRow("encounter-1", makeEid(1), rowFromCoding(INVALID_CODING_0)) - .withRow("encounter-2", makeEid(0), rowFromCoding(CODING_B)) - .withRow("encounter-3", null, null) - .buildWithStructValue(); - - final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("Encounter.class") - .singular(false) - .definition(definition) - .buildDefined(); - - // Prepare the inputs to the function. - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .idColumn(inputExpression.getIdColumn()) - .terminologyClientFactory(terminologyServiceFactory) - .build(); - - final Optional maybeUseLiteral = maybeUse.map(CodingLiteral::toLiteral); - final Optional maybeLanguageLiteral = maybeLanguage.map(lang -> "'" + lang + "'"); - - final List arguments = Stream.of( - maybeUseLiteral - .map(useLiteral -> CodingCollection.fromLiteral(useLiteral, inputExpression)), - maybeLanguageLiteral.map( - languageLiteral -> StringCollection.fromLiteral(languageLiteral, inputExpression)) - ).flatMap(Optional::stream).collect(Collectors.toUnmodifiableList()); - - final NamedFunctionInput propertyInput = new NamedFunctionInput(parserContext, inputExpression, - arguments); - - // Invoke the function. - final Collection result = NamedFunction.getInstance("designation").invoke(propertyInput); - - final String expectedExpression = String.format("Encounter.class.designation(%s)", - Stream.of(maybeUseLiteral, maybeLanguageLiteral) - .flatMap(Optional::stream).collect(Collectors.joining(", "))); - // Check the result. - assertThat(result) - .hasExpression(expectedExpression) - .isElementPath(PrimitivePath.class) - .hasFhirType(FHIRDefinedType.STRING) - .isNotSingular() - .selectOrderedResultWithEid() - .hasRows(expectedResult); - } - - @Test - public void designationWithASingleResult() { - - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("encounter-1", makeEid(0, 0), "A_C_en") - .withRow("encounter-1", makeEid(1, 0), null) - .withRow("encounter-2", makeEid(0, 0), null) - .withRow("encounter-3", null, null) - .build(); - checkDesignationsOfCoding(Optional.of(CODING_C), Optional.of("en"), - expectedResult); - } - - @Test - public void designationWithMultipleResults() { - - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("encounter-1", makeEid(0, 0), null) - .withRow("encounter-1", makeEid(1, 0), null) - .withRow("encounter-2", makeEid(0, 0), "B_D_fr.0") - .withRow("encounter-2", makeEid(0, 1), "B_D_fr.1") - .withRow("encounter-3", null, null) - .build(); - checkDesignationsOfCoding(Optional.of(CODING_D), Optional.of("fr"), - expectedResult); - } - - - @Test - public void designationWithDefaultLanguage() { - - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("encounter-1", makeEid(0, 0), "A_D_en") - .withRow("encounter-1", makeEid(1, 0), null) - .withRow("encounter-2", makeEid(0, 0), "B_D_en") - .withRow("encounter-2", makeEid(0, 1), "B_D_fr.0") - .withRow("encounter-2", makeEid(0, 2), "B_D_fr.1") - .withRow("encounter-3", null, null) - .build(); - checkDesignationsOfCoding(Optional.of(CODING_D), Optional.empty(), - expectedResult); - } - - @Test - public void designationWithDefaultLanguageAndUse() { - - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("encounter-1", makeEid(0, 0), "A_C_en") - .withRow("encounter-1", makeEid(0, 1), "A_D_en") - .withRow("encounter-1", makeEid(0, 2), "A_?_??") - .withRow("encounter-1", makeEid(1, 0), null) - .withRow("encounter-2", makeEid(0, 0), "B_D_en") - .withRow("encounter-2", makeEid(0, 1), "B_D_fr.0") - .withRow("encounter-2", makeEid(0, 2), "B_D_fr.1") - .withRow("encounter-3", null, null) - .build(); - checkDesignationsOfCoding(Optional.empty(), Optional.empty(), - expectedResult); - } - - @Test - void throwsErrorIfInputTypeIsUnsupported() { - final Collection mockContext = new ElementPathBuilder(spark).build(); - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .expression("name.given") - .build(); - final Collection argument = StringCollection.fromLiteral("some-property", mockContext); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(mock(TerminologyServiceFactory.class)) - .build(); - - final NamedFunctionInput designationInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new DesignationFunction().invoke(designationInput)); - assertEquals("Input to designation function must be Coding but is: name.given", - error.getMessage()); - } - - void assertThrowsErrorForArguments(@Nonnull final String expectedError, - @Nonnull final Function> argsFactory) { - - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Encounter", "class"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .definition(definition) - .buildDefined(); - - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(mock(TerminologyServiceFactory.class)) - .build(); - - final NamedFunctionInput designationInput = new NamedFunctionInput(context, input, - argsFactory.apply(input)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new DesignationFunction().invoke(designationInput)); - assertEquals(expectedError, - error.getMessage()); - } - - @Test - void throwsErrorIfFirstArgumentIsNotCoding() { - assertThrowsErrorForArguments("Function `designation` expects `Coding literal` as argument 1", - input -> Collections.singletonList( - IntegerLiteralPath.fromString("4", input))); - } - - @Test - void throwsErrorIfSecondArgumentIsNotBoolean() { - assertThrowsErrorForArguments("Function `designation` expects `String literal` as argument 2", - input -> Arrays.asList( - CodingCollection.fromLiteral("system|code", input), - IntegerLiteralPath.fromString("5", input))); - } - - - @Test - void throwsErrorIfTooManyArguments() { - assertThrowsErrorForArguments( - "designation function accepts two optional arguments", - input -> Arrays.asList( - CodingCollection.fromLiteral("system|code", input), - StringCollection.fromLiteral("'false'", input), - StringCollection.fromLiteral("'false'", input) - )); - } - - @Test - void throwsErrorIfTerminologyServiceNotConfigured() { - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .build(); - final Collection argument = StringCollection.fromLiteral("some string", input); - - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .build(); - - final NamedFunctionInput translateInput = new NamedFunctionInput(context, input, - Collections.singletonList(argument)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new TranslateFunction().invoke(translateInput)); - assertEquals( - "Attempt to call terminology function translate when terminology service has not been configured", - error.getMessage()); - } + // TODO: implement with columns + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @Autowired + // TerminologyServiceFactory terminologyServiceFactory; + // + // @Autowired + // TerminologyService terminologyService; + // + // @BeforeEach + // void setUp() { + // reset(terminologyService); + // } + // + // + // private void checkDesignationsOfCoding( + // @Nonnull final Optional maybeUse, + // @Nonnull final Optional maybeLanguage, + // @Nonnull final Dataset expectedResult) { + // + // assertTrue(maybeLanguage.isEmpty() || maybeUse.isPresent(), + // "'use' is required when 'language' is provided."); + // + // TerminologyServiceHelpers.setupLookup(terminologyService) + // .withDesignation(CODING_A, CODING_C, "en", "A_C_en") + // .withDesignation(CODING_A, CODING_D, "en", "A_D_en") + // .withDesignation(CODING_A, null, null, "A_?_??") + // .withDesignation(CODING_B, CODING_D, "en", "B_D_en") + // .withDesignation(CODING_B, CODING_D, "fr", "B_D_fr.0", "B_D_fr.1") + // .done(); + // + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Encounter", "class"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(codingStructType()) + // .withRow("encounter-1", makeEid(0), rowFromCoding(CODING_A)) + // .withRow("encounter-1", makeEid(1), rowFromCoding(INVALID_CODING_0)) + // .withRow("encounter-2", makeEid(0), rowFromCoding(CODING_B)) + // .withRow("encounter-3", null, null) + // .buildWithStructValue(); + // + // final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("Encounter.class") + // .singular(false) + // .definition(definition) + // .buildDefined(); + // + // // Prepare the inputs to the function. + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .idColumn(inputExpression.getIdColumn()) + // .terminologyClientFactory(terminologyServiceFactory) + // .build(); + // + // final Optional maybeUseLiteral = maybeUse.map(CodingLiteral::toLiteral); + // final Optional maybeLanguageLiteral = maybeLanguage.map(lang -> "'" + lang + "'"); + // + // final List arguments = Stream.of( + // maybeUseLiteral + // .map(useLiteral -> CodingCollection.fromLiteral(useLiteral, inputExpression)), + // maybeLanguageLiteral.map( + // languageLiteral -> StringCollection.fromLiteral(languageLiteral, inputExpression)) + // ).flatMap(Optional::stream).collect(Collectors.toUnmodifiableList()); + // + // final NamedFunctionInput propertyInput = new NamedFunctionInput(parserContext, inputExpression, + // arguments); + // + // // Invoke the function. + // final Collection result = NamedFunction.getInstance("designation").invoke(propertyInput); + // + // final String expectedExpression = String.format("Encounter.class.designation(%s)", + // Stream.of(maybeUseLiteral, maybeLanguageLiteral) + // .flatMap(Optional::stream).collect(Collectors.joining(", "))); + // // Check the result. + // assertThat(result) + // .hasExpression(expectedExpression) + // .isElementPath(PrimitivePath.class) + // .hasFhirType(FHIRDefinedType.STRING) + // .isNotSingular() + // .selectOrderedResultWithEid() + // .hasRows(expectedResult); + // } + // + // @Test + // public void designationWithASingleResult() { + // + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("encounter-1", makeEid(0, 0), "A_C_en") + // .withRow("encounter-1", makeEid(1, 0), null) + // .withRow("encounter-2", makeEid(0, 0), null) + // .withRow("encounter-3", null, null) + // .build(); + // checkDesignationsOfCoding(Optional.of(CODING_C), Optional.of("en"), + // expectedResult); + // } + // + // @Test + // public void designationWithMultipleResults() { + // + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("encounter-1", makeEid(0, 0), null) + // .withRow("encounter-1", makeEid(1, 0), null) + // .withRow("encounter-2", makeEid(0, 0), "B_D_fr.0") + // .withRow("encounter-2", makeEid(0, 1), "B_D_fr.1") + // .withRow("encounter-3", null, null) + // .build(); + // checkDesignationsOfCoding(Optional.of(CODING_D), Optional.of("fr"), + // expectedResult); + // } + // + // + // @Test + // public void designationWithDefaultLanguage() { + // + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("encounter-1", makeEid(0, 0), "A_D_en") + // .withRow("encounter-1", makeEid(1, 0), null) + // .withRow("encounter-2", makeEid(0, 0), "B_D_en") + // .withRow("encounter-2", makeEid(0, 1), "B_D_fr.0") + // .withRow("encounter-2", makeEid(0, 2), "B_D_fr.1") + // .withRow("encounter-3", null, null) + // .build(); + // checkDesignationsOfCoding(Optional.of(CODING_D), Optional.empty(), + // expectedResult); + // } + // + // @Test + // public void designationWithDefaultLanguageAndUse() { + // + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("encounter-1", makeEid(0, 0), "A_C_en") + // .withRow("encounter-1", makeEid(0, 1), "A_D_en") + // .withRow("encounter-1", makeEid(0, 2), "A_?_??") + // .withRow("encounter-1", makeEid(1, 0), null) + // .withRow("encounter-2", makeEid(0, 0), "B_D_en") + // .withRow("encounter-2", makeEid(0, 1), "B_D_fr.0") + // .withRow("encounter-2", makeEid(0, 2), "B_D_fr.1") + // .withRow("encounter-3", null, null) + // .build(); + // checkDesignationsOfCoding(Optional.empty(), Optional.empty(), + // expectedResult); + // } + // + // @Test + // void throwsErrorIfInputTypeIsUnsupported() { + // final Collection mockContext = new ElementPathBuilder(spark).build(); + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .expression("name.given") + // .build(); + // final Collection argument = StringCollection.fromLiteral("some-property", mockContext); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .terminologyClientFactory(mock(TerminologyServiceFactory.class)) + // .build(); + // + // final NamedFunctionInput designationInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new DesignationFunction().invoke(designationInput)); + // assertEquals("Input to designation function must be Coding but is: name.given", + // error.getMessage()); + // } + // + // void assertThrowsErrorForArguments(@Nonnull final String expectedError, + // @Nonnull final Function> argsFactory) { + // + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Encounter", "class"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .definition(definition) + // .buildDefined(); + // + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .terminologyClientFactory(mock(TerminologyServiceFactory.class)) + // .build(); + // + // final NamedFunctionInput designationInput = new NamedFunctionInput(context, input, + // argsFactory.apply(input)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new DesignationFunction().invoke(designationInput)); + // assertEquals(expectedError, + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfFirstArgumentIsNotCoding() { + // assertThrowsErrorForArguments("Function `designation` expects `Coding literal` as argument 1", + // input -> Collections.singletonList( + // IntegerLiteralPath.fromString("4", input))); + // } + // + // @Test + // void throwsErrorIfSecondArgumentIsNotBoolean() { + // assertThrowsErrorForArguments("Function `designation` expects `String literal` as argument 2", + // input -> Arrays.asList( + // CodingCollection.fromLiteral("system|code", input), + // IntegerLiteralPath.fromString("5", input))); + // } + // + // + // @Test + // void throwsErrorIfTooManyArguments() { + // assertThrowsErrorForArguments( + // "designation function accepts two optional arguments", + // input -> Arrays.asList( + // CodingCollection.fromLiteral("system|code", input), + // StringCollection.fromLiteral("'false'", input), + // StringCollection.fromLiteral("'false'", input) + // )); + // } + // + // @Test + // void throwsErrorIfTerminologyServiceNotConfigured() { + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .build(); + // final Collection argument = StringCollection.fromLiteral("some string", input); + // + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .build(); + // + // final NamedFunctionInput translateInput = new NamedFunctionInput(context, input, + // Collections.singletonList(argument)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new TranslateFunction().invoke(translateInput)); + // assertEquals( + // "Attempt to call terminology function translate when terminology service has not been configured", + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunctionTest.java index 970c5e24ab..88038131fb 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/DisplayFunctionTest.java @@ -17,242 +17,202 @@ package au.csiro.pathling.fhirpath.function.terminology; -import static au.csiro.pathling.test.AbstractTerminologyTestBase.INVALID_CODING_0; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static au.csiro.pathling.test.helpers.SparkHelpers.codingStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromCoding; -import static au.csiro.pathling.test.helpers.TerminologyHelpers.CD_SNOMED_VER_63816008; -import static au.csiro.pathling.test.helpers.TerminologyHelpers.LC_55915_3; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.CodingCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.terminology.TerminologyService; -import au.csiro.pathling.terminology.TerminologyServiceFactory; -import au.csiro.pathling.test.SharedMocks; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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 au.csiro.pathling.test.helpers.TerminologyServiceHelpers; -import ca.uhn.fhir.context.FhirContext; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -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.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author Piotr Szul */ @SpringBootUnitTest +@NotImplemented class DisplayFunctionTest { - public static final String LC_55915_3_DE_DISPLAY = "LC_55915_3 (DE)"; - public static final String CD_SNOMED_VER_63816008_DE_DISPLAY = "CD_SNOMED_VER_63816008 (DE)"; - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @Autowired - TerminologyServiceFactory terminologyServiceFactory; - - @Autowired - TerminologyService terminologyService; - - @BeforeEach - void setUp() { - SharedMocks.resetAll(); - } - - - private void checkDisplayCoding(final Optional maybeLanguage, - final String display_LC_55915_3, final String display_CD_SNOMED_VER_63816008) { - - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Encounter", "class"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(codingStructType()) - .withRow("encounter-1", makeEid(0), rowFromCoding(LC_55915_3)) - .withRow("encounter-1", makeEid(1), rowFromCoding(INVALID_CODING_0)) - .withRow("encounter-2", makeEid(0), rowFromCoding(CD_SNOMED_VER_63816008)) - .withRow("encounter-3", null, null) - .buildWithStructValue(); - - final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("Encounter.class") - .singular(false) - .definition(definition) - .buildDefined(); - - // Prepare the inputs to the function. - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .idColumn(inputExpression.getIdColumn()) - .terminologyClientFactory(terminologyServiceFactory) - .build(); - - final Optional maybeArgumentExpression = maybeLanguage.map( - lang -> StringCollection - .fromLiteral("'" + lang + "'", inputExpression)); - - final NamedFunctionInput displayInput = new NamedFunctionInput(parserContext, inputExpression, - maybeArgumentExpression.stream().collect(Collectors.toUnmodifiableList())); - - // Invoke the function. - final Collection result = new DisplayFunction().invoke(displayInput); - - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("encounter-1", makeEid(0), display_LC_55915_3) - .withRow("encounter-1", makeEid(1), null) - .withRow("encounter-2", makeEid(0), display_CD_SNOMED_VER_63816008) - .withRow("encounter-3", null, null) - .build(); - - // Check the result. - assertThat(result) - .hasExpression("Encounter.class.display(" + maybeArgumentExpression.map( - StringLiteralPath::getExpression).orElse("") + ")") - .isElementPath(PrimitivePath.class) - .hasFhirType(FHIRDefinedType.STRING) - .isNotSingular() - .selectOrderedResultWithEid() - .hasRows(expectedResult); - } - - @Test - public void displayCoding() { - // Setup mocks - TerminologyServiceHelpers.setupLookup(terminologyService) - .withDisplay(LC_55915_3) - .withDisplay(CD_SNOMED_VER_63816008); - checkDisplayCoding(Optional.empty(), LC_55915_3.getDisplay(), - CD_SNOMED_VER_63816008.getDisplay()); - } - - @Test - public void displayCodingLanguage() { - - // Setup mocks - TerminologyServiceHelpers.setupLookup(terminologyService) - .withDisplay(LC_55915_3, LC_55915_3_DE_DISPLAY, "de") - .withDisplay(CD_SNOMED_VER_63816008, CD_SNOMED_VER_63816008_DE_DISPLAY, "de"); - - checkDisplayCoding(Optional.of("de"), LC_55915_3_DE_DISPLAY, CD_SNOMED_VER_63816008_DE_DISPLAY); - } - - @Test - void throwsErrorIfTerminologyServiceNotConfigured() { - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .build(); - - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .build(); - - final NamedFunctionInput displayInput = new NamedFunctionInput(context, input, - Collections.emptyList()); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new DisplayFunction().invoke(displayInput)); - assertEquals( - "Attempt to call terminology function display when terminology service has not been configured", - error.getMessage()); - } - - @Test - void inputMustNotContainTwoArguments() { - final PrimitivePath input = new ElementPathBuilder(spark).build(); - final StringLiteralPath argument1 = StringCollection - .fromLiteral("'some argument'", input); - final StringLiteralPath argument2 = StringCollection - .fromLiteral("'some argument'", input); - final List arguments = new ArrayList<>(); - arguments.add(argument1); - arguments.add(argument2); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput displayInput = new NamedFunctionInput(parserContext, input, - arguments); - - final NamedFunction displayFunction = NamedFunction.getInstance("display"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> displayFunction.invoke(displayInput)); - assertEquals( - "display function accepts one optional language argument", - error.getMessage()); - } - - @Test - void inputMustNotContainNonStringArgument() { - final PrimitivePath input = new ElementPathBuilder(spark).build(); - final IntegerLiteralPath argument = IntegerLiteralPath - .fromString("9493948", input); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final NamedFunctionInput displayInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final NamedFunction displayFunction = NamedFunction.getInstance("display"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> displayFunction.invoke(displayInput)); - assertEquals( - "Function `display` expects `String literal` as argument 1", - error.getMessage()); - } - - @Test - void throwsErrorIfInputNotCoding() { - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .expression("valueInteger") - .build(); - - final ParserContext parserContext = new ParserContextBuilder(spark, - fhirContext).terminologyClientFactory(terminologyServiceFactory) - .build(); - final NamedFunctionInput displayInput = new NamedFunctionInput(parserContext, input, - Collections.emptyList()); - - final NamedFunction displayFunction = NamedFunction.getInstance("display"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> displayFunction.invoke(displayInput)); - assertEquals( - "Input to display function must be Coding but is: valueInteger", - error.getMessage()); - } + // TODO: implement with columns + // public static final String LC_55915_3_DE_DISPLAY = "LC_55915_3 (DE)"; + // public static final String CD_SNOMED_VER_63816008_DE_DISPLAY = "CD_SNOMED_VER_63816008 (DE)"; + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @Autowired + // TerminologyServiceFactory terminologyServiceFactory; + // + // @Autowired + // TerminologyService terminologyService; + // + // @BeforeEach + // void setUp() { + // SharedMocks.resetAll(); + // } + // + // + // private void checkDisplayCoding(final Optional maybeLanguage, + // final String display_LC_55915_3, final String display_CD_SNOMED_VER_63816008) { + // + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Encounter", "class"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(codingStructType()) + // .withRow("encounter-1", makeEid(0), rowFromCoding(LC_55915_3)) + // .withRow("encounter-1", makeEid(1), rowFromCoding(INVALID_CODING_0)) + // .withRow("encounter-2", makeEid(0), rowFromCoding(CD_SNOMED_VER_63816008)) + // .withRow("encounter-3", null, null) + // .buildWithStructValue(); + // + // final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("Encounter.class") + // .singular(false) + // .definition(definition) + // .buildDefined(); + // + // // Prepare the inputs to the function. + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .idColumn(inputExpression.getIdColumn()) + // .terminologyClientFactory(terminologyServiceFactory) + // .build(); + // + // final Optional maybeArgumentExpression = maybeLanguage.map( + // lang -> StringCollection + // .fromLiteral("'" + lang + "'", inputExpression)); + // + // final NamedFunctionInput displayInput = new NamedFunctionInput(parserContext, inputExpression, + // maybeArgumentExpression.stream().collect(Collectors.toUnmodifiableList())); + // + // // Invoke the function. + // final Collection result = new DisplayFunction().invoke(displayInput); + // + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("encounter-1", makeEid(0), display_LC_55915_3) + // .withRow("encounter-1", makeEid(1), null) + // .withRow("encounter-2", makeEid(0), display_CD_SNOMED_VER_63816008) + // .withRow("encounter-3", null, null) + // .build(); + // + // // Check the result. + // assertThat(result) + // .hasExpression("Encounter.class.display(" + maybeArgumentExpression.map( + // StringLiteralPath::getExpression).orElse("") + ")") + // .isElementPath(PrimitivePath.class) + // .hasFhirType(FHIRDefinedType.STRING) + // .isNotSingular() + // .selectOrderedResultWithEid() + // .hasRows(expectedResult); + // } + // + // @Test + // public void displayCoding() { + // // Setup mocks + // TerminologyServiceHelpers.setupLookup(terminologyService) + // .withDisplay(LC_55915_3) + // .withDisplay(CD_SNOMED_VER_63816008); + // checkDisplayCoding(Optional.empty(), LC_55915_3.getDisplay(), + // CD_SNOMED_VER_63816008.getDisplay()); + // } + // + // @Test + // public void displayCodingLanguage() { + // + // // Setup mocks + // TerminologyServiceHelpers.setupLookup(terminologyService) + // .withDisplay(LC_55915_3, LC_55915_3_DE_DISPLAY, "de") + // .withDisplay(CD_SNOMED_VER_63816008, CD_SNOMED_VER_63816008_DE_DISPLAY, "de"); + // + // checkDisplayCoding(Optional.of("de"), LC_55915_3_DE_DISPLAY, CD_SNOMED_VER_63816008_DE_DISPLAY); + // } + // + // @Test + // void throwsErrorIfTerminologyServiceNotConfigured() { + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .build(); + // + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .build(); + // + // final NamedFunctionInput displayInput = new NamedFunctionInput(context, input, + // Collections.emptyList()); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new DisplayFunction().invoke(displayInput)); + // assertEquals( + // "Attempt to call terminology function display when terminology service has not been configured", + // error.getMessage()); + // } + // + // @Test + // void inputMustNotContainTwoArguments() { + // final PrimitivePath input = new ElementPathBuilder(spark).build(); + // final StringLiteralPath argument1 = StringCollection + // .fromLiteral("'some argument'", input); + // final StringLiteralPath argument2 = StringCollection + // .fromLiteral("'some argument'", input); + // final List arguments = new ArrayList<>(); + // arguments.add(argument1); + // arguments.add(argument2); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput displayInput = new NamedFunctionInput(parserContext, input, + // arguments); + // + // final NamedFunction displayFunction = NamedFunction.getInstance("display"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> displayFunction.invoke(displayInput)); + // assertEquals( + // "display function accepts one optional language argument", + // error.getMessage()); + // } + // + // @Test + // void inputMustNotContainNonStringArgument() { + // final PrimitivePath input = new ElementPathBuilder(spark).build(); + // final IntegerLiteralPath argument = IntegerLiteralPath + // .fromString("9493948", input); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final NamedFunctionInput displayInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final NamedFunction displayFunction = NamedFunction.getInstance("display"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> displayFunction.invoke(displayInput)); + // assertEquals( + // "Function `display` expects `String literal` as argument 1", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfInputNotCoding() { + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .expression("valueInteger") + // .build(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, + // fhirContext).terminologyClientFactory(terminologyServiceFactory) + // .build(); + // final NamedFunctionInput displayInput = new NamedFunctionInput(parserContext, input, + // Collections.emptyList()); + // + // final NamedFunction displayFunction = NamedFunction.getInstance("display"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> displayFunction.invoke(displayInput)); + // assertEquals( + // "Input to display function must be Coding but is: valueInteger", + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunctionTest.java index 08eab3b6fa..23d68ef453 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/MemberOfFunctionTest.java @@ -17,396 +17,349 @@ package au.csiro.pathling.fhirpath.function.terminology; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static au.csiro.pathling.test.helpers.FhirMatchers.deepEq; -import static au.csiro.pathling.test.helpers.SparkHelpers.codeableConceptStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.codingStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromCodeableConcept; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromCoding; -import static au.csiro.pathling.test.helpers.TestHelpers.LOINC_URL; -import static au.csiro.pathling.test.helpers.TestHelpers.SNOMED_URL; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.collection.BooleanCollection; -import au.csiro.pathling.fhirpath.collection.CodingCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.terminology.TerminologyService; -import au.csiro.pathling.terminology.TerminologyServiceFactory; -import au.csiro.pathling.test.SharedMocks; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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 au.csiro.pathling.test.helpers.TerminologyServiceHelpers; -import ca.uhn.fhir.context.FhirContext; -import java.util.Arrays; -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.SparkSession; -import org.apache.spark.sql.types.DataTypes; -import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class MemberOfFunctionTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @Autowired - TerminologyServiceFactory terminologyServiceFactory; - - @Autowired - TerminologyService terminologyService; - - @BeforeEach - void setUp() { - SharedMocks.resetAll(); - } - - static final String MY_VALUE_SET_URL = "https://csiro.au/fhir/ValueSet/my-value-set"; - - @Test - void memberOfCoding() { - final Coding coding1 = new Coding(MY_VALUE_SET_URL, "AMB", "ambulatory"); - final Coding coding2 = new Coding(MY_VALUE_SET_URL, "EMER", null); - final Coding coding3 = new Coding(MY_VALUE_SET_URL, "IMP", "inpatient encounter"); - final Coding coding4 = new Coding(MY_VALUE_SET_URL, "IMP", null); - final Coding coding5 = new Coding(MY_VALUE_SET_URL, "ACUTE", "inpatient acute"); - - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Encounter", "class"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(codingStructType()) - .withRow("encounter-1", makeEid(1), rowFromCoding(coding1)) - .withRow("encounter-1", makeEid(0), rowFromCoding(coding5)) - .withRow("encounter-2", makeEid(0), rowFromCoding(coding2)) - .withRow("encounter-3", makeEid(0), rowFromCoding(coding3)) - .withRow("encounter-4", makeEid(0), rowFromCoding(coding4)) - .withRow("encounter-5", makeEid(0), rowFromCoding(coding5)) - .withRow("encounter-6", null, null) - .buildWithStructValue(); - - final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("Encounter.class") - .singular(false) - .definition(definition) - .buildDefined(); - - final StringLiteralPath argumentExpression = StringCollection - .fromLiteral("'" + MY_VALUE_SET_URL + "'", inputExpression); - - // Setup mocks - TerminologyServiceHelpers.setupValidate(terminologyService) - .withValueSet(MY_VALUE_SET_URL, coding2, coding5); - // Prepare the inputs to the function. - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .idColumn(inputExpression.getIdColumn()) - .terminologyClientFactory(terminologyServiceFactory) - .build(); - - final NamedFunctionInput memberOfInput = new NamedFunctionInput(parserContext, inputExpression, - Collections.singletonList(argumentExpression)); - - // Invoke the function. - final Collection result = new MemberOfFunction().invoke(memberOfInput); - - // The outcome is somehow random with regard to the sequence passed to MemberOfMapperAnswerer. - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.BooleanType) - .withRow("encounter-1", makeEid(0), true) - .withRow("encounter-1", makeEid(1), false) - .withRow("encounter-2", makeEid(0), true) - .withRow("encounter-3", makeEid(0), false) - .withRow("encounter-4", makeEid(0), false) - .withRow("encounter-5", makeEid(0), true) - .withRow("encounter-6", null, null) - .build(); - - // Check the result. - assertThat(result) - .hasExpression("Encounter.class.memberOf('" + MY_VALUE_SET_URL + "')") - .isElementPath(BooleanCollection.class) - .hasFhirType(FHIRDefinedType.BOOLEAN) - .isNotSingular() - .selectOrderedResultWithEid() - .hasRows(expectedResult); - - verify(terminologyService).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding1)); - verify(terminologyService).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding2)); - verify(terminologyService).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding3)); - verify(terminologyService).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding4)); - verify(terminologyService, times(2)).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding5)); - verifyNoMoreInteractions(terminologyService); - } - - - @Test - void memberOfEmptyCodingDatasetDoesNotCallTerminology() { - - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Encounter", "class"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(codingStructType()) - .buildWithStructValue(); - - final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("Encounter.class") - .singular(false) - .definition(definition) - .buildDefined(); - - final StringLiteralPath argumentExpression = StringCollection - .fromLiteral("'" + MY_VALUE_SET_URL + "'", inputExpression); - - // Prepare the inputs to the function. - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .idColumn(inputExpression.getIdColumn()) - .terminologyClientFactory(terminologyServiceFactory) - .build(); - - final NamedFunctionInput memberOfInput = new NamedFunctionInput(parserContext, inputExpression, - Collections.singletonList(argumentExpression)); - - // Invoke the function. - final Collection result = new MemberOfFunction().invoke(memberOfInput); - - // The outcome is somehow random with regard to the sequence passed to MemberOfMapperAnswerer. - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.BooleanType) - .build(); - - // Check the result. - assertThat(result) - .hasExpression("Encounter.class.memberOf('" + MY_VALUE_SET_URL + "')") - .isElementPath(BooleanCollection.class) - .hasFhirType(FHIRDefinedType.BOOLEAN) - .isNotSingular() - .selectOrderedResultWithEid() - .hasRows(expectedResult); - - verifyNoMoreInteractions(terminologyService); - } - - @Test - void memberOfCodeableConcept() { - final Coding coding1 = new Coding(LOINC_URL, "10337-4", - "Procollagen type I [Mass/volume] in Serum"); - final Coding coding2 = new Coding(LOINC_URL, "10428-1", - "Varicella zoster virus immune globulin given [Volume]"); - final Coding coding3 = new Coding(LOINC_URL, "10555-1", null); - final Coding coding4 = new Coding(LOINC_URL, "10665-8", - "Fungus colony count [#/volume] in Unspecified specimen by Environmental culture"); - final Coding coding5 = new Coding(SNOMED_URL, "416399002", - "Procollagen type I amino-terminal propeptide level"); - - final CodeableConcept codeableConcept1 = new CodeableConcept(coding1); - codeableConcept1.addCoding(coding5); - final CodeableConcept codeableConcept2 = new CodeableConcept(coding2); - final CodeableConcept codeableConcept3 = new CodeableConcept(coding3); - final CodeableConcept codeableConcept4 = new CodeableConcept(coding3); - final CodeableConcept codeableConcept5 = new CodeableConcept(coding4); - final CodeableConcept codeableConcept6 = new CodeableConcept(coding1); - - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "DiagnosticReport", "code"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withStructTypeColumns(codeableConceptStructType()) - .withRow("diagnosticreport-1", rowFromCodeableConcept(codeableConcept1)) - .withRow("diagnosticreport-2", rowFromCodeableConcept(codeableConcept2)) - .withRow("diagnosticreport-3", rowFromCodeableConcept(codeableConcept3)) - .withRow("diagnosticreport-4", rowFromCodeableConcept(codeableConcept4)) - .withRow("diagnosticreport-5", rowFromCodeableConcept(codeableConcept5)) - .withRow("diagnosticreport-6", rowFromCodeableConcept(codeableConcept6)) - .withRow("diagnosticreport-7", null) - .buildWithStructValue(); - - final PrimitivePath inputExpression = new ElementPathBuilder(spark) - .dataset(inputDataset) - .idAndValueColumns() - .expression("DiagnosticReport.code") - .singular(true) - .definition(definition) - .buildDefined(); - - final StringLiteralPath argumentExpression = StringCollection - .fromLiteral("'" + MY_VALUE_SET_URL + "'", inputExpression); - - // Setup mocks: true for (codeableConcept1, codeableConcept3, codeableConcept4) - TerminologyServiceHelpers.setupValidate(terminologyService) - .withValueSet(MY_VALUE_SET_URL, coding1, coding3, coding5); - // Prepare the inputs to the function. - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(terminologyServiceFactory) - .build(); - - final NamedFunctionInput memberOfInput = new NamedFunctionInput(parserContext, inputExpression, - Collections.singletonList(argumentExpression)); - - // Invoke the function. - final Collection result = new MemberOfFunction().invoke(memberOfInput); - - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.BooleanType) - .withRow("diagnosticreport-1", true) - .withRow("diagnosticreport-2", false) - .withRow("diagnosticreport-3", true) - .withRow("diagnosticreport-4", true) - .withRow("diagnosticreport-5", false) - .withRow("diagnosticreport-6", true) - .withRow("diagnosticreport-7", null) - .build(); - - // Check the result. - assertTrue(result instanceof BooleanCollection); - assertThat((BooleanCollection) result) - .hasExpression("DiagnosticReport.code.memberOf('" + MY_VALUE_SET_URL + "')") - .isSingular() - .hasFhirType(FHIRDefinedType.BOOLEAN) - .isElementPath(BooleanCollection.class) - .selectOrderedResult() - .hasRows(expectedResult); - - verify(terminologyService, atLeastOnce()).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding1)); - verify(terminologyService, atLeastOnce()).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding2)); - verify(terminologyService, atLeastOnce()).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding3)); - verify(terminologyService, atLeastOnce()).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding4)); - verifyNoMoreInteractions(terminologyService); - } - - @Test - void throwsErrorIfInputTypeIsUnsupported() { - final Collection mockContext = new ElementPathBuilder(spark).build(); - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .expression("name.given") - .build(); - final Collection argument = StringCollection.fromLiteral(MY_VALUE_SET_URL, mockContext); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(mock(TerminologyServiceFactory.class)) - .build(); - - final NamedFunctionInput memberOfInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new MemberOfFunction().invoke(memberOfInput)); - assertEquals("Input to memberOf function is of unsupported type: name.given", - error.getMessage()); - } - - @Test - void throwsErrorIfArgumentIsNotString() { - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .build(); - final IntegerLiteralPath argument = IntegerLiteralPath.fromString("4", input); - - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(mock(TerminologyServiceFactory.class)) - .build(); - - final NamedFunctionInput memberOfInput = new NamedFunctionInput(context, input, - Collections.singletonList(argument)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new MemberOfFunction().invoke(memberOfInput)); - assertEquals("memberOf function accepts one argument of type String literal", - error.getMessage()); - } - - @Test - void throwsErrorIfMoreThanOneArgument() { - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .build(); - final StringLiteralPath argument1 = StringCollection.fromLiteral("'foo'", input), - argument2 = StringCollection.fromLiteral("'bar'", input); - - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(mock(TerminologyServiceFactory.class)) - .build(); - - final NamedFunctionInput memberOfInput = new NamedFunctionInput(context, input, - Arrays.asList(argument1, argument2)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new MemberOfFunction().invoke(memberOfInput)); - assertEquals("memberOf function accepts one argument of type String", - error.getMessage()); - } - - @Test - void throwsErrorIfTerminologyServiceNotConfigured() { - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .build(); - final Collection argument = StringCollection.fromLiteral("some string", input); - - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .build(); - - final NamedFunctionInput memberOfInput = new NamedFunctionInput(context, input, - Collections.singletonList(argument)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new MemberOfFunction().invoke(memberOfInput)); - assertEquals( - "Attempt to call terminology function memberOf when terminology service has not been configured", - error.getMessage()); - } + // TODO: implement with columns + + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @Autowired + // TerminologyServiceFactory terminologyServiceFactory; + // + // @Autowired + // TerminologyService terminologyService; + // + // @BeforeEach + // void setUp() { + // SharedMocks.resetAll(); + // } + // + // static final String MY_VALUE_SET_URL = "https://csiro.au/fhir/ValueSet/my-value-set"; + // + // @Test + // void memberOfCoding() { + // final Coding coding1 = new Coding(MY_VALUE_SET_URL, "AMB", "ambulatory"); + // final Coding coding2 = new Coding(MY_VALUE_SET_URL, "EMER", null); + // final Coding coding3 = new Coding(MY_VALUE_SET_URL, "IMP", "inpatient encounter"); + // final Coding coding4 = new Coding(MY_VALUE_SET_URL, "IMP", null); + // final Coding coding5 = new Coding(MY_VALUE_SET_URL, "ACUTE", "inpatient acute"); + // + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Encounter", "class"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(codingStructType()) + // .withRow("encounter-1", makeEid(1), rowFromCoding(coding1)) + // .withRow("encounter-1", makeEid(0), rowFromCoding(coding5)) + // .withRow("encounter-2", makeEid(0), rowFromCoding(coding2)) + // .withRow("encounter-3", makeEid(0), rowFromCoding(coding3)) + // .withRow("encounter-4", makeEid(0), rowFromCoding(coding4)) + // .withRow("encounter-5", makeEid(0), rowFromCoding(coding5)) + // .withRow("encounter-6", null, null) + // .buildWithStructValue(); + // + // final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("Encounter.class") + // .singular(false) + // .definition(definition) + // .buildDefined(); + // + // final StringLiteralPath argumentExpression = StringCollection + // .fromLiteral("'" + MY_VALUE_SET_URL + "'", inputExpression); + // + // // Setup mocks + // TerminologyServiceHelpers.setupValidate(terminologyService) + // .withValueSet(MY_VALUE_SET_URL, coding2, coding5); + // // Prepare the inputs to the function. + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .idColumn(inputExpression.getIdColumn()) + // .terminologyClientFactory(terminologyServiceFactory) + // .build(); + // + // final NamedFunctionInput memberOfInput = new NamedFunctionInput(parserContext, inputExpression, + // Collections.singletonList(argumentExpression)); + // + // // Invoke the function. + // final Collection result = new MemberOfFunction().invoke(memberOfInput); + // + // // The outcome is somehow random with regard to the sequence passed to MemberOfMapperAnswerer. + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow("encounter-1", makeEid(0), true) + // .withRow("encounter-1", makeEid(1), false) + // .withRow("encounter-2", makeEid(0), true) + // .withRow("encounter-3", makeEid(0), false) + // .withRow("encounter-4", makeEid(0), false) + // .withRow("encounter-5", makeEid(0), true) + // .withRow("encounter-6", null, null) + // .build(); + // + // // Check the result. + // assertThat(result) + // .hasExpression("Encounter.class.memberOf('" + MY_VALUE_SET_URL + "')") + // .isElementPath(BooleanCollection.class) + // .hasFhirType(FHIRDefinedType.BOOLEAN) + // .isNotSingular() + // .selectOrderedResultWithEid() + // .hasRows(expectedResult); + // + // verify(terminologyService).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding1)); + // verify(terminologyService).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding2)); + // verify(terminologyService).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding3)); + // verify(terminologyService).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding4)); + // verify(terminologyService, times(2)).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding5)); + // verifyNoMoreInteractions(terminologyService); + // } + // + // + // @Test + // void memberOfEmptyCodingDatasetDoesNotCallTerminology() { + // + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Encounter", "class"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(codingStructType()) + // .buildWithStructValue(); + // + // final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("Encounter.class") + // .singular(false) + // .definition(definition) + // .buildDefined(); + // + // final StringLiteralPath argumentExpression = StringCollection + // .fromLiteral("'" + MY_VALUE_SET_URL + "'", inputExpression); + // + // // Prepare the inputs to the function. + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .idColumn(inputExpression.getIdColumn()) + // .terminologyClientFactory(terminologyServiceFactory) + // .build(); + // + // final NamedFunctionInput memberOfInput = new NamedFunctionInput(parserContext, inputExpression, + // Collections.singletonList(argumentExpression)); + // + // // Invoke the function. + // final Collection result = new MemberOfFunction().invoke(memberOfInput); + // + // // The outcome is somehow random with regard to the sequence passed to MemberOfMapperAnswerer. + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.BooleanType) + // .build(); + // + // // Check the result. + // assertThat(result) + // .hasExpression("Encounter.class.memberOf('" + MY_VALUE_SET_URL + "')") + // .isElementPath(BooleanCollection.class) + // .hasFhirType(FHIRDefinedType.BOOLEAN) + // .isNotSingular() + // .selectOrderedResultWithEid() + // .hasRows(expectedResult); + // + // verifyNoMoreInteractions(terminologyService); + // } + // + // @Test + // void memberOfCodeableConcept() { + // final Coding coding1 = new Coding(LOINC_URL, "10337-4", + // "Procollagen type I [Mass/volume] in Serum"); + // final Coding coding2 = new Coding(LOINC_URL, "10428-1", + // "Varicella zoster virus immune globulin given [Volume]"); + // final Coding coding3 = new Coding(LOINC_URL, "10555-1", null); + // final Coding coding4 = new Coding(LOINC_URL, "10665-8", + // "Fungus colony count [#/volume] in Unspecified specimen by Environmental culture"); + // final Coding coding5 = new Coding(SNOMED_URL, "416399002", + // "Procollagen type I amino-terminal propeptide level"); + // + // final CodeableConcept codeableConcept1 = new CodeableConcept(coding1); + // codeableConcept1.addCoding(coding5); + // final CodeableConcept codeableConcept2 = new CodeableConcept(coding2); + // final CodeableConcept codeableConcept3 = new CodeableConcept(coding3); + // final CodeableConcept codeableConcept4 = new CodeableConcept(coding3); + // final CodeableConcept codeableConcept5 = new CodeableConcept(coding4); + // final CodeableConcept codeableConcept6 = new CodeableConcept(coding1); + // + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "DiagnosticReport", "code"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withStructTypeColumns(codeableConceptStructType()) + // .withRow("diagnosticreport-1", rowFromCodeableConcept(codeableConcept1)) + // .withRow("diagnosticreport-2", rowFromCodeableConcept(codeableConcept2)) + // .withRow("diagnosticreport-3", rowFromCodeableConcept(codeableConcept3)) + // .withRow("diagnosticreport-4", rowFromCodeableConcept(codeableConcept4)) + // .withRow("diagnosticreport-5", rowFromCodeableConcept(codeableConcept5)) + // .withRow("diagnosticreport-6", rowFromCodeableConcept(codeableConcept6)) + // .withRow("diagnosticreport-7", null) + // .buildWithStructValue(); + // + // final PrimitivePath inputExpression = new ElementPathBuilder(spark) + // .dataset(inputDataset) + // .idAndValueColumns() + // .expression("DiagnosticReport.code") + // .singular(true) + // .definition(definition) + // .buildDefined(); + // + // final StringLiteralPath argumentExpression = StringCollection + // .fromLiteral("'" + MY_VALUE_SET_URL + "'", inputExpression); + // + // // Setup mocks: true for (codeableConcept1, codeableConcept3, codeableConcept4) + // TerminologyServiceHelpers.setupValidate(terminologyService) + // .withValueSet(MY_VALUE_SET_URL, coding1, coding3, coding5); + // // Prepare the inputs to the function. + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .terminologyClientFactory(terminologyServiceFactory) + // .build(); + // + // final NamedFunctionInput memberOfInput = new NamedFunctionInput(parserContext, inputExpression, + // Collections.singletonList(argumentExpression)); + // + // // Invoke the function. + // final Collection result = new MemberOfFunction().invoke(memberOfInput); + // + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow("diagnosticreport-1", true) + // .withRow("diagnosticreport-2", false) + // .withRow("diagnosticreport-3", true) + // .withRow("diagnosticreport-4", true) + // .withRow("diagnosticreport-5", false) + // .withRow("diagnosticreport-6", true) + // .withRow("diagnosticreport-7", null) + // .build(); + // + // // Check the result. + // assertTrue(result instanceof BooleanCollection); + // assertThat((BooleanCollection) result) + // .hasExpression("DiagnosticReport.code.memberOf('" + MY_VALUE_SET_URL + "')") + // .isSingular() + // .hasFhirType(FHIRDefinedType.BOOLEAN) + // .isElementPath(BooleanCollection.class) + // .selectOrderedResult() + // .hasRows(expectedResult); + // + // verify(terminologyService, atLeastOnce()).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding1)); + // verify(terminologyService, atLeastOnce()).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding2)); + // verify(terminologyService, atLeastOnce()).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding3)); + // verify(terminologyService, atLeastOnce()).validateCode(eq(MY_VALUE_SET_URL), deepEq(coding4)); + // verifyNoMoreInteractions(terminologyService); + // } + // + // @Test + // void throwsErrorIfInputTypeIsUnsupported() { + // final Collection mockContext = new ElementPathBuilder(spark).build(); + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .expression("name.given") + // .build(); + // final Collection argument = StringCollection.fromLiteral(MY_VALUE_SET_URL, mockContext); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .terminologyClientFactory(mock(TerminologyServiceFactory.class)) + // .build(); + // + // final NamedFunctionInput memberOfInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new MemberOfFunction().invoke(memberOfInput)); + // assertEquals("Input to memberOf function is of unsupported type: name.given", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfArgumentIsNotString() { + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .build(); + // final IntegerLiteralPath argument = IntegerLiteralPath.fromString("4", input); + // + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .terminologyClientFactory(mock(TerminologyServiceFactory.class)) + // .build(); + // + // final NamedFunctionInput memberOfInput = new NamedFunctionInput(context, input, + // Collections.singletonList(argument)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new MemberOfFunction().invoke(memberOfInput)); + // assertEquals("memberOf function accepts one argument of type String literal", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfMoreThanOneArgument() { + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .build(); + // final StringLiteralPath argument1 = StringCollection.fromLiteral("'foo'", input), + // argument2 = StringCollection.fromLiteral("'bar'", input); + // + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .terminologyClientFactory(mock(TerminologyServiceFactory.class)) + // .build(); + // + // final NamedFunctionInput memberOfInput = new NamedFunctionInput(context, input, + // Arrays.asList(argument1, argument2)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new MemberOfFunction().invoke(memberOfInput)); + // assertEquals("memberOf function accepts one argument of type String", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfTerminologyServiceNotConfigured() { + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .build(); + // final Collection argument = StringCollection.fromLiteral("some string", input); + // + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .build(); + // + // final NamedFunctionInput memberOfInput = new NamedFunctionInput(context, input, + // Collections.singletonList(argument)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new MemberOfFunction().invoke(memberOfInput)); + // assertEquals( + // "Attempt to call terminology function memberOf when terminology service has not been configured", + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunctionTest.java index dee8092d00..29d591a8c3 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/PropertyFunctionTest.java @@ -17,376 +17,331 @@ package au.csiro.pathling.fhirpath.function.terminology; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static au.csiro.pathling.test.helpers.SparkHelpers.codingStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromCoding; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.collection.CodingCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteral; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.terminology.TerminologyService; -import au.csiro.pathling.terminology.TerminologyServiceFactory; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.AbstractTerminologyTestBase; import au.csiro.pathling.test.SpringBootUnitTest; -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 au.csiro.pathling.test.helpers.TerminologyServiceHelpers; -import ca.uhn.fhir.context.FhirContext; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.apache.spark.sql.types.DataType; -import org.apache.spark.sql.types.DataTypes; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.StringType; -import org.hl7.fhir.r4.model.Type; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.springframework.beans.factory.annotation.Autowired; /** * @author Piotr Szul */ @SpringBootUnitTest +@NotImplemented class PropertyFunctionTest extends AbstractTerminologyTestBase { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @Autowired - TerminologyServiceFactory terminologyServiceFactory; - - @Autowired - TerminologyService terminologyService; - - @BeforeEach - void setUp() { - reset(terminologyService); - } - - private void checkPropertyOfCoding(final String propertyCode, - final Optional maybePropertyType, - final Optional maybeLanguage, - final Type[] propertyAFhirValues, final Type[] propertyBFhirValues, - final FHIRDefinedType expectedResultType, - final Dataset expectedResult) { - - // check the that if type is provided if language is provided - assertTrue(maybeLanguage.isEmpty() || maybePropertyType.isPresent()); - - TerminologyServiceHelpers.setupLookup(terminologyService) - .withProperty(CODING_A, "propertyA", maybeLanguage.orElse(null), propertyAFhirValues) - .withProperty(CODING_B, "propertyB", maybeLanguage.orElse(null), propertyBFhirValues); - - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Encounter", "class"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(codingStructType()) - .withRow("encounter-1", makeEid(0), rowFromCoding(CODING_A)) - .withRow("encounter-1", makeEid(1), rowFromCoding(INVALID_CODING_0)) - .withRow("encounter-2", makeEid(0), rowFromCoding(CODING_B)) - .withRow("encounter-3", null, null) - .buildWithStructValue(); - - final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("Encounter.class") - .singular(false) - .definition(definition) - .buildDefined(); - - // Prepare the inputs to the function. - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .idColumn(inputExpression.getIdColumn()) - .terminologyClientFactory(terminologyServiceFactory) - .build(); - - final List parameterLiterals = Stream.of(Optional.of(propertyCode), - maybePropertyType, - maybeLanguage).flatMap(Optional::stream) - .map(StringLiteral::toLiteral).collect(Collectors.toUnmodifiableList()); - - final List arguments = parameterLiterals.stream() - .map(lit -> StringCollection.fromLiteral(lit, inputExpression)) - .collect(Collectors.toUnmodifiableList()); - - final NamedFunctionInput propertyInput = new NamedFunctionInput(parserContext, inputExpression, - arguments); - - // Invoke the function. - final Collection result = NamedFunction.getInstance("property").invoke(propertyInput); - - // Check the result. - assertThat(result).hasExpression( - String.format("Encounter.class.property(%s)", - String.join(", ", parameterLiterals))) - .isElementPath(PrimitivePath.class) - .hasFhirType(expectedResultType) - .isNotSingular() - .selectOrderedResultWithEid() - .hasRows(expectedResult); - - // Check that the ElementPath for CODING type has the correct Coding definition. - if (FHIRDefinedType.CODING.equals(expectedResultType)) { - assertThat(result) - .isElementPath(PrimitivePath.class) - .hasDefinition(definition); - } - } - - @SuppressWarnings("unused") - @ParameterizedTest - @MethodSource("propertyParameters") - public void propertyAOfCoding(final String propertyType, final DataType resultDataType, - final Type[] propertyAFhirValues, final Type[] propertyBFhirValues, - final Object[] propertyASqlValues, final Object[] propertyBSqlValues) { - - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(resultDataType) - .withRow("encounter-1", makeEid(0, 0), propertyASqlValues[0]) - .withRow("encounter-1", makeEid(1, 0), null) - .withRow("encounter-2", makeEid(0, 0), null) - .withRow("encounter-3", null, null) - .build(); - checkPropertyOfCoding("propertyA", - Optional.of(propertyType), - Optional.empty(), - propertyAFhirValues, propertyBFhirValues, - FHIRDefinedType.fromCode(propertyType), - expectedResult); - } - - @SuppressWarnings("unused") - @ParameterizedTest - @MethodSource("propertyParameters") - public void propertyBOfCoding(final String propertyType, final DataType resultDataType, - final Type[] propertyAFhirValues, final Type[] propertyBFhirValues, - final Object[] propertyASqlValues, final Object[] propertyBSqlValues) { - - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(resultDataType) - .withRow("encounter-1", makeEid(0, 0), null) - .withRow("encounter-1", makeEid(1, 0), null) - .withRow("encounter-2", makeEid(0, 0), propertyBSqlValues[0]) - .withRow("encounter-2", makeEid(0, 1), propertyBSqlValues[1]) - .withRow("encounter-3", null, null) - .build(); - - checkPropertyOfCoding("propertyB", - Optional.of(propertyType), - Optional.empty(), - propertyAFhirValues, - propertyBFhirValues, - FHIRDefinedType.fromCode(propertyType), - expectedResult); - } - - @Test - public void propertyAOfCodingWithDefaultType() { - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("encounter-1", makeEid(0, 0), "value_1") - .withRow("encounter-1", makeEid(0, 1), "value_2") - .withRow("encounter-1", makeEid(1, 0), null) - .withRow("encounter-2", makeEid(0, 0), null) - .withRow("encounter-3", null, null) - .build(); - - checkPropertyOfCoding("propertyA", - Optional.empty(), - Optional.empty(), - new Type[]{new StringType("value_1"), new StringType("value_2")}, new Type[]{}, - FHIRDefinedType.STRING, - expectedResult); - } - - - @SuppressWarnings("unused") - @ParameterizedTest - @MethodSource("propertyParameters") - public void propertyBOfCodingLanguage(final String propertyType, final DataType resultDataType, - final Type[] propertyAFhirValues, final Type[] propertyBFhirValues, - final Object[] propertyASqlValues, final Object[] propertyBSqlValues) { - - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(resultDataType) - .withRow("encounter-1", makeEid(0, 0), null) - .withRow("encounter-1", makeEid(1, 0), null) - .withRow("encounter-2", makeEid(0, 0), propertyBSqlValues[0]) - .withRow("encounter-2", makeEid(0, 1), propertyBSqlValues[1]) - .withRow("encounter-3", null, null) - .build(); - - checkPropertyOfCoding("propertyB", - Optional.of(propertyType), - Optional.of("de"), - propertyAFhirValues, - propertyBFhirValues, - FHIRDefinedType.fromCode(propertyType), - expectedResult); - } - - @Test - void throwsErrorIfInputTypeIsUnsupported() { - final Collection mockContext = new ElementPathBuilder(spark).build(); - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .expression("name.given") - .build(); - final Collection argument = StringCollection.fromLiteral("some-property", mockContext); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(mock(TerminologyServiceFactory.class)) - .build(); - - final NamedFunctionInput propertyInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new PropertyFunction().invoke(propertyInput)); - assertEquals("Input to property function must be Coding but is: name.given", - error.getMessage()); - } - - void assertThrowsErrorForArguments(@Nonnull final String expectedError, - @Nonnull final Function> argsFactory) { - - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Encounter", "class"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .definition(definition) - .buildDefined(); - - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(mock(TerminologyServiceFactory.class)) - .build(); - - final NamedFunctionInput propertyInput = new NamedFunctionInput(context, input, - argsFactory.apply(input)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new PropertyFunction().invoke(propertyInput)); - assertEquals(expectedError, - error.getMessage()); - - } - - @Test - void throwsErrorIfNoArguments() { - assertThrowsErrorForArguments( - "property function accepts one required and one optional arguments", - input -> Collections.emptyList()); - } - - @Test - void throwsErrorIfFirstArgumentIsNotString() { - assertThrowsErrorForArguments("Function `property` expects `String literal` as argument 1", - input -> Collections.singletonList( - IntegerLiteralPath.fromString("4", input))); - } - - @Test - void throwsErrorIfSecondArgumentIsNotBoolean() { - assertThrowsErrorForArguments("Function `property` expects `String literal` as argument 2", - input -> Arrays.asList( - StringCollection.fromLiteral("'foo'", input), - IntegerLiteralPath.fromString("5", input))); - } - - @Test - void throwsErrorIfThirdArgumentIsNotBoolean() { - assertThrowsErrorForArguments("Function `property` expects `String literal` as argument 3", - input -> Arrays.asList( - StringCollection.fromLiteral("'foo'", input), - StringCollection.fromLiteral("'foo'", input), - IntegerLiteralPath.fromString("5", input))); - } - - @Test - void throwsErrorIfTooManyArguments() { - assertThrowsErrorForArguments( - "property function accepts one required and one optional arguments", - input -> Arrays.asList( - StringCollection.fromLiteral("'foo'", input), - StringCollection.fromLiteral("'false'", input), - StringCollection.fromLiteral("'false'", input), - StringCollection.fromLiteral("'false'", input) - )); - } - - @Test - void throwsErrorIfCannotParsePropertyType() { - assertThrowsErrorForArguments( - "Unknown FHIRDefinedType code 'not-an-fhir-type'", - input -> Arrays.asList( - StringCollection.fromLiteral("'foo'", input), - StringCollection.fromLiteral("'not-an-fhir-type'", input) - )); - } - - @Test - void throwsErrorIfTerminologyServiceNotConfigured() { - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .build(); - final Collection argument = StringCollection.fromLiteral("some string", input); - - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .build(); - - final NamedFunctionInput translateInput = new NamedFunctionInput(context, input, - Collections.singletonList(argument)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new TranslateFunction().invoke(translateInput)); - assertEquals( - "Attempt to call terminology function translate when terminology service has not been configured", - error.getMessage()); - } + // TODO: implement with columns + + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @Autowiredø + // TerminologyServiceFactory terminologyServiceFactory; + // + // @Autowired + // TerminologyService terminologyService; + // + // @BeforeEach + // void setUp() { + // reset(terminologyService); + // } + // + // private void checkPropertyOfCoding(final String propertyCode, + // final Optional maybePropertyType, + // final Optional maybeLanguage, + // final Type[] propertyAFhirValues, final Type[] propertyBFhirValues, + // final FHIRDefinedType expectedResultType, + // final Dataset expectedResult) { + // + // // check the that if type is provided if language is provided + // assertTrue(maybeLanguage.isEmpty() || maybePropertyType.isPresent()); + // + // TerminologyServiceHelpers.setupLookup(terminologyService) + // .withProperty(CODING_A, "propertyA", maybeLanguage.orElse(null), propertyAFhirValues) + // .withProperty(CODING_B, "propertyB", maybeLanguage.orElse(null), propertyBFhirValues); + // + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Encounter", "class"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(codingStructType()) + // .withRow("encounter-1", makeEid(0), rowFromCoding(CODING_A)) + // .withRow("encounter-1", makeEid(1), rowFromCoding(INVALID_CODING_0)) + // .withRow("encounter-2", makeEid(0), rowFromCoding(CODING_B)) + // .withRow("encounter-3", null, null) + // .buildWithStructValue(); + // + // final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("Encounter.class") + // .singular(false) + // .definition(definition) + // .buildDefined(); + // + // // Prepare the inputs to the function. + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .idColumn(inputExpression.getIdColumn()) + // .terminologyClientFactory(terminologyServiceFactory) + // .build(); + // + // final List parameterLiterals = Stream.of(Optional.of(propertyCode), + // maybePropertyType, + // maybeLanguage).flatMap(Optional::stream) + // .map(StringLiteral::toLiteral).collect(Collectors.toUnmodifiableList()); + // + // final List arguments = parameterLiterals.stream() + // .map(lit -> StringCollection.fromLiteral(lit, inputExpression)) + // .collect(Collectors.toUnmodifiableList()); + // + // final NamedFunctionInput propertyInput = new NamedFunctionInput(parserContext, inputExpression, + // arguments); + // + // // Invoke the function. + // final Collection result = NamedFunction.getInstance("property").invoke(propertyInput); + // + // // Check the result. + // assertThat(result).hasExpression( + // String.format("Encounter.class.property(%s)", + // String.join(", ", parameterLiterals))) + // .isElementPath(PrimitivePath.class) + // .hasFhirType(expectedResultType) + // .isNotSingular() + // .selectOrderedResultWithEid() + // .hasRows(expectedResult); + // + // // Check that the ElementPath for CODING type has the correct Coding definition. + // if (FHIRDefinedType.CODING.equals(expectedResultType)) { + // assertThat(result) + // .isElementPath(PrimitivePath.class) + // .hasDefinition(definition); + // } + // } + // + // @SuppressWarnings("unused") + // @ParameterizedTest + // @MethodSource("propertyParameters") + // public void propertyAOfCoding(final String propertyType, final DataType resultDataType, + // final Type[] propertyAFhirValues, final Type[] propertyBFhirValues, + // final Object[] propertyASqlValues, final Object[] propertyBSqlValues) { + // + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(resultDataType) + // .withRow("encounter-1", makeEid(0, 0), propertyASqlValues[0]) + // .withRow("encounter-1", makeEid(1, 0), null) + // .withRow("encounter-2", makeEid(0, 0), null) + // .withRow("encounter-3", null, null) + // .build(); + // checkPropertyOfCoding("propertyA", + // Optional.of(propertyType), + // Optional.empty(), + // propertyAFhirValues, propertyBFhirValues, + // FHIRDefinedType.fromCode(propertyType), + // expectedResult); + // } + // + // @SuppressWarnings("unused") + // @ParameterizedTest + // @MethodSource("propertyParameters") + // public void propertyBOfCoding(final String propertyType, final DataType resultDataType, + // final Type[] propertyAFhirValues, final Type[] propertyBFhirValues, + // final Object[] propertyASqlValues, final Object[] propertyBSqlValues) { + // + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(resultDataType) + // .withRow("encounter-1", makeEid(0, 0), null) + // .withRow("encounter-1", makeEid(1, 0), null) + // .withRow("encounter-2", makeEid(0, 0), propertyBSqlValues[0]) + // .withRow("encounter-2", makeEid(0, 1), propertyBSqlValues[1]) + // .withRow("encounter-3", null, null) + // .build(); + // + // checkPropertyOfCoding("propertyB", + // Optional.of(propertyType), + // Optional.empty(), + // propertyAFhirValues, + // propertyBFhirValues, + // FHIRDefinedType.fromCode(propertyType), + // expectedResult); + // } + // + // @Test + // public void propertyAOfCodingWithDefaultType() { + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("encounter-1", makeEid(0, 0), "value_1") + // .withRow("encounter-1", makeEid(0, 1), "value_2") + // .withRow("encounter-1", makeEid(1, 0), null) + // .withRow("encounter-2", makeEid(0, 0), null) + // .withRow("encounter-3", null, null) + // .build(); + // + // checkPropertyOfCoding("propertyA", + // Optional.empty(), + // Optional.empty(), + // new Type[]{new StringType("value_1"), new StringType("value_2")}, new Type[]{}, + // FHIRDefinedType.STRING, + // expectedResult); + // } + // + // + // @SuppressWarnings("unused") + // @ParameterizedTest + // @MethodSource("propertyParameters") + // public void propertyBOfCodingLanguage(final String propertyType, final DataType resultDataType, + // final Type[] propertyAFhirValues, final Type[] propertyBFhirValues, + // final Object[] propertyASqlValues, final Object[] propertyBSqlValues) { + // + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(resultDataType) + // .withRow("encounter-1", makeEid(0, 0), null) + // .withRow("encounter-1", makeEid(1, 0), null) + // .withRow("encounter-2", makeEid(0, 0), propertyBSqlValues[0]) + // .withRow("encounter-2", makeEid(0, 1), propertyBSqlValues[1]) + // .withRow("encounter-3", null, null) + // .build(); + // + // checkPropertyOfCoding("propertyB", + // Optional.of(propertyType), + // Optional.of("de"), + // propertyAFhirValues, + // propertyBFhirValues, + // FHIRDefinedType.fromCode(propertyType), + // expectedResult); + // } + // + // @Test + // void throwsErrorIfInputTypeIsUnsupported() { + // final Collection mockContext = new ElementPathBuilder(spark).build(); + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .expression("name.given") + // .build(); + // final Collection argument = StringCollection.fromLiteral("some-property", mockContext); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .terminologyClientFactory(mock(TerminologyServiceFactory.class)) + // .build(); + // + // final NamedFunctionInput propertyInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new PropertyFunction().invoke(propertyInput)); + // assertEquals("Input to property function must be Coding but is: name.given", + // error.getMessage()); + // } + // + // void assertThrowsErrorForArguments(@Nonnull final String expectedError, + // @Nonnull final Function> argsFactory) { + // + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Encounter", "class"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .definition(definition) + // .buildDefined(); + // + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .terminologyClientFactory(mock(TerminologyServiceFactory.class)) + // .build(); + // + // final NamedFunctionInput propertyInput = new NamedFunctionInput(context, input, + // argsFactory.apply(input)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new PropertyFunction().invoke(propertyInput)); + // assertEquals(expectedError, + // error.getMessage()); + // + // } + // + // @Test + // void throwsErrorIfNoArguments() { + // assertThrowsErrorForArguments( + // "property function accepts one required and one optional arguments", + // input -> Collections.emptyList()); + // } + // + // @Test + // void throwsErrorIfFirstArgumentIsNotString() { + // assertThrowsErrorForArguments("Function `property` expects `String literal` as argument 1", + // input -> Collections.singletonList( + // IntegerLiteralPath.fromString("4", input))); + // } + // + // @Test + // void throwsErrorIfSecondArgumentIsNotBoolean() { + // assertThrowsErrorForArguments("Function `property` expects `String literal` as argument 2", + // input -> Arrays.asList( + // StringCollection.fromLiteral("'foo'", input), + // IntegerLiteralPath.fromString("5", input))); + // } + // + // @Test + // void throwsErrorIfThirdArgumentIsNotBoolean() { + // assertThrowsErrorForArguments("Function `property` expects `String literal` as argument 3", + // input -> Arrays.asList( + // StringCollection.fromLiteral("'foo'", input), + // StringCollection.fromLiteral("'foo'", input), + // IntegerLiteralPath.fromString("5", input))); + // } + // + // @Test + // void throwsErrorIfTooManyArguments() { + // assertThrowsErrorForArguments( + // "property function accepts one required and one optional arguments", + // input -> Arrays.asList( + // StringCollection.fromLiteral("'foo'", input), + // StringCollection.fromLiteral("'false'", input), + // StringCollection.fromLiteral("'false'", input), + // StringCollection.fromLiteral("'false'", input) + // )); + // } + // + // @Test + // void throwsErrorIfCannotParsePropertyType() { + // assertThrowsErrorForArguments( + // "Unknown FHIRDefinedType code 'not-an-fhir-type'", + // input -> Arrays.asList( + // StringCollection.fromLiteral("'foo'", input), + // StringCollection.fromLiteral("'not-an-fhir-type'", input) + // )); + // } + // + // @Test + // void throwsErrorIfTerminologyServiceNotConfigured() { + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .build(); + // final Collection argument = StringCollection.fromLiteral("some string", input); + // + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .build(); + // + // final NamedFunctionInput translateInput = new NamedFunctionInput(context, input, + // Collections.singletonList(argument)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new TranslateFunction().invoke(translateInput)); + // assertEquals( + // "Attempt to call terminology function translate when terminology service has not been configured", + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunctionTest.java index dbba84211d..95e53d1100 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/SubsumesFunctionTest.java @@ -17,57 +17,17 @@ package au.csiro.pathling.fhirpath.function.terminology; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static au.csiro.pathling.test.helpers.SparkHelpers.codeableConceptStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.codingStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromCodeableConcept; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromCoding; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.NonLiteralPath; -import au.csiro.pathling.fhirpath.collection.BooleanCollection; -import au.csiro.pathling.fhirpath.collection.CodingCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.terminology.TerminologyService; -import au.csiro.pathling.terminology.TerminologyServiceFactory; -import au.csiro.pathling.test.SharedMocks; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.assertions.DatasetAssert; -import au.csiro.pathling.test.assertions.ElementPathAssertion; -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.TerminologyServiceHelpers; -import ca.uhn.fhir.context.FhirContext; import java.util.Arrays; -import java.util.Collections; import java.util.List; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.apache.spark.sql.types.DataTypes; -import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author Piotr Szul */ @SpringBootUnitTest +@NotImplemented class SubsumesFunctionTest { static final String TEST_SYSTEM = "uuid:1"; @@ -90,538 +50,540 @@ class SubsumesFunctionTest { static final List ALL_RES_IDS = Arrays.asList(RES_ID1, RES_ID2, RES_ID3, RES_ID4, RES_ID5); - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @Autowired - TerminologyService terminologyService; - - @Autowired - TerminologyServiceFactory terminologyServiceFactory; - - static Row codeableConceptRowFromCoding(final Coding coding) { - return codeableConceptRowFromCoding(coding, CODING_OTHER4); - } - - static Row codeableConceptRowFromCoding(final Coding coding, final Coding otherCoding) { - return rowFromCodeableConcept(new CodeableConcept(coding).addCoding(otherCoding)); - } - - @BeforeEach - void setUp() { - SharedMocks.resetAll(); - TerminologyServiceHelpers.setupSubsumes(terminologyService) - .withSubsumes(CODING_LARGE, CODING_MEDIUM) - .withSubsumes(CODING_MEDIUM, CODING_SMALL) - .withSubsumes(CODING_LARGE, CODING_SMALL); - } - - CodingCollection createCodingInput() { - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(codingStructType()) - .withRow(RES_ID1, makeEid(1), rowFromCoding(CODING_SMALL)) - .withRow(RES_ID2, makeEid(1), rowFromCoding(CODING_MEDIUM)) - .withRow(RES_ID3, makeEid(1), rowFromCoding(CODING_LARGE)) - .withRow(RES_ID4, makeEid(1), rowFromCoding(CODING_OTHER1)) - .withRow(RES_ID5, null, null /* NULL coding value */) - .withRow(RES_ID1, makeEid(0), rowFromCoding(CODING_OTHER2)) - .withRow(RES_ID2, makeEid(0), rowFromCoding(CODING_OTHER2)) - .withRow(RES_ID3, makeEid(0), rowFromCoding(CODING_OTHER2)) - .withRow(RES_ID4, makeEid(0), rowFromCoding(CODING_OTHER2)) - .buildWithStructValue(); - final PrimitivePath inputExpression = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .dataset(dataset) - .idAndEidAndValueColumns() - .singular(false) - .build(); - - return (CodingCollection) inputExpression; - } - - CodingCollection createSingularCodingInput() { - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withStructTypeColumns(codingStructType()) - .withRow(RES_ID1, rowFromCoding(CODING_SMALL)) - .withRow(RES_ID2, rowFromCoding(CODING_MEDIUM)) - .withRow(RES_ID3, rowFromCoding(CODING_LARGE)) - .withRow(RES_ID4, rowFromCoding(CODING_OTHER1)) - .withRow(RES_ID5, null /* NULL coding value */) - .buildWithStructValue(); - final PrimitivePath inputExpression = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .dataset(dataset) - .idAndValueColumns() - .singular(true) - .build(); - - return (CodingCollection) inputExpression; - } - - - PrimitivePath createCodeableConceptInput() { - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(codeableConceptStructType()) - .withRow(RES_ID1, makeEid(1), codeableConceptRowFromCoding(CODING_SMALL)) - .withRow(RES_ID2, makeEid(1), codeableConceptRowFromCoding(CODING_MEDIUM)) - .withRow(RES_ID3, makeEid(1), codeableConceptRowFromCoding(CODING_LARGE)) - .withRow(RES_ID4, makeEid(1), codeableConceptRowFromCoding(CODING_OTHER1)) - .withRow(RES_ID5, null, null /* NULL codeable concept value */) - .withRow(RES_ID1, makeEid(0), codeableConceptRowFromCoding(CODING_OTHER2)) - .withRow(RES_ID2, makeEid(0), codeableConceptRowFromCoding(CODING_OTHER2)) - .withRow(RES_ID3, makeEid(0), codeableConceptRowFromCoding(CODING_OTHER2)) - .withRow(RES_ID4, makeEid(0), codeableConceptRowFromCoding(CODING_OTHER2)) - .buildWithStructValue(); - - return new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .dataset(dataset) - .idAndEidAndValueColumns() - .singular(false) - .build(); - } - - CodingLiteralPath createLiteralArgOrInput() { - final Dataset literalContextDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.BooleanType) - .withIdsAndValue(false, ALL_RES_IDS) - .build(); - final PrimitivePath literalContext = new ElementPathBuilder(spark) - .dataset(literalContextDataset) - .idAndValueColumns() - .build(); - - return CodingCollection.fromLiteral(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), - literalContext); - } - - CodingCollection createCodingArg() { - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withStructTypeColumns(codingStructType()) - .withIdValueRows(ALL_RES_IDS, id -> rowFromCoding(CODING_MEDIUM)) - .withIdValueRows(ALL_RES_IDS, id -> rowFromCoding(CODING_OTHER3)) - .buildWithStructValue(); - final PrimitivePath argument = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .dataset(dataset) - .idAndValueColumns() - .build(); - - return (CodingCollection) argument; - } - - PrimitivePath createCodeableConceptArg() { - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withStructTypeColumns(codeableConceptStructType()) - .withIdValueRows(ALL_RES_IDS, - id -> codeableConceptRowFromCoding(CODING_MEDIUM, CODING_OTHER5)) - .withIdValueRows(ALL_RES_IDS, - id -> codeableConceptRowFromCoding(CODING_OTHER3, CODING_OTHER5)) - .buildWithStructValue(); - - return new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .dataset(dataset) - .idAndValueColumns() - .build(); - } - - CodingCollection createEmptyCodingInput() { - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(codingStructType()) - .buildWithStructValue(); - - final PrimitivePath argument = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .dataset(dataset) - .idAndEidAndValueColumns() - .build(); - - return (CodingCollection) argument; - } - - CodingCollection createNullCodingInput() { - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withIdEidValueRows(ALL_RES_IDS, id -> null, id -> null) - .withStructTypeColumns(codingStructType()) - .buildWithStructValue(); - - final PrimitivePath argument = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .dataset(dataset) - .idAndEidAndValueColumns() - .build(); - - return (CodingCollection) argument; - } - - PrimitivePath createEmptyCodeableConceptInput() { - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(codeableConceptStructType()) - .buildWithStructValue(); - - return new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .dataset(dataset) - .idAndEidAndValueColumns() - .build(); - } - - PrimitivePath createNullCodeableConceptInput() { - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withIdEidValueRows(ALL_RES_IDS, id -> null, id -> null) - .withStructTypeColumns(codeableConceptStructType()) - .buildWithStructValue(); - - return new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .dataset(dataset) - .idAndEidAndValueColumns() - .build(); - } - - CodingCollection createNullCodingArg() { - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn() - .withStructTypeColumns(codingStructType()) - .withIdValueRows(ALL_RES_IDS, id -> null) - .buildWithStructValue(); - - final PrimitivePath argument = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .dataset(dataset) - .idAndValueColumns() - .build(); - - return (CodingCollection) argument; - } - - DatasetBuilder expectedSubsumes() { - return new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.BooleanType) - .withRow(RES_ID1, makeEid(0), false) - .withRow(RES_ID1, makeEid(1), false) - .withRow(RES_ID2, makeEid(0), false) - .withRow(RES_ID2, makeEid(1), true) - .withRow(RES_ID3, makeEid(0), false) - .withRow(RES_ID3, makeEid(1), true) - .withRow(RES_ID4, makeEid(0), false) - .withRow(RES_ID4, makeEid(1), false) - .withRow(RES_ID5, null, null); - } - - DatasetBuilder expectedSubsumedBy() { - return new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.BooleanType) - .withRow(RES_ID1, makeEid(0), false) - .withRow(RES_ID1, makeEid(1), true) - .withRow(RES_ID2, makeEid(0), false) - .withRow(RES_ID2, makeEid(1), true) - .withRow(RES_ID3, makeEid(0), false) - .withRow(RES_ID3, makeEid(1), false) - .withRow(RES_ID4, makeEid(0), false) - .withRow(RES_ID4, makeEid(1), false) - .withRow(RES_ID5, null, null); - } - - @Nonnull - DatasetBuilder expectedAllNonNull(final boolean result) { - return new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.BooleanType) - .withRow(RES_ID1, makeEid(0), result) - .withRow(RES_ID1, makeEid(1), result) - .withRow(RES_ID2, makeEid(0), result) - .withRow(RES_ID2, makeEid(1), result) - .withRow(RES_ID3, makeEid(0), result) - .withRow(RES_ID3, makeEid(1), result) - .withRow(RES_ID4, makeEid(0), result) - .withRow(RES_ID4, makeEid(1), result) - .withRow(RES_ID5, null, null); - } - - @Nonnull - DatasetBuilder expectedEmpty() { - return new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.BooleanType); - } - - @Nonnull - DatasetBuilder expectedNull() { - return new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withIdEidValueRows(ALL_RES_IDS, id -> null, id -> null) - .withColumn(DataTypes.BooleanType); - } - - ElementPathAssertion assertCallSuccess(final NamedFunction function, - final NonLiteralPath inputExpression, final Collection argumentExpression) { - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(terminologyServiceFactory) - .build(); - - final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, inputExpression, - Collections.singletonList(argumentExpression)); - final Collection result = function.invoke(functionInput); - - return assertThat(result) - .isElementPath(BooleanCollection.class) - .preservesCardinalityOf(inputExpression); - } - - DatasetAssert assertSubsumesSuccess(final NonLiteralPath inputExpression, - final Collection argumentExpression) { - return assertCallSuccess(NamedFunction.getInstance("subsumes"), inputExpression, - argumentExpression).selectOrderedResultWithEid(); - } - - DatasetAssert assertSubsumedBySuccess(final NonLiteralPath inputExpression, - final Collection argumentExpression) { - return assertCallSuccess(NamedFunction.getInstance("subsumedBy"), inputExpression, - argumentExpression).selectOrderedResultWithEid(); - } + // TODO: implement with columns + // @Autowired + // SparkSession spark; // - // Test subsumes on selected pairs of argument types - // (Coding, CodingLiteral) && (CodeableConcept, Coding) && (Literal, CodeableConcept) - // plus (Coding, Coding) + // @Autowired + // FhirContext fhirContext; // - @Test - void testSubsumesCodingWithLiteralCorrectly() { - assertSubsumesSuccess(createCodingInput(), createLiteralArgOrInput()) - .hasRows(expectedSubsumes()); - } - - @Test - void testSubsumesCodeableConceptWithCodingCorrectly() { - assertSubsumesSuccess(createCodeableConceptInput(), createCodingArg()) - .hasRows(expectedSubsumes()); - } - - @Test - void testSubsumesCodingWithCodingCorrectly() { - assertSubsumesSuccess(createCodingInput(), createCodingArg()).hasRows(expectedSubsumes()); - } - + // @Autowired + // TerminologyService terminologyService; // - // Test subsumedBy on selected pairs of argument types - // (Coding, CodeableConcept) && (CodeableConcept, Literal) && (Literal, Coding) - // plus (CodeableConcept, CodeableConcept) + // @Autowired + // TerminologyServiceFactory terminologyServiceFactory; // - - @Test - void testSubsumedByCodingWithCodeableConceptCorrectly() { - - assertSubsumedBySuccess(createCodingInput(), createCodeableConceptArg()) - .hasRows(expectedSubsumedBy()); - } - - @Test - void testSubsumedByCodeableConceptWithLiteralCorrectly() { - assertSubsumedBySuccess(createCodeableConceptInput(), createLiteralArgOrInput()) - .hasRows(expectedSubsumedBy()); - } - - @Test - void testSubsumedByCodeableConceptWithCodeableConceptCorrectly() { - // call subsumedBy but expect subsumes result - // because input is switched with argument - assertSubsumedBySuccess(createCodeableConceptInput(), createCodeableConceptArg()) - .hasRows(expectedSubsumedBy()); - } - + // static Row codeableConceptRowFromCoding(final Coding coding) { + // return codeableConceptRowFromCoding(coding, CODING_OTHER4); + // } // - // Test against nulls + // static Row codeableConceptRowFromCoding(final Coding coding, final Coding otherCoding) { + // return rowFromCodeableConcept(new CodeableConcept(coding).addCoding(otherCoding)); + // } // - - @Test - void testAllFalseWhenSubsumesNullCoding() { - assertSubsumesSuccess(createCodingInput(), createNullCodingArg()) - .hasRows(expectedAllNonNull(false)); - } - - @Test - void testAllFalseWhenSubsumedByNullCoding() { - assertSubsumedBySuccess(createCodeableConceptInput(), createNullCodingArg()) - .hasRows(expectedAllNonNull(false)); - } - - - @Test - void testAllNonNullTrueWhenSubsumesItself() { - - final DatasetBuilder expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.BooleanType) - .withRow(RES_ID1, null, true) - .withRow(RES_ID2, null, true) - .withRow(RES_ID3, null, true) - .withRow(RES_ID4, null, true) - .withRow(RES_ID5, null, null); - - assertSubsumesSuccess(createSingularCodingInput(), createSingularCodingInput()) - .hasRows(expectedResult); - } - - - @Test - void testAllNonNullTrueSubsumedByItself() { - assertSubsumedBySuccess(createCodeableConceptInput(), createCodeableConceptInput()) - .hasRows(expectedAllNonNull(true)); - } - - @Test - void testEmptyCodingInput() { - assertSubsumedBySuccess(createEmptyCodingInput(), createCodingArg()) - .hasRows(expectedEmpty()); - } - - @Test - void testNullCodingInput() { - assertSubsumedBySuccess(createNullCodingInput(), createCodingArg()) - .hasRows(expectedNull()); - } - - @Test - void testEmptyCodeableConceptInput() { - assertSubsumedBySuccess(createEmptyCodeableConceptInput(), createCodingArg()) - .hasRows(expectedEmpty()); - } - - @Test - void testNullCodeableConceptInput() { - assertSubsumedBySuccess(createNullCodeableConceptInput(), createCodingArg()) - .hasRows(expectedNull()); - } - + // @BeforeEach + // void setUp() { + // SharedMocks.resetAll(); + // TerminologyServiceHelpers.setupSubsumes(terminologyService) + // .withSubsumes(CODING_LARGE, CODING_MEDIUM) + // .withSubsumes(CODING_MEDIUM, CODING_SMALL) + // .withSubsumes(CODING_LARGE, CODING_SMALL); + // } // - // Test for various validation errors + // CodingCollection createCodingInput() { + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(codingStructType()) + // .withRow(RES_ID1, makeEid(1), rowFromCoding(CODING_SMALL)) + // .withRow(RES_ID2, makeEid(1), rowFromCoding(CODING_MEDIUM)) + // .withRow(RES_ID3, makeEid(1), rowFromCoding(CODING_LARGE)) + // .withRow(RES_ID4, makeEid(1), rowFromCoding(CODING_OTHER1)) + // .withRow(RES_ID5, null, null /* NULL coding value */) + // .withRow(RES_ID1, makeEid(0), rowFromCoding(CODING_OTHER2)) + // .withRow(RES_ID2, makeEid(0), rowFromCoding(CODING_OTHER2)) + // .withRow(RES_ID3, makeEid(0), rowFromCoding(CODING_OTHER2)) + // .withRow(RES_ID4, makeEid(0), rowFromCoding(CODING_OTHER2)) + // .buildWithStructValue(); + // final PrimitivePath inputExpression = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .dataset(dataset) + // .idAndEidAndValueColumns() + // .singular(false) + // .build(); // - - @Test - void throwsErrorIfInputTypeIsUnsupported() { - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(mock(TerminologyServiceFactory.class)) - .build(); - - final PrimitivePath argument = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .build(); - - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .build(); - - final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - final NamedFunction subsumesFunction = NamedFunction.getInstance("subsumedBy"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> subsumesFunction.invoke(functionInput)); - assertEquals( - "subsumedBy function accepts input of type Coding or CodeableConcept", - error.getMessage()); - } - - @Test - void throwsErrorIfArgumentTypeIsUnsupported() { - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(mock(TerminologyServiceFactory.class)) - .build(); - - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .build(); - final StringLiteralPath argument = StringCollection - .fromLiteral("'str'", input); - - final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - final NamedFunction subsumesFunction = NamedFunction.getInstance("subsumes"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> subsumesFunction.invoke(functionInput)); - assertEquals("subsumes function accepts argument of type Coding or CodeableConcept", - error.getMessage()); - } - - - @Test - void throwsErrorIfMoreThanOneArgument() { - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(mock(TerminologyServiceFactory.class)) - .build(); - - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .build(); - - final CodingLiteralPath argument1 = CodingCollection - .fromLiteral(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), - input); - - final CodingLiteralPath argument2 = CodingCollection - .fromLiteral(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), - input); - - final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, - Arrays.asList(argument1, argument2)); - final NamedFunction subsumesFunction = NamedFunction.getInstance("subsumes"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> subsumesFunction.invoke(functionInput)); - assertEquals("subsumes function accepts one argument of type Coding or CodeableConcept", - error.getMessage()); - } - - @Test - void throwsErrorIfTerminologyServiceNotConfigured() { - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .build(); - - final CodingLiteralPath argument = CodingCollection - .fromLiteral(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), - input); - - final ParserContext context = new ParserContextBuilder(spark, fhirContext).build(); - - final NamedFunctionInput functionInput = new NamedFunctionInput(context, input, - Collections.singletonList(argument)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new SubsumesFunction().invoke(functionInput)); - assertEquals( - "Attempt to call terminology function subsumes when terminology service has not been configured", - error.getMessage()); - } + // return (CodingCollection) inputExpression; + // } + // + // CodingCollection createSingularCodingInput() { + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withStructTypeColumns(codingStructType()) + // .withRow(RES_ID1, rowFromCoding(CODING_SMALL)) + // .withRow(RES_ID2, rowFromCoding(CODING_MEDIUM)) + // .withRow(RES_ID3, rowFromCoding(CODING_LARGE)) + // .withRow(RES_ID4, rowFromCoding(CODING_OTHER1)) + // .withRow(RES_ID5, null /* NULL coding value */) + // .buildWithStructValue(); + // final PrimitivePath inputExpression = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .dataset(dataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // + // return (CodingCollection) inputExpression; + // } + // + // + // PrimitivePath createCodeableConceptInput() { + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(codeableConceptStructType()) + // .withRow(RES_ID1, makeEid(1), codeableConceptRowFromCoding(CODING_SMALL)) + // .withRow(RES_ID2, makeEid(1), codeableConceptRowFromCoding(CODING_MEDIUM)) + // .withRow(RES_ID3, makeEid(1), codeableConceptRowFromCoding(CODING_LARGE)) + // .withRow(RES_ID4, makeEid(1), codeableConceptRowFromCoding(CODING_OTHER1)) + // .withRow(RES_ID5, null, null /* NULL codeable concept value */) + // .withRow(RES_ID1, makeEid(0), codeableConceptRowFromCoding(CODING_OTHER2)) + // .withRow(RES_ID2, makeEid(0), codeableConceptRowFromCoding(CODING_OTHER2)) + // .withRow(RES_ID3, makeEid(0), codeableConceptRowFromCoding(CODING_OTHER2)) + // .withRow(RES_ID4, makeEid(0), codeableConceptRowFromCoding(CODING_OTHER2)) + // .buildWithStructValue(); + // + // return new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .dataset(dataset) + // .idAndEidAndValueColumns() + // .singular(false) + // .build(); + // } + // + // CodingLiteralPath createLiteralArgOrInput() { + // final Dataset literalContextDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.BooleanType) + // .withIdsAndValue(false, ALL_RES_IDS) + // .build(); + // final PrimitivePath literalContext = new ElementPathBuilder(spark) + // .dataset(literalContextDataset) + // .idAndValueColumns() + // .build(); + // + // return CodingCollection.fromLiteral(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), + // literalContext); + // } + // + // CodingCollection createCodingArg() { + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withStructTypeColumns(codingStructType()) + // .withIdValueRows(ALL_RES_IDS, id -> rowFromCoding(CODING_MEDIUM)) + // .withIdValueRows(ALL_RES_IDS, id -> rowFromCoding(CODING_OTHER3)) + // .buildWithStructValue(); + // final PrimitivePath argument = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .dataset(dataset) + // .idAndValueColumns() + // .build(); + // + // return (CodingCollection) argument; + // } + // + // PrimitivePath createCodeableConceptArg() { + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withStructTypeColumns(codeableConceptStructType()) + // .withIdValueRows(ALL_RES_IDS, + // id -> codeableConceptRowFromCoding(CODING_MEDIUM, CODING_OTHER5)) + // .withIdValueRows(ALL_RES_IDS, + // id -> codeableConceptRowFromCoding(CODING_OTHER3, CODING_OTHER5)) + // .buildWithStructValue(); + // + // return new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .dataset(dataset) + // .idAndValueColumns() + // .build(); + // } + // + // CodingCollection createEmptyCodingInput() { + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(codingStructType()) + // .buildWithStructValue(); + // + // final PrimitivePath argument = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .dataset(dataset) + // .idAndEidAndValueColumns() + // .build(); + // + // return (CodingCollection) argument; + // } + // + // CodingCollection createNullCodingInput() { + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withIdEidValueRows(ALL_RES_IDS, id -> null, id -> null) + // .withStructTypeColumns(codingStructType()) + // .buildWithStructValue(); + // + // final PrimitivePath argument = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .dataset(dataset) + // .idAndEidAndValueColumns() + // .build(); + // + // return (CodingCollection) argument; + // } + // + // PrimitivePath createEmptyCodeableConceptInput() { + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(codeableConceptStructType()) + // .buildWithStructValue(); + // + // return new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .dataset(dataset) + // .idAndEidAndValueColumns() + // .build(); + // } + // + // PrimitivePath createNullCodeableConceptInput() { + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withIdEidValueRows(ALL_RES_IDS, id -> null, id -> null) + // .withStructTypeColumns(codeableConceptStructType()) + // .buildWithStructValue(); + // + // return new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .dataset(dataset) + // .idAndEidAndValueColumns() + // .build(); + // } + // + // CodingCollection createNullCodingArg() { + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withStructTypeColumns(codingStructType()) + // .withIdValueRows(ALL_RES_IDS, id -> null) + // .buildWithStructValue(); + // + // final PrimitivePath argument = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .dataset(dataset) + // .idAndValueColumns() + // .build(); + // + // return (CodingCollection) argument; + // } + // + // DatasetBuilder expectedSubsumes() { + // return new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow(RES_ID1, makeEid(0), false) + // .withRow(RES_ID1, makeEid(1), false) + // .withRow(RES_ID2, makeEid(0), false) + // .withRow(RES_ID2, makeEid(1), true) + // .withRow(RES_ID3, makeEid(0), false) + // .withRow(RES_ID3, makeEid(1), true) + // .withRow(RES_ID4, makeEid(0), false) + // .withRow(RES_ID4, makeEid(1), false) + // .withRow(RES_ID5, null, null); + // } + // + // DatasetBuilder expectedSubsumedBy() { + // return new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow(RES_ID1, makeEid(0), false) + // .withRow(RES_ID1, makeEid(1), true) + // .withRow(RES_ID2, makeEid(0), false) + // .withRow(RES_ID2, makeEid(1), true) + // .withRow(RES_ID3, makeEid(0), false) + // .withRow(RES_ID3, makeEid(1), false) + // .withRow(RES_ID4, makeEid(0), false) + // .withRow(RES_ID4, makeEid(1), false) + // .withRow(RES_ID5, null, null); + // } + // + // @Nonnull + // DatasetBuilder expectedAllNonNull(final boolean result) { + // return new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow(RES_ID1, makeEid(0), result) + // .withRow(RES_ID1, makeEid(1), result) + // .withRow(RES_ID2, makeEid(0), result) + // .withRow(RES_ID2, makeEid(1), result) + // .withRow(RES_ID3, makeEid(0), result) + // .withRow(RES_ID3, makeEid(1), result) + // .withRow(RES_ID4, makeEid(0), result) + // .withRow(RES_ID4, makeEid(1), result) + // .withRow(RES_ID5, null, null); + // } + // + // @Nonnull + // DatasetBuilder expectedEmpty() { + // return new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.BooleanType); + // } + // + // @Nonnull + // DatasetBuilder expectedNull() { + // return new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withIdEidValueRows(ALL_RES_IDS, id -> null, id -> null) + // .withColumn(DataTypes.BooleanType); + // } + // + // ElementPathAssertion assertCallSuccess(final NamedFunction function, + // final NonLiteralPath inputExpression, final Collection argumentExpression) { + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .terminologyClientFactory(terminologyServiceFactory) + // .build(); + // + // final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, inputExpression, + // Collections.singletonList(argumentExpression)); + // final Collection result = function.invoke(functionInput); + // + // return assertThat(result) + // .isElementPath(BooleanCollection.class) + // .preservesCardinalityOf(inputExpression); + // } + // + // DatasetAssert assertSubsumesSuccess(final NonLiteralPath inputExpression, + // final Collection argumentExpression) { + // return assertCallSuccess(NamedFunction.getInstance("subsumes"), inputExpression, + // argumentExpression).selectOrderedResultWithEid(); + // } + // + // DatasetAssert assertSubsumedBySuccess(final NonLiteralPath inputExpression, + // final Collection argumentExpression) { + // return assertCallSuccess(NamedFunction.getInstance("subsumedBy"), inputExpression, + // argumentExpression).selectOrderedResultWithEid(); + // } + // + // // + // // Test subsumes on selected pairs of argument types + // // (Coding, CodingLiteral) && (CodeableConcept, Coding) && (Literal, CodeableConcept) + // // plus (Coding, Coding) + // // + // @Test + // void testSubsumesCodingWithLiteralCorrectly() { + // assertSubsumesSuccess(createCodingInput(), createLiteralArgOrInput()) + // .hasRows(expectedSubsumes()); + // } + // + // @Test + // void testSubsumesCodeableConceptWithCodingCorrectly() { + // assertSubsumesSuccess(createCodeableConceptInput(), createCodingArg()) + // .hasRows(expectedSubsumes()); + // } + // + // @Test + // void testSubsumesCodingWithCodingCorrectly() { + // assertSubsumesSuccess(createCodingInput(), createCodingArg()).hasRows(expectedSubsumes()); + // } + // + // // + // // Test subsumedBy on selected pairs of argument types + // // (Coding, CodeableConcept) && (CodeableConcept, Literal) && (Literal, Coding) + // // plus (CodeableConcept, CodeableConcept) + // // + // + // @Test + // void testSubsumedByCodingWithCodeableConceptCorrectly() { + // + // assertSubsumedBySuccess(createCodingInput(), createCodeableConceptArg()) + // .hasRows(expectedSubsumedBy()); + // } + // + // @Test + // void testSubsumedByCodeableConceptWithLiteralCorrectly() { + // assertSubsumedBySuccess(createCodeableConceptInput(), createLiteralArgOrInput()) + // .hasRows(expectedSubsumedBy()); + // } + // + // @Test + // void testSubsumedByCodeableConceptWithCodeableConceptCorrectly() { + // // call subsumedBy but expect subsumes result + // // because input is switched with argument + // assertSubsumedBySuccess(createCodeableConceptInput(), createCodeableConceptArg()) + // .hasRows(expectedSubsumedBy()); + // } + // + // // + // // Test against nulls + // // + // + // @Test + // void testAllFalseWhenSubsumesNullCoding() { + // assertSubsumesSuccess(createCodingInput(), createNullCodingArg()) + // .hasRows(expectedAllNonNull(false)); + // } + // + // @Test + // void testAllFalseWhenSubsumedByNullCoding() { + // assertSubsumedBySuccess(createCodeableConceptInput(), createNullCodingArg()) + // .hasRows(expectedAllNonNull(false)); + // } + // + // + // @Test + // void testAllNonNullTrueWhenSubsumesItself() { + // + // final DatasetBuilder expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.BooleanType) + // .withRow(RES_ID1, null, true) + // .withRow(RES_ID2, null, true) + // .withRow(RES_ID3, null, true) + // .withRow(RES_ID4, null, true) + // .withRow(RES_ID5, null, null); + // + // assertSubsumesSuccess(createSingularCodingInput(), createSingularCodingInput()) + // .hasRows(expectedResult); + // } + // + // + // @Test + // void testAllNonNullTrueSubsumedByItself() { + // assertSubsumedBySuccess(createCodeableConceptInput(), createCodeableConceptInput()) + // .hasRows(expectedAllNonNull(true)); + // } + // + // @Test + // void testEmptyCodingInput() { + // assertSubsumedBySuccess(createEmptyCodingInput(), createCodingArg()) + // .hasRows(expectedEmpty()); + // } + // + // @Test + // void testNullCodingInput() { + // assertSubsumedBySuccess(createNullCodingInput(), createCodingArg()) + // .hasRows(expectedNull()); + // } + // + // @Test + // void testEmptyCodeableConceptInput() { + // assertSubsumedBySuccess(createEmptyCodeableConceptInput(), createCodingArg()) + // .hasRows(expectedEmpty()); + // } + // + // @Test + // void testNullCodeableConceptInput() { + // assertSubsumedBySuccess(createNullCodeableConceptInput(), createCodingArg()) + // .hasRows(expectedNull()); + // } + // + // // + // // Test for various validation errors + // // + // + // @Test + // void throwsErrorIfInputTypeIsUnsupported() { + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .terminologyClientFactory(mock(TerminologyServiceFactory.class)) + // .build(); + // + // final PrimitivePath argument = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .build(); + // + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .build(); + // + // final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // final NamedFunction subsumesFunction = NamedFunction.getInstance("subsumedBy"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> subsumesFunction.invoke(functionInput)); + // assertEquals( + // "subsumedBy function accepts input of type Coding or CodeableConcept", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfArgumentTypeIsUnsupported() { + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .terminologyClientFactory(mock(TerminologyServiceFactory.class)) + // .build(); + // + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .build(); + // final StringLiteralPath argument = StringCollection + // .fromLiteral("'str'", input); + // + // final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // final NamedFunction subsumesFunction = NamedFunction.getInstance("subsumes"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> subsumesFunction.invoke(functionInput)); + // assertEquals("subsumes function accepts argument of type Coding or CodeableConcept", + // error.getMessage()); + // } + // + // + // @Test + // void throwsErrorIfMoreThanOneArgument() { + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .terminologyClientFactory(mock(TerminologyServiceFactory.class)) + // .build(); + // + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .build(); + // + // final CodingLiteralPath argument1 = CodingCollection + // .fromLiteral(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), + // input); + // + // final CodingLiteralPath argument2 = CodingCollection + // .fromLiteral(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), + // input); + // + // final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, + // Arrays.asList(argument1, argument2)); + // final NamedFunction subsumesFunction = NamedFunction.getInstance("subsumes"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> subsumesFunction.invoke(functionInput)); + // assertEquals("subsumes function accepts one argument of type Coding or CodeableConcept", + // error.getMessage()); + // } + // + // @Test + // void throwsErrorIfTerminologyServiceNotConfigured() { + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .build(); + // + // final CodingLiteralPath argument = CodingCollection + // .fromLiteral(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), + // input); + // + // final ParserContext context = new ParserContextBuilder(spark, fhirContext).build(); + // + // final NamedFunctionInput functionInput = new NamedFunctionInput(context, input, + // Collections.singletonList(argument)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new SubsumesFunction().invoke(functionInput)); + // assertEquals( + // "Attempt to call terminology function subsumes when terminology service has not been configured", + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunctionTest.java index e347b3979c..d5fc80373a 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/terminology/TranslateFunctionTest.java @@ -17,466 +17,415 @@ package au.csiro.pathling.fhirpath.function.terminology; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static au.csiro.pathling.test.helpers.FhirMatchers.deepEq; -import static au.csiro.pathling.test.helpers.SparkHelpers.codeableConceptStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.codingStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromCodeableConcept; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromCoding; -import static org.hl7.fhir.r4.model.codesystems.ConceptMapEquivalence.EQUIVALENT; -import static org.hl7.fhir.r4.model.codesystems.ConceptMapEquivalence.NARROWER; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.CodingCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; -import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.terminology.TerminologyService; -import au.csiro.pathling.terminology.TerminologyService.Translation; -import au.csiro.pathling.terminology.TerminologyServiceFactory; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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 au.csiro.pathling.test.helpers.TerminologyServiceHelpers; -import ca.uhn.fhir.context.FhirContext; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class TranslateFunctionTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - static final String SOURCE_SYSTEM_URI = "uuid:source"; - static final String DEST_SYSTEM_URI = "uuid:dest"; - - static final String CONCEPT_MAP1_URI = "http://snomed.info/sct?fhir_cm=100"; - static final String CONCEPT_MAP2_URI = "http://snomed.info/sct?fhir_cm=200"; - - - static final Coding CODING_1 = new Coding(SOURCE_SYSTEM_URI, "AMB", "ambulatory"); - static final Coding CODING_2 = new Coding(SOURCE_SYSTEM_URI, "EMER", null); - static final Coding CODING_3 = new Coding(SOURCE_SYSTEM_URI, "IMP", - "inpatient encounter"); - static final Coding CODING_4 = new Coding(SOURCE_SYSTEM_URI, "OTHER", null); - static final Coding CODING_5 = new Coding(SOURCE_SYSTEM_URI, "ACUTE", "inpatient acute"); - - static final Coding TRANSLATED_1 = new Coding(DEST_SYSTEM_URI, "TEST1", "Test1"); - static final Coding TRANSLATED_2 = new Coding(DEST_SYSTEM_URI, "TEST2", "Test2"); - - - @Autowired - TerminologyServiceFactory terminologyServiceFactory; - - @Autowired - TerminologyService terminologyService; - - @BeforeEach - void setUp() { - reset(terminologyService); - } - - @Test - void translateCodingWithDefaultArguments() { - - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Encounter", "class"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - - // The translations are - // { - // coding1 -> [translated1], - // coding2 -> [translated1, translated2] - // } - - // Use cases: - // 1. [ C2,C3,C1 ] -> [ [T1, T2],[], [T1]] - // 2. [ C3, C5 ] -> [[],[]] - // 3. [ C2 ] -> [ [T1, T2] ] - // 4. [] -> [] - // 5. null -> null - - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(codingStructType()) - // TC-1 - .withRow("encounter-1", makeEid(0), rowFromCoding(CODING_2)) - .withRow("encounter-1", makeEid(1), rowFromCoding(CODING_3)) - .withRow("encounter-1", makeEid(2), rowFromCoding(CODING_1)) - // TC-2 - .withRow("encounter-2", makeEid(0), rowFromCoding(CODING_3)) - .withRow("encounter-2", makeEid(1), rowFromCoding(CODING_5)) - // TC-3 - .withRow("encounter-3", makeEid(0), rowFromCoding(CODING_2)) - // TC-4 - .withRow("encounter-4", makeEid(0), null) - // TC-5 - .withRow("encounter-5", null, null) - .buildWithStructValue(); - - final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("Encounter.class") - .singular(false) - .definition(definition) - .buildDefined(); - - TerminologyServiceHelpers.setupTranslate(terminologyService) - .withTranslations(CODING_1, CONCEPT_MAP1_URI, - Translation.of(EQUIVALENT, TRANSLATED_1)) - .withTranslations(CODING_2, CONCEPT_MAP1_URI, - Translation.of(EQUIVALENT, TRANSLATED_1), - Translation.of(EQUIVALENT, TRANSLATED_2) - ); - - // Prepare the inputs to the function. - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .idColumn(inputExpression.getIdColumn()) - .terminologyClientFactory(terminologyServiceFactory) - .build(); - - final StringLiteralPath conceptMapUrlArgument = StringCollection - .fromLiteral("'" + CONCEPT_MAP1_URI + "'", inputExpression); - - final NamedFunctionInput translateInput = new NamedFunctionInput(parserContext, inputExpression, - Collections.singletonList(conceptMapUrlArgument)); - // Invoke the function. - final Collection result = new TranslateFunction().invoke(translateInput); - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(codingStructType()) - // TC-1 - .withRow("encounter-1", makeEid(0, 0), rowFromCoding(TRANSLATED_1)) - .withRow("encounter-1", makeEid(0, 1), rowFromCoding(TRANSLATED_2)) - .withRow("encounter-1", makeEid(1, 0), null) - .withRow("encounter-1", makeEid(2, 0), rowFromCoding(TRANSLATED_1)) - // TC-2 - .withRow("encounter-2", makeEid(0, 0), null) - .withRow("encounter-2", makeEid(1, 0), null) - // TC-3 - .withRow("encounter-3", makeEid(0, 0), rowFromCoding(TRANSLATED_1)) - .withRow("encounter-3", makeEid(0, 1), rowFromCoding(TRANSLATED_2)) - // TC-4 - .withRow("encounter-4", makeEid(0, 0), null) - // TC-5 - .withRow("encounter-5", null, null) - .buildWithStructValue(); - - // Check the result. - assertThat(result) - .hasExpression( - "Encounter.class.translate('" + CONCEPT_MAP1_URI + "')") - .isElementPath(CodingCollection.class) - .hasFhirType(FHIRDefinedType.CODING) - .isNotSingular() - .selectOrderedResultWithEid() - .hasRows(expectedResult); - - // Verify mocks - Stream.of(CODING_1, CODING_2, CODING_3, CODING_5).forEach(coding -> - verify(terminologyService, atLeastOnce()) - .translate(deepEq(coding), eq(CONCEPT_MAP1_URI), eq(false), isNull()) - ); - verifyNoMoreInteractions(terminologyService); - } - - @Test - void translateCodeableConceptWithNonDefaultArguments() { - - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Encounter", "type"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - - // The translations are - // { - // coding1 -> [translated1], - // coding2 -> [translated1, translated2] - // coding4 -> [translated2] - // } - - // Use cases: - // 1. [ {C2,C3,C1}, {C3}, {C4} ] -> [ [T1, T2],[], [T2]] - // 2. [ {C3, C5}, {C3} ] -> [ [], [] ] - // 3. [ {C2} ] -> [[T1, T2]] - // 4. [ {C3}] -> [[]] - // 5. [ ]-> [] - // 6. null -> null - - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(codeableConceptStructType()) - // TC-1 - .withRow("encounter-1", makeEid(0), - rowFromCodeableConcept( - new CodeableConcept(CODING_2).addCoding(CODING_3).addCoding(CODING_1))) - .withRow("encounter-1", makeEid(1), - rowFromCodeableConcept( - new CodeableConcept(CODING_3).addCoding(CODING_5))) - .withRow("encounter-1", makeEid(2), - rowFromCodeableConcept( - new CodeableConcept(CODING_4))) - // TC-2 - .withRow("encounter-2", makeEid(0), - rowFromCodeableConcept(new CodeableConcept(CODING_3).addCoding(CODING_5))) - .withRow("encounter-2", makeEid(1), - rowFromCodeableConcept(new CodeableConcept(CODING_3))) - // TC-3 - .withRow("encounter-3", makeEid(0), rowFromCodeableConcept(new CodeableConcept(CODING_2))) - // TC-4 - .withRow("encounter-4", makeEid(0), rowFromCodeableConcept(new CodeableConcept(CODING_3))) - // TC-5 - .withRow("encounter-5", makeEid(0), null) - .withRow("encounter-6", null, null) - .buildWithStructValue(); - - final PrimitivePath inputExpression = new ElementPathBuilder(spark) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("Encounter.type") - .singular(false) - .definition(definition) - .buildDefined(); - - TerminologyServiceHelpers.setupTranslate(terminologyService) - .withTranslations(CODING_1, CONCEPT_MAP2_URI, true, - Translation.of(EQUIVALENT, TRANSLATED_1)) - .withTranslations(CODING_2, CONCEPT_MAP2_URI, true, - Translation.of(EQUIVALENT, TRANSLATED_1), - Translation.of(EQUIVALENT, TRANSLATED_2) - ).withTranslations(CODING_4, CONCEPT_MAP2_URI, true, - Translation.of(NARROWER, TRANSLATED_2) - ); - - // Prepare the inputs to the function. - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .idColumn(inputExpression.getIdColumn()) - .terminologyClientFactory(terminologyServiceFactory) - .build(); - - final StringLiteralPath conceptMapUrlArgument = StringCollection - .fromLiteral("'" + CONCEPT_MAP2_URI + "'", inputExpression); - - final BooleanLiteralPath reverseArgument = BooleanLiteralPath - .fromString("true", inputExpression); - - final StringLiteralPath equivalenceArgument = StringCollection - .fromLiteral("narrower,equivalent", inputExpression); - - final NamedFunctionInput translateInput = new NamedFunctionInput(parserContext, inputExpression, - Arrays.asList(conceptMapUrlArgument, reverseArgument, equivalenceArgument)); - // Invoke the function. - final Collection result = new TranslateFunction().invoke(translateInput); - - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(codingStructType()) - // TC-1 - .withRow("encounter-1", makeEid(0, 0), rowFromCoding(TRANSLATED_1)) - .withRow("encounter-1", makeEid(0, 1), rowFromCoding(TRANSLATED_2)) - .withRow("encounter-1", makeEid(1, 0), null) - .withRow("encounter-1", makeEid(2, 0), rowFromCoding(TRANSLATED_2)) - // TC-2 - .withRow("encounter-2", makeEid(0, 0), null) - .withRow("encounter-2", makeEid(1, 0), null) - // TC-3 - .withRow("encounter-3", makeEid(0, 0), rowFromCoding(TRANSLATED_1)) - .withRow("encounter-3", makeEid(0, 1), rowFromCoding(TRANSLATED_2)) - // TC-4 - .withRow("encounter-4", makeEid(0, 0), null) - // TC-5 - .withRow("encounter-5", makeEid(0, 0), null) - .withRow("encounter-6", null, null) - .buildWithStructValue(); - - // Check the result. - assertThat(result) - .hasExpression( - "Encounter.type.translate('" + CONCEPT_MAP2_URI + "', true, 'narrower,equivalent')") - .isElementPath(CodingCollection.class) - .hasFhirType(FHIRDefinedType.CODING) - .isNotSingular() - .selectOrderedResultWithEid() - .hasRows(expectedResult); - - // Verify mocks - Stream.of(CODING_1, CODING_2, CODING_3, CODING_4, CODING_5).forEach(coding -> - verify(terminologyService, atLeastOnce()) - .translate(deepEq(coding), eq(CONCEPT_MAP2_URI), eq(true), isNull()) - ); - verifyNoMoreInteractions(terminologyService); - } - - - @Test - void throwsErrorIfInputTypeIsUnsupported() { - final Collection mockContext = new ElementPathBuilder(spark).build(); - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .expression("name.given") - .build(); - final Collection argument = StringCollection.fromLiteral(SOURCE_SYSTEM_URI, mockContext); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(mock(TerminologyServiceFactory.class)) - .build(); - - final NamedFunctionInput translateInput = new NamedFunctionInput(parserContext, input, - Collections.singletonList(argument)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new TranslateFunction().invoke(translateInput)); - assertEquals("Input to translate function is of unsupported type: name.given", - error.getMessage()); - } - - - void assertThrowsErrorForArguments(@Nonnull final String expectedError, - @Nonnull final Function> argsFactory) { - - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Encounter", "class"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .definition(definition) - .buildDefined(); - - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(mock(TerminologyServiceFactory.class)) - .build(); - - final NamedFunctionInput translateInput = new NamedFunctionInput(context, input, - argsFactory.apply(input)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new TranslateFunction().invoke(translateInput)); - assertEquals(expectedError, - error.getMessage()); - - } - - @Test - void throwsErrorIfNoArguments() { - assertThrowsErrorForArguments( - "translate function accepts one required and two optional arguments", - input -> Collections.emptyList()); - } - - @Test - void throwsErrorIfFirstArgumentIsNotString() { - assertThrowsErrorForArguments("Function `translate` expects `String literal` as argument 1", - input -> Collections.singletonList( - IntegerLiteralPath.fromString("4", input))); - } - - @Test - void throwsErrorIfSecondArgumentIsNotBoolean() { - assertThrowsErrorForArguments("Function `translate` expects `Boolean literal` as argument 2", - input -> Arrays.asList( - StringCollection.fromLiteral("'foo'", input), - StringCollection.fromLiteral("'bar'", input))); - } - - - @Test - void throwsErrorIfThirdArgumentIsNotString() { - assertThrowsErrorForArguments("Function `translate` expects `String literal` as argument 3", - input -> Arrays.asList( - StringCollection.fromLiteral("'foo'", input), - BooleanLiteralPath.fromString("true", input), - BooleanLiteralPath.fromString("false", input))); - } - - - @Test - void throwsErrorIfTooManyArguments() { - assertThrowsErrorForArguments( - "translate function accepts one required and two optional arguments", - input -> Arrays.asList( - StringCollection.fromLiteral("'foo'", input), - BooleanLiteralPath.fromString("true", input), - StringCollection.fromLiteral("'false'", input), - StringCollection.fromLiteral("'false'", input) - )); - } - - @Test - void throwsErrorIfCannotParseEquivalences() { - assertThrowsErrorForArguments( - "Unknown ConceptMapEquivalence code 'not-an-equivalence'", - input -> Arrays.asList( - StringCollection.fromLiteral("'foo'", input), - BooleanLiteralPath.fromString("true", input), - StringCollection.fromLiteral("'not-an-equivalence'", input) - )); - } - - @Test - void throwsErrorIfTerminologyServiceNotConfigured() { - final PrimitivePath input = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .build(); - final Collection argument = StringCollection.fromLiteral("some string", input); - - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .build(); - - final NamedFunctionInput translateInput = new NamedFunctionInput(context, input, - Collections.singletonList(argument)); - - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - () -> new TranslateFunction().invoke(translateInput)); - assertEquals( - "Attempt to call terminology function translate when terminology service has not been configured", - error.getMessage()); - } + // TODO: implement with columns + + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // static final String SOURCE_SYSTEM_URI = "uuid:source"; + // static final String DEST_SYSTEM_URI = "uuid:dest"; + // + // static final String CONCEPT_MAP1_URI = "http://snomed.info/sct?fhir_cm=100"; + // static final String CONCEPT_MAP2_URI = "http://snomed.info/sct?fhir_cm=200"; + // + // + // static final Coding CODING_1 = new Coding(SOURCE_SYSTEM_URI, "AMB", "ambulatory"); + // static final Coding CODING_2 = new Coding(SOURCE_SYSTEM_URI, "EMER", null); + // static final Coding CODING_3 = new Coding(SOURCE_SYSTEM_URI, "IMP", + // "inpatient encounter"); + // static final Coding CODING_4 = new Coding(SOURCE_SYSTEM_URI, "OTHER", null); + // static final Coding CODING_5 = new Coding(SOURCE_SYSTEM_URI, "ACUTE", "inpatient acute"); + // + // static final Coding TRANSLATED_1 = new Coding(DEST_SYSTEM_URI, "TEST1", "Test1"); + // static final Coding TRANSLATED_2 = new Coding(DEST_SYSTEM_URI, "TEST2", "Test2"); + // + // + // @Autowired + // TerminologyServiceFactory terminologyServiceFactory; + // + // @Autowired + // TerminologyService terminologyService; + // + // @BeforeEach + // void setUp() { + // reset(terminologyService); + // } + // + // @Test + // void translateCodingWithDefaultArguments() { + // + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Encounter", "class"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // + // // The translations are + // // { + // // coding1 -> [translated1], + // // coding2 -> [translated1, translated2] + // // } + // + // // Use cases: + // // 1. [ C2,C3,C1 ] -> [ [T1, T2],[], [T1]] + // // 2. [ C3, C5 ] -> [[],[]] + // // 3. [ C2 ] -> [ [T1, T2] ] + // // 4. [] -> [] + // // 5. null -> null + // + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(codingStructType()) + // // TC-1 + // .withRow("encounter-1", makeEid(0), rowFromCoding(CODING_2)) + // .withRow("encounter-1", makeEid(1), rowFromCoding(CODING_3)) + // .withRow("encounter-1", makeEid(2), rowFromCoding(CODING_1)) + // // TC-2 + // .withRow("encounter-2", makeEid(0), rowFromCoding(CODING_3)) + // .withRow("encounter-2", makeEid(1), rowFromCoding(CODING_5)) + // // TC-3 + // .withRow("encounter-3", makeEid(0), rowFromCoding(CODING_2)) + // // TC-4 + // .withRow("encounter-4", makeEid(0), null) + // // TC-5 + // .withRow("encounter-5", null, null) + // .buildWithStructValue(); + // + // final CodingCollection inputExpression = (CodingCollection) new ElementPathBuilder(spark) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("Encounter.class") + // .singular(false) + // .definition(definition) + // .buildDefined(); + // + // TerminologyServiceHelpers.setupTranslate(terminologyService) + // .withTranslations(CODING_1, CONCEPT_MAP1_URI, + // Translation.of(EQUIVALENT, TRANSLATED_1)) + // .withTranslations(CODING_2, CONCEPT_MAP1_URI, + // Translation.of(EQUIVALENT, TRANSLATED_1), + // Translation.of(EQUIVALENT, TRANSLATED_2) + // ); + // + // // Prepare the inputs to the function. + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .idColumn(inputExpression.getIdColumn()) + // .terminologyClientFactory(terminologyServiceFactory) + // .build(); + // + // final StringLiteralPath conceptMapUrlArgument = StringCollection + // .fromLiteral("'" + CONCEPT_MAP1_URI + "'", inputExpression); + // + // final NamedFunctionInput translateInput = new NamedFunctionInput(parserContext, inputExpression, + // Collections.singletonList(conceptMapUrlArgument)); + // // Invoke the function. + // final Collection result = new TranslateFunction().invoke(translateInput); + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(codingStructType()) + // // TC-1 + // .withRow("encounter-1", makeEid(0, 0), rowFromCoding(TRANSLATED_1)) + // .withRow("encounter-1", makeEid(0, 1), rowFromCoding(TRANSLATED_2)) + // .withRow("encounter-1", makeEid(1, 0), null) + // .withRow("encounter-1", makeEid(2, 0), rowFromCoding(TRANSLATED_1)) + // // TC-2 + // .withRow("encounter-2", makeEid(0, 0), null) + // .withRow("encounter-2", makeEid(1, 0), null) + // // TC-3 + // .withRow("encounter-3", makeEid(0, 0), rowFromCoding(TRANSLATED_1)) + // .withRow("encounter-3", makeEid(0, 1), rowFromCoding(TRANSLATED_2)) + // // TC-4 + // .withRow("encounter-4", makeEid(0, 0), null) + // // TC-5 + // .withRow("encounter-5", null, null) + // .buildWithStructValue(); + // + // // Check the result. + // assertThat(result) + // .hasExpression( + // "Encounter.class.translate('" + CONCEPT_MAP1_URI + "')") + // .isElementPath(CodingCollection.class) + // .hasFhirType(FHIRDefinedType.CODING) + // .isNotSingular() + // .selectOrderedResultWithEid() + // .hasRows(expectedResult); + // + // // Verify mocks + // Stream.of(CODING_1, CODING_2, CODING_3, CODING_5).forEach(coding -> + // verify(terminologyService, atLeastOnce()) + // .translate(deepEq(coding), eq(CONCEPT_MAP1_URI), eq(false), isNull()) + // ); + // verifyNoMoreInteractions(terminologyService); + // } + // + // @Test + // void translateCodeableConceptWithNonDefaultArguments() { + // + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Encounter", "type"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // + // // The translations are + // // { + // // coding1 -> [translated1], + // // coding2 -> [translated1, translated2] + // // coding4 -> [translated2] + // // } + // + // // Use cases: + // // 1. [ {C2,C3,C1}, {C3}, {C4} ] -> [ [T1, T2],[], [T2]] + // // 2. [ {C3, C5}, {C3} ] -> [ [], [] ] + // // 3. [ {C2} ] -> [[T1, T2]] + // // 4. [ {C3}] -> [[]] + // // 5. [ ]-> [] + // // 6. null -> null + // + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(codeableConceptStructType()) + // // TC-1 + // .withRow("encounter-1", makeEid(0), + // rowFromCodeableConcept( + // new CodeableConcept(CODING_2).addCoding(CODING_3).addCoding(CODING_1))) + // .withRow("encounter-1", makeEid(1), + // rowFromCodeableConcept( + // new CodeableConcept(CODING_3).addCoding(CODING_5))) + // .withRow("encounter-1", makeEid(2), + // rowFromCodeableConcept( + // new CodeableConcept(CODING_4))) + // // TC-2 + // .withRow("encounter-2", makeEid(0), + // rowFromCodeableConcept(new CodeableConcept(CODING_3).addCoding(CODING_5))) + // .withRow("encounter-2", makeEid(1), + // rowFromCodeableConcept(new CodeableConcept(CODING_3))) + // // TC-3 + // .withRow("encounter-3", makeEid(0), rowFromCodeableConcept(new CodeableConcept(CODING_2))) + // // TC-4 + // .withRow("encounter-4", makeEid(0), rowFromCodeableConcept(new CodeableConcept(CODING_3))) + // // TC-5 + // .withRow("encounter-5", makeEid(0), null) + // .withRow("encounter-6", null, null) + // .buildWithStructValue(); + // + // final PrimitivePath inputExpression = new ElementPathBuilder(spark) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("Encounter.type") + // .singular(false) + // .definition(definition) + // .buildDefined(); + // + // TerminologyServiceHelpers.setupTranslate(terminologyService) + // .withTranslations(CODING_1, CONCEPT_MAP2_URI, true, + // Translation.of(EQUIVALENT, TRANSLATED_1)) + // .withTranslations(CODING_2, CONCEPT_MAP2_URI, true, + // Translation.of(EQUIVALENT, TRANSLATED_1), + // Translation.of(EQUIVALENT, TRANSLATED_2) + // ).withTranslations(CODING_4, CONCEPT_MAP2_URI, true, + // Translation.of(NARROWER, TRANSLATED_2) + // ); + // + // // Prepare the inputs to the function. + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .idColumn(inputExpression.getIdColumn()) + // .terminologyClientFactory(terminologyServiceFactory) + // .build(); + // + // final StringLiteralPath conceptMapUrlArgument = StringCollection + // .fromLiteral("'" + CONCEPT_MAP2_URI + "'", inputExpression); + // + // final BooleanLiteralPath reverseArgument = BooleanLiteralPath + // .fromString("true", inputExpression); + // + // final StringLiteralPath equivalenceArgument = StringCollection + // .fromLiteral("narrower,equivalent", inputExpression); + // + // final NamedFunctionInput translateInput = new NamedFunctionInput(parserContext, inputExpression, + // Arrays.asList(conceptMapUrlArgument, reverseArgument, equivalenceArgument)); + // // Invoke the function. + // final Collection result = new TranslateFunction().invoke(translateInput); + // + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(codingStructType()) + // // TC-1 + // .withRow("encounter-1", makeEid(0, 0), rowFromCoding(TRANSLATED_1)) + // .withRow("encounter-1", makeEid(0, 1), rowFromCoding(TRANSLATED_2)) + // .withRow("encounter-1", makeEid(1, 0), null) + // .withRow("encounter-1", makeEid(2, 0), rowFromCoding(TRANSLATED_2)) + // // TC-2 + // .withRow("encounter-2", makeEid(0, 0), null) + // .withRow("encounter-2", makeEid(1, 0), null) + // // TC-3 + // .withRow("encounter-3", makeEid(0, 0), rowFromCoding(TRANSLATED_1)) + // .withRow("encounter-3", makeEid(0, 1), rowFromCoding(TRANSLATED_2)) + // // TC-4 + // .withRow("encounter-4", makeEid(0, 0), null) + // // TC-5 + // .withRow("encounter-5", makeEid(0, 0), null) + // .withRow("encounter-6", null, null) + // .buildWithStructValue(); + // + // // Check the result. + // assertThat(result) + // .hasExpression( + // "Encounter.type.translate('" + CONCEPT_MAP2_URI + "', true, 'narrower,equivalent')") + // .isElementPath(CodingCollection.class) + // .hasFhirType(FHIRDefinedType.CODING) + // .isNotSingular() + // .selectOrderedResultWithEid() + // .hasRows(expectedResult); + // + // // Verify mocks + // Stream.of(CODING_1, CODING_2, CODING_3, CODING_4, CODING_5).forEach(coding -> + // verify(terminologyService, atLeastOnce()) + // .translate(deepEq(coding), eq(CONCEPT_MAP2_URI), eq(true), isNull()) + // ); + // verifyNoMoreInteractions(terminologyService); + // } + // + // + // @Test + // void throwsErrorIfInputTypeIsUnsupported() { + // final Collection mockContext = new ElementPathBuilder(spark).build(); + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .expression("name.given") + // .build(); + // final Collection argument = StringCollection.fromLiteral(SOURCE_SYSTEM_URI, mockContext); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .terminologyClientFactory(mock(TerminologyServiceFactory.class)) + // .build(); + // + // final NamedFunctionInput translateInput = new NamedFunctionInput(parserContext, input, + // Collections.singletonList(argument)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new TranslateFunction().invoke(translateInput)); + // assertEquals("Input to translate function is of unsupported type: name.given", + // error.getMessage()); + // } + // + // + // void assertThrowsErrorForArguments(@Nonnull final String expectedError, + // @Nonnull final Function> argsFactory) { + // + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Encounter", "class"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .definition(definition) + // .buildDefined(); + // + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .terminologyClientFactory(mock(TerminologyServiceFactory.class)) + // .build(); + // + // final NamedFunctionInput translateInput = new NamedFunctionInput(context, input, + // argsFactory.apply(input)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new TranslateFunction().invoke(translateInput)); + // assertEquals(expectedError, + // error.getMessage()); + // + // } + // + // @Test + // void throwsErrorIfNoArguments() { + // assertThrowsErrorForArguments( + // "translate function accepts one required and two optional arguments", + // input -> Collections.emptyList()); + // } + // + // @Test + // void throwsErrorIfFirstArgumentIsNotString() { + // assertThrowsErrorForArguments("Function `translate` expects `String literal` as argument 1", + // input -> Collections.singletonList( + // IntegerLiteralPath.fromString("4", input))); + // } + // + // @Test + // void throwsErrorIfSecondArgumentIsNotBoolean() { + // assertThrowsErrorForArguments("Function `translate` expects `Boolean literal` as argument 2", + // input -> Arrays.asList( + // StringCollection.fromLiteral("'foo'", input), + // StringCollection.fromLiteral("'bar'", input))); + // } + // + // + // @Test + // void throwsErrorIfThirdArgumentIsNotString() { + // assertThrowsErrorForArguments("Function `translate` expects `String literal` as argument 3", + // input -> Arrays.asList( + // StringCollection.fromLiteral("'foo'", input), + // BooleanLiteralPath.fromString("true", input), + // BooleanLiteralPath.fromString("false", input))); + // } + // + // + // @Test + // void throwsErrorIfTooManyArguments() { + // assertThrowsErrorForArguments( + // "translate function accepts one required and two optional arguments", + // input -> Arrays.asList( + // StringCollection.fromLiteral("'foo'", input), + // BooleanLiteralPath.fromString("true", input), + // StringCollection.fromLiteral("'false'", input), + // StringCollection.fromLiteral("'false'", input) + // )); + // } + // + // @Test + // void throwsErrorIfCannotParseEquivalences() { + // assertThrowsErrorForArguments( + // "Unknown ConceptMapEquivalence code 'not-an-equivalence'", + // input -> Arrays.asList( + // StringCollection.fromLiteral("'foo'", input), + // BooleanLiteralPath.fromString("true", input), + // StringCollection.fromLiteral("'not-an-equivalence'", input) + // )); + // } + // + // @Test + // void throwsErrorIfTerminologyServiceNotConfigured() { + // final PrimitivePath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .build(); + // final Collection argument = StringCollection.fromLiteral("some string", input); + // + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .build(); + // + // final NamedFunctionInput translateInput = new NamedFunctionInput(context, input, + // Collections.singletonList(argument)); + // + // final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, + // () -> new TranslateFunction().invoke(translateInput)); + // assertEquals( + // "Attempt to call terminology function translate when terminology service has not been configured", + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPathTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPathTest.java index dceceb465c..92eeb7d9cd 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPathTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/literal/CodingLiteralPathTest.java @@ -17,156 +17,142 @@ package au.csiro.pathling.fhirpath.literal; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.collection.CodingCollection; -import au.csiro.pathling.io.source.DataSource; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.ResourceDatasetBuilder; -import au.csiro.pathling.test.builders.ResourcePathBuilder; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class CodingLiteralPathTest { - @Autowired - SparkSession spark; - - ResourceCollection inputContext; - - @BeforeEach - void setUp() { - final DataSource dataSource = mock(DataSource.class); - final Dataset inputContextDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withRow("observation-1") - .withRow("observation-2") - .withRow("observation-3") - .withRow("observation-4") - .withRow("observation-5") - .build(); - when(dataSource.read(ResourceType.OBSERVATION)).thenReturn(inputContextDataset); - inputContext = new ResourcePathBuilder(spark) - .expression("Observation") - .resourceType(ResourceType.OBSERVATION) - .database(dataSource) - .singular(true) - .build(); - } - - @Test - void roundTrip() { - final String expression = "http://snomed.info/sct|166056000|http://snomed.info/sct/32506021000036107/version/20201231"; - final CodingLiteralPath codingLiteralPath = CodingCollection.fromLiteral( - expression, - inputContext); - final Coding literalValue = codingLiteralPath.getValue(); - assertEquals("http://snomed.info/sct", literalValue.getSystem()); - assertEquals("http://snomed.info/sct/32506021000036107/version/20201231", - literalValue.getVersion()); - assertEquals("166056000", literalValue.getCode()); - - final String actualExpression = codingLiteralPath.getExpression(); - assertEquals(expression, actualExpression); - } - - @Test - void roundTripNoVersion() { - final String expression = "http://snomed.info/sct|166056000"; - final CodingLiteralPath codingLiteralPath = CodingCollection - .fromLiteral(expression, inputContext); - final Coding literalValue = codingLiteralPath.getValue(); - assertEquals("http://snomed.info/sct", literalValue.getSystem()); - assertNull(literalValue.getVersion()); - assertEquals("166056000", literalValue.getCode()); - - final String actualExpression = codingLiteralPath.getExpression(); - assertEquals(expression, actualExpression); - } - - @Test - void roundTripWithQuotedComponent() { - final String expression = - "http://snomed.info/sct" - + "|'397956004 |Prosthetic arthroplasty of the hip|: 363704007 |Procedure site| = " - + "( 24136001 |Hip joint structure|: 272741003 |Laterality| = 7771000 |Left| )'" - + "|http://snomed.info/sct/32506021000036107/version/20201231"; - final CodingLiteralPath codingLiteralPath = CodingCollection - .fromLiteral(expression, inputContext); - final Coding literalValue = codingLiteralPath.getValue(); - assertEquals("http://snomed.info/sct", literalValue.getSystem()); - assertEquals("http://snomed.info/sct/32506021000036107/version/20201231", - literalValue.getVersion()); - assertEquals( - "397956004 |Prosthetic arthroplasty of the hip|: 363704007 |Procedure site| = " - + "( 24136001 |Hip joint structure|: 272741003 |Laterality| = 7771000 |Left| )", - literalValue.getCode()); - - final String actualExpression = codingLiteralPath.getExpression(); - assertEquals(expression, actualExpression); - } - - @Test - void roundTripWithQuotedComponentWithComma() { - final String expression = - "http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231"; - final CodingLiteralPath codingLiteralPath = CodingCollection - .fromLiteral(expression, inputContext); - final Coding literalValue = codingLiteralPath.getValue(); - assertEquals("http://snomed.info/sct", literalValue.getSystem()); - assertEquals("http://snomed.info/sct/32506021000036107/version/20201231", - literalValue.getVersion()); - assertEquals("46,2", literalValue.getCode()); - - final String actualExpression = codingLiteralPath.getExpression(); - assertEquals(expression, actualExpression); - } - - @Test - void roundTripWithQuotedComponentWithSingleQuote() { - final String expression = "'Someone\\'s CodeSystem'|166056000"; - final CodingLiteralPath codingLiteralPath = CodingCollection - .fromLiteral(expression, inputContext); - final Coding literalValue = codingLiteralPath.getValue(); - assertEquals("Someone's CodeSystem", literalValue.getSystem()); - assertEquals("166056000", literalValue.getCode()); - - final String actualExpression = codingLiteralPath.getExpression(); - assertEquals(expression, actualExpression); - } - - @Test - void roundTripWithQuotedComponentWithSpace() { - final String expression = "'Some CodeSystem'|166056000"; - final CodingLiteralPath codingLiteralPath = CodingCollection - .fromLiteral(expression, inputContext); - final Coding literalValue = codingLiteralPath.getValue(); - assertEquals("Some CodeSystem", literalValue.getSystem()); - assertEquals("166056000", literalValue.getCode()); - - final String actualExpression = codingLiteralPath.getExpression(); - assertEquals(expression, actualExpression); - } - - @Test - void fromMalformedString() { - assertThrows(IllegalArgumentException.class, () -> CodingCollection.fromLiteral( - "http://snomed.info/sct", inputContext)); - } - + // TODO: implement with columns + // + // + // @Autowired + // SparkSession spark; + // + // ResourceCollection inputContext; + // + // @BeforeEach + // void setUp() { + // final DataSource dataSource = mock(DataSource.class); + // final Dataset inputContextDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withRow("observation-1") + // .withRow("observation-2") + // .withRow("observation-3") + // .withRow("observation-4") + // .withRow("observation-5") + // .build(); + // when(dataSource.read(ResourceType.OBSERVATION)).thenReturn(inputContextDataset); + // inputContext = new ResourcePathBuilder(spark) + // .expression("Observation") + // .resourceType(ResourceType.OBSERVATION) + // .database(dataSource) + // .singular(true) + // .build(); + // } + // + // @Test + // void roundTrip() { + // final String expression = "http://snomed.info/sct|166056000|http://snomed.info/sct/32506021000036107/version/20201231"; + // final CodingLiteralPath codingLiteralPath = CodingCollection.fromLiteral( + // expression, + // inputContext); + // final Coding literalValue = codingLiteralPath.getValue(); + // assertEquals("http://snomed.info/sct", literalValue.getSystem()); + // assertEquals("http://snomed.info/sct/32506021000036107/version/20201231", + // literalValue.getVersion()); + // assertEquals("166056000", literalValue.getCode()); + // + // final String actualExpression = codingLiteralPath.getExpression(); + // assertEquals(expression, actualExpression); + // } + // + // @Test + // void roundTripNoVersion() { + // final String expression = "http://snomed.info/sct|166056000"; + // final CodingLiteralPath codingLiteralPath = CodingCollection + // .fromLiteral(expression, inputContext); + // final Coding literalValue = codingLiteralPath.getValue(); + // assertEquals("http://snomed.info/sct", literalValue.getSystem()); + // assertNull(literalValue.getVersion()); + // assertEquals("166056000", literalValue.getCode()); + // + // final String actualExpression = codingLiteralPath.getExpression(); + // assertEquals(expression, actualExpression); + // } + // + // @Test + // void roundTripWithQuotedComponent() { + // final String expression = + // "http://snomed.info/sct" + // + "|'397956004 |Prosthetic arthroplasty of the hip|: 363704007 |Procedure site| = " + // + "( 24136001 |Hip joint structure|: 272741003 |Laterality| = 7771000 |Left| )'" + // + "|http://snomed.info/sct/32506021000036107/version/20201231"; + // final CodingLiteralPath codingLiteralPath = CodingCollection + // .fromLiteral(expression, inputContext); + // final Coding literalValue = codingLiteralPath.getValue(); + // assertEquals("http://snomed.info/sct", literalValue.getSystem()); + // assertEquals("http://snomed.info/sct/32506021000036107/version/20201231", + // literalValue.getVersion()); + // assertEquals( + // "397956004 |Prosthetic arthroplasty of the hip|: 363704007 |Procedure site| = " + // + "( 24136001 |Hip joint structure|: 272741003 |Laterality| = 7771000 |Left| )", + // literalValue.getCode()); + // + // final String actualExpression = codingLiteralPath.getExpression(); + // assertEquals(expression, actualExpression); + // } + // + // @Test + // void roundTripWithQuotedComponentWithComma() { + // final String expression = + // "http://snomed.info/sct|'46,2'|http://snomed.info/sct/32506021000036107/version/20201231"; + // final CodingLiteralPath codingLiteralPath = CodingCollection + // .fromLiteral(expression, inputContext); + // final Coding literalValue = codingLiteralPath.getValue(); + // assertEquals("http://snomed.info/sct", literalValue.getSystem()); + // assertEquals("http://snomed.info/sct/32506021000036107/version/20201231", + // literalValue.getVersion()); + // assertEquals("46,2", literalValue.getCode()); + // + // final String actualExpression = codingLiteralPath.getExpression(); + // assertEquals(expression, actualExpression); + // } + // + // @Test + // void roundTripWithQuotedComponentWithSingleQuote() { + // final String expression = "'Someone\\'s CodeSystem'|166056000"; + // final CodingLiteralPath codingLiteralPath = CodingCollection + // .fromLiteral(expression, inputContext); + // final Coding literalValue = codingLiteralPath.getValue(); + // assertEquals("Someone's CodeSystem", literalValue.getSystem()); + // assertEquals("166056000", literalValue.getCode()); + // + // final String actualExpression = codingLiteralPath.getExpression(); + // assertEquals(expression, actualExpression); + // } + // + // @Test + // void roundTripWithQuotedComponentWithSpace() { + // final String expression = "'Some CodeSystem'|166056000"; + // final CodingLiteralPath codingLiteralPath = CodingCollection + // .fromLiteral(expression, inputContext); + // final Coding literalValue = codingLiteralPath.getValue(); + // assertEquals("Some CodeSystem", literalValue.getSystem()); + // assertEquals("166056000", literalValue.getCode()); + // + // final String actualExpression = codingLiteralPath.getExpression(); + // assertEquals(expression, actualExpression); + // } + // + // @Test + // void fromMalformedString() { + // assertThrows(IllegalArgumentException.class, () -> CodingCollection.fromLiteral( + // "http://snomed.info/sct", inputContext)); + // } + // } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorTest.java index ccfca12de9..57a7428f9b 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorTest.java @@ -17,205 +17,191 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.DatasetBuilder; -import au.csiro.pathling.test.builders.ElementPathBuilder; -import au.csiro.pathling.test.builders.ParserContextBuilder; -import ca.uhn.fhir.context.FhirContext; -import java.util.Collections; -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.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class BooleanOperatorTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - static final String ID_ALIAS = "_abc123"; - - Collection left; - Collection right; - ParserContext parserContext; - - @BeforeEach - void setUp() { - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.BooleanType) - .withRow("patient-1", true) - .withRow("patient-2", true) - .withRow("patient-3", false) - .withRow("patient-4", false) - .withRow("patient-5", null) - .withRow("patient-6", null) - .withRow("patient-7", true) - .withRow("patient-8", null) - .build(); - left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .dataset(leftDataset) - .idAndValueColumns() - .singular(true) - .expression("estimatedAge") - .build(); - - final Dataset rightDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.BooleanType) - .withRow("patient-1", false) - .withRow("patient-2", null) - .withRow("patient-3", true) - .withRow("patient-4", null) - .withRow("patient-5", true) - .withRow("patient-6", false) - .withRow("patient-7", true) - .withRow("patient-8", null) - .build(); - right = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .dataset(rightDataset) - .idAndValueColumns() - .singular(true) - .expression("deceasedBoolean") - .build(); - - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(left.getIdColumn())) - .build(); - } - - @Test - void and() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - - final BinaryOperator booleanOperator = BinaryOperator.getInstance("and"); - final Collection result = booleanOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", false), - RowFactory.create("patient-2", null), - RowFactory.create("patient-3", false), - RowFactory.create("patient-4", false), - RowFactory.create("patient-5", null), - RowFactory.create("patient-6", false), - RowFactory.create("patient-7", true), - RowFactory.create("patient-8", null) - ); - } - - @Test - void or() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - - final BinaryOperator booleanOperator = BinaryOperator.getInstance("or"); - final Collection result = booleanOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", true), - RowFactory.create("patient-2", true), - RowFactory.create("patient-3", true), - RowFactory.create("patient-4", null), - RowFactory.create("patient-5", true), - RowFactory.create("patient-6", null), - RowFactory.create("patient-7", true), - RowFactory.create("patient-8", null) - ); - } - - @Test - void xor() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - - final BinaryOperator booleanOperator = BinaryOperator.getInstance("xor"); - final Collection result = booleanOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", true), - RowFactory.create("patient-2", null), - RowFactory.create("patient-3", true), - RowFactory.create("patient-4", null), - RowFactory.create("patient-5", null), - RowFactory.create("patient-6", null), - RowFactory.create("patient-7", false), - RowFactory.create("patient-8", null) - ); - } - - @Test - void implies() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - - final BinaryOperator booleanOperator = BinaryOperator.getInstance("implies"); - final Collection result = booleanOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", false), - RowFactory.create("patient-2", null), - RowFactory.create("patient-3", true), - RowFactory.create("patient-4", true), - RowFactory.create("patient-5", true), - RowFactory.create("patient-6", null), - RowFactory.create("patient-7", true), - RowFactory.create("patient-8", null) - ); - } - - @Test - void leftIsLiteral() { - final Collection literalLeft = BooleanLiteralPath.fromString("true", left); - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, literalLeft, right); - - final BinaryOperator booleanOperator = BinaryOperator.getInstance("and"); - final Collection result = booleanOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", false), - RowFactory.create("patient-2", null), - RowFactory.create("patient-3", true), - RowFactory.create("patient-4", null), - RowFactory.create("patient-5", true), - RowFactory.create("patient-6", false), - RowFactory.create("patient-7", true), - RowFactory.create("patient-8", null) - ); - } - - @Test - void rightIsLiteral() { - final Collection literalRight = BooleanLiteralPath.fromString("true", right); - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, literalRight); - - final BinaryOperator booleanOperator = BinaryOperator.getInstance("and"); - final Collection result = booleanOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", true), - RowFactory.create("patient-2", true), - RowFactory.create("patient-3", false), - RowFactory.create("patient-4", false), - RowFactory.create("patient-5", null), - RowFactory.create("patient-6", null), - RowFactory.create("patient-7", true), - RowFactory.create("patient-8", null) - ); - } + // TODO: implement with columns + // + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // static final String ID_ALIAS = "_abc123"; + // + // Collection left; + // Collection right; + // ParserContext parserContext; + // + // @BeforeEach + // void setUp() { + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.BooleanType) + // .withRow("patient-1", true) + // .withRow("patient-2", true) + // .withRow("patient-3", false) + // .withRow("patient-4", false) + // .withRow("patient-5", null) + // .withRow("patient-6", null) + // .withRow("patient-7", true) + // .withRow("patient-8", null) + // .build(); + // left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .dataset(leftDataset) + // .idAndValueColumns() + // .singular(true) + // .expression("estimatedAge") + // .build(); + // + // final Dataset rightDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.BooleanType) + // .withRow("patient-1", false) + // .withRow("patient-2", null) + // .withRow("patient-3", true) + // .withRow("patient-4", null) + // .withRow("patient-5", true) + // .withRow("patient-6", false) + // .withRow("patient-7", true) + // .withRow("patient-8", null) + // .build(); + // right = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .dataset(rightDataset) + // .idAndValueColumns() + // .singular(true) + // .expression("deceasedBoolean") + // .build(); + // + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(left.getIdColumn())) + // .build(); + // } + // + // @Test + // void and() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // + // final BinaryOperator booleanOperator = BinaryOperator.getInstance("and"); + // final Collection result = booleanOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", false), + // RowFactory.create("patient-2", null), + // RowFactory.create("patient-3", false), + // RowFactory.create("patient-4", false), + // RowFactory.create("patient-5", null), + // RowFactory.create("patient-6", false), + // RowFactory.create("patient-7", true), + // RowFactory.create("patient-8", null) + // ); + // } + // + // @Test + // void or() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // + // final BinaryOperator booleanOperator = BinaryOperator.getInstance("or"); + // final Collection result = booleanOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", true), + // RowFactory.create("patient-2", true), + // RowFactory.create("patient-3", true), + // RowFactory.create("patient-4", null), + // RowFactory.create("patient-5", true), + // RowFactory.create("patient-6", null), + // RowFactory.create("patient-7", true), + // RowFactory.create("patient-8", null) + // ); + // } + // + // @Test + // void xor() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // + // final BinaryOperator booleanOperator = BinaryOperator.getInstance("xor"); + // final Collection result = booleanOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", true), + // RowFactory.create("patient-2", null), + // RowFactory.create("patient-3", true), + // RowFactory.create("patient-4", null), + // RowFactory.create("patient-5", null), + // RowFactory.create("patient-6", null), + // RowFactory.create("patient-7", false), + // RowFactory.create("patient-8", null) + // ); + // } + // + // @Test + // void implies() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // + // final BinaryOperator booleanOperator = BinaryOperator.getInstance("implies"); + // final Collection result = booleanOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", false), + // RowFactory.create("patient-2", null), + // RowFactory.create("patient-3", true), + // RowFactory.create("patient-4", true), + // RowFactory.create("patient-5", true), + // RowFactory.create("patient-6", null), + // RowFactory.create("patient-7", true), + // RowFactory.create("patient-8", null) + // ); + // } + // + // @Test + // void leftIsLiteral() { + // final Collection literalLeft = BooleanLiteralPath.fromString("true", left); + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, literalLeft, right); + // + // final BinaryOperator booleanOperator = BinaryOperator.getInstance("and"); + // final Collection result = booleanOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", false), + // RowFactory.create("patient-2", null), + // RowFactory.create("patient-3", true), + // RowFactory.create("patient-4", null), + // RowFactory.create("patient-5", true), + // RowFactory.create("patient-6", false), + // RowFactory.create("patient-7", true), + // RowFactory.create("patient-8", null) + // ); + // } + // + // @Test + // void rightIsLiteral() { + // final Collection literalRight = BooleanLiteralPath.fromString("true", right); + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, literalRight); + // + // final BinaryOperator booleanOperator = BinaryOperator.getInstance("and"); + // final Collection result = booleanOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", true), + // RowFactory.create("patient-2", true), + // RowFactory.create("patient-3", false), + // RowFactory.create("patient-4", false), + // RowFactory.create("patient-5", null), + // RowFactory.create("patient-6", null), + // RowFactory.create("patient-7", true), + // RowFactory.create("patient-8", null) + // ); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorValidationTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorValidationTest.java index 5a79015047..7001881ec9 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorValidationTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/BooleanOperatorValidationTest.java @@ -17,105 +17,96 @@ package au.csiro.pathling.fhirpath.operator; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.ElementPathBuilder; -import au.csiro.pathling.test.builders.ParserContextBuilder; -import ca.uhn.fhir.context.FhirContext; -import org.apache.spark.sql.SparkSession; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class BooleanOperatorValidationTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - ParserContext parserContext; - - @BeforeEach - void setUp() { - parserContext = new ParserContextBuilder(spark, fhirContext).build(); - } - - @Test - void operandIsNotSingular() { - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .singular(false) - .expression("estimatedAge") - .build(); - final PrimitivePath right = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .singular(true) - .expression("deceasedBoolean") - .build(); - - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - - final BinaryOperator booleanOperator = BinaryOperator.getInstance("and"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> booleanOperator.invoke(input)); - assertEquals( - "Left operand to and operator must be singular: estimatedAge", - error.getMessage()); - - // Now test the right operand. - final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); - final InvalidUserInputError reversedError = assertThrows( - InvalidUserInputError.class, - () -> booleanOperator.invoke(reversedInput)); - assertEquals( - "Right operand to and operator must be singular: estimatedAge", - reversedError.getMessage()); - } - - @Test - void operandIsNotBoolean() { - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .singular(true) - .expression("estimatedAge") - .build(); - final PrimitivePath right = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .singular(true) - .expression("deceasedBoolean") - .build(); - - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - - final BinaryOperator booleanOperator = BinaryOperator.getInstance("and"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> booleanOperator.invoke(input)); - assertEquals( - "Left operand to and operator must be Boolean: estimatedAge", - error.getMessage()); - - // Now test the right operand. - final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); - final InvalidUserInputError reversedError = assertThrows( - InvalidUserInputError.class, - () -> booleanOperator.invoke(reversedInput)); - assertEquals( - "Right operand to and operator must be Boolean: estimatedAge", - reversedError.getMessage()); - } - + // TODO: implement with columns + // + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // ParserContext parserContext; + // + // @BeforeEach + // void setUp() { + // parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // } + // + // @Test + // void operandIsNotSingular() { + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .singular(false) + // .expression("estimatedAge") + // .build(); + // final PrimitivePath right = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .singular(true) + // .expression("deceasedBoolean") + // .build(); + // + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // + // final BinaryOperator booleanOperator = BinaryOperator.getInstance("and"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> booleanOperator.invoke(input)); + // assertEquals( + // "Left operand to and operator must be singular: estimatedAge", + // error.getMessage()); + // + // // Now test the right operand. + // final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); + // final InvalidUserInputError reversedError = assertThrows( + // InvalidUserInputError.class, + // () -> booleanOperator.invoke(reversedInput)); + // assertEquals( + // "Right operand to and operator must be singular: estimatedAge", + // reversedError.getMessage()); + // } + // + // @Test + // void operandIsNotBoolean() { + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .singular(true) + // .expression("estimatedAge") + // .build(); + // final PrimitivePath right = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .singular(true) + // .expression("deceasedBoolean") + // .build(); + // + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // + // final BinaryOperator booleanOperator = BinaryOperator.getInstance("and"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> booleanOperator.invoke(input)); + // assertEquals( + // "Left operand to and operator must be Boolean: estimatedAge", + // error.getMessage()); + // + // // Now test the right operand. + // final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); + // final InvalidUserInputError reversedError = assertThrows( + // InvalidUserInputError.class, + // () -> booleanOperator.invoke(reversedInput)); + // assertEquals( + // "Right operand to and operator must be Boolean: estimatedAge", + // reversedError.getMessage()); + // } + // } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/CombineOperatorTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/CombineOperatorTest.java index 0fc40ea7ba..e8447352c5 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/CombineOperatorTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/CombineOperatorTest.java @@ -17,240 +17,213 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static au.csiro.pathling.test.helpers.SparkHelpers.codingStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromCoding; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.collection.CodingCollection; -import au.csiro.pathling.fhirpath.collection.IntegerCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; -import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.builders.ResourcePathBuilder; -import ca.uhn.fhir.context.FhirContext; -import java.util.Arrays; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.apache.spark.sql.types.DataTypes; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class CombineOperatorTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - ParserContext parserContext; - String idColumnName; - - @BeforeEach - void setUp() { - final Dataset input = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.BooleanType) - .withIdsAndValue(null, Arrays.asList("observation-1", "observation-2", "observation-3")) - .build(); - final ResourceCollection inputContext = new ResourcePathBuilder(spark) - .resourceType(ResourceType.OBSERVATION) - .dataset(input) - .idAndValueColumns() - .buildCustom(); - parserContext = new ParserContextBuilder(spark, fhirContext) - .inputContext(inputContext) - .build(); - idColumnName = parserContext.getInputContext().getIdColumn().toString(); - } - - @Test - void returnsCorrectResult() { - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(idColumnName) - .withEidColumn() - .withColumn(DataTypes.IntegerType) - .withRow("observation-1", makeEid(0), 3) - .withRow("observation-1", makeEid(1), 5) - .withRow("observation-1", makeEid(2), 7) - .withRow("observation-2", null, null) - .withRow("observation-3", makeEid(0), -1) - .build(); - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .dataset(leftDataset) - .idAndEidAndValueColumns() - .expression("valueInteger") - .singular(false) - .build(); - final Dataset rightDataset = new DatasetBuilder(spark) - .withIdColumn(idColumnName) - .withEidColumn() - .withColumn(DataTypes.IntegerType) - .withRow("observation-1", makeEid(0), 2) - .withRow("observation-1", makeEid(1), 4) - .withRow("observation-2", null, null) - .withRow("observation-3", makeEid(0), 14) - .build(); - final PrimitivePath right = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .dataset(rightDataset) - .idAndEidAndValueColumns() - .expression("valueInteger") - .singular(false) - .build(); - - final BinaryOperatorInput combineInput = new BinaryOperatorInput(parserContext, left, right); - final Collection result = BinaryOperator.getInstance("combine").invoke(combineInput); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.IntegerType) - .withRow("observation-1", 3) - .withRow("observation-1", 5) - .withRow("observation-1", 7) - .withRow("observation-1", 2) - .withRow("observation-1", 4) - .withRow("observation-2", null) - .withRow("observation-2", null) - .withRow("observation-3", -1) - .withRow("observation-3", 14) - .build(); - assertThat(result) - .hasExpression("valueInteger combine valueInteger") - .isNotSingular() - .isElementPath(IntegerCollection.class) - .selectResult() - .hasRowsUnordered(expectedDataset); - } - - @Test - void worksWithDifferentCombinableTypes() { - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(idColumnName) - .withEidColumn() - .withColumn(DataTypes.IntegerType) - .withRow("observation-1", makeEid(0), 3) - .withRow("observation-1", makeEid(1), 5) - .withRow("observation-1", makeEid(2), 7) - .withRow("observation-2", null, null) - .withRow("observation-3", makeEid(0), -1) - .build(); - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .dataset(leftDataset) - .idAndEidAndValueColumns() - .expression("valueInteger") - .singular(false) - .build(); - final IntegerLiteralPath right = IntegerLiteralPath - .fromString("99", parserContext.getInputContext()); - - final BinaryOperatorInput combineInput = new BinaryOperatorInput(parserContext, left, right); - final Collection result = BinaryOperator.getInstance("combine").invoke(combineInput); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.IntegerType) - .withRow("observation-1", 3) - .withRow("observation-1", 5) - .withRow("observation-1", 7) - .withRow("observation-1", 99) - .withRow("observation-2", null) - .withRow("observation-2", 99) - .withRow("observation-3", -1) - .withRow("observation-3", 99) - .build(); - assertThat(result) - .hasExpression("valueInteger combine 99") - .isNotSingular() - .isElementPath(IntegerCollection.class) - .selectResult() - .hasRowsUnordered(expectedDataset); - } - - @Test - void worksWithLiteralAndNonLiteralCodingValues() { - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(idColumnName) - .withEidColumn() - .withStructTypeColumns(codingStructType()) - .withRow("observation-1", makeEid(0), - rowFromCoding(new Coding("http://snomed.info/sct", "18001011000036104", null))) - .buildWithStructValue(); - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .dataset(leftDataset) - .idAndEidAndValueColumns() - .expression("valueCoding") - .singular(false) - .build(); - final CodingLiteralPath right = CodingCollection - .fromLiteral("http://snomed.info/sct|373882004", parserContext.getInputContext()); - - final BinaryOperatorInput combineInput = new BinaryOperatorInput(parserContext, left, right); - final Collection result = BinaryOperator.getInstance("combine").invoke(combineInput); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn(idColumnName) - .withStructTypeColumns(codingStructType()) - .withRow("observation-1", - rowFromCoding(new Coding("http://snomed.info/sct", "18001011000036104", null))) - .withRow("observation-1", - rowFromCoding(new Coding("http://snomed.info/sct", "373882004", null))) - .withRow("observation-2", - rowFromCoding(new Coding("http://snomed.info/sct", "373882004", null))) - .withRow("observation-3", - rowFromCoding(new Coding("http://snomed.info/sct", "373882004", null))) - .buildWithStructValue(); - assertThat(result) - .hasExpression("valueCoding combine http://snomed.info/sct|373882004") - .isNotSingular() - .isElementPath(CodingCollection.class) - .selectResult() - .hasRowsUnordered(expectedDataset); - } - - @Test - void throwsErrorIfInputsAreNotCombinable() { - final PrimitivePath left = new ElementPathBuilder(spark) - .expression("valueInteger") - .fhirType(FHIRDefinedType.INTEGER) - .build(); - final PrimitivePath right = new ElementPathBuilder(spark) - .expression("valueString") - .fhirType(FHIRDefinedType.STRING) - .build(); - - final BinaryOperatorInput combineInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator combineOperator = BinaryOperator.getInstance("combine"); - - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> combineOperator.invoke(combineInput)); - assertEquals( - "Paths cannot be merged into a collection together: valueInteger, valueString", - error.getMessage()); - } - + // TODO: implement with columns + // + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // ParserContext parserContext; + // String idColumnName; + // + // @BeforeEach + // void setUp() { + // final Dataset input = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.BooleanType) + // .withIdsAndValue(null, Arrays.asList("observation-1", "observation-2", "observation-3")) + // .build(); + // final ResourceCollection inputContext = new ResourcePathBuilder(spark) + // .resourceType(ResourceType.OBSERVATION) + // .dataset(input) + // .idAndValueColumns() + // .buildCustom(); + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .inputContext(inputContext) + // .build(); + // idColumnName = parserContext.getInputContext().getIdColumn().toString(); + // } + // + // @Test + // void returnsCorrectResult() { + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(idColumnName) + // .withEidColumn() + // .withColumn(DataTypes.IntegerType) + // .withRow("observation-1", makeEid(0), 3) + // .withRow("observation-1", makeEid(1), 5) + // .withRow("observation-1", makeEid(2), 7) + // .withRow("observation-2", null, null) + // .withRow("observation-3", makeEid(0), -1) + // .build(); + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .dataset(leftDataset) + // .idAndEidAndValueColumns() + // .expression("valueInteger") + // .singular(false) + // .build(); + // final Dataset rightDataset = new DatasetBuilder(spark) + // .withIdColumn(idColumnName) + // .withEidColumn() + // .withColumn(DataTypes.IntegerType) + // .withRow("observation-1", makeEid(0), 2) + // .withRow("observation-1", makeEid(1), 4) + // .withRow("observation-2", null, null) + // .withRow("observation-3", makeEid(0), 14) + // .build(); + // final PrimitivePath right = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .dataset(rightDataset) + // .idAndEidAndValueColumns() + // .expression("valueInteger") + // .singular(false) + // .build(); + // + // final BinaryOperatorInput combineInput = new BinaryOperatorInput(parserContext, left, right); + // final Collection result = BinaryOperator.getInstance("combine").invoke(combineInput); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.IntegerType) + // .withRow("observation-1", 3) + // .withRow("observation-1", 5) + // .withRow("observation-1", 7) + // .withRow("observation-1", 2) + // .withRow("observation-1", 4) + // .withRow("observation-2", null) + // .withRow("observation-2", null) + // .withRow("observation-3", -1) + // .withRow("observation-3", 14) + // .build(); + // assertThat(result) + // .hasExpression("valueInteger combine valueInteger") + // .isNotSingular() + // .isElementPath(IntegerCollection.class) + // .selectResult() + // .hasRowsUnordered(expectedDataset); + // } + // + // @Test + // void worksWithDifferentCombinableTypes() { + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(idColumnName) + // .withEidColumn() + // .withColumn(DataTypes.IntegerType) + // .withRow("observation-1", makeEid(0), 3) + // .withRow("observation-1", makeEid(1), 5) + // .withRow("observation-1", makeEid(2), 7) + // .withRow("observation-2", null, null) + // .withRow("observation-3", makeEid(0), -1) + // .build(); + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .dataset(leftDataset) + // .idAndEidAndValueColumns() + // .expression("valueInteger") + // .singular(false) + // .build(); + // final IntegerLiteralPath right = IntegerLiteralPath + // .fromString("99", parserContext.getInputContext()); + // + // final BinaryOperatorInput combineInput = new BinaryOperatorInput(parserContext, left, right); + // final Collection result = BinaryOperator.getInstance("combine").invoke(combineInput); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.IntegerType) + // .withRow("observation-1", 3) + // .withRow("observation-1", 5) + // .withRow("observation-1", 7) + // .withRow("observation-1", 99) + // .withRow("observation-2", null) + // .withRow("observation-2", 99) + // .withRow("observation-3", -1) + // .withRow("observation-3", 99) + // .build(); + // assertThat(result) + // .hasExpression("valueInteger combine 99") + // .isNotSingular() + // .isElementPath(IntegerCollection.class) + // .selectResult() + // .hasRowsUnordered(expectedDataset); + // } + // + // @Test + // void worksWithLiteralAndNonLiteralCodingValues() { + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(idColumnName) + // .withEidColumn() + // .withStructTypeColumns(codingStructType()) + // .withRow("observation-1", makeEid(0), + // rowFromCoding(new Coding("http://snomed.info/sct", "18001011000036104", null))) + // .buildWithStructValue(); + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .dataset(leftDataset) + // .idAndEidAndValueColumns() + // .expression("valueCoding") + // .singular(false) + // .build(); + // final CodingLiteralPath right = CodingCollection + // .fromLiteral("http://snomed.info/sct|373882004", parserContext.getInputContext()); + // + // final BinaryOperatorInput combineInput = new BinaryOperatorInput(parserContext, left, right); + // final Collection result = BinaryOperator.getInstance("combine").invoke(combineInput); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn(idColumnName) + // .withStructTypeColumns(codingStructType()) + // .withRow("observation-1", + // rowFromCoding(new Coding("http://snomed.info/sct", "18001011000036104", null))) + // .withRow("observation-1", + // rowFromCoding(new Coding("http://snomed.info/sct", "373882004", null))) + // .withRow("observation-2", + // rowFromCoding(new Coding("http://snomed.info/sct", "373882004", null))) + // .withRow("observation-3", + // rowFromCoding(new Coding("http://snomed.info/sct", "373882004", null))) + // .buildWithStructValue(); + // assertThat(result) + // .hasExpression("valueCoding combine http://snomed.info/sct|373882004") + // .isNotSingular() + // .isElementPath(CodingCollection.class) + // .selectResult() + // .hasRowsUnordered(expectedDataset); + // } + // + // @Test + // void throwsErrorIfInputsAreNotCombinable() { + // final PrimitivePath left = new ElementPathBuilder(spark) + // .expression("valueInteger") + // .fhirType(FHIRDefinedType.INTEGER) + // .build(); + // final PrimitivePath right = new ElementPathBuilder(spark) + // .expression("valueString") + // .fhirType(FHIRDefinedType.STRING) + // .build(); + // + // final BinaryOperatorInput combineInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator combineOperator = BinaryOperator.getInstance("combine"); + // + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> combineOperator.invoke(combineInput)); + // assertEquals( + // "Paths cannot be merged into a collection together: valueInteger, valueString", + // error.getMessage()); + // } + // } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTest.java index ec9f722c40..e4780a81a8 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTest.java @@ -17,225 +17,207 @@ 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.collection.Collection; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.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.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class ComparisonOperatorDateTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - static final String ID_ALIAS = "_abc123"; - private PrimitivePath left; - private PrimitivePath right; - private ParserContext parserContext; - - @BeforeEach - void setUp() { - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Patient", "birthDate"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - assertTrue(definition.getFhirType().isPresent()); - assertEquals(FHIRDefinedType.DATE, definition.getFhirType().get()); - - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-01", "2013-06-10") // Equal, years, months and days - .withRow("patient-02", "2013-06") // Equal, years and months - .withRow("patient-03", "2013") // Equal, years - .withRow("patient-04", "2013-06-01") // Different precisions - .withRow("patient-05", "2013-06-10") // Less than, years, months and days - .withRow("patient-06", "2013-06") // Less than, years and months - .withRow("patient-07", "2013") // Less than, years - .withRow("patient-08", "2013-06-10") // Greater than, years, months and days - .withRow("patient-09", "2013-06") // Greater than, years and months - .withRow("patient-10", "2013") // Greater than, years - .build(); - left = new ElementPathBuilder(spark) - .dataset(leftDataset) - .idAndValueColumns() - .expression("birthDate") - .singular(true) - .definition(definition) - .buildDefined(); - - final Dataset rightDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-01", "2013-06-10") // Equal, years, months and days - .withRow("patient-02", "2013-06") // Equal, years and months - .withRow("patient-03", "2013") // Equal, years - .withRow("patient-04", "2013-06") // Different precisions - .withRow("patient-05", "2013-06-11") // Less than, years, months and days - .withRow("patient-06", "2013-07") // Less than, years and months - .withRow("patient-07", "2014") // Less than, years - .withRow("patient-08", "2013-05-10") // Greater than, years, months and days - .withRow("patient-09", "2012-06") // Greater than, years and months - .withRow("patient-10", "2012") // Greater than, years - .build(); - right = new ElementPathBuilder(spark) - .dataset(rightDataset) - .idAndValueColumns() - .expression("birthDate") - .singular(true) - .definition(definition) - .buildDefined(); - - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(left.getIdColumn())) - .build(); - } - - @Test - void equals() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("="); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", true), // Equal, years, months and days - RowFactory.create("patient-02", true), // Equal, years and months - RowFactory.create("patient-03", true), // Equal, years - RowFactory.create("patient-04", null), // Different precisions - RowFactory.create("patient-05", false), // Less than, years, months and days - RowFactory.create("patient-06", false), // Less than, years and months - RowFactory.create("patient-07", false), // Less than, years - RowFactory.create("patient-08", false), // Greater than, years, months and days - RowFactory.create("patient-09", false), // Greater than, years and months - RowFactory.create("patient-10", false) // Greater than, years - ); - } - - @Test - void notEquals() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("!="); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", false), // Equal, years, months and days - RowFactory.create("patient-02", false), // Equal, years and months - RowFactory.create("patient-03", false), // Equal, years - RowFactory.create("patient-04", null), // Different precisions - RowFactory.create("patient-05", true), // Less than, years, months and days - RowFactory.create("patient-06", true), // Less than, years and months - RowFactory.create("patient-07", true), // Less than, years - RowFactory.create("patient-08", true), // Greater than, years, months and days - RowFactory.create("patient-09", true), // Greater than, years and months - RowFactory.create("patient-10", true) // Greater than, years - ); - } - - @Test - void lessThan() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("<"); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", false), // Equal, years, months and days - RowFactory.create("patient-02", false), // Equal, years and months - RowFactory.create("patient-03", false), // Equal, years - RowFactory.create("patient-04", false), // Different precisions - RowFactory.create("patient-05", true), // Less than, years, months and days - RowFactory.create("patient-06", true), // Less than, years and months - RowFactory.create("patient-07", true), // Less than, years - RowFactory.create("patient-08", false), // Greater than, years, months and days - RowFactory.create("patient-09", false), // Greater than, years and months - RowFactory.create("patient-10", false) // Greater than, years - ); - } - - @Test - void lessThanOrEqualTo() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("<="); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", true), // Equal, years, months and days - RowFactory.create("patient-02", true), // Equal, years and months - RowFactory.create("patient-03", true), // Equal, years - RowFactory.create("patient-04", null), // Different precisions - RowFactory.create("patient-05", true), // Less than, years, months and days - RowFactory.create("patient-06", true), // Less than, years and months - RowFactory.create("patient-07", true), // Less than, years - RowFactory.create("patient-08", false), // Greater than, years, months and days - RowFactory.create("patient-09", false), // Greater than, years and months - RowFactory.create("patient-10", false) // Greater than, years - ); - } - - @Test - void greaterThan() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance(">"); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", false), // Equal, years, months and days - RowFactory.create("patient-02", false), // Equal, years and months - RowFactory.create("patient-03", false), // Equal, years - RowFactory.create("patient-04", false), // Different precisions - RowFactory.create("patient-05", false), // Less than, years, months and days - RowFactory.create("patient-06", false), // Less than, years and months - RowFactory.create("patient-07", false), // Less than, years - RowFactory.create("patient-08", true), // Greater than, years, months and days - RowFactory.create("patient-09", true), // Greater than, years and months - RowFactory.create("patient-10", true) // Greater than, years - ); - } - - @Test - void greaterThanOrEqualTo() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance(">="); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", true), // Equal, years, months and days - RowFactory.create("patient-02", true), // Equal, years and months - RowFactory.create("patient-03", true), // Equal, years - RowFactory.create("patient-04", null), // Different precisions - RowFactory.create("patient-05", false), // Less than, years, months and days - RowFactory.create("patient-06", false), // Less than, years and months - RowFactory.create("patient-07", false), // Less than, years - RowFactory.create("patient-08", true), // Greater than, years, months and days - RowFactory.create("patient-09", true), // Greater than, years and months - RowFactory.create("patient-10", true) // Greater than, years - ); - } - + // TODO: implement with columns + // + // + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // static final String ID_ALIAS = "_abc123"; + // private PrimitivePath left; + // private PrimitivePath right; + // private ParserContext parserContext; + // + // @BeforeEach + // void setUp() { + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Patient", "birthDate"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // assertTrue(definition.getFhirType().isPresent()); + // assertEquals(FHIRDefinedType.DATE, definition.getFhirType().get()); + // + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-01", "2013-06-10") // Equal, years, months and days + // .withRow("patient-02", "2013-06") // Equal, years and months + // .withRow("patient-03", "2013") // Equal, years + // .withRow("patient-04", "2013-06-01") // Different precisions + // .withRow("patient-05", "2013-06-10") // Less than, years, months and days + // .withRow("patient-06", "2013-06") // Less than, years and months + // .withRow("patient-07", "2013") // Less than, years + // .withRow("patient-08", "2013-06-10") // Greater than, years, months and days + // .withRow("patient-09", "2013-06") // Greater than, years and months + // .withRow("patient-10", "2013") // Greater than, years + // .build(); + // left = new ElementPathBuilder(spark) + // .dataset(leftDataset) + // .idAndValueColumns() + // .expression("birthDate") + // .singular(true) + // .definition(definition) + // .buildDefined(); + // + // final Dataset rightDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-01", "2013-06-10") // Equal, years, months and days + // .withRow("patient-02", "2013-06") // Equal, years and months + // .withRow("patient-03", "2013") // Equal, years + // .withRow("patient-04", "2013-06") // Different precisions + // .withRow("patient-05", "2013-06-11") // Less than, years, months and days + // .withRow("patient-06", "2013-07") // Less than, years and months + // .withRow("patient-07", "2014") // Less than, years + // .withRow("patient-08", "2013-05-10") // Greater than, years, months and days + // .withRow("patient-09", "2012-06") // Greater than, years and months + // .withRow("patient-10", "2012") // Greater than, years + // .build(); + // right = new ElementPathBuilder(spark) + // .dataset(rightDataset) + // .idAndValueColumns() + // .expression("birthDate") + // .singular(true) + // .definition(definition) + // .buildDefined(); + // + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(left.getIdColumn())) + // .build(); + // } + // + // @Test + // void equals() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("="); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", true), // Equal, years, months and days + // RowFactory.create("patient-02", true), // Equal, years and months + // RowFactory.create("patient-03", true), // Equal, years + // RowFactory.create("patient-04", null), // Different precisions + // RowFactory.create("patient-05", false), // Less than, years, months and days + // RowFactory.create("patient-06", false), // Less than, years and months + // RowFactory.create("patient-07", false), // Less than, years + // RowFactory.create("patient-08", false), // Greater than, years, months and days + // RowFactory.create("patient-09", false), // Greater than, years and months + // RowFactory.create("patient-10", false) // Greater than, years + // ); + // } + // + // @Test + // void notEquals() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("!="); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", false), // Equal, years, months and days + // RowFactory.create("patient-02", false), // Equal, years and months + // RowFactory.create("patient-03", false), // Equal, years + // RowFactory.create("patient-04", null), // Different precisions + // RowFactory.create("patient-05", true), // Less than, years, months and days + // RowFactory.create("patient-06", true), // Less than, years and months + // RowFactory.create("patient-07", true), // Less than, years + // RowFactory.create("patient-08", true), // Greater than, years, months and days + // RowFactory.create("patient-09", true), // Greater than, years and months + // RowFactory.create("patient-10", true) // Greater than, years + // ); + // } + // + // @Test + // void lessThan() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("<"); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", false), // Equal, years, months and days + // RowFactory.create("patient-02", false), // Equal, years and months + // RowFactory.create("patient-03", false), // Equal, years + // RowFactory.create("patient-04", false), // Different precisions + // RowFactory.create("patient-05", true), // Less than, years, months and days + // RowFactory.create("patient-06", true), // Less than, years and months + // RowFactory.create("patient-07", true), // Less than, years + // RowFactory.create("patient-08", false), // Greater than, years, months and days + // RowFactory.create("patient-09", false), // Greater than, years and months + // RowFactory.create("patient-10", false) // Greater than, years + // ); + // } + // + // @Test + // void lessThanOrEqualTo() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("<="); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", true), // Equal, years, months and days + // RowFactory.create("patient-02", true), // Equal, years and months + // RowFactory.create("patient-03", true), // Equal, years + // RowFactory.create("patient-04", null), // Different precisions + // RowFactory.create("patient-05", true), // Less than, years, months and days + // RowFactory.create("patient-06", true), // Less than, years and months + // RowFactory.create("patient-07", true), // Less than, years + // RowFactory.create("patient-08", false), // Greater than, years, months and days + // RowFactory.create("patient-09", false), // Greater than, years and months + // RowFactory.create("patient-10", false) // Greater than, years + // ); + // } + // + // @Test + // void greaterThan() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance(">"); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", false), // Equal, years, months and days + // RowFactory.create("patient-02", false), // Equal, years and months + // RowFactory.create("patient-03", false), // Equal, years + // RowFactory.create("patient-04", false), // Different precisions + // RowFactory.create("patient-05", false), // Less than, years, months and days + // RowFactory.create("patient-06", false), // Less than, years and months + // RowFactory.create("patient-07", false), // Less than, years + // RowFactory.create("patient-08", true), // Greater than, years, months and days + // RowFactory.create("patient-09", true), // Greater than, years and months + // RowFactory.create("patient-10", true) // Greater than, years + // ); + // } + // + // @Test + // void greaterThanOrEqualTo() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance(">="); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", true), // Equal, years, months and days + // RowFactory.create("patient-02", true), // Equal, years and months + // RowFactory.create("patient-03", true), // Equal, years + // RowFactory.create("patient-04", null), // Different precisions + // RowFactory.create("patient-05", false), // Less than, years, months and days + // RowFactory.create("patient-06", false), // Less than, years and months + // RowFactory.create("patient-07", false), // Less than, years + // RowFactory.create("patient-08", true), // Greater than, years, months and days + // RowFactory.create("patient-09", true), // Greater than, years and months + // RowFactory.create("patient-10", true) // Greater than, years + // ); + // } + // } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTimeTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTimeTest.java index fd7284cc8a..89a5f1cbb3 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTimeTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorDateTimeTest.java @@ -17,232 +17,213 @@ 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.collection.Collection; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.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.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class ComparisonOperatorDateTimeTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - static final String ID_ALIAS = "_abc123"; - private PrimitivePath left; - private PrimitivePath right; - private ParserContext parserContext; - - @BeforeEach - void setUp() { - final Optional optionalLeftDefinition = FhirHelpers - .getChildOfResource(fhirContext, "MedicationRequest", "authoredOn"); - assertTrue(optionalLeftDefinition.isPresent()); - final ElementDefinition leftDefinition = optionalLeftDefinition.get(); - assertTrue(leftDefinition.getFhirType().isPresent()); - assertEquals(FHIRDefinedType.DATETIME, leftDefinition.getFhirType().get()); - - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-01", "2013-06-10T15:33:22Z") // Equal, exact - .withRow("patient-02", "2013-06-10T15:33:22Z") // Equal, different time zones - .withRow("patient-03", "2013-06-10T15:33:22+00:00") // Equal, different time zone syntax - .withRow("patient-04", "2013-06-10T15:33:22.000Z") // Equal, different precisions - .withRow("patient-05", "2013-06-10T15:33:21.900Z") // Less than - .withRow("patient-06", "2013-06-11T15:33:22Z") // Greater than - .withRow("patient-07", "foo") // Invalid on the left - .withRow("patient-08", "2013-06-11T15:33:22Z") // Invalid on the right - .withRow("patient-09", null) // Null on the left - .withRow("patient-10", "2013-06-11T15:33:22Z") // Null on the right - .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-01", "2013-06-10T15:33:22Z") // Equal, exact - .withRow("patient-02", "2013-06-11T01:33:22+10:00") // Equal, different time zones - .withRow("patient-03", "2013-06-10T15:33:22Z") // Equal, different time zone syntax - .withRow("patient-04", "2013-06-10T15:33:22Z") // Equal, different precisions - .withRow("patient-05", "2013-06-10T15:33:22Z") // Less than - .withRow("patient-06", "2013-06-10T15:33:22Z") // Greater than - .withRow("patient-07", "2013-06-11T15:33:22Z") // Invalid on the left - .withRow("patient-08", "foo") // Invalid on the right - .withRow("patient-09", "2013-06-11T15:33:22Z") // Null on the left - .withRow("patient-10", null) // Null on the right - .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 BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("="); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", true), // Equal, exact - RowFactory.create("patient-02", true), // Equal, different time zones - RowFactory.create("patient-03", true), // Equal, different time zone syntax - RowFactory.create("patient-04", true), // Equal, different precisions - RowFactory.create("patient-05", false), // Less than - RowFactory.create("patient-06", false), // Greater than - RowFactory.create("patient-07", null), // Invalid on the left - RowFactory.create("patient-08", null), // Invalid on the right - RowFactory.create("patient-09", null), // Null on the left - RowFactory.create("patient-10", null) // Null on the right - ); - } - - @Test - void notEquals() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("!="); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", false), // Equal, exact - RowFactory.create("patient-02", false), // Equal, different time zones - RowFactory.create("patient-03", false), // Equal, different time zone syntax - RowFactory.create("patient-04", false), // Equal, different precisions - RowFactory.create("patient-05", true), // Less than - RowFactory.create("patient-06", true), // Greater than - RowFactory.create("patient-07", null), // Invalid on the left - RowFactory.create("patient-08", null), // Invalid on the right - RowFactory.create("patient-09", null), // Null on the left - RowFactory.create("patient-10", null) // Null on the right - ); - } - - @Test - void lessThan() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("<"); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", false), // Equal, exact - RowFactory.create("patient-02", false), // Equal, different time zones - RowFactory.create("patient-03", false), // Equal, different time zone syntax - RowFactory.create("patient-04", false), // Equal, different precisions - RowFactory.create("patient-05", true), // Less than - RowFactory.create("patient-06", false), // Greater than - RowFactory.create("patient-07", null), // Invalid on the left - RowFactory.create("patient-08", null), // Invalid on the right - RowFactory.create("patient-09", null), // Null on the left - RowFactory.create("patient-10", null) // Null on the right - ); - } - - @Test - void lessThanOrEqualTo() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("<="); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", true), // Equal, exact - RowFactory.create("patient-02", true), // Equal, different time zones - RowFactory.create("patient-03", true), // Equal, different time zone syntax - RowFactory.create("patient-04", true), // Equal, different precisions - RowFactory.create("patient-05", true), // Less than - RowFactory.create("patient-06", false), // Greater than - RowFactory.create("patient-07", null), // Invalid on the left - RowFactory.create("patient-08", null), // Invalid on the right - RowFactory.create("patient-09", null), // Null on the left - RowFactory.create("patient-10", null) // Null on the right - ); - } - - @Test - void greaterThan() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance(">"); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", false), // Equal, exact - RowFactory.create("patient-02", false), // Equal, different time zones - RowFactory.create("patient-03", false), // Equal, different time zone syntax - RowFactory.create("patient-04", false), // Equal, different precisions - RowFactory.create("patient-05", false), // Less than - RowFactory.create("patient-06", true), // Greater than - RowFactory.create("patient-07", null), // Invalid on the left - RowFactory.create("patient-08", null), // Invalid on the right - RowFactory.create("patient-09", null), // Null on the left - RowFactory.create("patient-10", null) // Null on the right - ); - } - - @Test - void greaterThanOrEqualTo() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance(">="); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", true), // Equal, exact - RowFactory.create("patient-02", true), // Equal, different time zones - RowFactory.create("patient-03", true), // Equal, different time zone syntax - RowFactory.create("patient-04", true), // Equal, different precisions - RowFactory.create("patient-05", false), // Less than - RowFactory.create("patient-06", true), // Greater than - RowFactory.create("patient-07", null), // Invalid on the left - RowFactory.create("patient-08", null), // Invalid on the right - RowFactory.create("patient-09", null), // Null on the left - RowFactory.create("patient-10", null) // Null on the right - ); - } - + // TODO: implement with columns + // + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // static final String ID_ALIAS = "_abc123"; + // private PrimitivePath left; + // private PrimitivePath right; + // private ParserContext parserContext; + // + // @BeforeEach + // void setUp() { + // final Optional optionalLeftDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "MedicationRequest", "authoredOn"); + // assertTrue(optionalLeftDefinition.isPresent()); + // final ElementDefinition leftDefinition = optionalLeftDefinition.get(); + // assertTrue(leftDefinition.getFhirType().isPresent()); + // assertEquals(FHIRDefinedType.DATETIME, leftDefinition.getFhirType().get()); + // + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-01", "2013-06-10T15:33:22Z") // Equal, exact + // .withRow("patient-02", "2013-06-10T15:33:22Z") // Equal, different time zones + // .withRow("patient-03", "2013-06-10T15:33:22+00:00") // Equal, different time zone syntax + // .withRow("patient-04", "2013-06-10T15:33:22.000Z") // Equal, different precisions + // .withRow("patient-05", "2013-06-10T15:33:21.900Z") // Less than + // .withRow("patient-06", "2013-06-11T15:33:22Z") // Greater than + // .withRow("patient-07", "foo") // Invalid on the left + // .withRow("patient-08", "2013-06-11T15:33:22Z") // Invalid on the right + // .withRow("patient-09", null) // Null on the left + // .withRow("patient-10", "2013-06-11T15:33:22Z") // Null on the right + // .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-01", "2013-06-10T15:33:22Z") // Equal, exact + // .withRow("patient-02", "2013-06-11T01:33:22+10:00") // Equal, different time zones + // .withRow("patient-03", "2013-06-10T15:33:22Z") // Equal, different time zone syntax + // .withRow("patient-04", "2013-06-10T15:33:22Z") // Equal, different precisions + // .withRow("patient-05", "2013-06-10T15:33:22Z") // Less than + // .withRow("patient-06", "2013-06-10T15:33:22Z") // Greater than + // .withRow("patient-07", "2013-06-11T15:33:22Z") // Invalid on the left + // .withRow("patient-08", "foo") // Invalid on the right + // .withRow("patient-09", "2013-06-11T15:33:22Z") // Null on the left + // .withRow("patient-10", null) // Null on the right + // .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 BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("="); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", true), // Equal, exact + // RowFactory.create("patient-02", true), // Equal, different time zones + // RowFactory.create("patient-03", true), // Equal, different time zone syntax + // RowFactory.create("patient-04", true), // Equal, different precisions + // RowFactory.create("patient-05", false), // Less than + // RowFactory.create("patient-06", false), // Greater than + // RowFactory.create("patient-07", null), // Invalid on the left + // RowFactory.create("patient-08", null), // Invalid on the right + // RowFactory.create("patient-09", null), // Null on the left + // RowFactory.create("patient-10", null) // Null on the right + // ); + // } + // + // @Test + // void notEquals() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("!="); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", false), // Equal, exact + // RowFactory.create("patient-02", false), // Equal, different time zones + // RowFactory.create("patient-03", false), // Equal, different time zone syntax + // RowFactory.create("patient-04", false), // Equal, different precisions + // RowFactory.create("patient-05", true), // Less than + // RowFactory.create("patient-06", true), // Greater than + // RowFactory.create("patient-07", null), // Invalid on the left + // RowFactory.create("patient-08", null), // Invalid on the right + // RowFactory.create("patient-09", null), // Null on the left + // RowFactory.create("patient-10", null) // Null on the right + // ); + // } + // + // @Test + // void lessThan() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("<"); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", false), // Equal, exact + // RowFactory.create("patient-02", false), // Equal, different time zones + // RowFactory.create("patient-03", false), // Equal, different time zone syntax + // RowFactory.create("patient-04", false), // Equal, different precisions + // RowFactory.create("patient-05", true), // Less than + // RowFactory.create("patient-06", false), // Greater than + // RowFactory.create("patient-07", null), // Invalid on the left + // RowFactory.create("patient-08", null), // Invalid on the right + // RowFactory.create("patient-09", null), // Null on the left + // RowFactory.create("patient-10", null) // Null on the right + // ); + // } + // + // @Test + // void lessThanOrEqualTo() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("<="); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", true), // Equal, exact + // RowFactory.create("patient-02", true), // Equal, different time zones + // RowFactory.create("patient-03", true), // Equal, different time zone syntax + // RowFactory.create("patient-04", true), // Equal, different precisions + // RowFactory.create("patient-05", true), // Less than + // RowFactory.create("patient-06", false), // Greater than + // RowFactory.create("patient-07", null), // Invalid on the left + // RowFactory.create("patient-08", null), // Invalid on the right + // RowFactory.create("patient-09", null), // Null on the left + // RowFactory.create("patient-10", null) // Null on the right + // ); + // } + // + // @Test + // void greaterThan() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance(">"); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", false), // Equal, exact + // RowFactory.create("patient-02", false), // Equal, different time zones + // RowFactory.create("patient-03", false), // Equal, different time zone syntax + // RowFactory.create("patient-04", false), // Equal, different precisions + // RowFactory.create("patient-05", false), // Less than + // RowFactory.create("patient-06", true), // Greater than + // RowFactory.create("patient-07", null), // Invalid on the left + // RowFactory.create("patient-08", null), // Invalid on the right + // RowFactory.create("patient-09", null), // Null on the left + // RowFactory.create("patient-10", null) // Null on the right + // ); + // } + // + // @Test + // void greaterThanOrEqualTo() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance(">="); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", true), // Equal, exact + // RowFactory.create("patient-02", true), // Equal, different time zones + // RowFactory.create("patient-03", true), // Equal, different time zone syntax + // RowFactory.create("patient-04", true), // Equal, different precisions + // RowFactory.create("patient-05", false), // Less than + // RowFactory.create("patient-06", true), // Greater than + // RowFactory.create("patient-07", null), // Invalid on the left + // RowFactory.create("patient-08", null), // Invalid on the right + // RowFactory.create("patient-09", null), // Null on the left + // RowFactory.create("patient-10", null) // Null on the right + // ); + // } + // } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java index 19db5f3a77..deaf3b09c4 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorInstantTest.java @@ -17,177 +17,157 @@ 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.collection.Collection; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class ComparisonOperatorInstantTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - static final String ID_ALIAS = "_abc123"; - private PrimitivePath left; - private PrimitivePath 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 BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("="); - final Collection 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 BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("!="); - final Collection 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 BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("<"); - final Collection 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 BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("<="); - final Collection 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 BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance(">"); - final Collection 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 BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance(">="); - final Collection 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 - ); - } - + // TODO: implement with columns + // + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // static final String ID_ALIAS = "_abc123"; + // private PrimitivePath left; + // private PrimitivePath 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 BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("="); + // final Collection 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 BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("!="); + // final Collection 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 BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("<"); + // final Collection 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 BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("<="); + // final Collection 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 BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance(">"); + // final Collection 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 BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance(">="); + // final Collection 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 + // ); + // } + // } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorQuantityTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorQuantityTest.java index c8716ede0f..3fd83e93b9 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorQuantityTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorQuantityTest.java @@ -17,245 +17,226 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.helpers.SparkHelpers.quantityStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromQuantity; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.QuantityCollection; -import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.TestHelpers; -import ca.uhn.fhir.context.FhirContext; -import java.util.Collections; -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.fhir.ucum.UcumService; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.Quantity; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; @SpringBootUnitTest +@NotImplemented public class ComparisonOperatorQuantityTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @Autowired - UcumService ucumService; - - static final String ID_ALIAS = "_abc123"; - - Collection left; - Collection right; - ParserContext parserContext; - QuantityLiteralPath ucumQuantityLiteral1; - QuantityLiteralPath ucumQuantityLiteral2; - QuantityLiteralPath ucumQuantityLiteral3; - QuantityLiteralPath calendarDurationLiteral1; - QuantityLiteralPath calendarDurationLiteral2; - QuantityLiteralPath calendarDurationLiteral3; - - @BeforeEach - void setUp() { - final Quantity quantity1 = new Quantity(); - quantity1.setValue(500); - quantity1.setUnit("mg"); - quantity1.setSystem(TestHelpers.UCUM_URL); - quantity1.setCode("mg"); - - final Quantity quantity2 = new Quantity(); - quantity2.setValue(0.5); - quantity2.setUnit("g"); - quantity2.setSystem(TestHelpers.UCUM_URL); - quantity2.setCode("g"); - - final Quantity quantity3 = new Quantity(); - quantity3.setValue(1.8); - quantity3.setUnit("m"); - quantity3.setSystem(TestHelpers.UCUM_URL); - quantity3.setCode("m"); - - final Quantity quantity4 = new Quantity(); - quantity4.setValue(0.5); - quantity4.setUnit("g"); - quantity4.setSystem(TestHelpers.SNOMED_URL); - quantity4.setCode("258682000"); - - final Quantity quantity5 = new Quantity(); - quantity5.setValue(650); - quantity5.setUnit("mg"); - quantity5.setSystem(TestHelpers.UCUM_URL); - quantity5.setCode("mg"); - - final Quantity quantity6 = new Quantity(); - quantity6.setValue(30); - quantity6.setUnit("d"); - quantity6.setSystem(TestHelpers.UCUM_URL); - quantity6.setCode("d"); - - final Quantity quantity7 = new Quantity(); - quantity7.setValue(60); - quantity7.setUnit("s"); - quantity7.setSystem(TestHelpers.UCUM_URL); - quantity7.setCode("s"); - - final Quantity quantity8 = new Quantity(); - quantity8.setValue(1000); - quantity8.setUnit("ms"); - quantity8.setSystem(TestHelpers.UCUM_URL); - quantity8.setCode("ms"); - - final Quantity quantity9 = new Quantity(); - quantity9.setValue(0.2); - quantity9.setUnit("g"); - quantity9.setSystem(TestHelpers.UCUM_URL); - quantity9.setCode("g"); - - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withStructTypeColumns(quantityStructType()) - .withRow("patient-01", rowFromQuantity(quantity1)) // 500 mg - .withRow("patient-02", rowFromQuantity(quantity1)) // 500 mg - .withRow("patient-03", rowFromQuantity(quantity1)) // 500 mg - .withRow("patient-04", rowFromQuantity(quantity5)) // 650 mg - .withRow("patient-05", null) - .withRow("patient-06", rowFromQuantity(quantity1)) // 500 mg - .withRow("patient-07", rowFromQuantity(quantity6)) // 30 d - .withRow("patient-08", rowFromQuantity(quantity7)) // 60 s - .withRow("patient-09", rowFromQuantity(quantity8)) // 1000 ms - .withRow("patient-10", rowFromQuantity(quantity9)) // 0.2 g - .buildWithStructValue(); - left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.QUANTITY) - .singular(true) - .dataset(leftDataset) - .idAndValueColumns() - .build(); - - final Dataset rightDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withStructTypeColumns(quantityStructType()) - .withRow("patient-01", rowFromQuantity(quantity1)) // 500 mg - .withRow("patient-02", rowFromQuantity(quantity2)) // 0.5 g - .withRow("patient-03", rowFromQuantity(quantity3)) // 1.8 m - .withRow("patient-04", rowFromQuantity(quantity1)) // 500 mg - .withRow("patient-05", rowFromQuantity(quantity1)) // 500 mg - .withRow("patient-06", null) - .withRow("patient-07", rowFromQuantity(quantity6)) // 30 d - .withRow("patient-08", rowFromQuantity(quantity7)) // 60 s - .withRow("patient-09", rowFromQuantity(quantity8)) // 1000 ms - .withRow("patient-10", rowFromQuantity(quantity1)) // 500 mg - .buildWithStructValue(); - right = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.QUANTITY) - .singular(true) - .dataset(rightDataset) - .idAndValueColumns() - .build(); - - ucumQuantityLiteral1 = QuantityCollection.fromUcumString("500 'mg'", left, ucumService); - ucumQuantityLiteral2 = QuantityCollection.fromUcumString("0.5 'g'", left, ucumService); - ucumQuantityLiteral3 = QuantityCollection.fromUcumString("1.8 'm'", left, ucumService); - calendarDurationLiteral1 = QuantityCollection.fromCalendarDurationString("30 days", left); - calendarDurationLiteral2 = QuantityCollection.fromCalendarDurationString("60 seconds", left); - calendarDurationLiteral3 = QuantityCollection.fromCalendarDurationString("1000 milliseconds", - left); - - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(left.getIdColumn())) - .build(); - } - - @Test - void lessThan() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator equalityOperator = BinaryOperator.getInstance("<"); - final Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", false), // 500 mg < 500 mg - RowFactory.create("patient-02", false), // 500 mg < 0.5 g - RowFactory.create("patient-03", null), // 500 mg < 1.8 m - RowFactory.create("patient-04", false), // 650 mg < 500 mg - RowFactory.create("patient-05", null), // {} < 500 mg - RowFactory.create("patient-06", null), // 500 mg < {} - RowFactory.create("patient-07", false), // 30 d < 30 d - RowFactory.create("patient-08", false), // 60 s < 60 s - RowFactory.create("patient-09", false), // 1000 ms < 1000 ms - RowFactory.create("patient-10", true) // 0.2 g < 500 mg - ); - } - - @Test - void lessThanOrEqualTo() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator equalityOperator = BinaryOperator.getInstance("<="); - final Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", true), // 500 mg <= 500 mg - RowFactory.create("patient-02", true), // 500 mg <= 0.5 g - RowFactory.create("patient-03", null), // 500 mg <= 1.8 m - RowFactory.create("patient-04", false), // 650 mg <= 500 mg - RowFactory.create("patient-05", null), // {} <= 500 mg - RowFactory.create("patient-06", null), // 500 mg <= {} - RowFactory.create("patient-07", true), // 30 d <= 30 d - RowFactory.create("patient-08", true), // 60 s <= 60 s - RowFactory.create("patient-09", true), // 1000 ms <= 1000 ms - RowFactory.create("patient-10", true) // 0.2 g <= 500 mg - ); - } - - @Test - void greaterThanOrEqualTo() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator equalityOperator = BinaryOperator.getInstance(">="); - final Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", true), // 500 mg >= 500 mg - RowFactory.create("patient-02", true), // 500 mg >= 0.5 g - RowFactory.create("patient-03", null), // 500 mg >= 1.8 m - RowFactory.create("patient-04", true), // 650 mg >= 500 mg - RowFactory.create("patient-05", null), // {} >= 500 mg - RowFactory.create("patient-06", null), // 500 mg >= {} - RowFactory.create("patient-07", true), // 30 d >= 30 d - RowFactory.create("patient-08", true), // 60 s >= 60 s - RowFactory.create("patient-09", true), // 1000 ms >= 1000 ms - RowFactory.create("patient-10", false) // 0.2 g >= 500 mg - ); - } - - @Test - void greaterThan() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator equalityOperator = BinaryOperator.getInstance(">"); - final Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", false), // 500 mg > 500 mg - RowFactory.create("patient-02", false), // 500 mg > 0.5 g - RowFactory.create("patient-03", null), // 500 mg > 1.8 m - RowFactory.create("patient-04", true), // 650 mg > 500 mg - RowFactory.create("patient-05", null), // {} > 500 mg - RowFactory.create("patient-06", null), // 500 mg > {} - RowFactory.create("patient-07", false), // 30 d > 30 d - RowFactory.create("patient-08", false), // 60 s > 60 s - RowFactory.create("patient-09", false), // 1000 ms > 1000 ms - RowFactory.create("patient-10", false) // 0.2 g > 500 mg - ); - } + // TODO: implement with columns + + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @Autowired + // UcumService ucumService; + // + // static final String ID_ALIAS = "_abc123"; + // + // Collection left; + // Collection right; + // ParserContext parserContext; + // QuantityLiteralPath ucumQuantityLiteral1; + // QuantityLiteralPath ucumQuantityLiteral2; + // QuantityLiteralPath ucumQuantityLiteral3; + // QuantityLiteralPath calendarDurationLiteral1; + // QuantityLiteralPath calendarDurationLiteral2; + // QuantityLiteralPath calendarDurationLiteral3; + // + // @BeforeEach + // void setUp() { + // final Quantity quantity1 = new Quantity(); + // quantity1.setValue(500); + // quantity1.setUnit("mg"); + // quantity1.setSystem(TestHelpers.UCUM_URL); + // quantity1.setCode("mg"); + // + // final Quantity quantity2 = new Quantity(); + // quantity2.setValue(0.5); + // quantity2.setUnit("g"); + // quantity2.setSystem(TestHelpers.UCUM_URL); + // quantity2.setCode("g"); + // + // final Quantity quantity3 = new Quantity(); + // quantity3.setValue(1.8); + // quantity3.setUnit("m"); + // quantity3.setSystem(TestHelpers.UCUM_URL); + // quantity3.setCode("m"); + // + // final Quantity quantity4 = new Quantity(); + // quantity4.setValue(0.5); + // quantity4.setUnit("g"); + // quantity4.setSystem(TestHelpers.SNOMED_URL); + // quantity4.setCode("258682000"); + // + // final Quantity quantity5 = new Quantity(); + // quantity5.setValue(650); + // quantity5.setUnit("mg"); + // quantity5.setSystem(TestHelpers.UCUM_URL); + // quantity5.setCode("mg"); + // + // final Quantity quantity6 = new Quantity(); + // quantity6.setValue(30); + // quantity6.setUnit("d"); + // quantity6.setSystem(TestHelpers.UCUM_URL); + // quantity6.setCode("d"); + // + // final Quantity quantity7 = new Quantity(); + // quantity7.setValue(60); + // quantity7.setUnit("s"); + // quantity7.setSystem(TestHelpers.UCUM_URL); + // quantity7.setCode("s"); + // + // final Quantity quantity8 = new Quantity(); + // quantity8.setValue(1000); + // quantity8.setUnit("ms"); + // quantity8.setSystem(TestHelpers.UCUM_URL); + // quantity8.setCode("ms"); + // + // final Quantity quantity9 = new Quantity(); + // quantity9.setValue(0.2); + // quantity9.setUnit("g"); + // quantity9.setSystem(TestHelpers.UCUM_URL); + // quantity9.setCode("g"); + // + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withStructTypeColumns(quantityStructType()) + // .withRow("patient-01", rowFromQuantity(quantity1)) // 500 mg + // .withRow("patient-02", rowFromQuantity(quantity1)) // 500 mg + // .withRow("patient-03", rowFromQuantity(quantity1)) // 500 mg + // .withRow("patient-04", rowFromQuantity(quantity5)) // 650 mg + // .withRow("patient-05", null) + // .withRow("patient-06", rowFromQuantity(quantity1)) // 500 mg + // .withRow("patient-07", rowFromQuantity(quantity6)) // 30 d + // .withRow("patient-08", rowFromQuantity(quantity7)) // 60 s + // .withRow("patient-09", rowFromQuantity(quantity8)) // 1000 ms + // .withRow("patient-10", rowFromQuantity(quantity9)) // 0.2 g + // .buildWithStructValue(); + // left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.QUANTITY) + // .singular(true) + // .dataset(leftDataset) + // .idAndValueColumns() + // .build(); + // + // final Dataset rightDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withStructTypeColumns(quantityStructType()) + // .withRow("patient-01", rowFromQuantity(quantity1)) // 500 mg + // .withRow("patient-02", rowFromQuantity(quantity2)) // 0.5 g + // .withRow("patient-03", rowFromQuantity(quantity3)) // 1.8 m + // .withRow("patient-04", rowFromQuantity(quantity1)) // 500 mg + // .withRow("patient-05", rowFromQuantity(quantity1)) // 500 mg + // .withRow("patient-06", null) + // .withRow("patient-07", rowFromQuantity(quantity6)) // 30 d + // .withRow("patient-08", rowFromQuantity(quantity7)) // 60 s + // .withRow("patient-09", rowFromQuantity(quantity8)) // 1000 ms + // .withRow("patient-10", rowFromQuantity(quantity1)) // 500 mg + // .buildWithStructValue(); + // right = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.QUANTITY) + // .singular(true) + // .dataset(rightDataset) + // .idAndValueColumns() + // .build(); + // + // ucumQuantityLiteral1 = QuantityCollection.fromUcumString("500 'mg'", left, ucumService); + // ucumQuantityLiteral2 = QuantityCollection.fromUcumString("0.5 'g'", left, ucumService); + // ucumQuantityLiteral3 = QuantityCollection.fromUcumString("1.8 'm'", left, ucumService); + // calendarDurationLiteral1 = QuantityCollection.fromCalendarDurationString("30 days", left); + // calendarDurationLiteral2 = QuantityCollection.fromCalendarDurationString("60 seconds", left); + // calendarDurationLiteral3 = QuantityCollection.fromCalendarDurationString("1000 milliseconds", + // left); + // + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(left.getIdColumn())) + // .build(); + // } + // + // @Test + // void lessThan() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance("<"); + // final Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", false), // 500 mg < 500 mg + // RowFactory.create("patient-02", false), // 500 mg < 0.5 g + // RowFactory.create("patient-03", null), // 500 mg < 1.8 m + // RowFactory.create("patient-04", false), // 650 mg < 500 mg + // RowFactory.create("patient-05", null), // {} < 500 mg + // RowFactory.create("patient-06", null), // 500 mg < {} + // RowFactory.create("patient-07", false), // 30 d < 30 d + // RowFactory.create("patient-08", false), // 60 s < 60 s + // RowFactory.create("patient-09", false), // 1000 ms < 1000 ms + // RowFactory.create("patient-10", true) // 0.2 g < 500 mg + // ); + // } + // + // @Test + // void lessThanOrEqualTo() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance("<="); + // final Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", true), // 500 mg <= 500 mg + // RowFactory.create("patient-02", true), // 500 mg <= 0.5 g + // RowFactory.create("patient-03", null), // 500 mg <= 1.8 m + // RowFactory.create("patient-04", false), // 650 mg <= 500 mg + // RowFactory.create("patient-05", null), // {} <= 500 mg + // RowFactory.create("patient-06", null), // 500 mg <= {} + // RowFactory.create("patient-07", true), // 30 d <= 30 d + // RowFactory.create("patient-08", true), // 60 s <= 60 s + // RowFactory.create("patient-09", true), // 1000 ms <= 1000 ms + // RowFactory.create("patient-10", true) // 0.2 g <= 500 mg + // ); + // } + // + // @Test + // void greaterThanOrEqualTo() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance(">="); + // final Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", true), // 500 mg >= 500 mg + // RowFactory.create("patient-02", true), // 500 mg >= 0.5 g + // RowFactory.create("patient-03", null), // 500 mg >= 1.8 m + // RowFactory.create("patient-04", true), // 650 mg >= 500 mg + // RowFactory.create("patient-05", null), // {} >= 500 mg + // RowFactory.create("patient-06", null), // 500 mg >= {} + // RowFactory.create("patient-07", true), // 30 d >= 30 d + // RowFactory.create("patient-08", true), // 60 s >= 60 s + // RowFactory.create("patient-09", true), // 1000 ms >= 1000 ms + // RowFactory.create("patient-10", false) // 0.2 g >= 500 mg + // ); + // } + // + // @Test + // void greaterThan() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance(">"); + // final Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", false), // 500 mg > 500 mg + // RowFactory.create("patient-02", false), // 500 mg > 0.5 g + // RowFactory.create("patient-03", null), // 500 mg > 1.8 m + // RowFactory.create("patient-04", true), // 650 mg > 500 mg + // RowFactory.create("patient-05", null), // {} > 500 mg + // RowFactory.create("patient-06", null), // 500 mg > {} + // RowFactory.create("patient-07", false), // 30 d > 30 d + // RowFactory.create("patient-08", false), // 60 s > 60 s + // RowFactory.create("patient-09", false), // 1000 ms > 1000 ms + // RowFactory.create("patient-10", false) // 0.2 g > 500 mg + // ); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTest.java index 686a15e749..22a03704fd 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTest.java @@ -17,520 +17,494 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.DateCollection; -import au.csiro.pathling.fhirpath.collection.DecimalCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; -import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; -import au.csiro.pathling.fhirpath.literal.LiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.DatasetBuilder; -import au.csiro.pathling.test.builders.ElementPathBuilder; -import au.csiro.pathling.test.builders.ParserContextBuilder; -import ca.uhn.fhir.context.FhirContext; -import java.math.BigDecimal; -import java.text.ParseException; -import java.util.Collections; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import lombok.Value; -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.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest @TestInstance(Lifecycle.PER_CLASS) +@NotImplemented class ComparisonOperatorTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - static final String ID_ALIAS = "_abc123"; - - @Value - static class TestParameters { - - @Nonnull - String name; - - @Nonnull - Collection left; - - @Nonnull - Collection right; - - @Nonnull - Collection literal; - - @Nonnull - ParserContext context; - - @Override - public String toString() { - return name; - } - - } - - Stream parameters() { - return Stream.of( - "String", - "Integer", - "Decimal", - "DateTime", - "Date", - "Date (YYYY-MM)", - "Date (YYYY)" - ).map(this::buildTestParameters); - } - - TestParameters buildTestParameters(@Nonnull final String name) { - switch (name) { - case "String": - return buildStringExpressions(name); - case "Integer": - return buildIntegerExpressions(name); - case "Decimal": - return buildDecimalExpressions(name); - case "DateTime": - return buildDateTimeExpressions(name, - "2015-02-07T13:28:17-05:00", - "2015-02-08T13:28:17-05:00", - FHIRDefinedType.DATETIME); - case "Date": - return buildDateTimeExpressions(name, - "2015-02-07", - "2015-02-08", - FHIRDefinedType.DATE); - case "Date (YYYY-MM)": - return buildDateTimeExpressions(name, - "2015-02", - "2015-03", - FHIRDefinedType.DATE); - case "Date (YYYY)": - return buildDateTimeExpressions(name, - "2015", - "2016", - FHIRDefinedType.DATE); - default: - throw new RuntimeException("Invalid data type"); - } - } - - TestParameters buildStringExpressions(final String name) { - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-1", "Evelyn") - .withRow("patient-2", "Evelyn") - .withRow("patient-3", "Jude") - .withRow("patient-4", null) - .withRow("patient-5", "Evelyn") - .withRow("patient-6", null) - .build(); - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(leftDataset) - .idAndValueColumns() - .singular(true) - .build(); - final Dataset rightDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-1", "Evelyn") - .withRow("patient-2", "Jude") - .withRow("patient-3", "Evelyn") - .withRow("patient-4", "Evelyn") - .withRow("patient-5", null) - .withRow("patient-6", null) - .build(); - final PrimitivePath right = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(rightDataset) - .idAndValueColumns() - .singular(true) - .build(); - final StringLiteralPath literal = StringCollection.fromLiteral("'Evelyn'", left); - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(left.getIdColumn())) - .build(); - return new TestParameters(name, left, right, literal, context); - } - - TestParameters buildIntegerExpressions(final String name) { - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.IntegerType) - .withRow("patient-1", 1) - .withRow("patient-2", 1) - .withRow("patient-3", 2) - .withRow("patient-4", null) - .withRow("patient-5", 1) - .withRow("patient-6", null) - .build(); - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .dataset(leftDataset) - .idAndValueColumns() - .singular(true) - .build(); - final Dataset rightDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.IntegerType) - .withRow("patient-1", 1) - .withRow("patient-2", 2) - .withRow("patient-3", 1) - .withRow("patient-4", 1) - .withRow("patient-5", null) - .withRow("patient-6", null) - .build(); - final PrimitivePath right = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .dataset(rightDataset) - .idAndValueColumns() - .singular(true) - .build(); - final IntegerLiteralPath literal = IntegerLiteralPath.fromString("1", left); - final ParserContext context = new ParserContextBuilder(spark, fhirContext).groupingColumns( - Collections.singletonList(left.getIdColumn())).build(); - return new TestParameters(name, left, right, literal, context); - } - - TestParameters buildDecimalExpressions(final String name) { - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.createDecimalType()) - .withRow("patient-1", new BigDecimal("1.0")) - .withRow("patient-2", new BigDecimal("1.0")) - .withRow("patient-3", new BigDecimal("2.0")) - .withRow("patient-4", null) - .withRow("patient-5", new BigDecimal("1.0")) - .withRow("patient-6", null) - .build(); - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DECIMAL) - .dataset(leftDataset) - .idAndValueColumns() - .singular(true) - .build(); - final Dataset rightDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.createDecimalType()) - .withRow("patient-1", new BigDecimal("1.0")) - .withRow("patient-2", new BigDecimal("2.0")) - .withRow("patient-3", new BigDecimal("1.0")) - .withRow("patient-4", new BigDecimal("1.0")) - .withRow("patient-5", null) - .withRow("patient-6", null) - .build(); - final PrimitivePath right = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DECIMAL) - .dataset(rightDataset) - .idAndValueColumns() - .singular(true) - .build(); - final DecimalCollection literal = DecimalCollection.fromLiteral("1.0", left); - final ParserContext context = new ParserContextBuilder(spark, fhirContext).groupingColumns( - Collections.singletonList(left.getIdColumn())).build(); - return new TestParameters(name, left, right, literal, context); - } - - TestParameters buildDateTimeExpressions(final String name, - final String lesserDate, - final String greaterDate, - final FHIRDefinedType fhirType) { - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-1", lesserDate) - .withRow("patient-2", lesserDate) - .withRow("patient-3", greaterDate) - .withRow("patient-4", null) - .withRow("patient-5", lesserDate) - .withRow("patient-6", null) - .build(); - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(fhirType) - .dataset(leftDataset) - .idAndValueColumns() - .singular(true) - .build(); - final Dataset rightDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-1", lesserDate) - .withRow("patient-2", greaterDate) - .withRow("patient-3", lesserDate) - .withRow("patient-4", lesserDate) - .withRow("patient-5", null) - .withRow("patient-6", null) - .build(); - final PrimitivePath right = new ElementPathBuilder(spark) - .fhirType(fhirType) - .dataset(rightDataset) - .idAndValueColumns() - .singular(true) - .build(); - final LiteralPath literal; - try { - literal = (fhirType == FHIRDefinedType.DATETIME) - ? DateTimeLiteralPath.fromString(lesserDate, left) - : DateCollection.fromLiteral(lesserDate, left); - } catch (final ParseException e) { - throw new RuntimeException("Error parsing literal date or date time"); - } - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(left.getIdColumn())) - .build(); - return new TestParameters(name, left, right, literal, context); - } - - @ParameterizedTest - @MethodSource("parameters") - void lessThanOrEqualTo(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLeft(), - parameters.getRight()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<="); - final Collection result = comparisonOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", true), - RowFactory.create("patient-2", true), - RowFactory.create("patient-3", false), - RowFactory.create("patient-4", null), - RowFactory.create("patient-5", null), - RowFactory.create("patient-6", null) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void lessThan(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLeft(), - parameters.getRight()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<"); - final Collection result = comparisonOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", false), - RowFactory.create("patient-2", true), - RowFactory.create("patient-3", false), - RowFactory.create("patient-4", null), - RowFactory.create("patient-5", null), - RowFactory.create("patient-6", null) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void greaterThanOrEqualTo(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLeft(), - parameters.getRight()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">="); - final Collection result = comparisonOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", true), - RowFactory.create("patient-2", false), - RowFactory.create("patient-3", true), - RowFactory.create("patient-4", null), - RowFactory.create("patient-5", null), - RowFactory.create("patient-6", null) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void greaterThan(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLeft(), - parameters.getRight()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">"); - final Collection result = comparisonOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", false), - RowFactory.create("patient-2", false), - RowFactory.create("patient-3", true), - RowFactory.create("patient-4", null), - RowFactory.create("patient-5", null), - RowFactory.create("patient-6", null) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void literalLessThanOrEqualTo(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLiteral(), - parameters.getRight()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<="); - final Collection result = comparisonOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", true), - RowFactory.create("patient-2", true), - RowFactory.create("patient-3", true), - RowFactory.create("patient-4", true), - RowFactory.create("patient-5", null), - RowFactory.create("patient-6", null) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void literalLessThan(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLiteral(), - parameters.getRight()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<"); - final Collection result = comparisonOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", false), - RowFactory.create("patient-2", true), - RowFactory.create("patient-3", false), - RowFactory.create("patient-4", false), - RowFactory.create("patient-5", null), - RowFactory.create("patient-6", null) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void literalGreaterThanOrEqualTo(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLiteral(), - parameters.getRight()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">="); - final Collection result = comparisonOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", true), - RowFactory.create("patient-2", false), - RowFactory.create("patient-3", true), - RowFactory.create("patient-4", true), - RowFactory.create("patient-5", null), - RowFactory.create("patient-6", null) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void literalGreaterThan(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLiteral(), - parameters.getRight()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">"); - final Collection result = comparisonOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", false), - RowFactory.create("patient-2", false), - RowFactory.create("patient-3", false), - RowFactory.create("patient-4", false), - RowFactory.create("patient-5", null), - RowFactory.create("patient-6", null) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void lessThanOrEqualToLiteral(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLeft(), - parameters.getLiteral()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<="); - final Collection result = comparisonOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", true), - RowFactory.create("patient-2", true), - RowFactory.create("patient-3", false), - RowFactory.create("patient-4", null), - RowFactory.create("patient-5", true), - RowFactory.create("patient-6", null) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void lessThanLiteral(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLeft(), - parameters.getLiteral()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<"); - final Collection result = comparisonOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", false), - RowFactory.create("patient-2", false), - RowFactory.create("patient-3", false), - RowFactory.create("patient-4", null), - RowFactory.create("patient-5", false), - RowFactory.create("patient-6", null) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void greaterThanOrEqualToLiteral(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLeft(), - parameters.getLiteral()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">="); - final Collection result = comparisonOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", true), - RowFactory.create("patient-2", true), - RowFactory.create("patient-3", true), - RowFactory.create("patient-4", null), - RowFactory.create("patient-5", true), - RowFactory.create("patient-6", null) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void greaterThanLiteral(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLeft(), - parameters.getLiteral()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">"); - final Collection result = comparisonOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", false), - RowFactory.create("patient-2", false), - RowFactory.create("patient-3", true), - RowFactory.create("patient-4", null), - RowFactory.create("patient-5", false), - RowFactory.create("patient-6", null) - ); - } + // TODO: implement with columns + // + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // static final String ID_ALIAS = "_abc123"; + // + // @Value + // static class TestParameters { + // + // @Nonnull + // String name; + // + // @Nonnull + // Collection left; + // + // @Nonnull + // Collection right; + // + // @Nonnull + // Collection literal; + // + // @Nonnull + // ParserContext context; + // + // @Override + // public String toString() { + // return name; + // } + // + // } + // + // Stream parameters() { + // return Stream.of( + // "String", + // "Integer", + // "Decimal", + // "DateTime", + // "Date", + // "Date (YYYY-MM)", + // "Date (YYYY)" + // ).map(this::buildTestParameters); + // } + // + // TestParameters buildTestParameters(@Nonnull final String name) { + // switch (name) { + // case "String": + // return buildStringExpressions(name); + // case "Integer": + // return buildIntegerExpressions(name); + // case "Decimal": + // return buildDecimalExpressions(name); + // case "DateTime": + // return buildDateTimeExpressions(name, + // "2015-02-07T13:28:17-05:00", + // "2015-02-08T13:28:17-05:00", + // FHIRDefinedType.DATETIME); + // case "Date": + // return buildDateTimeExpressions(name, + // "2015-02-07", + // "2015-02-08", + // FHIRDefinedType.DATE); + // case "Date (YYYY-MM)": + // return buildDateTimeExpressions(name, + // "2015-02", + // "2015-03", + // FHIRDefinedType.DATE); + // case "Date (YYYY)": + // return buildDateTimeExpressions(name, + // "2015", + // "2016", + // FHIRDefinedType.DATE); + // default: + // throw new RuntimeException("Invalid data type"); + // } + // } + // + // TestParameters buildStringExpressions(final String name) { + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "Evelyn") + // .withRow("patient-2", "Evelyn") + // .withRow("patient-3", "Jude") + // .withRow("patient-4", null) + // .withRow("patient-5", "Evelyn") + // .withRow("patient-6", null) + // .build(); + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(leftDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // final Dataset rightDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "Evelyn") + // .withRow("patient-2", "Jude") + // .withRow("patient-3", "Evelyn") + // .withRow("patient-4", "Evelyn") + // .withRow("patient-5", null) + // .withRow("patient-6", null) + // .build(); + // final PrimitivePath right = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(rightDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // final StringLiteralPath literal = StringCollection.fromLiteral("'Evelyn'", left); + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(left.getIdColumn())) + // .build(); + // return new TestParameters(name, left, right, literal, context); + // } + // + // TestParameters buildIntegerExpressions(final String name) { + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.IntegerType) + // .withRow("patient-1", 1) + // .withRow("patient-2", 1) + // .withRow("patient-3", 2) + // .withRow("patient-4", null) + // .withRow("patient-5", 1) + // .withRow("patient-6", null) + // .build(); + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .dataset(leftDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // final Dataset rightDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.IntegerType) + // .withRow("patient-1", 1) + // .withRow("patient-2", 2) + // .withRow("patient-3", 1) + // .withRow("patient-4", 1) + // .withRow("patient-5", null) + // .withRow("patient-6", null) + // .build(); + // final PrimitivePath right = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .dataset(rightDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // final IntegerLiteralPath literal = IntegerLiteralPath.fromString("1", left); + // final ParserContext context = new ParserContextBuilder(spark, fhirContext).groupingColumns( + // Collections.singletonList(left.getIdColumn())).build(); + // return new TestParameters(name, left, right, literal, context); + // } + // + // TestParameters buildDecimalExpressions(final String name) { + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.createDecimalType()) + // .withRow("patient-1", new BigDecimal("1.0")) + // .withRow("patient-2", new BigDecimal("1.0")) + // .withRow("patient-3", new BigDecimal("2.0")) + // .withRow("patient-4", null) + // .withRow("patient-5", new BigDecimal("1.0")) + // .withRow("patient-6", null) + // .build(); + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DECIMAL) + // .dataset(leftDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // final Dataset rightDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.createDecimalType()) + // .withRow("patient-1", new BigDecimal("1.0")) + // .withRow("patient-2", new BigDecimal("2.0")) + // .withRow("patient-3", new BigDecimal("1.0")) + // .withRow("patient-4", new BigDecimal("1.0")) + // .withRow("patient-5", null) + // .withRow("patient-6", null) + // .build(); + // final PrimitivePath right = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DECIMAL) + // .dataset(rightDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // final DecimalCollection literal = DecimalCollection.fromLiteral("1.0", left); + // final ParserContext context = new ParserContextBuilder(spark, fhirContext).groupingColumns( + // Collections.singletonList(left.getIdColumn())).build(); + // return new TestParameters(name, left, right, literal, context); + // } + // + // TestParameters buildDateTimeExpressions(final String name, + // final String lesserDate, + // final String greaterDate, + // final FHIRDefinedType fhirType) { + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", lesserDate) + // .withRow("patient-2", lesserDate) + // .withRow("patient-3", greaterDate) + // .withRow("patient-4", null) + // .withRow("patient-5", lesserDate) + // .withRow("patient-6", null) + // .build(); + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(fhirType) + // .dataset(leftDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // final Dataset rightDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", lesserDate) + // .withRow("patient-2", greaterDate) + // .withRow("patient-3", lesserDate) + // .withRow("patient-4", lesserDate) + // .withRow("patient-5", null) + // .withRow("patient-6", null) + // .build(); + // final PrimitivePath right = new ElementPathBuilder(spark) + // .fhirType(fhirType) + // .dataset(rightDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // final LiteralPath literal; + // try { + // literal = (fhirType == FHIRDefinedType.DATETIME) + // ? DateTimeLiteralPath.fromString(lesserDate, left) + // : DateCollection.fromLiteral(lesserDate, left); + // } catch (final ParseException e) { + // throw new RuntimeException("Error parsing literal date or date time"); + // } + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(left.getIdColumn())) + // .build(); + // return new TestParameters(name, left, right, literal, context); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void lessThanOrEqualTo(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLeft(), + // parameters.getRight()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<="); + // final Collection result = comparisonOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", true), + // RowFactory.create("patient-2", true), + // RowFactory.create("patient-3", false), + // RowFactory.create("patient-4", null), + // RowFactory.create("patient-5", null), + // RowFactory.create("patient-6", null) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void lessThan(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLeft(), + // parameters.getRight()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<"); + // final Collection result = comparisonOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", false), + // RowFactory.create("patient-2", true), + // RowFactory.create("patient-3", false), + // RowFactory.create("patient-4", null), + // RowFactory.create("patient-5", null), + // RowFactory.create("patient-6", null) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void greaterThanOrEqualTo(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLeft(), + // parameters.getRight()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">="); + // final Collection result = comparisonOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", true), + // RowFactory.create("patient-2", false), + // RowFactory.create("patient-3", true), + // RowFactory.create("patient-4", null), + // RowFactory.create("patient-5", null), + // RowFactory.create("patient-6", null) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void greaterThan(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLeft(), + // parameters.getRight()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">"); + // final Collection result = comparisonOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", false), + // RowFactory.create("patient-2", false), + // RowFactory.create("patient-3", true), + // RowFactory.create("patient-4", null), + // RowFactory.create("patient-5", null), + // RowFactory.create("patient-6", null) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void literalLessThanOrEqualTo(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLiteral(), + // parameters.getRight()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<="); + // final Collection result = comparisonOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", true), + // RowFactory.create("patient-2", true), + // RowFactory.create("patient-3", true), + // RowFactory.create("patient-4", true), + // RowFactory.create("patient-5", null), + // RowFactory.create("patient-6", null) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void literalLessThan(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLiteral(), + // parameters.getRight()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<"); + // final Collection result = comparisonOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", false), + // RowFactory.create("patient-2", true), + // RowFactory.create("patient-3", false), + // RowFactory.create("patient-4", false), + // RowFactory.create("patient-5", null), + // RowFactory.create("patient-6", null) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void literalGreaterThanOrEqualTo(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLiteral(), + // parameters.getRight()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">="); + // final Collection result = comparisonOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", true), + // RowFactory.create("patient-2", false), + // RowFactory.create("patient-3", true), + // RowFactory.create("patient-4", true), + // RowFactory.create("patient-5", null), + // RowFactory.create("patient-6", null) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void literalGreaterThan(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLiteral(), + // parameters.getRight()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">"); + // final Collection result = comparisonOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", false), + // RowFactory.create("patient-2", false), + // RowFactory.create("patient-3", false), + // RowFactory.create("patient-4", false), + // RowFactory.create("patient-5", null), + // RowFactory.create("patient-6", null) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void lessThanOrEqualToLiteral(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLeft(), + // parameters.getLiteral()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<="); + // final Collection result = comparisonOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", true), + // RowFactory.create("patient-2", true), + // RowFactory.create("patient-3", false), + // RowFactory.create("patient-4", null), + // RowFactory.create("patient-5", true), + // RowFactory.create("patient-6", null) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void lessThanLiteral(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLeft(), + // parameters.getLiteral()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance("<"); + // final Collection result = comparisonOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", false), + // RowFactory.create("patient-2", false), + // RowFactory.create("patient-3", false), + // RowFactory.create("patient-4", null), + // RowFactory.create("patient-5", false), + // RowFactory.create("patient-6", null) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void greaterThanOrEqualToLiteral(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLeft(), + // parameters.getLiteral()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">="); + // final Collection result = comparisonOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", true), + // RowFactory.create("patient-2", true), + // RowFactory.create("patient-3", true), + // RowFactory.create("patient-4", null), + // RowFactory.create("patient-5", true), + // RowFactory.create("patient-6", null) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void greaterThanLiteral(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLeft(), + // parameters.getLiteral()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">"); + // final Collection result = comparisonOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", false), + // RowFactory.create("patient-2", false), + // RowFactory.create("patient-3", true), + // RowFactory.create("patient-4", null), + // RowFactory.create("patient-5", false), + // RowFactory.create("patient-6", null) + // ); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTimeTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTimeTest.java index 3a2cecec8e..0dafb6115e 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTimeTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorTimeTest.java @@ -17,225 +17,206 @@ 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.collection.Collection; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.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.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class ComparisonOperatorTimeTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - static final String ID_ALIAS = "_abc123"; - private PrimitivePath left; - private PrimitivePath right; - private ParserContext parserContext; - - @BeforeEach - void setUp() { - final Optional optionalDefinition = FhirHelpers - .getChildOfResource(fhirContext, "Observation", "valueTime"); - assertTrue(optionalDefinition.isPresent()); - final ElementDefinition definition = optionalDefinition.get(); - assertTrue(definition.getFhirType().isPresent()); - assertEquals(FHIRDefinedType.TIME, definition.getFhirType().get()); - - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-01", "03:15:36") // Equal, hours, minutes and seconds - .withRow("patient-02", "03:15") // Equal, hours and minutes - .withRow("patient-03", "03") // Equal, hours - .withRow("patient-04", "03:15:36") // Different precisions - .withRow("patient-05", "03:15:36") // Less than, hours, minutes and seconds - .withRow("patient-06", "03:15") // Less than, hours and minutes - .withRow("patient-07", "03") // Less than, hours - .withRow("patient-08", "03:15:36") // Greater than, hours, minutes and seconds - .withRow("patient-09", "03:15") // Greater than, hours and minutes - .withRow("patient-10", "03") // Greater than, hours - .build(); - left = new ElementPathBuilder(spark) - .dataset(leftDataset) - .idAndValueColumns() - .expression("valueTime") - .singular(true) - .definition(definition) - .buildDefined(); - - final Dataset rightDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-01", "03:15:36") // Equal, hours, minutes and seconds - .withRow("patient-02", "03:15") // Equal, hours and minutes - .withRow("patient-03", "03") // Equal, hours - .withRow("patient-04", "03:15") // Different precisions - .withRow("patient-05", "03:16:36") // Less than, hours, minutes and seconds - .withRow("patient-06", "03:16") // Less than, hours and minutes - .withRow("patient-07", "04") // Less than, hours - .withRow("patient-08", "02:15:36") // Greater than, hours, minutes and seconds - .withRow("patient-09", "03:14") // Greater than, hours and minutes - .withRow("patient-10", "01") // Greater than, hours - .build(); - right = new ElementPathBuilder(spark) - .dataset(rightDataset) - .idAndValueColumns() - .expression("valueTime") - .singular(true) - .definition(definition) - .buildDefined(); - - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(left.getIdColumn())) - .build(); - } - - @Test - void equals() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("="); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", true), // Equal, hours, minutes and seconds - RowFactory.create("patient-02", true), // Equal, hours and minutes - RowFactory.create("patient-03", true), // Equal, hours - RowFactory.create("patient-04", false), // Different precisions - RowFactory.create("patient-05", false), // Less than, hours, minutes and seconds - RowFactory.create("patient-06", false), // Less than, hours and minutes - RowFactory.create("patient-07", false), // Less than, hours - RowFactory.create("patient-08", false), // Greater than, hours, minutes and seconds - RowFactory.create("patient-09", false), // Greater than, hours and minutes - RowFactory.create("patient-10", false) // Greater than, hours - ); - } - - @Test - void notEquals() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("!="); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", false), // Equal, hours, minutes and seconds - RowFactory.create("patient-02", false), // Equal, hours and minutes - RowFactory.create("patient-03", false), // Equal, hours - RowFactory.create("patient-04", true), // Different precisions - RowFactory.create("patient-05", true), // Less than, hours, minutes and seconds - RowFactory.create("patient-06", true), // Less than, hours and minutes - RowFactory.create("patient-07", true), // Less than, hours - RowFactory.create("patient-08", true), // Greater than, hours, minutes and seconds - RowFactory.create("patient-09", true), // Greater than, hours and minutes - RowFactory.create("patient-10", true) // Greater than, hours - ); - } - - @Test - void lessThan() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("<"); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", false), // Equal, hours, minutes and seconds - RowFactory.create("patient-02", false), // Equal, hours and minutes - RowFactory.create("patient-03", false), // Equal, hours - RowFactory.create("patient-04", false), // Different precisions - RowFactory.create("patient-05", true), // Less than, hours, minutes and seconds - RowFactory.create("patient-06", true), // Less than, hours and minutes - RowFactory.create("patient-07", true), // Less than, hours - RowFactory.create("patient-08", false), // Greater than, hours, minutes and seconds - RowFactory.create("patient-09", false), // Greater than, hours and minutes - RowFactory.create("patient-10", false) // Greater than, hours - ); - } - - @Test - void lessThanOrEqualTo() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance("<="); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", true), // Equal, hours, minutes and seconds - RowFactory.create("patient-02", true), // Equal, hours and minutes - RowFactory.create("patient-03", true), // Equal, hours - RowFactory.create("patient-04", false), // Different precisions - RowFactory.create("patient-05", true), // Less than, hours, minutes and seconds - RowFactory.create("patient-06", true), // Less than, hours and minutes - RowFactory.create("patient-07", true), // Less than, hours - RowFactory.create("patient-08", false), // Greater than, hours, minutes and seconds - RowFactory.create("patient-09", false), // Greater than, hours and minutes - RowFactory.create("patient-10", false) // Greater than, hours - ); - } - - @Test - void greaterThan() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance(">"); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", false), // Equal, hours, minutes and seconds - RowFactory.create("patient-02", false), // Equal, hours and minutes - RowFactory.create("patient-03", false), // Equal, hours - RowFactory.create("patient-04", true), // Different precisions - RowFactory.create("patient-05", false), // Less than, hours, minutes and seconds - RowFactory.create("patient-06", false), // Less than, hours and minutes - RowFactory.create("patient-07", false), // Less than, hours - RowFactory.create("patient-08", true), // Greater than, hours, minutes and seconds - RowFactory.create("patient-09", true), // Greater than, hours and minutes - RowFactory.create("patient-10", true) // Greater than, hours - ); - } - - @Test - void greaterThanOrEqualTo() { - final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator lessThan = BinaryOperator.getInstance(">="); - final Collection result = lessThan.invoke(comparisonInput); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-01", true), // Equal, hours, minutes and seconds - RowFactory.create("patient-02", true), // Equal, hours and minutes - RowFactory.create("patient-03", true), // Equal, hours - RowFactory.create("patient-04", true), // Different precisions - RowFactory.create("patient-05", false), // Less than, hours, minutes and seconds - RowFactory.create("patient-06", false), // Less than, hours and minutes - RowFactory.create("patient-07", false), // Less than, hours - RowFactory.create("patient-08", true), // Greater than, hours, minutes and seconds - RowFactory.create("patient-09", true), // Greater than, hours and minutes - RowFactory.create("patient-10", true) // Greater than, hours - ); - } - + // TODO: implement with columns + // + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // static final String ID_ALIAS = "_abc123"; + // private PrimitivePath left; + // private PrimitivePath right; + // private ParserContext parserContext; + // + // @BeforeEach + // void setUp() { + // final Optional optionalDefinition = FhirHelpers + // .getChildOfResource(fhirContext, "Observation", "valueTime"); + // assertTrue(optionalDefinition.isPresent()); + // final ElementDefinition definition = optionalDefinition.get(); + // assertTrue(definition.getFhirType().isPresent()); + // assertEquals(FHIRDefinedType.TIME, definition.getFhirType().get()); + // + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-01", "03:15:36") // Equal, hours, minutes and seconds + // .withRow("patient-02", "03:15") // Equal, hours and minutes + // .withRow("patient-03", "03") // Equal, hours + // .withRow("patient-04", "03:15:36") // Different precisions + // .withRow("patient-05", "03:15:36") // Less than, hours, minutes and seconds + // .withRow("patient-06", "03:15") // Less than, hours and minutes + // .withRow("patient-07", "03") // Less than, hours + // .withRow("patient-08", "03:15:36") // Greater than, hours, minutes and seconds + // .withRow("patient-09", "03:15") // Greater than, hours and minutes + // .withRow("patient-10", "03") // Greater than, hours + // .build(); + // left = new ElementPathBuilder(spark) + // .dataset(leftDataset) + // .idAndValueColumns() + // .expression("valueTime") + // .singular(true) + // .definition(definition) + // .buildDefined(); + // + // final Dataset rightDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-01", "03:15:36") // Equal, hours, minutes and seconds + // .withRow("patient-02", "03:15") // Equal, hours and minutes + // .withRow("patient-03", "03") // Equal, hours + // .withRow("patient-04", "03:15") // Different precisions + // .withRow("patient-05", "03:16:36") // Less than, hours, minutes and seconds + // .withRow("patient-06", "03:16") // Less than, hours and minutes + // .withRow("patient-07", "04") // Less than, hours + // .withRow("patient-08", "02:15:36") // Greater than, hours, minutes and seconds + // .withRow("patient-09", "03:14") // Greater than, hours and minutes + // .withRow("patient-10", "01") // Greater than, hours + // .build(); + // right = new ElementPathBuilder(spark) + // .dataset(rightDataset) + // .idAndValueColumns() + // .expression("valueTime") + // .singular(true) + // .definition(definition) + // .buildDefined(); + // + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(left.getIdColumn())) + // .build(); + // } + // + // @Test + // void equals() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("="); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", true), // Equal, hours, minutes and seconds + // RowFactory.create("patient-02", true), // Equal, hours and minutes + // RowFactory.create("patient-03", true), // Equal, hours + // RowFactory.create("patient-04", false), // Different precisions + // RowFactory.create("patient-05", false), // Less than, hours, minutes and seconds + // RowFactory.create("patient-06", false), // Less than, hours and minutes + // RowFactory.create("patient-07", false), // Less than, hours + // RowFactory.create("patient-08", false), // Greater than, hours, minutes and seconds + // RowFactory.create("patient-09", false), // Greater than, hours and minutes + // RowFactory.create("patient-10", false) // Greater than, hours + // ); + // } + // + // @Test + // void notEquals() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("!="); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", false), // Equal, hours, minutes and seconds + // RowFactory.create("patient-02", false), // Equal, hours and minutes + // RowFactory.create("patient-03", false), // Equal, hours + // RowFactory.create("patient-04", true), // Different precisions + // RowFactory.create("patient-05", true), // Less than, hours, minutes and seconds + // RowFactory.create("patient-06", true), // Less than, hours and minutes + // RowFactory.create("patient-07", true), // Less than, hours + // RowFactory.create("patient-08", true), // Greater than, hours, minutes and seconds + // RowFactory.create("patient-09", true), // Greater than, hours and minutes + // RowFactory.create("patient-10", true) // Greater than, hours + // ); + // } + // + // @Test + // void lessThan() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("<"); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", false), // Equal, hours, minutes and seconds + // RowFactory.create("patient-02", false), // Equal, hours and minutes + // RowFactory.create("patient-03", false), // Equal, hours + // RowFactory.create("patient-04", false), // Different precisions + // RowFactory.create("patient-05", true), // Less than, hours, minutes and seconds + // RowFactory.create("patient-06", true), // Less than, hours and minutes + // RowFactory.create("patient-07", true), // Less than, hours + // RowFactory.create("patient-08", false), // Greater than, hours, minutes and seconds + // RowFactory.create("patient-09", false), // Greater than, hours and minutes + // RowFactory.create("patient-10", false) // Greater than, hours + // ); + // } + // + // @Test + // void lessThanOrEqualTo() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance("<="); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", true), // Equal, hours, minutes and seconds + // RowFactory.create("patient-02", true), // Equal, hours and minutes + // RowFactory.create("patient-03", true), // Equal, hours + // RowFactory.create("patient-04", false), // Different precisions + // RowFactory.create("patient-05", true), // Less than, hours, minutes and seconds + // RowFactory.create("patient-06", true), // Less than, hours and minutes + // RowFactory.create("patient-07", true), // Less than, hours + // RowFactory.create("patient-08", false), // Greater than, hours, minutes and seconds + // RowFactory.create("patient-09", false), // Greater than, hours and minutes + // RowFactory.create("patient-10", false) // Greater than, hours + // ); + // } + // + // @Test + // void greaterThan() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance(">"); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", false), // Equal, hours, minutes and seconds + // RowFactory.create("patient-02", false), // Equal, hours and minutes + // RowFactory.create("patient-03", false), // Equal, hours + // RowFactory.create("patient-04", true), // Different precisions + // RowFactory.create("patient-05", false), // Less than, hours, minutes and seconds + // RowFactory.create("patient-06", false), // Less than, hours and minutes + // RowFactory.create("patient-07", false), // Less than, hours + // RowFactory.create("patient-08", true), // Greater than, hours, minutes and seconds + // RowFactory.create("patient-09", true), // Greater than, hours and minutes + // RowFactory.create("patient-10", true) // Greater than, hours + // ); + // } + // + // @Test + // void greaterThanOrEqualTo() { + // final BinaryOperatorInput comparisonInput = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator lessThan = BinaryOperator.getInstance(">="); + // final Collection result = lessThan.invoke(comparisonInput); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-01", true), // Equal, hours, minutes and seconds + // RowFactory.create("patient-02", true), // Equal, hours and minutes + // RowFactory.create("patient-03", true), // Equal, hours + // RowFactory.create("patient-04", true), // Different precisions + // RowFactory.create("patient-05", false), // Less than, hours, minutes and seconds + // RowFactory.create("patient-06", false), // Less than, hours and minutes + // RowFactory.create("patient-07", false), // Less than, hours + // RowFactory.create("patient-08", true), // Greater than, hours, minutes and seconds + // RowFactory.create("patient-09", true), // Greater than, hours and minutes + // RowFactory.create("patient-10", true) // Greater than, hours + // ); + // } + // } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorValidationTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorValidationTest.java index 3391488eac..956189c796 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorValidationTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/ComparisonOperatorValidationTest.java @@ -17,70 +17,61 @@ package au.csiro.pathling.fhirpath.operator; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.ElementPathBuilder; -import au.csiro.pathling.test.builders.ParserContextBuilder; -import ca.uhn.fhir.context.FhirContext; -import org.apache.spark.sql.SparkSession; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class ComparisonOperatorValidationTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - ParserContext parserContext; - - @BeforeEach - void setUp() { - parserContext = new ParserContextBuilder(spark, fhirContext).build(); - } - - @Test - void operandsAreNotComparable() { - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.BOOLEAN) - .singular(true) - .expression("foo") - .build(); - final PrimitivePath right = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .singular(true) - .expression("bar") - .build(); - - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> comparisonOperator.invoke(input)); - assertEquals("Left operand to > operator is not comparable to right operand: foo > bar", - error.getMessage()); - - // Now test the right operand. - final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); - final InvalidUserInputError reversedError = assertThrows( - InvalidUserInputError.class, - () -> comparisonOperator.invoke(reversedInput)); - assertEquals( - "Left operand to > operator is not comparable to right operand: bar > foo", - reversedError.getMessage()); - } + // TODO: implement with columns + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // ParserContext parserContext; + // + // @BeforeEach + // void setUp() { + // parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // } + // + // @Test + // void operandsAreNotComparable() { + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.BOOLEAN) + // .singular(true) + // .expression("foo") + // .build(); + // final PrimitivePath right = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .singular(true) + // .expression("bar") + // .build(); + // + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance(">"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> comparisonOperator.invoke(input)); + // assertEquals("Left operand to > operator is not comparable to right operand: foo > bar", + // error.getMessage()); + // + // // Now test the right operand. + // final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); + // final InvalidUserInputError reversedError = assertThrows( + // InvalidUserInputError.class, + // () -> comparisonOperator.invoke(reversedInput)); + // assertEquals( + // "Left operand to > operator is not comparable to right operand: bar > foo", + // reversedError.getMessage()); + // } + // } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/DateArithmeticTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/DateArithmeticTest.java index 03e2717be8..1fc0779b69 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/DateArithmeticTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/DateArithmeticTest.java @@ -17,779 +17,753 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.DateCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.QuantityCollection; -import au.csiro.pathling.fhirpath.collection.TimeCollection; -import au.csiro.pathling.fhirpath.literal.DateLiteralPath; -import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; -import au.csiro.pathling.fhirpath.literal.TimeLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.DatasetBuilder; -import au.csiro.pathling.test.builders.ElementPathBuilder; -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.Collections; -import java.util.List; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import lombok.Value; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -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.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.springframework.beans.factory.annotation.Autowired; @SpringBootUnitTest @TestInstance(Lifecycle.PER_CLASS) +@NotImplemented public class DateArithmeticTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - static final String ID_ALIAS = "_abc123"; - - @Value - static class TestParameters { - - @Nonnull - String name; - - @Nonnull - Collection left; - - @Nonnull - Collection right; - - @Nonnull - ParserContext context; - - @Nonnull - BinaryOperator operator; - - @Nonnull - Dataset expectedResult; - - @Override - public String toString() { - return name; - } - - } - - @Nonnull - Stream parameters() throws ParseException { - final List parameters = new ArrayList<>(); - - final Dataset dateTimeDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07T13:28:17.000-05:00") - .withRow("patient-2", "2017-01-01T00:00:00Z") - .withRow("patient-3", "2025-06-21T00:15:00+10:00") - .build(); - final PrimitivePath dateTimePath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DATETIME) - .dataset(dateTimeDataset) - .idAndValueColumns() - .singular(true) - .build(); - - final Dataset dateDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07") - .withRow("patient-2", "2017-01-01") - .withRow("patient-3", "2025-06-21") - .build(); - final PrimitivePath datePath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DATE) - .dataset(dateDataset) - .idAndValueColumns() - .singular(true) - .build(); - - final Dataset timeDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.StringType) - .withRow("patient-1", "13:28:17") - .withRow("patient-2", "08:00") - .withRow("patient-3", "00") - .build(); - final PrimitivePath timePath = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.TIME) - .dataset(timeDataset) - .idAndValueColumns() - .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 PrimitivePath 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 = DateCollection.fromLiteral("@2015-02-07", datePath); - final TimeLiteralPath timeLiteral = TimeCollection.fromLiteral("@T08:00", timePath); - - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(dateTimePath.getIdColumn())) - .build(); - - parameters.addAll(dateTimeAddition(dateTimePath, context)); - 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)); - parameters.addAll(dateLiteralSubtraction(dateLiteral, context)); - - return parameters.stream(); - } - - java.util.Collection dateTimeAddition( - final Collection dateTimePath, final ParserContext context) { - final List parameters = new ArrayList<>(); - parameters.add(new TestParameters("DateTime + 10 years", dateTimePath, - QuantityCollection.fromCalendarDurationString("10 years", dateTimePath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2025-02-07T13:28:17.000-05:00") - .withRow("patient-2", "2027-01-01T00:00:00Z") - .withRow("patient-3", "2035-06-21T00:15:00+10:00") - .build()) - ); - - parameters.add(new TestParameters("DateTime + 9 months", dateTimePath, - QuantityCollection.fromCalendarDurationString("9 months", dateTimePath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-11-07T13:28:17.000-05:00") - .withRow("patient-2", "2017-10-01T00:00:00Z") - .withRow("patient-3", "2026-03-21T00:15:00+10:00") - .build()) - ); - - parameters.add(new TestParameters("DateTime + 30 days", dateTimePath, - QuantityCollection.fromCalendarDurationString("30 days", dateTimePath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-03-09T13:28:17.000-05:00") - .withRow("patient-2", "2017-01-31T00:00:00Z") - .withRow("patient-3", "2025-07-21T00:15:00+10:00") - .build()) - ); - - parameters.add(new TestParameters("DateTime + 12 hours", dateTimePath, - QuantityCollection.fromCalendarDurationString("12 hours", dateTimePath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-08T01:28:17.000-05:00") - .withRow("patient-2", "2017-01-01T12:00:00Z") - .withRow("patient-3", "2025-06-21T12:15:00+10:00") - .build()) - ); - - parameters.add(new TestParameters("DateTime + 30 minutes", dateTimePath, - QuantityCollection.fromCalendarDurationString("30 minutes", dateTimePath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07T13:58:17.000-05:00") - .withRow("patient-2", "2017-01-01T00:30:00Z") - .withRow("patient-3", "2025-06-21T00:45:00+10:00") - .build()) - ); - - parameters.add(new TestParameters("DateTime + 10 seconds", dateTimePath, - QuantityCollection.fromCalendarDurationString("10 seconds", dateTimePath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07T13:28:27.000-05:00") - .withRow("patient-2", "2017-01-01T00:00:10Z") - .withRow("patient-3", "2025-06-21T00:15:10+10:00") - .build()) - ); - - parameters.add(new TestParameters("DateTime + 300 milliseconds", dateTimePath, - QuantityCollection.fromCalendarDurationString("300 milliseconds", dateTimePath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07T13:28:17.300-05:00") - .withRow("patient-2", "2017-01-01T00:00:00Z") - .withRow("patient-3", "2025-06-21T00:15:00+10:00") - .build()) - ); - return parameters; - } - - java.util.Collection dateTimeSubtraction( - final Collection dateTimePath, final ParserContext context) { - final List parameters = new ArrayList<>(); - parameters.add(new TestParameters("DateTime - 10 years", dateTimePath, - QuantityCollection.fromCalendarDurationString("10 years", dateTimePath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2005-02-07T13:28:17.000-05:00") - .withRow("patient-2", "2007-01-01T00:00:00Z") - .withRow("patient-3", "2015-06-21T00:15:00+10:00") - .build()) - ); - - parameters.add(new TestParameters("DateTime - 9 months", dateTimePath, - QuantityCollection.fromCalendarDurationString("9 months", dateTimePath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2014-05-07T13:28:17.000-05:00") - .withRow("patient-2", "2016-04-01T00:00:00Z") - .withRow("patient-3", "2024-09-21T00:15:00+10:00") - .build()) - ); - - parameters.add(new TestParameters("DateTime - 30 days", dateTimePath, - QuantityCollection.fromCalendarDurationString("30 days", dateTimePath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-01-08T13:28:17.000-05:00") - .withRow("patient-2", "2016-12-02T00:00:00Z") - .withRow("patient-3", "2025-05-22T00:15:00+10:00") - .build()) - ); - - parameters.add(new TestParameters("DateTime - 12 hours", dateTimePath, - QuantityCollection.fromCalendarDurationString("12 hours", dateTimePath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07T01:28:17.000-05:00") - .withRow("patient-2", "2016-12-31T12:00:00Z") - .withRow("patient-3", "2025-06-20T12:15:00+10:00") - .build()) - ); - - parameters.add(new TestParameters("DateTime - 30 minutes", dateTimePath, - QuantityCollection.fromCalendarDurationString("30 minutes", dateTimePath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07T12:58:17.000-05:00") - .withRow("patient-2", "2016-12-31T23:30:00Z") - .withRow("patient-3", "2025-06-20T23:45:00+10:00") - .build()) - ); - - parameters.add(new TestParameters("DateTime - 10 seconds", dateTimePath, - QuantityCollection.fromCalendarDurationString("10 seconds", dateTimePath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07T13:28:07.000-05:00") - .withRow("patient-2", "2016-12-31T23:59:50Z") - .withRow("patient-3", "2025-06-21T00:14:50+10:00") - .build()) - ); - - parameters.add(new TestParameters("DateTime - 300 milliseconds", dateTimePath, - QuantityCollection.fromCalendarDurationString("300 milliseconds", dateTimePath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07T13:28:16.700-05:00") - .withRow("patient-2", "2016-12-31T23:59:59Z") - .withRow("patient-3", "2025-06-21T00:14:59+10:00") - .build()) - ); - return parameters; - } - - java.util.Collection dateAddition( - final Collection datePath, final ParserContext context) { - final List parameters = new ArrayList<>(); - parameters.add(new TestParameters("Date + 10 years", datePath, - QuantityCollection.fromCalendarDurationString("10 years", datePath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2025-02-07") - .withRow("patient-2", "2027-01-01") - .withRow("patient-3", "2035-06-21") - .build()) - ); - - parameters.add(new TestParameters("Date + 9 months", datePath, - QuantityCollection.fromCalendarDurationString("9 months", datePath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-11-07") - .withRow("patient-2", "2017-10-01") - .withRow("patient-3", "2026-03-21") - .build()) - ); - - parameters.add(new TestParameters("Date + 30 days", datePath, - QuantityCollection.fromCalendarDurationString("30 days", datePath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-03-09") - .withRow("patient-2", "2017-01-31") - .withRow("patient-3", "2025-07-21") - .build()) - ); - - return parameters; - } - - java.util.Collection dateSubtraction( - final Collection datePath, final ParserContext context) { - final List parameters = new ArrayList<>(); - parameters.add(new TestParameters("Date - 10 years", datePath, - QuantityCollection.fromCalendarDurationString("10 years", datePath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2005-02-07") - .withRow("patient-2", "2007-01-01") - .withRow("patient-3", "2015-06-21") - .build()) - ); - - parameters.add(new TestParameters("Date - 9 months", datePath, - QuantityCollection.fromCalendarDurationString("9 months", datePath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2014-05-07") - .withRow("patient-2", "2016-04-01") - .withRow("patient-3", "2024-09-21") - .build()) - ); - - parameters.add(new TestParameters("Date - 30 days", datePath, - QuantityCollection.fromCalendarDurationString("30 days", datePath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-01-08") - .withRow("patient-2", "2016-12-02") - .withRow("patient-3", "2025-05-22") - .build()) - ); - - return parameters; - } - - java.util.Collection instantAddition( - final Collection instantPath, final ParserContext context) { - final List parameters = new ArrayList<>(); - parameters.add(new TestParameters("Instant + 10 years", instantPath, - QuantityCollection.fromCalendarDurationString("10 years", instantPath), context, - BinaryOperator.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, - QuantityCollection.fromCalendarDurationString("9 months", instantPath), context, - BinaryOperator.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, - QuantityCollection.fromCalendarDurationString("30 days", instantPath), context, - BinaryOperator.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, - QuantityCollection.fromCalendarDurationString("12 hours", instantPath), context, - BinaryOperator.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, - QuantityCollection.fromCalendarDurationString("30 minutes", instantPath), context, - BinaryOperator.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, - QuantityCollection.fromCalendarDurationString("10 seconds", instantPath), context, - BinaryOperator.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, - QuantityCollection.fromCalendarDurationString("300 milliseconds", instantPath), context, - BinaryOperator.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; - } - - java.util.Collection instantSubtraction( - final Collection instantPath, final ParserContext context) { - final List parameters = new ArrayList<>(); - parameters.add(new TestParameters("Instant - 10 years", instantPath, - QuantityCollection.fromCalendarDurationString("10 years", instantPath), context, - BinaryOperator.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, - QuantityCollection.fromCalendarDurationString("9 months", instantPath), context, - BinaryOperator.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, - QuantityCollection.fromCalendarDurationString("30 days", instantPath), context, - BinaryOperator.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, - QuantityCollection.fromCalendarDurationString("12 hours", instantPath), context, - BinaryOperator.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, - QuantityCollection.fromCalendarDurationString("30 minutes", instantPath), context, - BinaryOperator.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, - QuantityCollection.fromCalendarDurationString("10 seconds", instantPath), context, - BinaryOperator.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, - QuantityCollection.fromCalendarDurationString("300 milliseconds", instantPath), context, - BinaryOperator.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; - } - - java.util.Collection dateTimeLiteralAddition( - final Collection dateTimeLiteralPath, final ParserContext context) { - final List parameters = new ArrayList<>(); - parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 + 10 years", dateTimeLiteralPath, - QuantityCollection.fromCalendarDurationString("10 years", dateTimeLiteralPath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2025-02-07T18:28:17+00:00") - .withRow("patient-2", "2025-02-07T18:28:17+00:00") - .withRow("patient-3", "2025-02-07T18:28:17+00:00") - .build()) - ); - - parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 + 9 months", dateTimeLiteralPath, - QuantityCollection.fromCalendarDurationString("9 months", dateTimeLiteralPath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-11-07T18:28:17+00:00") - .withRow("patient-2", "2015-11-07T18:28:17+00:00") - .withRow("patient-3", "2015-11-07T18:28:17+00:00") - .build()) - ); - - parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 + 30 days", dateTimeLiteralPath, - QuantityCollection.fromCalendarDurationString("30 days", dateTimeLiteralPath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-03-09T18:28:17+00:00") - .withRow("patient-2", "2015-03-09T18:28:17+00:00") - .withRow("patient-3", "2015-03-09T18:28:17+00:00") - .build()) - ); - - parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 + 12 hours", dateTimeLiteralPath, - QuantityCollection.fromCalendarDurationString("12 hours", dateTimeLiteralPath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-08T06:28:17+00:00") - .withRow("patient-2", "2015-02-08T06:28:17+00:00") - .withRow("patient-3", "2015-02-08T06:28:17+00:00") - .build()) - ); - - parameters.add( - new TestParameters("@2015-02-07T18:28:17+00:00 + 30 minutes", dateTimeLiteralPath, - QuantityCollection.fromCalendarDurationString("30 minutes", dateTimeLiteralPath), - context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07T18:58:17+00:00") - .withRow("patient-2", "2015-02-07T18:58:17+00:00") - .withRow("patient-3", "2015-02-07T18:58:17+00:00") - .build()) - ); - - parameters.add( - new TestParameters("@2015-02-07T18:28:17+00:00 + 10 seconds", dateTimeLiteralPath, - QuantityCollection.fromCalendarDurationString("10 seconds", dateTimeLiteralPath), - context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07T18:28:27+00:00") - .withRow("patient-2", "2015-02-07T18:28:27+00:00") - .withRow("patient-3", "2015-02-07T18:28:27+00:00") - .build()) - ); - - parameters.add( - new TestParameters("@2015-02-07T18:28:17+00:00 + 300 milliseconds", dateTimeLiteralPath, - QuantityCollection.fromCalendarDurationString("300 milliseconds", dateTimeLiteralPath), - context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07T18:28:17+00:00") - .withRow("patient-2", "2015-02-07T18:28:17+00:00") - .withRow("patient-3", "2015-02-07T18:28:17+00:00") - .build()) - ); - - return parameters; - } - - java.util.Collection dateTimeLiteralSubtraction( - final Collection dateTimeLiteralPath, final ParserContext context) { - final List parameters = new ArrayList<>(); - parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 - 10 years", dateTimeLiteralPath, - QuantityCollection.fromCalendarDurationString("10 years", dateTimeLiteralPath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2005-02-07T18:28:17+00:00") - .withRow("patient-2", "2005-02-07T18:28:17+00:00") - .withRow("patient-3", "2005-02-07T18:28:17+00:00") - .build()) - ); - - parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 - 9 months", dateTimeLiteralPath, - QuantityCollection.fromCalendarDurationString("9 months", dateTimeLiteralPath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2014-05-07T18:28:17+00:00") - .withRow("patient-2", "2014-05-07T18:28:17+00:00") - .withRow("patient-3", "2014-05-07T18:28:17+00:00") - .build()) - ); - - parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 - 30 days", dateTimeLiteralPath, - QuantityCollection.fromCalendarDurationString("30 days", dateTimeLiteralPath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-01-08T18:28:17+00:00") - .withRow("patient-2", "2015-01-08T18:28:17+00:00") - .withRow("patient-3", "2015-01-08T18:28:17+00:00") - .build()) - ); - - parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 - 12 hours", dateTimeLiteralPath, - QuantityCollection.fromCalendarDurationString("12 hours", dateTimeLiteralPath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07T06:28:17+00:00") - .withRow("patient-2", "2015-02-07T06:28:17+00:00") - .withRow("patient-3", "2015-02-07T06:28:17+00:00") - .build()) - ); - - parameters.add( - new TestParameters("@2015-02-07T18:28:17+00:00 - 30 minutes", dateTimeLiteralPath, - QuantityCollection.fromCalendarDurationString("30 minutes", dateTimeLiteralPath), - context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07T17:58:17+00:00") - .withRow("patient-2", "2015-02-07T17:58:17+00:00") - .withRow("patient-3", "2015-02-07T17:58:17+00:00") - .build()) - ); - - parameters.add( - new TestParameters("@2015-02-07T18:28:17+00:00 - 10 seconds", dateTimeLiteralPath, - QuantityCollection.fromCalendarDurationString("10 seconds", dateTimeLiteralPath), - context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07T18:28:07+00:00") - .withRow("patient-2", "2015-02-07T18:28:07+00:00") - .withRow("patient-3", "2015-02-07T18:28:07+00:00") - .build()) - ); - - parameters.add( - new TestParameters("@2015-02-07T18:28:17+00:00 - 300 milliseconds", dateTimeLiteralPath, - QuantityCollection.fromCalendarDurationString("300 milliseconds", dateTimeLiteralPath), - context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-02-07T18:28:16+00:00") - .withRow("patient-2", "2015-02-07T18:28:16+00:00") - .withRow("patient-3", "2015-02-07T18:28:16+00:00") - .build()) - ); - - return parameters; - } - - java.util.Collection dateLiteralAddition( - final Collection dateLiteralPath, final ParserContext context) { - final List parameters = new ArrayList<>(); - parameters.add(new TestParameters("@2015-02-07 + 10 years", dateLiteralPath, - QuantityCollection.fromCalendarDurationString("10 years", dateLiteralPath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2025-02-07") - .withRow("patient-2", "2025-02-07") - .withRow("patient-3", "2025-02-07") - .build()) - ); - - parameters.add(new TestParameters("@2015-02-07 + 9 months", dateLiteralPath, - QuantityCollection.fromCalendarDurationString("9 months", dateLiteralPath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-11-07") - .withRow("patient-2", "2015-11-07") - .withRow("patient-3", "2015-11-07") - .build()) - ); - - parameters.add(new TestParameters("@2015-02-07 + 30 days", dateLiteralPath, - QuantityCollection.fromCalendarDurationString("30 days", dateLiteralPath), context, - BinaryOperator.getInstance("+"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-03-09") - .withRow("patient-2", "2015-03-09") - .withRow("patient-3", "2015-03-09") - .build()) - ); - - return parameters; - } - - java.util.Collection dateLiteralSubtraction( - final Collection dateLiteralPath, final ParserContext context) { - final List parameters = new ArrayList<>(); - parameters.add(new TestParameters("@2015-02-07 - 10 years", dateLiteralPath, - QuantityCollection.fromCalendarDurationString("10 years", dateLiteralPath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2005-02-07") - .withRow("patient-2", "2005-02-07") - .withRow("patient-3", "2005-02-07") - .build()) - ); - - parameters.add(new TestParameters("@2015-02-07 - 9 months", dateLiteralPath, - QuantityCollection.fromCalendarDurationString("9 months", dateLiteralPath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2014-05-07") - .withRow("patient-2", "2014-05-07") - .withRow("patient-3", "2014-05-07") - .build()) - ); - - parameters.add(new TestParameters("@2015-02-07 - 30 days", dateLiteralPath, - QuantityCollection.fromCalendarDurationString("30 days", dateLiteralPath), context, - BinaryOperator.getInstance("-"), - new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) - .withRow("patient-1", "2015-01-08") - .withRow("patient-2", "2015-01-08") - .withRow("patient-3", "2015-01-08") - .build()) - ); - - return parameters; - } - - @ParameterizedTest - @MethodSource("parameters") - void test(@Nonnull final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLeft(), parameters.getRight()); - final Collection result = parameters.getOperator().invoke(input); - assertThat(result).selectOrderedResult().hasRows(parameters.getExpectedResult()); - } + // TODO: implement with columns + + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // static final String ID_ALIAS = "_abc123"; + // + // @Value + // static class TestParameters { + // + // @Nonnull + // String name; + // + // @Nonnull + // Collection left; + // + // @Nonnull + // Collection right; + // + // @Nonnull + // ParserContext context; + // + // @Nonnull + // BinaryOperator operator; + // + // @Nonnull + // Dataset expectedResult; + // + // @Override + // public String toString() { + // return name; + // } + // + // } + // + // @Nonnull + // Stream parameters() throws ParseException { + // final List parameters = new ArrayList<>(); + // + // final Dataset dateTimeDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07T13:28:17.000-05:00") + // .withRow("patient-2", "2017-01-01T00:00:00Z") + // .withRow("patient-3", "2025-06-21T00:15:00+10:00") + // .build(); + // final PrimitivePath dateTimePath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DATETIME) + // .dataset(dateTimeDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // + // final Dataset dateDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07") + // .withRow("patient-2", "2017-01-01") + // .withRow("patient-3", "2025-06-21") + // .build(); + // final PrimitivePath datePath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DATE) + // .dataset(dateDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // + // final Dataset timeDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "13:28:17") + // .withRow("patient-2", "08:00") + // .withRow("patient-3", "00") + // .build(); + // final PrimitivePath timePath = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.TIME) + // .dataset(timeDataset) + // .idAndValueColumns() + // .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 PrimitivePath 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 = DateCollection.fromLiteral("@2015-02-07", datePath); + // final TimeLiteralPath timeLiteral = TimeCollection.fromLiteral("@T08:00", timePath); + // + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(dateTimePath.getIdColumn())) + // .build(); + // + // parameters.addAll(dateTimeAddition(dateTimePath, context)); + // 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)); + // parameters.addAll(dateLiteralSubtraction(dateLiteral, context)); + // + // return parameters.stream(); + // } + // + // java.util.Collection dateTimeAddition( + // final Collection dateTimePath, final ParserContext context) { + // final List parameters = new ArrayList<>(); + // parameters.add(new TestParameters("DateTime + 10 years", dateTimePath, + // QuantityCollection.fromCalendarDurationString("10 years", dateTimePath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2025-02-07T13:28:17.000-05:00") + // .withRow("patient-2", "2027-01-01T00:00:00Z") + // .withRow("patient-3", "2035-06-21T00:15:00+10:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("DateTime + 9 months", dateTimePath, + // QuantityCollection.fromCalendarDurationString("9 months", dateTimePath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-11-07T13:28:17.000-05:00") + // .withRow("patient-2", "2017-10-01T00:00:00Z") + // .withRow("patient-3", "2026-03-21T00:15:00+10:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("DateTime + 30 days", dateTimePath, + // QuantityCollection.fromCalendarDurationString("30 days", dateTimePath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-03-09T13:28:17.000-05:00") + // .withRow("patient-2", "2017-01-31T00:00:00Z") + // .withRow("patient-3", "2025-07-21T00:15:00+10:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("DateTime + 12 hours", dateTimePath, + // QuantityCollection.fromCalendarDurationString("12 hours", dateTimePath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-08T01:28:17.000-05:00") + // .withRow("patient-2", "2017-01-01T12:00:00Z") + // .withRow("patient-3", "2025-06-21T12:15:00+10:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("DateTime + 30 minutes", dateTimePath, + // QuantityCollection.fromCalendarDurationString("30 minutes", dateTimePath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07T13:58:17.000-05:00") + // .withRow("patient-2", "2017-01-01T00:30:00Z") + // .withRow("patient-3", "2025-06-21T00:45:00+10:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("DateTime + 10 seconds", dateTimePath, + // QuantityCollection.fromCalendarDurationString("10 seconds", dateTimePath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07T13:28:27.000-05:00") + // .withRow("patient-2", "2017-01-01T00:00:10Z") + // .withRow("patient-3", "2025-06-21T00:15:10+10:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("DateTime + 300 milliseconds", dateTimePath, + // QuantityCollection.fromCalendarDurationString("300 milliseconds", dateTimePath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07T13:28:17.300-05:00") + // .withRow("patient-2", "2017-01-01T00:00:00Z") + // .withRow("patient-3", "2025-06-21T00:15:00+10:00") + // .build()) + // ); + // return parameters; + // } + // + // java.util.Collection dateTimeSubtraction( + // final Collection dateTimePath, final ParserContext context) { + // final List parameters = new ArrayList<>(); + // parameters.add(new TestParameters("DateTime - 10 years", dateTimePath, + // QuantityCollection.fromCalendarDurationString("10 years", dateTimePath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2005-02-07T13:28:17.000-05:00") + // .withRow("patient-2", "2007-01-01T00:00:00Z") + // .withRow("patient-3", "2015-06-21T00:15:00+10:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("DateTime - 9 months", dateTimePath, + // QuantityCollection.fromCalendarDurationString("9 months", dateTimePath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2014-05-07T13:28:17.000-05:00") + // .withRow("patient-2", "2016-04-01T00:00:00Z") + // .withRow("patient-3", "2024-09-21T00:15:00+10:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("DateTime - 30 days", dateTimePath, + // QuantityCollection.fromCalendarDurationString("30 days", dateTimePath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-01-08T13:28:17.000-05:00") + // .withRow("patient-2", "2016-12-02T00:00:00Z") + // .withRow("patient-3", "2025-05-22T00:15:00+10:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("DateTime - 12 hours", dateTimePath, + // QuantityCollection.fromCalendarDurationString("12 hours", dateTimePath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07T01:28:17.000-05:00") + // .withRow("patient-2", "2016-12-31T12:00:00Z") + // .withRow("patient-3", "2025-06-20T12:15:00+10:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("DateTime - 30 minutes", dateTimePath, + // QuantityCollection.fromCalendarDurationString("30 minutes", dateTimePath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07T12:58:17.000-05:00") + // .withRow("patient-2", "2016-12-31T23:30:00Z") + // .withRow("patient-3", "2025-06-20T23:45:00+10:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("DateTime - 10 seconds", dateTimePath, + // QuantityCollection.fromCalendarDurationString("10 seconds", dateTimePath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07T13:28:07.000-05:00") + // .withRow("patient-2", "2016-12-31T23:59:50Z") + // .withRow("patient-3", "2025-06-21T00:14:50+10:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("DateTime - 300 milliseconds", dateTimePath, + // QuantityCollection.fromCalendarDurationString("300 milliseconds", dateTimePath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07T13:28:16.700-05:00") + // .withRow("patient-2", "2016-12-31T23:59:59Z") + // .withRow("patient-3", "2025-06-21T00:14:59+10:00") + // .build()) + // ); + // return parameters; + // } + // + // java.util.Collection dateAddition( + // final Collection datePath, final ParserContext context) { + // final List parameters = new ArrayList<>(); + // parameters.add(new TestParameters("Date + 10 years", datePath, + // QuantityCollection.fromCalendarDurationString("10 years", datePath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2025-02-07") + // .withRow("patient-2", "2027-01-01") + // .withRow("patient-3", "2035-06-21") + // .build()) + // ); + // + // parameters.add(new TestParameters("Date + 9 months", datePath, + // QuantityCollection.fromCalendarDurationString("9 months", datePath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-11-07") + // .withRow("patient-2", "2017-10-01") + // .withRow("patient-3", "2026-03-21") + // .build()) + // ); + // + // parameters.add(new TestParameters("Date + 30 days", datePath, + // QuantityCollection.fromCalendarDurationString("30 days", datePath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-03-09") + // .withRow("patient-2", "2017-01-31") + // .withRow("patient-3", "2025-07-21") + // .build()) + // ); + // + // return parameters; + // } + // + // java.util.Collection dateSubtraction( + // final Collection datePath, final ParserContext context) { + // final List parameters = new ArrayList<>(); + // parameters.add(new TestParameters("Date - 10 years", datePath, + // QuantityCollection.fromCalendarDurationString("10 years", datePath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2005-02-07") + // .withRow("patient-2", "2007-01-01") + // .withRow("patient-3", "2015-06-21") + // .build()) + // ); + // + // parameters.add(new TestParameters("Date - 9 months", datePath, + // QuantityCollection.fromCalendarDurationString("9 months", datePath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2014-05-07") + // .withRow("patient-2", "2016-04-01") + // .withRow("patient-3", "2024-09-21") + // .build()) + // ); + // + // parameters.add(new TestParameters("Date - 30 days", datePath, + // QuantityCollection.fromCalendarDurationString("30 days", datePath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-01-08") + // .withRow("patient-2", "2016-12-02") + // .withRow("patient-3", "2025-05-22") + // .build()) + // ); + // + // return parameters; + // } + // + // java.util.Collection instantAddition( + // final Collection instantPath, final ParserContext context) { + // final List parameters = new ArrayList<>(); + // parameters.add(new TestParameters("Instant + 10 years", instantPath, + // QuantityCollection.fromCalendarDurationString("10 years", instantPath), context, + // BinaryOperator.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, + // QuantityCollection.fromCalendarDurationString("9 months", instantPath), context, + // BinaryOperator.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, + // QuantityCollection.fromCalendarDurationString("30 days", instantPath), context, + // BinaryOperator.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, + // QuantityCollection.fromCalendarDurationString("12 hours", instantPath), context, + // BinaryOperator.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, + // QuantityCollection.fromCalendarDurationString("30 minutes", instantPath), context, + // BinaryOperator.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, + // QuantityCollection.fromCalendarDurationString("10 seconds", instantPath), context, + // BinaryOperator.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, + // QuantityCollection.fromCalendarDurationString("300 milliseconds", instantPath), context, + // BinaryOperator.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; + // } + // + // java.util.Collection instantSubtraction( + // final Collection instantPath, final ParserContext context) { + // final List parameters = new ArrayList<>(); + // parameters.add(new TestParameters("Instant - 10 years", instantPath, + // QuantityCollection.fromCalendarDurationString("10 years", instantPath), context, + // BinaryOperator.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, + // QuantityCollection.fromCalendarDurationString("9 months", instantPath), context, + // BinaryOperator.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, + // QuantityCollection.fromCalendarDurationString("30 days", instantPath), context, + // BinaryOperator.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, + // QuantityCollection.fromCalendarDurationString("12 hours", instantPath), context, + // BinaryOperator.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, + // QuantityCollection.fromCalendarDurationString("30 minutes", instantPath), context, + // BinaryOperator.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, + // QuantityCollection.fromCalendarDurationString("10 seconds", instantPath), context, + // BinaryOperator.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, + // QuantityCollection.fromCalendarDurationString("300 milliseconds", instantPath), context, + // BinaryOperator.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; + // } + // + // java.util.Collection dateTimeLiteralAddition( + // final Collection dateTimeLiteralPath, final ParserContext context) { + // final List parameters = new ArrayList<>(); + // parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 + 10 years", dateTimeLiteralPath, + // QuantityCollection.fromCalendarDurationString("10 years", dateTimeLiteralPath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2025-02-07T18:28:17+00:00") + // .withRow("patient-2", "2025-02-07T18:28:17+00:00") + // .withRow("patient-3", "2025-02-07T18:28:17+00:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 + 9 months", dateTimeLiteralPath, + // QuantityCollection.fromCalendarDurationString("9 months", dateTimeLiteralPath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-11-07T18:28:17+00:00") + // .withRow("patient-2", "2015-11-07T18:28:17+00:00") + // .withRow("patient-3", "2015-11-07T18:28:17+00:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 + 30 days", dateTimeLiteralPath, + // QuantityCollection.fromCalendarDurationString("30 days", dateTimeLiteralPath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-03-09T18:28:17+00:00") + // .withRow("patient-2", "2015-03-09T18:28:17+00:00") + // .withRow("patient-3", "2015-03-09T18:28:17+00:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 + 12 hours", dateTimeLiteralPath, + // QuantityCollection.fromCalendarDurationString("12 hours", dateTimeLiteralPath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-08T06:28:17+00:00") + // .withRow("patient-2", "2015-02-08T06:28:17+00:00") + // .withRow("patient-3", "2015-02-08T06:28:17+00:00") + // .build()) + // ); + // + // parameters.add( + // new TestParameters("@2015-02-07T18:28:17+00:00 + 30 minutes", dateTimeLiteralPath, + // QuantityCollection.fromCalendarDurationString("30 minutes", dateTimeLiteralPath), + // context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07T18:58:17+00:00") + // .withRow("patient-2", "2015-02-07T18:58:17+00:00") + // .withRow("patient-3", "2015-02-07T18:58:17+00:00") + // .build()) + // ); + // + // parameters.add( + // new TestParameters("@2015-02-07T18:28:17+00:00 + 10 seconds", dateTimeLiteralPath, + // QuantityCollection.fromCalendarDurationString("10 seconds", dateTimeLiteralPath), + // context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07T18:28:27+00:00") + // .withRow("patient-2", "2015-02-07T18:28:27+00:00") + // .withRow("patient-3", "2015-02-07T18:28:27+00:00") + // .build()) + // ); + // + // parameters.add( + // new TestParameters("@2015-02-07T18:28:17+00:00 + 300 milliseconds", dateTimeLiteralPath, + // QuantityCollection.fromCalendarDurationString("300 milliseconds", dateTimeLiteralPath), + // context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07T18:28:17+00:00") + // .withRow("patient-2", "2015-02-07T18:28:17+00:00") + // .withRow("patient-3", "2015-02-07T18:28:17+00:00") + // .build()) + // ); + // + // return parameters; + // } + // + // java.util.Collection dateTimeLiteralSubtraction( + // final Collection dateTimeLiteralPath, final ParserContext context) { + // final List parameters = new ArrayList<>(); + // parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 - 10 years", dateTimeLiteralPath, + // QuantityCollection.fromCalendarDurationString("10 years", dateTimeLiteralPath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2005-02-07T18:28:17+00:00") + // .withRow("patient-2", "2005-02-07T18:28:17+00:00") + // .withRow("patient-3", "2005-02-07T18:28:17+00:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 - 9 months", dateTimeLiteralPath, + // QuantityCollection.fromCalendarDurationString("9 months", dateTimeLiteralPath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2014-05-07T18:28:17+00:00") + // .withRow("patient-2", "2014-05-07T18:28:17+00:00") + // .withRow("patient-3", "2014-05-07T18:28:17+00:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 - 30 days", dateTimeLiteralPath, + // QuantityCollection.fromCalendarDurationString("30 days", dateTimeLiteralPath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-01-08T18:28:17+00:00") + // .withRow("patient-2", "2015-01-08T18:28:17+00:00") + // .withRow("patient-3", "2015-01-08T18:28:17+00:00") + // .build()) + // ); + // + // parameters.add(new TestParameters("@2015-02-07T18:28:17+00:00 - 12 hours", dateTimeLiteralPath, + // QuantityCollection.fromCalendarDurationString("12 hours", dateTimeLiteralPath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07T06:28:17+00:00") + // .withRow("patient-2", "2015-02-07T06:28:17+00:00") + // .withRow("patient-3", "2015-02-07T06:28:17+00:00") + // .build()) + // ); + // + // parameters.add( + // new TestParameters("@2015-02-07T18:28:17+00:00 - 30 minutes", dateTimeLiteralPath, + // QuantityCollection.fromCalendarDurationString("30 minutes", dateTimeLiteralPath), + // context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07T17:58:17+00:00") + // .withRow("patient-2", "2015-02-07T17:58:17+00:00") + // .withRow("patient-3", "2015-02-07T17:58:17+00:00") + // .build()) + // ); + // + // parameters.add( + // new TestParameters("@2015-02-07T18:28:17+00:00 - 10 seconds", dateTimeLiteralPath, + // QuantityCollection.fromCalendarDurationString("10 seconds", dateTimeLiteralPath), + // context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07T18:28:07+00:00") + // .withRow("patient-2", "2015-02-07T18:28:07+00:00") + // .withRow("patient-3", "2015-02-07T18:28:07+00:00") + // .build()) + // ); + // + // parameters.add( + // new TestParameters("@2015-02-07T18:28:17+00:00 - 300 milliseconds", dateTimeLiteralPath, + // QuantityCollection.fromCalendarDurationString("300 milliseconds", dateTimeLiteralPath), + // context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-02-07T18:28:16+00:00") + // .withRow("patient-2", "2015-02-07T18:28:16+00:00") + // .withRow("patient-3", "2015-02-07T18:28:16+00:00") + // .build()) + // ); + // + // return parameters; + // } + // + // java.util.Collection dateLiteralAddition( + // final Collection dateLiteralPath, final ParserContext context) { + // final List parameters = new ArrayList<>(); + // parameters.add(new TestParameters("@2015-02-07 + 10 years", dateLiteralPath, + // QuantityCollection.fromCalendarDurationString("10 years", dateLiteralPath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2025-02-07") + // .withRow("patient-2", "2025-02-07") + // .withRow("patient-3", "2025-02-07") + // .build()) + // ); + // + // parameters.add(new TestParameters("@2015-02-07 + 9 months", dateLiteralPath, + // QuantityCollection.fromCalendarDurationString("9 months", dateLiteralPath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-11-07") + // .withRow("patient-2", "2015-11-07") + // .withRow("patient-3", "2015-11-07") + // .build()) + // ); + // + // parameters.add(new TestParameters("@2015-02-07 + 30 days", dateLiteralPath, + // QuantityCollection.fromCalendarDurationString("30 days", dateLiteralPath), context, + // BinaryOperator.getInstance("+"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-03-09") + // .withRow("patient-2", "2015-03-09") + // .withRow("patient-3", "2015-03-09") + // .build()) + // ); + // + // return parameters; + // } + // + // java.util.Collection dateLiteralSubtraction( + // final Collection dateLiteralPath, final ParserContext context) { + // final List parameters = new ArrayList<>(); + // parameters.add(new TestParameters("@2015-02-07 - 10 years", dateLiteralPath, + // QuantityCollection.fromCalendarDurationString("10 years", dateLiteralPath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2005-02-07") + // .withRow("patient-2", "2005-02-07") + // .withRow("patient-3", "2005-02-07") + // .build()) + // ); + // + // parameters.add(new TestParameters("@2015-02-07 - 9 months", dateLiteralPath, + // QuantityCollection.fromCalendarDurationString("9 months", dateLiteralPath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2014-05-07") + // .withRow("patient-2", "2014-05-07") + // .withRow("patient-3", "2014-05-07") + // .build()) + // ); + // + // parameters.add(new TestParameters("@2015-02-07 - 30 days", dateLiteralPath, + // QuantityCollection.fromCalendarDurationString("30 days", dateLiteralPath), context, + // BinaryOperator.getInstance("-"), + // new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType) + // .withRow("patient-1", "2015-01-08") + // .withRow("patient-2", "2015-01-08") + // .withRow("patient-3", "2015-01-08") + // .build()) + // ); + // + // return parameters; + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void test(@Nonnull final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLeft(), parameters.getRight()); + // final Collection result = parameters.getOperator().invoke(input); + // assertThat(result).selectOrderedResult().hasRows(parameters.getExpectedResult()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorCodingTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorCodingTest.java index 0cd07c74b1..aa9b36a986 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorCodingTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorCodingTest.java @@ -17,229 +17,211 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.helpers.SparkHelpers.codingStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromCoding; -import static au.csiro.pathling.test.helpers.TestHelpers.LOINC_URL; -import static au.csiro.pathling.test.helpers.TestHelpers.SNOMED_URL; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.CodingCollection; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.DatasetBuilder; -import au.csiro.pathling.test.builders.ElementPathBuilder; -import au.csiro.pathling.test.builders.ParserContextBuilder; -import ca.uhn.fhir.context.FhirContext; -import java.util.Collections; -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.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class EqualityOperatorCodingTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - static final String ID_ALIAS = "_abc123"; - - Collection left; - Collection right; - Collection literalSnomedAll; - Collection literalLoincSystemCode; - ParserContext parserContext; - - @BeforeEach - void setUp() { - // all components - final Coding coding1 = new Coding(SNOMED_URL, "56459004", null); - coding1.setVersion("http://snomed.info/sct/32506021000036107/version/20191231"); - coding1.setDisplay("Display name"); - coding1.setUserSelected(true); - coding1.setId("some-fake-id"); - - final Coding coding2 = new Coding(SNOMED_URL, "56459004", null); - coding2.setVersion("http://snomed.info/sct/32506021000036107/version/20191231"); - coding2.setDisplay("Display name"); - - final Coding coding3 = new Coding(SNOMED_URL, "56459004", null); - coding3.setVersion("http://snomed.info/sct/32506021000036107/version/20191231"); - - final Coding coding4 = new Coding(LOINC_URL, "222|33", null); - coding4.setId("fake-id-1"); - final Coding coding5 = new Coding(LOINC_URL, "222|33", null); - coding5.setId("fake-id-2"); - - final Coding coding6 = new Coding(LOINC_URL, "56459004", null); - coding6.setVersion("http://snomed.info/sct/32506021000036107/version/20191231"); - coding6.setDisplay("Display name"); - coding6.setUserSelected(true); - coding6.setId("some-fake-id"); - - final Coding coding1_other = coding1.copy(); - coding1_other.setId("some-other-fake-id"); - - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withStructTypeColumns(codingStructType()) - .withRow("patient-1", rowFromCoding(coding1)) - .withRow("patient-2", rowFromCoding(coding2)) - .withRow("patient-3", rowFromCoding(coding3)) - .withRow("patient-4", rowFromCoding(coding4)) - .withRow("patient-5", rowFromCoding(coding5)) - .withRow("patient-6", rowFromCoding(coding6)) - .withRow("patient-7", null) - .buildWithStructValue(); - left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .singular(true) - .dataset(leftDataset) - .idAndValueColumns() - .build(); - final Dataset rightDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withStructTypeColumns(codingStructType()) - .withRow("patient-1", rowFromCoding(coding1_other)) - .withRow("patient-2", rowFromCoding(coding3)) - .withRow("patient-3", rowFromCoding(coding3)) - .withRow("patient-4", rowFromCoding(coding5)) - .withRow("patient-5", rowFromCoding(coding6)) - .withRow("patient-6", null) - .withRow("patient-7", null) - .buildWithStructValue(); - right = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .singular(true) - .dataset(rightDataset) - .idAndValueColumns() - .build(); - literalSnomedAll = CodingCollection.fromLiteral( - "http://snomed.info/sct|56459004|http://snomed.info/sct/32506021000036107/version/20191231|'Display name'|true", - left); - literalLoincSystemCode = CodingCollection.fromLiteral("http://loinc.org|'222|33'", left); - - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(left.getIdColumn())) - .build(); - } - - @Test - void equals() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); - final Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", true), - RowFactory.create("patient-2", false), - RowFactory.create("patient-3", true), - RowFactory.create("patient-4", true), - RowFactory.create("patient-5", false), - RowFactory.create("patient-6", null), - RowFactory.create("patient-7", null) - ); - } - - @Test - void notEquals() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); - final Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", false), - RowFactory.create("patient-2", true), - RowFactory.create("patient-3", false), - RowFactory.create("patient-4", false), - RowFactory.create("patient-5", true), - RowFactory.create("patient-6", null), - RowFactory.create("patient-7", null) - ); - } - - @Test - void literalEquals() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, literalLoincSystemCode, - left); - final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); - final Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", false), - RowFactory.create("patient-2", false), - RowFactory.create("patient-3", false), - RowFactory.create("patient-4", true), - RowFactory.create("patient-5", true), - RowFactory.create("patient-6", false), - RowFactory.create("patient-7", null) - ); - } - - @Test - void equalsLiteral() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, - literalSnomedAll); - final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); - final Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", true), - RowFactory.create("patient-2", false), - RowFactory.create("patient-3", false), - RowFactory.create("patient-4", false), - RowFactory.create("patient-5", false), - RowFactory.create("patient-6", false), - RowFactory.create("patient-7", null) - ); - } - - @Test - void literalNotEquals() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, literalLoincSystemCode, - left); - final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); - final Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", true), - RowFactory.create("patient-2", true), - RowFactory.create("patient-3", true), - RowFactory.create("patient-4", false), - RowFactory.create("patient-5", false), - RowFactory.create("patient-6", true), - RowFactory.create("patient-7", null) - ); - } - - @Test - void notEqualsLiteral() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, - literalSnomedAll); - final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); - final Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", false), - RowFactory.create("patient-2", true), - RowFactory.create("patient-3", true), - RowFactory.create("patient-4", true), - RowFactory.create("patient-5", true), - RowFactory.create("patient-6", true), - RowFactory.create("patient-7", null) - ); - } + // TODO: implement with columns + + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // static final String ID_ALIAS = "_abc123"; + // + // Collection left; + // Collection right; + // Collection literalSnomedAll; + // Collection literalLoincSystemCode; + // ParserContext parserContext; + // + // @BeforeEach + // void setUp() { + // // all components + // final Coding coding1 = new Coding(SNOMED_URL, "56459004", null); + // coding1.setVersion("http://snomed.info/sct/32506021000036107/version/20191231"); + // coding1.setDisplay("Display name"); + // coding1.setUserSelected(true); + // coding1.setId("some-fake-id"); + // + // final Coding coding2 = new Coding(SNOMED_URL, "56459004", null); + // coding2.setVersion("http://snomed.info/sct/32506021000036107/version/20191231"); + // coding2.setDisplay("Display name"); + // + // final Coding coding3 = new Coding(SNOMED_URL, "56459004", null); + // coding3.setVersion("http://snomed.info/sct/32506021000036107/version/20191231"); + // + // final Coding coding4 = new Coding(LOINC_URL, "222|33", null); + // coding4.setId("fake-id-1"); + // final Coding coding5 = new Coding(LOINC_URL, "222|33", null); + // coding5.setId("fake-id-2"); + // + // final Coding coding6 = new Coding(LOINC_URL, "56459004", null); + // coding6.setVersion("http://snomed.info/sct/32506021000036107/version/20191231"); + // coding6.setDisplay("Display name"); + // coding6.setUserSelected(true); + // coding6.setId("some-fake-id"); + // + // final Coding coding1_other = coding1.copy(); + // coding1_other.setId("some-other-fake-id"); + // + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withStructTypeColumns(codingStructType()) + // .withRow("patient-1", rowFromCoding(coding1)) + // .withRow("patient-2", rowFromCoding(coding2)) + // .withRow("patient-3", rowFromCoding(coding3)) + // .withRow("patient-4", rowFromCoding(coding4)) + // .withRow("patient-5", rowFromCoding(coding5)) + // .withRow("patient-6", rowFromCoding(coding6)) + // .withRow("patient-7", null) + // .buildWithStructValue(); + // left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .singular(true) + // .dataset(leftDataset) + // .idAndValueColumns() + // .build(); + // final Dataset rightDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withStructTypeColumns(codingStructType()) + // .withRow("patient-1", rowFromCoding(coding1_other)) + // .withRow("patient-2", rowFromCoding(coding3)) + // .withRow("patient-3", rowFromCoding(coding3)) + // .withRow("patient-4", rowFromCoding(coding5)) + // .withRow("patient-5", rowFromCoding(coding6)) + // .withRow("patient-6", null) + // .withRow("patient-7", null) + // .buildWithStructValue(); + // right = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .singular(true) + // .dataset(rightDataset) + // .idAndValueColumns() + // .build(); + // literalSnomedAll = CodingCollection.fromLiteral( + // "http://snomed.info/sct|56459004|http://snomed.info/sct/32506021000036107/version/20191231|'Display name'|true", + // left); + // literalLoincSystemCode = CodingCollection.fromLiteral("http://loinc.org|'222|33'", left); + // + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(left.getIdColumn())) + // .build(); + // } + // + // @Test + // void equals() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); + // final Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", true), + // RowFactory.create("patient-2", false), + // RowFactory.create("patient-3", true), + // RowFactory.create("patient-4", true), + // RowFactory.create("patient-5", false), + // RowFactory.create("patient-6", null), + // RowFactory.create("patient-7", null) + // ); + // } + // + // @Test + // void notEquals() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); + // final Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", false), + // RowFactory.create("patient-2", true), + // RowFactory.create("patient-3", false), + // RowFactory.create("patient-4", false), + // RowFactory.create("patient-5", true), + // RowFactory.create("patient-6", null), + // RowFactory.create("patient-7", null) + // ); + // } + // + // @Test + // void literalEquals() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, literalLoincSystemCode, + // left); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); + // final Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", false), + // RowFactory.create("patient-2", false), + // RowFactory.create("patient-3", false), + // RowFactory.create("patient-4", true), + // RowFactory.create("patient-5", true), + // RowFactory.create("patient-6", false), + // RowFactory.create("patient-7", null) + // ); + // } + // + // @Test + // void equalsLiteral() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, + // literalSnomedAll); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); + // final Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", true), + // RowFactory.create("patient-2", false), + // RowFactory.create("patient-3", false), + // RowFactory.create("patient-4", false), + // RowFactory.create("patient-5", false), + // RowFactory.create("patient-6", false), + // RowFactory.create("patient-7", null) + // ); + // } + // + // @Test + // void literalNotEquals() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, literalLoincSystemCode, + // left); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); + // final Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", true), + // RowFactory.create("patient-2", true), + // RowFactory.create("patient-3", true), + // RowFactory.create("patient-4", false), + // RowFactory.create("patient-5", false), + // RowFactory.create("patient-6", true), + // RowFactory.create("patient-7", null) + // ); + // } + // + // @Test + // void notEqualsLiteral() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, + // literalSnomedAll); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); + // final Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", false), + // RowFactory.create("patient-2", true), + // RowFactory.create("patient-3", true), + // RowFactory.create("patient-4", true), + // RowFactory.create("patient-5", true), + // RowFactory.create("patient-6", true), + // RowFactory.create("patient-7", null) + // ); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorQuantityTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorQuantityTest.java index 6d6286e33c..fcd3e4fc0e 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorQuantityTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/EqualityOperatorQuantityTest.java @@ -17,539 +17,517 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.helpers.SparkHelpers.quantityStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowForUcumQuantity; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromQuantity; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.QuantityCollection; -import au.csiro.pathling.fhirpath.literal.QuantityLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.TestHelpers; -import ca.uhn.fhir.context.FhirContext; -import java.util.Collections; -import java.util.List; -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.fhir.ucum.UcumService; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.Quantity; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; @SpringBootUnitTest +@NotImplemented public class EqualityOperatorQuantityTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @Autowired - UcumService ucumService; - - static final String ID_ALIAS = "_abc123"; - - Collection left; - Collection right; - ParserContext parserContext; - QuantityLiteralPath ucumQuantityLiteral1; - QuantityLiteralPath ucumQuantityLiteral2; - QuantityLiteralPath ucumQuantityLiteral3; - QuantityLiteralPath calendarDurationLiteral1; - QuantityLiteralPath calendarDurationLiteral2; - QuantityLiteralPath calendarDurationLiteral3; - QuantityLiteralPath ucumQuantityLiteralNoUnit; - - @BeforeEach - void setUp() { - final Quantity quantity1 = new Quantity(); - quantity1.setValue(500); - quantity1.setUnit("mg"); - quantity1.setSystem(TestHelpers.UCUM_URL); - quantity1.setCode("mg"); - - final Quantity quantity2 = new Quantity(); - quantity2.setValue(0.5); - quantity2.setUnit("g"); - quantity2.setSystem(TestHelpers.UCUM_URL); - quantity2.setCode("g"); - - final Quantity quantity3 = new Quantity(); - quantity3.setValue(1.8); - quantity3.setUnit("m"); - quantity3.setSystem(TestHelpers.UCUM_URL); - quantity3.setCode("m"); - - final Quantity quantity4 = new Quantity(); - quantity4.setValue(0.5); - quantity4.setUnit("g"); - quantity4.setSystem(TestHelpers.SNOMED_URL); - quantity4.setCode("258682000"); - - final Quantity quantity5 = new Quantity(); - quantity5.setValue(650); - quantity5.setUnit("mg"); - quantity5.setSystem(TestHelpers.UCUM_URL); - quantity5.setCode("mg"); - - final Quantity quantity6 = new Quantity(); - quantity6.setValue(30); - quantity6.setUnit("d"); - quantity6.setSystem(TestHelpers.UCUM_URL); - quantity6.setCode("d"); - - final Quantity quantity7 = new Quantity(); - quantity7.setValue(60); - quantity7.setUnit("s"); - quantity7.setSystem(TestHelpers.UCUM_URL); - quantity7.setCode("s"); - - final Quantity quantity8 = new Quantity(); - quantity8.setValue(1000); - quantity8.setUnit("ms"); - quantity8.setSystem(TestHelpers.UCUM_URL); - quantity8.setCode("ms"); - - final Quantity nonUcumQuantity = new Quantity(); - nonUcumQuantity.setValue(15); - nonUcumQuantity.setUnit("mSv"); - nonUcumQuantity.setSystem(TestHelpers.SNOMED_URL); - nonUcumQuantity.setCode("282250007"); - - final Dataset leftDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withStructTypeColumns(quantityStructType()) - .withRow("patient-1", rowFromQuantity(quantity1)) // 500 mg - .withRow("patient-2", rowFromQuantity(quantity1)) // 500 mg - .withRow("patient-3", rowFromQuantity(quantity1)) // 500 mg - .withRow("patient-4", rowFromQuantity(quantity5)) // 650 mg - .withRow("patient-5", null) - .withRow("patient-6", rowFromQuantity(quantity1)) // 500 mg - .withRow("patient-7", rowFromQuantity(quantity6)) // 30 d - .withRow("patient-8", rowFromQuantity(quantity7)) // 60 s - .withRow("patient-9", rowFromQuantity(quantity8)) // 1000 ms - .withRow("patient-a", rowForUcumQuantity("1000", "mmol")) // 1000 mmol - .withRow("patient-b", rowForUcumQuantity("49", "%")) // 49 % - .withRow("patient-c", rowFromQuantity(nonUcumQuantity)) // non-ucum - .buildWithStructValue(); - left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.QUANTITY) - .singular(true) - .dataset(leftDataset) - .idAndValueColumns() - .build(); - - final Dataset rightDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withStructTypeColumns(quantityStructType()) - .withRow("patient-1", rowFromQuantity(quantity1)) // 500 mg - .withRow("patient-2", rowFromQuantity(quantity2)) // 0.5 g - .withRow("patient-3", rowFromQuantity(quantity3)) // 1.8 m - .withRow("patient-4", rowFromQuantity(quantity1)) // 500 mg - .withRow("patient-5", rowFromQuantity(quantity1)) // 500 mg - .withRow("patient-6", null) - .withRow("patient-7", rowFromQuantity(quantity6)) // 30 d - .withRow("patient-8", rowFromQuantity(quantity7)) // 60 s - .withRow("patient-9", rowFromQuantity(quantity8)) // 1000 ms - .withRow("patient-a", rowForUcumQuantity("1", "mol")) // 1 mol - .withRow("patient-b", rowForUcumQuantity("0.5", "1")) // 0.5 '1' - .withRow("patient-c", rowForUcumQuantity("1", "%")) // 1 % - .buildWithStructValue(); - right = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.QUANTITY) - .singular(true) - .dataset(rightDataset) - .idAndValueColumns() - .build(); - - ucumQuantityLiteral1 = QuantityCollection.fromUcumString("500 'mg'", left, ucumService); - ucumQuantityLiteral2 = QuantityCollection.fromUcumString("0.5 'g'", left, ucumService); - ucumQuantityLiteral3 = QuantityCollection.fromUcumString("1.8 'm'", left, ucumService); - ucumQuantityLiteralNoUnit = QuantityCollection.fromUcumString("0.49 '1'", left, ucumService); - - calendarDurationLiteral1 = QuantityCollection.fromCalendarDurationString("30 days", left); - calendarDurationLiteral2 = QuantityCollection.fromCalendarDurationString("60 seconds", left); - calendarDurationLiteral3 = QuantityCollection.fromCalendarDurationString("1000 milliseconds", - left); - - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(left.getIdColumn())) - .build(); - } - - @Test - void equals() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); - final Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", true), // 500 mg = 500 mg - RowFactory.create("patient-2", true), // 500 mg = 0.5 g - RowFactory.create("patient-3", null), // 500 mg = 1.8 m - RowFactory.create("patient-4", false), // 650 mg = 500 mg - RowFactory.create("patient-5", null), // {} = 500 mg - RowFactory.create("patient-6", null), // 500 mg = {} - RowFactory.create("patient-7", true), // 30 d = 30 d - RowFactory.create("patient-8", true), // 60 s = 60 s - RowFactory.create("patient-9", true), // 1000 ms = 1000 ms - RowFactory.create("patient-a", true), // 1000 mmol = 1 mol - RowFactory.create("patient-b", false), // 49 % = 0.5 '' - RowFactory.create("patient-c", null) // non-ucum = 1 % - ); - } - - @Test - void notEquals() { - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); - final Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", false), // 500 mg != 500 mg - RowFactory.create("patient-2", false), // 500 mg != 0.5 g - RowFactory.create("patient-3", null), // 500 mg != 1.8 m - RowFactory.create("patient-4", true), // 650 mg != 500 mg - RowFactory.create("patient-5", null), // {} != 500 mg - RowFactory.create("patient-6", null), // 500 mg != {} - RowFactory.create("patient-7", false), // 30 d != 30 d - RowFactory.create("patient-8", false), // 60 s != 60 s - RowFactory.create("patient-9", false), // 1000 ms != 1000 ms - RowFactory.create("patient-a", false), // 1000 mmol != 1 mol - RowFactory.create("patient-b", true), // 49 % != 0.5 '' - RowFactory.create("patient-c", null) // non-ucum != 1 % - ); - } - - @Test - void ucumLiteralEquals() { - BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral1); - final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); - Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", true), // 500 mg = 500 mg - RowFactory.create("patient-2", true), // 500 mg = 500 mg - RowFactory.create("patient-3", true), // 500 mg = 500 mg - RowFactory.create("patient-4", false), // 650 mg = 500 mg - RowFactory.create("patient-5", null), // {} = 500 mg - RowFactory.create("patient-6", true), // 500 mg = 500 mg - RowFactory.create("patient-7", null), // 30 d = 500 mg - RowFactory.create("patient-8", null), // 60 s = 500 mg - RowFactory.create("patient-9", null), // 1000 ms = 500 mg - RowFactory.create("patient-a", null), // 1000 mmol = 500 mg - RowFactory.create("patient-b", null), // 49 % = 500 mg - RowFactory.create("patient-c", null) // non-ucum = 500 mg - ); - - input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral2); - result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", true), // 500 mg = 0.5 mg - RowFactory.create("patient-2", true), // 500 mg = 0.5 mg - RowFactory.create("patient-3", true), // 500 mg = 0.5 mg - RowFactory.create("patient-4", false), // 650 mg = 0.5 mg - RowFactory.create("patient-5", null), // {} = 0.5 mg - RowFactory.create("patient-6", true), // 500 mg = 0.5 mg - RowFactory.create("patient-7", null), // 30 d = 0.5 mg - RowFactory.create("patient-8", null), // 60 s = 0.5 mg - RowFactory.create("patient-9", null), // 1000 ms = 0.5 mg - RowFactory.create("patient-a", null), // 1000 mmol = 0.5 mg - RowFactory.create("patient-b", null), // 49 % = 0.5 mg - RowFactory.create("patient-c", null) // non-ucum = 0.5 mg - ); - - input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral3); - result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", null), // 500 mg = 1.8 m - RowFactory.create("patient-2", null), // 500 mg = 1.8 m - RowFactory.create("patient-3", null), // 500 mg = 1.8 m - RowFactory.create("patient-4", null), // 650 mg = 1.8 m - RowFactory.create("patient-5", null), // {} = 1.8 m - RowFactory.create("patient-6", null), // 500 mg = 1.8 m - RowFactory.create("patient-7", null), // 30 d = 1.8 m - RowFactory.create("patient-8", null), // 60 s = 1.8 m - RowFactory.create("patient-9", null), // 1000 ms = 1.8 m - RowFactory.create("patient-a", null), // 1000 mmol = 1.8 m - RowFactory.create("patient-b", null), // 49 % = 1.8 m - RowFactory.create("patient-c", null) // non-ucum = 1.8 m - ); - - input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteralNoUnit); - result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", null), // 500 mg = 0.49 '1' - RowFactory.create("patient-2", null), // 500 mg = 0.49 '1' - RowFactory.create("patient-3", null), // 500 mg = 0.49 '1' - RowFactory.create("patient-4", null), // 650 mg = 0.49 '1' - RowFactory.create("patient-5", null), // {} = 0.49 '1' - RowFactory.create("patient-6", null), // 500 mg = 0.49 '1 - RowFactory.create("patient-7", null), // 30 d = 0.49 '1' - RowFactory.create("patient-8", null), // 60 s = 0.49 '1' - RowFactory.create("patient-9", null), // 1000 ms = 0.49 '1' - RowFactory.create("patient-a", false), // 1000 mmol = 0.49 '1' - RowFactory.create("patient-b", true), // 49 % = 0.49 '1' - RowFactory.create("patient-c", null) // non-ucum = 0.49 '1' - ); - - input = new BinaryOperatorInput(parserContext, ucumQuantityLiteral1, ucumQuantityLiteral1); - result = equalityOperator.invoke(input); - - final Dataset allTrue = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.BooleanType) - .withIdsAndValue(true, - List.of("patient-1", "patient-2", "patient-3", "patient-4", "patient-5", "patient-6", - "patient-7", "patient-8", "patient-9", "patient-a", "patient-b", "patient-c")) - .build(); - assertThat(result).selectOrderedResult().hasRows(allTrue); - } - - @Test - void ucumLiteralNotEquals() { - BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral1); - final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); - Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", false), // 500 mg != 500 mg - RowFactory.create("patient-2", false), // 500 mg != 500 mg - RowFactory.create("patient-3", false), // 500 mg != 500 mg - RowFactory.create("patient-4", true), // 650 mg != 500 mg - RowFactory.create("patient-5", null), // {} != 500 mg - RowFactory.create("patient-6", false), // 500 mg != 500 mg - RowFactory.create("patient-7", null), // 30 d != 500 mg - RowFactory.create("patient-8", null), // 60 s != 500 mg - RowFactory.create("patient-9", null), // 1000 ms != 500 mg - RowFactory.create("patient-a", null), // 1000 mmol != 500 mg - RowFactory.create("patient-b", null), // 49 % != 500 mg - RowFactory.create("patient-c", null) // non-ucum != 500 mg - ); - - input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral2); - result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", false), // 500 mg != 0.5 mg - RowFactory.create("patient-2", false), // 500 mg != 0.5 mg - RowFactory.create("patient-3", false), // 500 mg != 0.5 mg - RowFactory.create("patient-4", true), // 650 mg != 0.5 mg - RowFactory.create("patient-5", null), // {} != 0.5 mg - RowFactory.create("patient-6", false), // 500 mg != 0.5 mg - RowFactory.create("patient-7", null), // 30 d != 0.5 mg - RowFactory.create("patient-8", null), // 60 s != 0.5 mg - RowFactory.create("patient-9", null), // 1000 ms != 0.5 mg - RowFactory.create("patient-a", null), // 1000 mmol != 0.5 mg - RowFactory.create("patient-b", null), // 49 % != 0.5 mg - RowFactory.create("patient-c", null) // non-ucum != 0.5 mg - ); - - input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral3); - result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", null), // 500 mg != 1.8 m - RowFactory.create("patient-2", null), // 500 mg != 1.8 m - RowFactory.create("patient-3", null), // 500 mg != 1.8 m - RowFactory.create("patient-4", null), // 650 mg != 1.8 m - RowFactory.create("patient-5", null), // {} != 1.8 m - RowFactory.create("patient-6", null), // 500 mg != 1.8 m - RowFactory.create("patient-7", null), // 30 d != 1.8 m - RowFactory.create("patient-8", null), // 60 s != 1.8 m - RowFactory.create("patient-9", null), // 1000 ms != 1.8 m - RowFactory.create("patient-a", null), // 1000 mmol != 1.8 m - RowFactory.create("patient-b", null), // 49 % != 1.8 m - RowFactory.create("patient-c", null) // non-ucum != 1.8 m - ); - - input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteralNoUnit); - result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", null), // 500 mg != 0.49 '' - RowFactory.create("patient-2", null), // 500 mg != 0.49 '' - RowFactory.create("patient-3", null), // 500 mg != 0.49 '' - RowFactory.create("patient-4", null), // 650 mg != 0.49 '' - RowFactory.create("patient-5", null), // {} != 0.49 '' - RowFactory.create("patient-6", null), // 500 mg != 0.49 '' - RowFactory.create("patient-7", null), // 30 d != 0.49 '' - RowFactory.create("patient-8", null), // 60 s != 0.49 '' - RowFactory.create("patient-9", null), // 1000 ms != 0.49 '' - RowFactory.create("patient-a", true), // 1000 mmol != 0.49 '' - RowFactory.create("patient-b", false), // 49 % != 0.49 '' - RowFactory.create("patient-c", null) // non-ucum != 0.49 '' - ); - - input = new BinaryOperatorInput(parserContext, ucumQuantityLiteral1, ucumQuantityLiteral1); - result = equalityOperator.invoke(input); - - final Dataset allFalse = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.BooleanType) - .withIdsAndValue(false, - List.of("patient-1", "patient-2", "patient-3", "patient-4", "patient-5", "patient-6", - "patient-7", "patient-8", "patient-9", "patient-a", "patient-b", "patient-c")) - .build(); - assertThat(result).selectOrderedResult().hasRows(allFalse); - } - - @Test - void calendarLiteralEquals() { - BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, - calendarDurationLiteral1); - final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); - Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", null), // 500 mg = 30 days - RowFactory.create("patient-2", null), // 500 mg = 30 days - RowFactory.create("patient-3", null), // 500 mg = 30 days - RowFactory.create("patient-4", null), // 650 mg = 30 days - RowFactory.create("patient-5", null), // {} = 30 days - RowFactory.create("patient-6", null), // 500 mg = 30 days - RowFactory.create("patient-7", null), // 30 d = 30 days - RowFactory.create("patient-8", null), // 60 s = 30 days - RowFactory.create("patient-9", null), // 1000 ms = 30 days - RowFactory.create("patient-a", null), // 1000 mmol = 30 days - RowFactory.create("patient-b", null), // 49 % = 30 days - RowFactory.create("patient-c", null) // non-ucum = 30 days - ); - - input = new BinaryOperatorInput(parserContext, left, calendarDurationLiteral2); - result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", null), // 500 mg = 60 seconds - RowFactory.create("patient-2", null), // 500 mg = 60 seconds - RowFactory.create("patient-3", null), // 500 mg = 60 seconds - RowFactory.create("patient-4", null), // 650 mg = 60 seconds - RowFactory.create("patient-5", null), // {} = 60 seconds - RowFactory.create("patient-6", null), // 500 mg = 60 seconds - RowFactory.create("patient-7", false), // 30 d = 60 seconds - RowFactory.create("patient-8", true), // 60 s = 60 seconds - RowFactory.create("patient-9", false), // 1000 ms = 60 seconds - RowFactory.create("patient-a", null), // 1000 mmol = 60 seconds - RowFactory.create("patient-b", null), // 49 % = 60 seconds - RowFactory.create("patient-c", null) // non-ucum = 60 seconds - ); - - input = new BinaryOperatorInput(parserContext, left, calendarDurationLiteral3); - result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", null), // 500 mg = 1000 milliseconds - RowFactory.create("patient-2", null), // 500 mg = 1000 milliseconds - RowFactory.create("patient-3", null), // 500 mg = 1000 milliseconds - RowFactory.create("patient-4", null), // 650 mg = 1000 milliseconds - RowFactory.create("patient-5", null), // {} = 1000 milliseconds - RowFactory.create("patient-6", null), // 500 mg = 1000 milliseconds - RowFactory.create("patient-7", false), // 30 d = 1000 milliseconds - RowFactory.create("patient-8", false), // 60 s = 1000 milliseconds - RowFactory.create("patient-9", true), // 1000 ms = 1000 milliseconds - RowFactory.create("patient-a", null), // 1000 mmol = 1000 milliseconds - RowFactory.create("patient-b", null), // 49 % = 1000 milliseconds - RowFactory.create("patient-c", null) // non-ucum = 1000 milliseconds - ); - - input = new BinaryOperatorInput(parserContext, calendarDurationLiteral2, - calendarDurationLiteral2); - result = equalityOperator.invoke(input); - - final Dataset allTrue = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.BooleanType) - .withIdsAndValue(true, - List.of("patient-1", "patient-2", "patient-3", "patient-4", "patient-5", "patient-6", - "patient-7", "patient-8", "patient-9", "patient-a", "patient-b", "patient-c")) - .build(); - assertThat(result).selectOrderedResult().hasRows(allTrue); - } - - @Test - void calendarLiteralNotEquals() { - BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, - calendarDurationLiteral1); - final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); - Collection result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", null), // 500 mg != 30 days - RowFactory.create("patient-2", null), // 500 mg != 30 days - RowFactory.create("patient-3", null), // 500 mg != 30 days - RowFactory.create("patient-4", null), // 650 mg != 30 days - RowFactory.create("patient-5", null), // {} != 30 days - RowFactory.create("patient-6", null), // 500 mg != 30 days - RowFactory.create("patient-7", null), // 30 d != 30 days - RowFactory.create("patient-8", null), // 60 s != 30 days - RowFactory.create("patient-9", null), // 1000 ms != 30 days - RowFactory.create("patient-a", null), // 1000 mmol != 30 days - RowFactory.create("patient-b", null), // 49 % != 30 days - RowFactory.create("patient-c", null) // non-ucum != 30 days - ); - - input = new BinaryOperatorInput(parserContext, left, calendarDurationLiteral2); - result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", null), // 500 mg != 60 s - RowFactory.create("patient-2", null), // 500 mg != 60 s - RowFactory.create("patient-3", null), // 500 mg != 60 s - RowFactory.create("patient-4", null), // 650 mg != 60 s - RowFactory.create("patient-5", null), // {} != 60 s - RowFactory.create("patient-6", null), // 500 mg != 60 s - RowFactory.create("patient-7", true), // 30 d != 60 s - RowFactory.create("patient-8", false), // 60 s != 60 s - RowFactory.create("patient-9", true), // 1000 ms != 60 s - RowFactory.create("patient-a", null), // 1000 mmol != 60 seconds - RowFactory.create("patient-b", null), // 49 % != 60 seconds - RowFactory.create("patient-c", null) // non-ucum != 60 seconds - ); - - input = new BinaryOperatorInput(parserContext, left, calendarDurationLiteral3); - result = equalityOperator.invoke(input); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", null), // 500 mg != 1000 ms - RowFactory.create("patient-2", null), // 500 mg != 1000 ms - RowFactory.create("patient-3", null), // 500 mg != 1000 ms - RowFactory.create("patient-4", null), // 650 mg != 1000 ms - RowFactory.create("patient-5", null), // {} != 1000 ms - RowFactory.create("patient-6", null), // 500 mg != 1000 ms - RowFactory.create("patient-7", true), // 30 d != 1000 ms - RowFactory.create("patient-8", true), // 60 s != 1000 ms - RowFactory.create("patient-9", false), // 1000 ms != 1000 ms - RowFactory.create("patient-a", null), // 1000 mmol != 1000 milliseconds - RowFactory.create("patient-b", null), // 49 % != 1000 milliseconds - RowFactory.create("patient-c", null) // non-ucum != 1000 milliseconds - ); - - input = new BinaryOperatorInput(parserContext, calendarDurationLiteral2, - calendarDurationLiteral2); - result = equalityOperator.invoke(input); - - final Dataset allFalse = new DatasetBuilder(spark) - .withIdColumn() - .withColumn(DataTypes.BooleanType) - .withIdsAndValue(false, - List.of("patient-1", "patient-2", "patient-3", "patient-4", "patient-5", "patient-6", - "patient-7", "patient-8", "patient-9", "patient-a", "patient-b", "patient-c")) - .build(); - assertThat(result).selectOrderedResult().hasRows(allFalse); - } - + // TODO: implement with columns + // + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @Autowired + // UcumService ucumService; + // + // static final String ID_ALIAS = "_abc123"; + // + // Collection left; + // Collection right; + // ParserContext parserContext; + // QuantityLiteralPath ucumQuantityLiteral1; + // QuantityLiteralPath ucumQuantityLiteral2; + // QuantityLiteralPath ucumQuantityLiteral3; + // QuantityLiteralPath calendarDurationLiteral1; + // QuantityLiteralPath calendarDurationLiteral2; + // QuantityLiteralPath calendarDurationLiteral3; + // QuantityLiteralPath ucumQuantityLiteralNoUnit; + // + // @BeforeEach + // void setUp() { + // final Quantity quantity1 = new Quantity(); + // quantity1.setValue(500); + // quantity1.setUnit("mg"); + // quantity1.setSystem(TestHelpers.UCUM_URL); + // quantity1.setCode("mg"); + // + // final Quantity quantity2 = new Quantity(); + // quantity2.setValue(0.5); + // quantity2.setUnit("g"); + // quantity2.setSystem(TestHelpers.UCUM_URL); + // quantity2.setCode("g"); + // + // final Quantity quantity3 = new Quantity(); + // quantity3.setValue(1.8); + // quantity3.setUnit("m"); + // quantity3.setSystem(TestHelpers.UCUM_URL); + // quantity3.setCode("m"); + // + // final Quantity quantity4 = new Quantity(); + // quantity4.setValue(0.5); + // quantity4.setUnit("g"); + // quantity4.setSystem(TestHelpers.SNOMED_URL); + // quantity4.setCode("258682000"); + // + // final Quantity quantity5 = new Quantity(); + // quantity5.setValue(650); + // quantity5.setUnit("mg"); + // quantity5.setSystem(TestHelpers.UCUM_URL); + // quantity5.setCode("mg"); + // + // final Quantity quantity6 = new Quantity(); + // quantity6.setValue(30); + // quantity6.setUnit("d"); + // quantity6.setSystem(TestHelpers.UCUM_URL); + // quantity6.setCode("d"); + // + // final Quantity quantity7 = new Quantity(); + // quantity7.setValue(60); + // quantity7.setUnit("s"); + // quantity7.setSystem(TestHelpers.UCUM_URL); + // quantity7.setCode("s"); + // + // final Quantity quantity8 = new Quantity(); + // quantity8.setValue(1000); + // quantity8.setUnit("ms"); + // quantity8.setSystem(TestHelpers.UCUM_URL); + // quantity8.setCode("ms"); + // + // final Quantity nonUcumQuantity = new Quantity(); + // nonUcumQuantity.setValue(15); + // nonUcumQuantity.setUnit("mSv"); + // nonUcumQuantity.setSystem(TestHelpers.SNOMED_URL); + // nonUcumQuantity.setCode("282250007"); + // + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withStructTypeColumns(quantityStructType()) + // .withRow("patient-1", rowFromQuantity(quantity1)) // 500 mg + // .withRow("patient-2", rowFromQuantity(quantity1)) // 500 mg + // .withRow("patient-3", rowFromQuantity(quantity1)) // 500 mg + // .withRow("patient-4", rowFromQuantity(quantity5)) // 650 mg + // .withRow("patient-5", null) + // .withRow("patient-6", rowFromQuantity(quantity1)) // 500 mg + // .withRow("patient-7", rowFromQuantity(quantity6)) // 30 d + // .withRow("patient-8", rowFromQuantity(quantity7)) // 60 s + // .withRow("patient-9", rowFromQuantity(quantity8)) // 1000 ms + // .withRow("patient-a", rowForUcumQuantity("1000", "mmol")) // 1000 mmol + // .withRow("patient-b", rowForUcumQuantity("49", "%")) // 49 % + // .withRow("patient-c", rowFromQuantity(nonUcumQuantity)) // non-ucum + // .buildWithStructValue(); + // left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.QUANTITY) + // .singular(true) + // .dataset(leftDataset) + // .idAndValueColumns() + // .build(); + // + // final Dataset rightDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withStructTypeColumns(quantityStructType()) + // .withRow("patient-1", rowFromQuantity(quantity1)) // 500 mg + // .withRow("patient-2", rowFromQuantity(quantity2)) // 0.5 g + // .withRow("patient-3", rowFromQuantity(quantity3)) // 1.8 m + // .withRow("patient-4", rowFromQuantity(quantity1)) // 500 mg + // .withRow("patient-5", rowFromQuantity(quantity1)) // 500 mg + // .withRow("patient-6", null) + // .withRow("patient-7", rowFromQuantity(quantity6)) // 30 d + // .withRow("patient-8", rowFromQuantity(quantity7)) // 60 s + // .withRow("patient-9", rowFromQuantity(quantity8)) // 1000 ms + // .withRow("patient-a", rowForUcumQuantity("1", "mol")) // 1 mol + // .withRow("patient-b", rowForUcumQuantity("0.5", "1")) // 0.5 '1' + // .withRow("patient-c", rowForUcumQuantity("1", "%")) // 1 % + // .buildWithStructValue(); + // right = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.QUANTITY) + // .singular(true) + // .dataset(rightDataset) + // .idAndValueColumns() + // .build(); + // + // ucumQuantityLiteral1 = QuantityCollection.fromUcumString("500 'mg'", left, ucumService); + // ucumQuantityLiteral2 = QuantityCollection.fromUcumString("0.5 'g'", left, ucumService); + // ucumQuantityLiteral3 = QuantityCollection.fromUcumString("1.8 'm'", left, ucumService); + // ucumQuantityLiteralNoUnit = QuantityCollection.fromUcumString("0.49 '1'", left, ucumService); + // + // calendarDurationLiteral1 = QuantityCollection.fromCalendarDurationString("30 days", left); + // calendarDurationLiteral2 = QuantityCollection.fromCalendarDurationString("60 seconds", left); + // calendarDurationLiteral3 = QuantityCollection.fromCalendarDurationString("1000 milliseconds", + // left); + // + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(left.getIdColumn())) + // .build(); + // } + // + // @Test + // void equals() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); + // final Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", true), // 500 mg = 500 mg + // RowFactory.create("patient-2", true), // 500 mg = 0.5 g + // RowFactory.create("patient-3", null), // 500 mg = 1.8 m + // RowFactory.create("patient-4", false), // 650 mg = 500 mg + // RowFactory.create("patient-5", null), // {} = 500 mg + // RowFactory.create("patient-6", null), // 500 mg = {} + // RowFactory.create("patient-7", true), // 30 d = 30 d + // RowFactory.create("patient-8", true), // 60 s = 60 s + // RowFactory.create("patient-9", true), // 1000 ms = 1000 ms + // RowFactory.create("patient-a", true), // 1000 mmol = 1 mol + // RowFactory.create("patient-b", false), // 49 % = 0.5 '' + // RowFactory.create("patient-c", null) // non-ucum = 1 % + // ); + // } + // + // @Test + // void notEquals() { + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); + // final Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", false), // 500 mg != 500 mg + // RowFactory.create("patient-2", false), // 500 mg != 0.5 g + // RowFactory.create("patient-3", null), // 500 mg != 1.8 m + // RowFactory.create("patient-4", true), // 650 mg != 500 mg + // RowFactory.create("patient-5", null), // {} != 500 mg + // RowFactory.create("patient-6", null), // 500 mg != {} + // RowFactory.create("patient-7", false), // 30 d != 30 d + // RowFactory.create("patient-8", false), // 60 s != 60 s + // RowFactory.create("patient-9", false), // 1000 ms != 1000 ms + // RowFactory.create("patient-a", false), // 1000 mmol != 1 mol + // RowFactory.create("patient-b", true), // 49 % != 0.5 '' + // RowFactory.create("patient-c", null) // non-ucum != 1 % + // ); + // } + // + // @Test + // void ucumLiteralEquals() { + // BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral1); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); + // Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", true), // 500 mg = 500 mg + // RowFactory.create("patient-2", true), // 500 mg = 500 mg + // RowFactory.create("patient-3", true), // 500 mg = 500 mg + // RowFactory.create("patient-4", false), // 650 mg = 500 mg + // RowFactory.create("patient-5", null), // {} = 500 mg + // RowFactory.create("patient-6", true), // 500 mg = 500 mg + // RowFactory.create("patient-7", null), // 30 d = 500 mg + // RowFactory.create("patient-8", null), // 60 s = 500 mg + // RowFactory.create("patient-9", null), // 1000 ms = 500 mg + // RowFactory.create("patient-a", null), // 1000 mmol = 500 mg + // RowFactory.create("patient-b", null), // 49 % = 500 mg + // RowFactory.create("patient-c", null) // non-ucum = 500 mg + // ); + // + // input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral2); + // result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", true), // 500 mg = 0.5 mg + // RowFactory.create("patient-2", true), // 500 mg = 0.5 mg + // RowFactory.create("patient-3", true), // 500 mg = 0.5 mg + // RowFactory.create("patient-4", false), // 650 mg = 0.5 mg + // RowFactory.create("patient-5", null), // {} = 0.5 mg + // RowFactory.create("patient-6", true), // 500 mg = 0.5 mg + // RowFactory.create("patient-7", null), // 30 d = 0.5 mg + // RowFactory.create("patient-8", null), // 60 s = 0.5 mg + // RowFactory.create("patient-9", null), // 1000 ms = 0.5 mg + // RowFactory.create("patient-a", null), // 1000 mmol = 0.5 mg + // RowFactory.create("patient-b", null), // 49 % = 0.5 mg + // RowFactory.create("patient-c", null) // non-ucum = 0.5 mg + // ); + // + // input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral3); + // result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", null), // 500 mg = 1.8 m + // RowFactory.create("patient-2", null), // 500 mg = 1.8 m + // RowFactory.create("patient-3", null), // 500 mg = 1.8 m + // RowFactory.create("patient-4", null), // 650 mg = 1.8 m + // RowFactory.create("patient-5", null), // {} = 1.8 m + // RowFactory.create("patient-6", null), // 500 mg = 1.8 m + // RowFactory.create("patient-7", null), // 30 d = 1.8 m + // RowFactory.create("patient-8", null), // 60 s = 1.8 m + // RowFactory.create("patient-9", null), // 1000 ms = 1.8 m + // RowFactory.create("patient-a", null), // 1000 mmol = 1.8 m + // RowFactory.create("patient-b", null), // 49 % = 1.8 m + // RowFactory.create("patient-c", null) // non-ucum = 1.8 m + // ); + // + // input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteralNoUnit); + // result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", null), // 500 mg = 0.49 '1' + // RowFactory.create("patient-2", null), // 500 mg = 0.49 '1' + // RowFactory.create("patient-3", null), // 500 mg = 0.49 '1' + // RowFactory.create("patient-4", null), // 650 mg = 0.49 '1' + // RowFactory.create("patient-5", null), // {} = 0.49 '1' + // RowFactory.create("patient-6", null), // 500 mg = 0.49 '1 + // RowFactory.create("patient-7", null), // 30 d = 0.49 '1' + // RowFactory.create("patient-8", null), // 60 s = 0.49 '1' + // RowFactory.create("patient-9", null), // 1000 ms = 0.49 '1' + // RowFactory.create("patient-a", false), // 1000 mmol = 0.49 '1' + // RowFactory.create("patient-b", true), // 49 % = 0.49 '1' + // RowFactory.create("patient-c", null) // non-ucum = 0.49 '1' + // ); + // + // input = new BinaryOperatorInput(parserContext, ucumQuantityLiteral1, ucumQuantityLiteral1); + // result = equalityOperator.invoke(input); + // + // final Dataset allTrue = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.BooleanType) + // .withIdsAndValue(true, + // List.of("patient-1", "patient-2", "patient-3", "patient-4", "patient-5", "patient-6", + // "patient-7", "patient-8", "patient-9", "patient-a", "patient-b", "patient-c")) + // .build(); + // assertThat(result).selectOrderedResult().hasRows(allTrue); + // } + // + // @Test + // void ucumLiteralNotEquals() { + // BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral1); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); + // Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", false), // 500 mg != 500 mg + // RowFactory.create("patient-2", false), // 500 mg != 500 mg + // RowFactory.create("patient-3", false), // 500 mg != 500 mg + // RowFactory.create("patient-4", true), // 650 mg != 500 mg + // RowFactory.create("patient-5", null), // {} != 500 mg + // RowFactory.create("patient-6", false), // 500 mg != 500 mg + // RowFactory.create("patient-7", null), // 30 d != 500 mg + // RowFactory.create("patient-8", null), // 60 s != 500 mg + // RowFactory.create("patient-9", null), // 1000 ms != 500 mg + // RowFactory.create("patient-a", null), // 1000 mmol != 500 mg + // RowFactory.create("patient-b", null), // 49 % != 500 mg + // RowFactory.create("patient-c", null) // non-ucum != 500 mg + // ); + // + // input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral2); + // result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", false), // 500 mg != 0.5 mg + // RowFactory.create("patient-2", false), // 500 mg != 0.5 mg + // RowFactory.create("patient-3", false), // 500 mg != 0.5 mg + // RowFactory.create("patient-4", true), // 650 mg != 0.5 mg + // RowFactory.create("patient-5", null), // {} != 0.5 mg + // RowFactory.create("patient-6", false), // 500 mg != 0.5 mg + // RowFactory.create("patient-7", null), // 30 d != 0.5 mg + // RowFactory.create("patient-8", null), // 60 s != 0.5 mg + // RowFactory.create("patient-9", null), // 1000 ms != 0.5 mg + // RowFactory.create("patient-a", null), // 1000 mmol != 0.5 mg + // RowFactory.create("patient-b", null), // 49 % != 0.5 mg + // RowFactory.create("patient-c", null) // non-ucum != 0.5 mg + // ); + // + // input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteral3); + // result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", null), // 500 mg != 1.8 m + // RowFactory.create("patient-2", null), // 500 mg != 1.8 m + // RowFactory.create("patient-3", null), // 500 mg != 1.8 m + // RowFactory.create("patient-4", null), // 650 mg != 1.8 m + // RowFactory.create("patient-5", null), // {} != 1.8 m + // RowFactory.create("patient-6", null), // 500 mg != 1.8 m + // RowFactory.create("patient-7", null), // 30 d != 1.8 m + // RowFactory.create("patient-8", null), // 60 s != 1.8 m + // RowFactory.create("patient-9", null), // 1000 ms != 1.8 m + // RowFactory.create("patient-a", null), // 1000 mmol != 1.8 m + // RowFactory.create("patient-b", null), // 49 % != 1.8 m + // RowFactory.create("patient-c", null) // non-ucum != 1.8 m + // ); + // + // input = new BinaryOperatorInput(parserContext, left, ucumQuantityLiteralNoUnit); + // result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", null), // 500 mg != 0.49 '' + // RowFactory.create("patient-2", null), // 500 mg != 0.49 '' + // RowFactory.create("patient-3", null), // 500 mg != 0.49 '' + // RowFactory.create("patient-4", null), // 650 mg != 0.49 '' + // RowFactory.create("patient-5", null), // {} != 0.49 '' + // RowFactory.create("patient-6", null), // 500 mg != 0.49 '' + // RowFactory.create("patient-7", null), // 30 d != 0.49 '' + // RowFactory.create("patient-8", null), // 60 s != 0.49 '' + // RowFactory.create("patient-9", null), // 1000 ms != 0.49 '' + // RowFactory.create("patient-a", true), // 1000 mmol != 0.49 '' + // RowFactory.create("patient-b", false), // 49 % != 0.49 '' + // RowFactory.create("patient-c", null) // non-ucum != 0.49 '' + // ); + // + // input = new BinaryOperatorInput(parserContext, ucumQuantityLiteral1, ucumQuantityLiteral1); + // result = equalityOperator.invoke(input); + // + // final Dataset allFalse = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.BooleanType) + // .withIdsAndValue(false, + // List.of("patient-1", "patient-2", "patient-3", "patient-4", "patient-5", "patient-6", + // "patient-7", "patient-8", "patient-9", "patient-a", "patient-b", "patient-c")) + // .build(); + // assertThat(result).selectOrderedResult().hasRows(allFalse); + // } + // + // @Test + // void calendarLiteralEquals() { + // BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, + // calendarDurationLiteral1); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance("="); + // Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", null), // 500 mg = 30 days + // RowFactory.create("patient-2", null), // 500 mg = 30 days + // RowFactory.create("patient-3", null), // 500 mg = 30 days + // RowFactory.create("patient-4", null), // 650 mg = 30 days + // RowFactory.create("patient-5", null), // {} = 30 days + // RowFactory.create("patient-6", null), // 500 mg = 30 days + // RowFactory.create("patient-7", null), // 30 d = 30 days + // RowFactory.create("patient-8", null), // 60 s = 30 days + // RowFactory.create("patient-9", null), // 1000 ms = 30 days + // RowFactory.create("patient-a", null), // 1000 mmol = 30 days + // RowFactory.create("patient-b", null), // 49 % = 30 days + // RowFactory.create("patient-c", null) // non-ucum = 30 days + // ); + // + // input = new BinaryOperatorInput(parserContext, left, calendarDurationLiteral2); + // result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", null), // 500 mg = 60 seconds + // RowFactory.create("patient-2", null), // 500 mg = 60 seconds + // RowFactory.create("patient-3", null), // 500 mg = 60 seconds + // RowFactory.create("patient-4", null), // 650 mg = 60 seconds + // RowFactory.create("patient-5", null), // {} = 60 seconds + // RowFactory.create("patient-6", null), // 500 mg = 60 seconds + // RowFactory.create("patient-7", false), // 30 d = 60 seconds + // RowFactory.create("patient-8", true), // 60 s = 60 seconds + // RowFactory.create("patient-9", false), // 1000 ms = 60 seconds + // RowFactory.create("patient-a", null), // 1000 mmol = 60 seconds + // RowFactory.create("patient-b", null), // 49 % = 60 seconds + // RowFactory.create("patient-c", null) // non-ucum = 60 seconds + // ); + // + // input = new BinaryOperatorInput(parserContext, left, calendarDurationLiteral3); + // result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", null), // 500 mg = 1000 milliseconds + // RowFactory.create("patient-2", null), // 500 mg = 1000 milliseconds + // RowFactory.create("patient-3", null), // 500 mg = 1000 milliseconds + // RowFactory.create("patient-4", null), // 650 mg = 1000 milliseconds + // RowFactory.create("patient-5", null), // {} = 1000 milliseconds + // RowFactory.create("patient-6", null), // 500 mg = 1000 milliseconds + // RowFactory.create("patient-7", false), // 30 d = 1000 milliseconds + // RowFactory.create("patient-8", false), // 60 s = 1000 milliseconds + // RowFactory.create("patient-9", true), // 1000 ms = 1000 milliseconds + // RowFactory.create("patient-a", null), // 1000 mmol = 1000 milliseconds + // RowFactory.create("patient-b", null), // 49 % = 1000 milliseconds + // RowFactory.create("patient-c", null) // non-ucum = 1000 milliseconds + // ); + // + // input = new BinaryOperatorInput(parserContext, calendarDurationLiteral2, + // calendarDurationLiteral2); + // result = equalityOperator.invoke(input); + // + // final Dataset allTrue = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.BooleanType) + // .withIdsAndValue(true, + // List.of("patient-1", "patient-2", "patient-3", "patient-4", "patient-5", "patient-6", + // "patient-7", "patient-8", "patient-9", "patient-a", "patient-b", "patient-c")) + // .build(); + // assertThat(result).selectOrderedResult().hasRows(allTrue); + // } + // + // @Test + // void calendarLiteralNotEquals() { + // BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, + // calendarDurationLiteral1); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance("!="); + // Collection result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", null), // 500 mg != 30 days + // RowFactory.create("patient-2", null), // 500 mg != 30 days + // RowFactory.create("patient-3", null), // 500 mg != 30 days + // RowFactory.create("patient-4", null), // 650 mg != 30 days + // RowFactory.create("patient-5", null), // {} != 30 days + // RowFactory.create("patient-6", null), // 500 mg != 30 days + // RowFactory.create("patient-7", null), // 30 d != 30 days + // RowFactory.create("patient-8", null), // 60 s != 30 days + // RowFactory.create("patient-9", null), // 1000 ms != 30 days + // RowFactory.create("patient-a", null), // 1000 mmol != 30 days + // RowFactory.create("patient-b", null), // 49 % != 30 days + // RowFactory.create("patient-c", null) // non-ucum != 30 days + // ); + // + // input = new BinaryOperatorInput(parserContext, left, calendarDurationLiteral2); + // result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", null), // 500 mg != 60 s + // RowFactory.create("patient-2", null), // 500 mg != 60 s + // RowFactory.create("patient-3", null), // 500 mg != 60 s + // RowFactory.create("patient-4", null), // 650 mg != 60 s + // RowFactory.create("patient-5", null), // {} != 60 s + // RowFactory.create("patient-6", null), // 500 mg != 60 s + // RowFactory.create("patient-7", true), // 30 d != 60 s + // RowFactory.create("patient-8", false), // 60 s != 60 s + // RowFactory.create("patient-9", true), // 1000 ms != 60 s + // RowFactory.create("patient-a", null), // 1000 mmol != 60 seconds + // RowFactory.create("patient-b", null), // 49 % != 60 seconds + // RowFactory.create("patient-c", null) // non-ucum != 60 seconds + // ); + // + // input = new BinaryOperatorInput(parserContext, left, calendarDurationLiteral3); + // result = equalityOperator.invoke(input); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", null), // 500 mg != 1000 ms + // RowFactory.create("patient-2", null), // 500 mg != 1000 ms + // RowFactory.create("patient-3", null), // 500 mg != 1000 ms + // RowFactory.create("patient-4", null), // 650 mg != 1000 ms + // RowFactory.create("patient-5", null), // {} != 1000 ms + // RowFactory.create("patient-6", null), // 500 mg != 1000 ms + // RowFactory.create("patient-7", true), // 30 d != 1000 ms + // RowFactory.create("patient-8", true), // 60 s != 1000 ms + // RowFactory.create("patient-9", false), // 1000 ms != 1000 ms + // RowFactory.create("patient-a", null), // 1000 mmol != 1000 milliseconds + // RowFactory.create("patient-b", null), // 49 % != 1000 milliseconds + // RowFactory.create("patient-c", null) // non-ucum != 1000 milliseconds + // ); + // + // input = new BinaryOperatorInput(parserContext, calendarDurationLiteral2, + // calendarDurationLiteral2); + // result = equalityOperator.invoke(input); + // + // final Dataset allFalse = new DatasetBuilder(spark) + // .withIdColumn() + // .withColumn(DataTypes.BooleanType) + // .withIdsAndValue(false, + // List.of("patient-1", "patient-2", "patient-3", "patient-4", "patient-5", "patient-6", + // "patient-7", "patient-8", "patient-9", "patient-a", "patient-b", "patient-c")) + // .build(); + // assertThat(result).selectOrderedResult().hasRows(allFalse); + // } + // } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorQuantityTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorQuantityTest.java index 22abb6ee0f..4548cf4fe3 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorQuantityTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorQuantityTest.java @@ -17,223 +17,198 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.helpers.SparkHelpers.quantityStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowForUcumQuantity; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromQuantity; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.QuantityCollection; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.TestHelpers; -import ca.uhn.fhir.context.FhirContext; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import lombok.Value; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.fhir.ucum.UcumService; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.hl7.fhir.r4.model.Quantity; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.springframework.beans.factory.annotation.Autowired; @SpringBootUnitTest @TestInstance(Lifecycle.PER_CLASS) +@NotImplemented public class MathOperatorQuantityTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @Autowired - UcumService ucumService; - - static final List OPERATORS = List.of("+", "-", "*", "/"); - static final String ID_ALIAS = "_abc123"; - - @Value - static class TestParameters { - - @Nonnull - String name; - - @Nonnull - Collection left; - - @Nonnull - Collection right; - - @Nonnull - ParserContext context; - - @Nonnull - BinaryOperator operator; - - @Nonnull - Dataset expectedResult; - - @Override - public String toString() { - return name; - } - - } - - @Nonnull - Stream parameters() { - final java.util.Collection parameters = new ArrayList<>(); - for (final String operator : OPERATORS) { - final String name = "Quantity " + operator + " Quantity"; - final Collection left = buildQuantityExpression(true); - final Collection right = buildQuantityExpression(false); - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(left.getIdColumn())) - .build(); - parameters.add( - new TestParameters(name, left, right, context, BinaryOperator.getInstance(operator), - expectedResult(operator))); - } - return parameters.stream(); - } - - Dataset expectedResult(@Nonnull final String operator) { - final Row result1; - final Row result2; - - switch (operator) { - case "+": - result1 = rowForUcumQuantity(new BigDecimal("1650.0"), "m"); - result2 = null; - break; - case "-": - result1 = rowForUcumQuantity(new BigDecimal("-1350.0"), "m"); - result2 = null; - break; - case "*": - result1 = null; - result2 = rowForUcumQuantity("1000.0", "g"); - break; - case "/": - result1 = rowForUcumQuantity(new BigDecimal("0.1"), "1"); - result2 = rowForUcumQuantity("4000", "g"); - break; - default: - result1 = null; - result2 = null; - } - return new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withStructTypeColumns(quantityStructType()) - .withRow("patient-1", result1) - .withRow("patient-2", result2) - .withRow("patient-3", null) - .withRow("patient-4", null) - .withRow("patient-5", null) - .withRow("patient-6", null) - .withRow("patient-7", null) - .buildWithStructValue(); - } - - @Nonnull - Collection buildQuantityExpression(final boolean leftOperand) { - final Quantity nonUcumQuantity = new Quantity(); - nonUcumQuantity.setValue(15); - nonUcumQuantity.setUnit("mSv"); - nonUcumQuantity.setSystem(TestHelpers.SNOMED_URL); - nonUcumQuantity.setCode("282250007"); - - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withStructTypeColumns(quantityStructType()) - .withRow("patient-1", leftOperand - ? rowForUcumQuantity(new BigDecimal("150.0"), "m") - : rowForUcumQuantity(new BigDecimal("1.5"), "km")) - .withRow("patient-2", leftOperand - ? rowForUcumQuantity(new BigDecimal("2.0"), "kg") - : rowForUcumQuantity(new BigDecimal("0.5"), "1")) - .withRow("patient-3", leftOperand - ? rowForUcumQuantity(new BigDecimal("7.7"), "mSv") - : rowForUcumQuantity(new BigDecimal("1.5"), "h")) - // Not comparable - .withRow("patient-4", leftOperand - ? rowForUcumQuantity(new BigDecimal("7.7"), "mSv") - : rowFromQuantity(nonUcumQuantity)) - // Not comparable - .withRow("patient-5", leftOperand - ? null - : rowForUcumQuantity(new BigDecimal("1.5"), "h")) - .withRow("patient-6", leftOperand - ? rowForUcumQuantity(new BigDecimal("7.7"), "mSv") - : null) - .withRow("patient-7", null) - .buildWithStructValue(); - return new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.QUANTITY) - .dataset(dataset) - .idAndValueColumns() - .singular(true) - .build(); - } - - @ParameterizedTest - @MethodSource("parameters") - void test(@Nonnull final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLeft(), parameters.getRight()); - final Collection result = parameters.getOperator().invoke(input); - assertThat(result).selectOrderedResult().hasRows(parameters.getExpectedResult()); - } - - /* - * This test requires a very high precision decimal representation, due to these units being - * canonicalized by UCUM to "reciprocal cubic meters". - * See: https://www.wolframalpha.com/input?i=3+mmol%2FL+in+m%5E-3 - */ - @Test - void volumeArithmetic() { - final Dataset rightDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withStructTypeColumns(quantityStructType()) - .withRow("patient-1", rowForUcumQuantity(new BigDecimal("1.0"), "mmol/L")) - .buildWithStructValue(); - final Collection right = new ElementPathBuilder(spark) - .expression("valueQuantity") - .fhirType(FHIRDefinedType.QUANTITY) - .singular(true) - .dataset(rightDataset) - .idAndValueColumns() - .build(); - final Collection left = QuantityCollection.fromUcumString("1.0 'mmol/L'", right, - ucumService); - final ParserContext context = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(left.getIdColumn())) - .build(); - final BinaryOperatorInput input = new BinaryOperatorInput(context, left, right); - final Collection result = BinaryOperator.getInstance("+").invoke(input); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withStructTypeColumns(quantityStructType()) - .withRow("patient-1", - rowForUcumQuantity(new BigDecimal("1204427340000000000000000"), "m-3")) - .buildWithStructValue(); - assertThat(result).selectOrderedResult().hasRows(expectedDataset); - } + // TODO: implement with columns + + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @Autowired + // UcumService ucumService; + // + // static final List OPERATORS = List.of("+", "-", "*", "/"); + // static final String ID_ALIAS = "_abc123"; + // + // @Value + // static class TestParameters { + // + // @Nonnull + // String name; + // + // @Nonnull + // Collection left; + // + // @Nonnull + // Collection right; + // + // @Nonnull + // ParserContext context; + // + // @Nonnull + // BinaryOperator operator; + // + // @Nonnull + // Dataset expectedResult; + // + // @Override + // public String toString() { + // return name; + // } + // + // } + // + // @Nonnull + // Stream parameters() { + // final java.util.Collection parameters = new ArrayList<>(); + // for (final String operator : OPERATORS) { + // final String name = "Quantity " + operator + " Quantity"; + // final Collection left = buildQuantityExpression(true); + // final Collection right = buildQuantityExpression(false); + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(left.getIdColumn())) + // .build(); + // parameters.add( + // new TestParameters(name, left, right, context, BinaryOperator.getInstance(operator), + // expectedResult(operator))); + // } + // return parameters.stream(); + // } + // + // Dataset expectedResult(@Nonnull final String operator) { + // final Row result1; + // final Row result2; + // + // switch (operator) { + // case "+": + // result1 = rowForUcumQuantity(new BigDecimal("1650.0"), "m"); + // result2 = null; + // break; + // case "-": + // result1 = rowForUcumQuantity(new BigDecimal("-1350.0"), "m"); + // result2 = null; + // break; + // case "*": + // result1 = null; + // result2 = rowForUcumQuantity("1000.0", "g"); + // break; + // case "/": + // result1 = rowForUcumQuantity(new BigDecimal("0.1"), "1"); + // result2 = rowForUcumQuantity("4000", "g"); + // break; + // default: + // result1 = null; + // result2 = null; + // } + // return new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withStructTypeColumns(quantityStructType()) + // .withRow("patient-1", result1) + // .withRow("patient-2", result2) + // .withRow("patient-3", null) + // .withRow("patient-4", null) + // .withRow("patient-5", null) + // .withRow("patient-6", null) + // .withRow("patient-7", null) + // .buildWithStructValue(); + // } + // + // @Nonnull + // Collection buildQuantityExpression(final boolean leftOperand) { + // final Quantity nonUcumQuantity = new Quantity(); + // nonUcumQuantity.setValue(15); + // nonUcumQuantity.setUnit("mSv"); + // nonUcumQuantity.setSystem(TestHelpers.SNOMED_URL); + // nonUcumQuantity.setCode("282250007"); + // + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withStructTypeColumns(quantityStructType()) + // .withRow("patient-1", leftOperand + // ? rowForUcumQuantity(new BigDecimal("150.0"), "m") + // : rowForUcumQuantity(new BigDecimal("1.5"), "km")) + // .withRow("patient-2", leftOperand + // ? rowForUcumQuantity(new BigDecimal("2.0"), "kg") + // : rowForUcumQuantity(new BigDecimal("0.5"), "1")) + // .withRow("patient-3", leftOperand + // ? rowForUcumQuantity(new BigDecimal("7.7"), "mSv") + // : rowForUcumQuantity(new BigDecimal("1.5"), "h")) + // // Not comparable + // .withRow("patient-4", leftOperand + // ? rowForUcumQuantity(new BigDecimal("7.7"), "mSv") + // : rowFromQuantity(nonUcumQuantity)) + // // Not comparable + // .withRow("patient-5", leftOperand + // ? null + // : rowForUcumQuantity(new BigDecimal("1.5"), "h")) + // .withRow("patient-6", leftOperand + // ? rowForUcumQuantity(new BigDecimal("7.7"), "mSv") + // : null) + // .withRow("patient-7", null) + // .buildWithStructValue(); + // return new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.QUANTITY) + // .dataset(dataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void test(@Nonnull final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLeft(), parameters.getRight()); + // final Collection result = parameters.getOperator().invoke(input); + // assertThat(result).selectOrderedResult().hasRows(parameters.getExpectedResult()); + // } + // + // /* + // * This test requires a very high precision decimal representation, due to these units being + // * canonicalized by UCUM to "reciprocal cubic meters". + // * See: https://www.wolframalpha.com/input?i=3+mmol%2FL+in+m%5E-3 + // */ + // @Test + // void volumeArithmetic() { + // final Dataset rightDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withStructTypeColumns(quantityStructType()) + // .withRow("patient-1", rowForUcumQuantity(new BigDecimal("1.0"), "mmol/L")) + // .buildWithStructValue(); + // final Collection right = new ElementPathBuilder(spark) + // .expression("valueQuantity") + // .fhirType(FHIRDefinedType.QUANTITY) + // .singular(true) + // .dataset(rightDataset) + // .idAndValueColumns() + // .build(); + // final Collection left = QuantityCollection.fromUcumString("1.0 'mmol/L'", right, + // ucumService); + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(left.getIdColumn())) + // .build(); + // final BinaryOperatorInput input = new BinaryOperatorInput(context, left, right); + // final Collection result = BinaryOperator.getInstance("+").invoke(input); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withStructTypeColumns(quantityStructType()) + // .withRow("patient-1", + // rowForUcumQuantity(new BigDecimal("1204427340000000000000000"), "m-3")) + // .buildWithStructValue(); + // assertThat(result).selectOrderedResult().hasRows(expectedDataset); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorTest.java index c2af233dec..bc73e87ad9 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorTest.java @@ -17,315 +17,292 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.DecimalCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.literal.IntegerLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.DatasetBuilder; -import au.csiro.pathling.test.builders.ElementPathBuilder; -import au.csiro.pathling.test.builders.ParserContextBuilder; -import ca.uhn.fhir.context.FhirContext; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import lombok.Value; -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.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest @TestInstance(Lifecycle.PER_CLASS) +@NotImplemented class MathOperatorTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - static final List EXPRESSION_TYPES = Arrays - .asList("Integer", "Decimal", "Integer (literal)", "Decimal (literal)"); - static final String ID_ALIAS = "_abc123"; - - @Value - static class TestParameters { - - @Nonnull - String name; - - @Nonnull - Collection left; - - @Nonnull - Collection right; - - @Nonnull - ParserContext context; - - boolean leftOperandIsInteger; - - boolean leftTypeIsLiteral; - - boolean rightTypeIsLiteral; - - @Override - public String toString() { - return name; - } - - } - - Stream parameters() { - final java.util.Collection parameters = new ArrayList<>(); - for (final String leftType : EXPRESSION_TYPES) { - for (final String rightType : EXPRESSION_TYPES) { - final Collection left = getExpressionForType(leftType, true); - final Collection right = getExpressionForType(rightType, false); - final boolean leftOperandIsInteger = - leftType.equals("Integer") || leftType.equals("Integer (literal)"); - final boolean leftTypeIsLiteral = - leftType.equals("Integer (literal)") || leftType.equals("Decimal (literal)"); - final boolean rightTypeIsLiteral = - rightType.equals("Integer (literal)") || rightType.equals("Decimal (literal)"); - final ParserContext context = new ParserContextBuilder(spark, fhirContext).groupingColumns( - Collections.singletonList(left.getIdColumn())).build(); - parameters.add( - new TestParameters(leftType + ", " + rightType, left, right, context, - leftOperandIsInteger, leftTypeIsLiteral, rightTypeIsLiteral)); - } - } - return parameters.stream(); - } - - Collection getExpressionForType(final String expressionType, - final boolean leftOperand) { - final Dataset literalContextDataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.BooleanType) - .withIdsAndValue(false, Arrays - .asList("patient-1", "patient-2", "patient-3", "patient-4")) - .build(); - final PrimitivePath literalContext = new ElementPathBuilder(spark) - .dataset(literalContextDataset) - .idAndValueColumns() - .build(); - switch (expressionType) { - case "Integer": - return buildIntegerExpression(leftOperand); - case "Integer (literal)": - return IntegerLiteralPath.fromString(leftOperand - ? "1" - : "2", literalContext); - case "Decimal": - return buildDecimalExpression(leftOperand); - case "Decimal (literal)": - return DecimalCollection.fromLiteral(leftOperand - ? "1.0" - : "2.0", literalContext); - default: - throw new RuntimeException("Invalid data type"); - } - } - - Collection buildIntegerExpression(final boolean leftOperand) { - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.IntegerType) - .withRow("patient-1", leftOperand - ? 1 - : 2) - .withRow("patient-2", leftOperand - ? null - : 2) - .withRow("patient-3", leftOperand - ? 1 - : null) - .withRow("patient-4", null) - .build(); - return new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .dataset(dataset) - .idAndValueColumns() - .singular(true) - .build(); - } - - Collection buildDecimalExpression(final boolean leftOperand) { - final Dataset dataset = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withColumn(DataTypes.createDecimalType()) - .withRow("patient-1", new BigDecimal(leftOperand - ? "1.0" - : "2.0")) - .withRow("patient-2", leftOperand - ? null - : new BigDecimal("2.0")) - .withRow("patient-3", leftOperand - ? new BigDecimal("1.0") - : null) - .withRow("patient-4", null) - .build(); - return new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DECIMAL) - .dataset(dataset) - .idAndValueColumns() - .singular(true) - .build(); - } - - @ParameterizedTest - @MethodSource("parameters") - void addition(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLeft(), - parameters.getRight()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance("+"); - final Collection result = comparisonOperator.invoke(input); - final Object value = parameters.isLeftOperandIsInteger() - ? 3 - : new BigDecimal("3.0"); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", value), - RowFactory.create("patient-2", parameters.isLeftTypeIsLiteral() - ? value - : null), - RowFactory.create("patient-3", parameters.isRightTypeIsLiteral() - ? value - : null), - RowFactory - .create("patient-4", - parameters.isLeftTypeIsLiteral() && parameters.isRightTypeIsLiteral() - ? value - : null) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void subtraction(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLeft(), - parameters.getRight()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance("-"); - final Collection result = comparisonOperator.invoke(input); - final Object value = parameters.isLeftOperandIsInteger() - ? -1 - : new BigDecimal("-1.0"); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", value), - RowFactory.create("patient-2", parameters.isLeftTypeIsLiteral() - ? value - : null), - RowFactory.create("patient-3", parameters.isRightTypeIsLiteral() - ? value - : null), - RowFactory - .create("patient-4", - parameters.isLeftTypeIsLiteral() && parameters.isRightTypeIsLiteral() - ? value - : null) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void multiplication(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLeft(), - parameters.getRight()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance("*"); - final Collection result = comparisonOperator.invoke(input); - final Object value = parameters.isLeftOperandIsInteger() - ? 2 - : new BigDecimal("2.0"); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", value), - RowFactory.create("patient-2", parameters.isLeftTypeIsLiteral() - ? value - : null), - RowFactory.create("patient-3", parameters.isRightTypeIsLiteral() - ? value - : null), - RowFactory - .create("patient-4", - parameters.isLeftTypeIsLiteral() && parameters.isRightTypeIsLiteral() - ? value - : null) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void division(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLeft(), - parameters.getRight()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance("/"); - final Collection result = comparisonOperator.invoke(input); - final Object value = new BigDecimal("0.5"); - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", value), - RowFactory.create("patient-2", parameters.isLeftTypeIsLiteral() - ? value - : null), - RowFactory.create("patient-3", parameters.isRightTypeIsLiteral() - ? value - : null), - RowFactory - .create("patient-4", - parameters.isLeftTypeIsLiteral() && parameters.isRightTypeIsLiteral() - ? value - : null) - ); - } - - @ParameterizedTest - @MethodSource("parameters") - void modulus(final TestParameters parameters) { - final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), - parameters.getLeft(), - parameters.getRight()); - final BinaryOperator comparisonOperator = BinaryOperator.getInstance("mod"); - final Collection result = comparisonOperator.invoke(input); - final Object value = 1; - - assertThat(result).selectOrderedResult().hasRows( - RowFactory.create("patient-1", value), - RowFactory.create("patient-2", parameters.isLeftTypeIsLiteral() - ? value - : null), - RowFactory.create("patient-3", parameters.isRightTypeIsLiteral() - ? value - : null), - RowFactory - .create("patient-4", - parameters.isLeftTypeIsLiteral() && parameters.isRightTypeIsLiteral() - ? value - : null) - ); - } + // TODO: implement with columns + + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // static final List EXPRESSION_TYPES = Arrays + // .asList("Integer", "Decimal", "Integer (literal)", "Decimal (literal)"); + // static final String ID_ALIAS = "_abc123"; + // + // @Value + // static class TestParameters { + // + // @Nonnull + // String name; + // + // @Nonnull + // Collection left; + // + // @Nonnull + // Collection right; + // + // @Nonnull + // ParserContext context; + // + // boolean leftOperandIsInteger; + // + // boolean leftTypeIsLiteral; + // + // boolean rightTypeIsLiteral; + // + // @Override + // public String toString() { + // return name; + // } + // + // } + // + // Stream parameters() { + // final java.util.Collection parameters = new ArrayList<>(); + // for (final String leftType : EXPRESSION_TYPES) { + // for (final String rightType : EXPRESSION_TYPES) { + // final Collection left = getExpressionForType(leftType, true); + // final Collection right = getExpressionForType(rightType, false); + // final boolean leftOperandIsInteger = + // leftType.equals("Integer") || leftType.equals("Integer (literal)"); + // final boolean leftTypeIsLiteral = + // leftType.equals("Integer (literal)") || leftType.equals("Decimal (literal)"); + // final boolean rightTypeIsLiteral = + // rightType.equals("Integer (literal)") || rightType.equals("Decimal (literal)"); + // final ParserContext context = new ParserContextBuilder(spark, fhirContext).groupingColumns( + // Collections.singletonList(left.getIdColumn())).build(); + // parameters.add( + // new TestParameters(leftType + ", " + rightType, left, right, context, + // leftOperandIsInteger, leftTypeIsLiteral, rightTypeIsLiteral)); + // } + // } + // return parameters.stream(); + // } + // + // Collection getExpressionForType(final String expressionType, + // final boolean leftOperand) { + // final Dataset literalContextDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.BooleanType) + // .withIdsAndValue(false, Arrays + // .asList("patient-1", "patient-2", "patient-3", "patient-4")) + // .build(); + // final PrimitivePath literalContext = new ElementPathBuilder(spark) + // .dataset(literalContextDataset) + // .idAndValueColumns() + // .build(); + // switch (expressionType) { + // case "Integer": + // return buildIntegerExpression(leftOperand); + // case "Integer (literal)": + // return IntegerLiteralPath.fromString(leftOperand + // ? "1" + // : "2", literalContext); + // case "Decimal": + // return buildDecimalExpression(leftOperand); + // case "Decimal (literal)": + // return DecimalCollection.fromLiteral(leftOperand + // ? "1.0" + // : "2.0", literalContext); + // default: + // throw new RuntimeException("Invalid data type"); + // } + // } + // + // Collection buildIntegerExpression(final boolean leftOperand) { + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.IntegerType) + // .withRow("patient-1", leftOperand + // ? 1 + // : 2) + // .withRow("patient-2", leftOperand + // ? null + // : 2) + // .withRow("patient-3", leftOperand + // ? 1 + // : null) + // .withRow("patient-4", null) + // .build(); + // return new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .dataset(dataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // } + // + // Collection buildDecimalExpression(final boolean leftOperand) { + // final Dataset dataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.createDecimalType()) + // .withRow("patient-1", new BigDecimal(leftOperand + // ? "1.0" + // : "2.0")) + // .withRow("patient-2", leftOperand + // ? null + // : new BigDecimal("2.0")) + // .withRow("patient-3", leftOperand + // ? new BigDecimal("1.0") + // : null) + // .withRow("patient-4", null) + // .build(); + // return new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DECIMAL) + // .dataset(dataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void addition(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLeft(), + // parameters.getRight()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance("+"); + // final Collection result = comparisonOperator.invoke(input); + // final Object value = parameters.isLeftOperandIsInteger() + // ? 3 + // : new BigDecimal("3.0"); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", value), + // RowFactory.create("patient-2", parameters.isLeftTypeIsLiteral() + // ? value + // : null), + // RowFactory.create("patient-3", parameters.isRightTypeIsLiteral() + // ? value + // : null), + // RowFactory + // .create("patient-4", + // parameters.isLeftTypeIsLiteral() && parameters.isRightTypeIsLiteral() + // ? value + // : null) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void subtraction(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLeft(), + // parameters.getRight()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance("-"); + // final Collection result = comparisonOperator.invoke(input); + // final Object value = parameters.isLeftOperandIsInteger() + // ? -1 + // : new BigDecimal("-1.0"); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", value), + // RowFactory.create("patient-2", parameters.isLeftTypeIsLiteral() + // ? value + // : null), + // RowFactory.create("patient-3", parameters.isRightTypeIsLiteral() + // ? value + // : null), + // RowFactory + // .create("patient-4", + // parameters.isLeftTypeIsLiteral() && parameters.isRightTypeIsLiteral() + // ? value + // : null) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void multiplication(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLeft(), + // parameters.getRight()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance("*"); + // final Collection result = comparisonOperator.invoke(input); + // final Object value = parameters.isLeftOperandIsInteger() + // ? 2 + // : new BigDecimal("2.0"); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", value), + // RowFactory.create("patient-2", parameters.isLeftTypeIsLiteral() + // ? value + // : null), + // RowFactory.create("patient-3", parameters.isRightTypeIsLiteral() + // ? value + // : null), + // RowFactory + // .create("patient-4", + // parameters.isLeftTypeIsLiteral() && parameters.isRightTypeIsLiteral() + // ? value + // : null) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void division(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLeft(), + // parameters.getRight()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance("/"); + // final Collection result = comparisonOperator.invoke(input); + // final Object value = new BigDecimal("0.5"); + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", value), + // RowFactory.create("patient-2", parameters.isLeftTypeIsLiteral() + // ? value + // : null), + // RowFactory.create("patient-3", parameters.isRightTypeIsLiteral() + // ? value + // : null), + // RowFactory + // .create("patient-4", + // parameters.isLeftTypeIsLiteral() && parameters.isRightTypeIsLiteral() + // ? value + // : null) + // ); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void modulus(final TestParameters parameters) { + // final BinaryOperatorInput input = new BinaryOperatorInput(parameters.getContext(), + // parameters.getLeft(), + // parameters.getRight()); + // final BinaryOperator comparisonOperator = BinaryOperator.getInstance("mod"); + // final Collection result = comparisonOperator.invoke(input); + // final Object value = 1; + // + // assertThat(result).selectOrderedResult().hasRows( + // RowFactory.create("patient-1", value), + // RowFactory.create("patient-2", parameters.isLeftTypeIsLiteral() + // ? value + // : null), + // RowFactory.create("patient-3", parameters.isRightTypeIsLiteral() + // ? value + // : null), + // RowFactory + // .create("patient-4", + // parameters.isLeftTypeIsLiteral() && parameters.isRightTypeIsLiteral() + // ? value + // : null) + // ); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorValidationTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorValidationTest.java index 1df25ff421..f5c0493012 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorValidationTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MathOperatorValidationTest.java @@ -17,132 +17,123 @@ package au.csiro.pathling.fhirpath.operator; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.ElementPathBuilder; -import au.csiro.pathling.test.builders.ParserContextBuilder; -import ca.uhn.fhir.context.FhirContext; -import org.apache.spark.sql.SparkSession; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class MathOperatorValidationTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - ParserContext parserContext; - - @BeforeEach - void setUp() { - parserContext = new ParserContextBuilder(spark, fhirContext).build(); - } - - @Test - void operandIsNotCorrectType() { - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.DATETIME) - .singular(true) - .expression("foo") - .build(); - final PrimitivePath right = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .singular(true) - .expression("bar") - .build(); - - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator mathOperator = BinaryOperator.getInstance("+"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> mathOperator.invoke(input)); - assertEquals("+ operator does not support left operand: foo", - error.getMessage()); - - // Now test the right operand. - final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); - final InvalidUserInputError reversedError = assertThrows( - InvalidUserInputError.class, - () -> mathOperator.invoke(reversedInput)); - assertEquals( - "+ operator does not support right operand: foo", - reversedError.getMessage()); - } - - @Test - void operandIsNotSingular() { - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .singular(false) - .expression("foo") - .build(); - final PrimitivePath right = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .singular(true) - .expression("bar") - .build(); - - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator mathOperator = BinaryOperator.getInstance("+"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> mathOperator.invoke(input)); - assertEquals("Left operand to + operator must be singular: foo", - error.getMessage()); - - // Now test the right operand. - final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); - final InvalidUserInputError reversedError = assertThrows( - InvalidUserInputError.class, - () -> mathOperator.invoke(reversedInput)); - assertEquals( - "Right operand to + operator must be singular: foo", - reversedError.getMessage()); - } - - @Test - void operandsAreNotComparable() { - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.INTEGER) - .singular(true) - .expression("foo") - .build(); - final PrimitivePath right = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.QUANTITY) - .singular(true) - .expression("bar") - .build(); - - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator mathOperator = BinaryOperator.getInstance("+"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> mathOperator.invoke(input)); - assertEquals("Left and right operands are not comparable: foo + bar", - error.getMessage()); - - // Now test the right operand. - final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); - final InvalidUserInputError reversedError = assertThrows( - InvalidUserInputError.class, - () -> mathOperator.invoke(reversedInput)); - assertEquals( - "Left and right operands are not comparable: bar + foo", - reversedError.getMessage()); - } + // TODO: implement with columns + + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // ParserContext parserContext; + // + // @BeforeEach + // void setUp() { + // parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // } + // + // @Test + // void operandIsNotCorrectType() { + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DATETIME) + // .singular(true) + // .expression("foo") + // .build(); + // final PrimitivePath right = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .singular(true) + // .expression("bar") + // .build(); + // + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator mathOperator = BinaryOperator.getInstance("+"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> mathOperator.invoke(input)); + // assertEquals("+ operator does not support left operand: foo", + // error.getMessage()); + // + // // Now test the right operand. + // final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); + // final InvalidUserInputError reversedError = assertThrows( + // InvalidUserInputError.class, + // () -> mathOperator.invoke(reversedInput)); + // assertEquals( + // "+ operator does not support right operand: foo", + // reversedError.getMessage()); + // } + // + // @Test + // void operandIsNotSingular() { + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .singular(false) + // .expression("foo") + // .build(); + // final PrimitivePath right = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .singular(true) + // .expression("bar") + // .build(); + // + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator mathOperator = BinaryOperator.getInstance("+"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> mathOperator.invoke(input)); + // assertEquals("Left operand to + operator must be singular: foo", + // error.getMessage()); + // + // // Now test the right operand. + // final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); + // final InvalidUserInputError reversedError = assertThrows( + // InvalidUserInputError.class, + // () -> mathOperator.invoke(reversedInput)); + // assertEquals( + // "Right operand to + operator must be singular: foo", + // reversedError.getMessage()); + // } + // + // @Test + // void operandsAreNotComparable() { + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.INTEGER) + // .singular(true) + // .expression("foo") + // .build(); + // final PrimitivePath right = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.QUANTITY) + // .singular(true) + // .expression("bar") + // .build(); + // + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator mathOperator = BinaryOperator.getInstance("+"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> mathOperator.invoke(input)); + // assertEquals("Left and right operands are not comparable: foo + bar", + // error.getMessage()); + // + // // Now test the right operand. + // final BinaryOperatorInput reversedInput = new BinaryOperatorInput(parserContext, right, left); + // final InvalidUserInputError reversedError = assertThrows( + // InvalidUserInputError.class, + // () -> mathOperator.invoke(reversedInput)); + // assertEquals( + // "Left and right operands are not comparable: bar + foo", + // reversedError.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MembershipOperatorTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MembershipOperatorTest.java index 1b495ddaf8..dc1ba9e327 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MembershipOperatorTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/MembershipOperatorTest.java @@ -17,278 +17,248 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.helpers.SparkHelpers.codingStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowFromCoding; -import static au.csiro.pathling.test.helpers.TestHelpers.LOINC_URL; -import static au.csiro.pathling.test.helpers.TestHelpers.SNOMED_URL; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.BooleanCollection; -import au.csiro.pathling.fhirpath.collection.CodingCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.literal.BooleanLiteralPath; -import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; -import au.csiro.pathling.fhirpath.literal.StringLiteralPath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.fixtures.StringPrimitiveRowFixture; -import ca.uhn.fhir.context.FhirContext; -import java.util.Collections; -import java.util.stream.Stream; -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.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.springframework.beans.factory.annotation.Autowired; /** * @author Piotr Szul */ @SpringBootUnitTest +@NotImplemented class MembershipOperatorTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - ParserContext parserContext; - - @BeforeEach - void setUp() { - parserContext = new ParserContextBuilder(spark, fhirContext).build(); - } - - static Stream parameters() { - return Stream.of("in", "contains"); - } - - Collection testOperator(final String operator, final Collection collection, - final Collection element) { - final BinaryOperatorInput operatorInput; - if ("in".equals(operator)) { - operatorInput = new BinaryOperatorInput(parserContext, element, collection); - } else if ("contains".equals(operator)) { - operatorInput = new BinaryOperatorInput(parserContext, collection, element); - } else { - throw new IllegalArgumentException("Membership operator '" + operator + "' cannot be tested"); - } - - final Collection result = BinaryOperator.getInstance(operator).invoke(operatorInput); - assertThat(result) - .isElementPath(BooleanCollection.class) - .isSingular(); - return result; - } - - @ParameterizedTest - @MethodSource("parameters") - void returnsCorrectResultWhenElementIsLiteral(final String operator) { - final PrimitivePath collection = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(StringPrimitiveRowFixture.createCompleteDataset(spark)) - .idAndValueColumns() - .build(); - final StringLiteralPath element = StringCollection.fromLiteral("'Samuel'", collection); - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(collection.getIdColumn())) - .build(); - - final Collection result = testOperator(operator, collection, element); - assertThat(result) - .selectOrderedResult() - .hasRows( - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_1, false), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_2, true), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_3, false), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_4, false), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_5, false)); - } - - @ParameterizedTest - @MethodSource("parameters") - void returnsCorrectResultWhenElementIsExpression(final String operator) { - final PrimitivePath collection = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(StringPrimitiveRowFixture.createCompleteDataset(spark)) - .idAndValueColumns() - .build(); - final PrimitivePath element = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(StringPrimitiveRowFixture - .createDataset(spark, RowFactory.create(StringPrimitiveRowFixture.ROW_ID_1, "Eva"), - StringPrimitiveRowFixture.STRING_2_SAMUEL, - StringPrimitiveRowFixture.STRING_3_NULL, - StringPrimitiveRowFixture.STRING_4_ADAM, - StringPrimitiveRowFixture.STRING_5_NULL)) - .idAndValueColumns() - .singular(true) - .expression("name.family.first()") - .build(); - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(collection.getIdColumn())) - .build(); - - final Collection result = testOperator(operator, collection, element); - assertThat(result) - .selectOrderedResult() - .hasRows( - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_1, false), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_2, true), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_3, null), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_4, true), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_5, null)); - } - - @ParameterizedTest - @MethodSource("parameters") - void resultIsFalseWhenCollectionIsEmpty(final String operator) { - final PrimitivePath collection = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(StringPrimitiveRowFixture.createNullRowsDataset(spark)) - .idAndValueColumns() - .build(); - final StringLiteralPath element = StringCollection.fromLiteral("'Samuel'", collection); - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(collection.getIdColumn())) - .build(); - - final Collection result = testOperator(operator, collection, element); - assertThat(result) - .selectOrderedResult() - .hasRows( - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_3, false), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_5, false)); - } - - @ParameterizedTest - @MethodSource("parameters") - void returnsEmptyWhenElementIsEmpty(final String operator) { - final PrimitivePath collection = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(StringPrimitiveRowFixture.createCompleteDataset(spark)) - .idAndValueColumns() - .build(); - final PrimitivePath element = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(StringPrimitiveRowFixture.createAllRowsNullDataset(spark)) - .idAndValueColumns() - .singular(true) - .expression("name.family.first()") - .build(); - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(collection.getIdColumn())) - .build(); - - final Collection result = testOperator(operator, collection, element); - assertThat(result) - .selectOrderedResult() - .hasRows( - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_1, null), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_2, null), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_3, null), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_4, null), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_5, null)); - } - - @ParameterizedTest - @MethodSource("parameters") - void worksForCodingLiterals(final String operator) { - - final Coding snomedCoding = new Coding(SNOMED_URL, "56459004", null); - final Coding loincCoding1 = new Coding(LOINC_URL, "56459004", null); - loincCoding1.setId("fake-id-1"); - final Coding loincCoding2 = new Coding(LOINC_URL, "56459004", null); - loincCoding2.setId("fake-id-2"); - final Coding loincCodingWithVersion = new Coding(LOINC_URL, "56459004", null); - loincCodingWithVersion.setVersion("version1"); - - final Dataset codingDataset = new DatasetBuilder(spark) - .withIdColumn() - .withStructTypeColumns(codingStructType()) - .withRow(StringPrimitiveRowFixture.ROW_ID_1, rowFromCoding(snomedCoding)) - .withRow(StringPrimitiveRowFixture.ROW_ID_1, rowFromCoding(loincCoding1)) - .withRow(StringPrimitiveRowFixture.ROW_ID_2, rowFromCoding(snomedCoding)) - .withRow(StringPrimitiveRowFixture.ROW_ID_2, rowFromCoding(loincCoding2)) - .withRow(StringPrimitiveRowFixture.ROW_ID_3, rowFromCoding(snomedCoding)) - .withRow(StringPrimitiveRowFixture.ROW_ID_3, rowFromCoding(loincCodingWithVersion)) - .withRow(StringPrimitiveRowFixture.ROW_ID_4, null) - .withRow(StringPrimitiveRowFixture.ROW_ID_4, null) - .buildWithStructValue(); - - final PrimitivePath collection = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODING) - .dataset(codingDataset) - .idAndValueColumns() - .build(); - - final CodingLiteralPath element = CodingCollection - .fromLiteral("http://loinc.org|56459004", collection); - parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(collection.getIdColumn())) - .build(); - - final Collection result = testOperator(operator, collection, element); - assertThat(result) - .selectOrderedResult() - .hasRows( - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_1, true), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_2, true), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_3, false), - RowFactory.create(StringPrimitiveRowFixture.ROW_ID_4, false)); - } - - @ParameterizedTest - @MethodSource("parameters") - void throwExceptionWhenElementIsNotSingular(final String operator) { - final PrimitivePath collection = new ElementPathBuilder(spark) - .singular(false) - .build(); - final PrimitivePath element = new ElementPathBuilder(spark) - .singular(false) - .expression("name.given") - .build(); - - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> testOperator(operator, collection, element)); - assertEquals("Element operand used with " + operator + " operator is not singular: name.given", - error.getMessage()); - } - - - @ParameterizedTest - @MethodSource("parameters") - void throwExceptionWhenIncompatibleTypes(final String operator) { - final PrimitivePath collection = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .expression("foo") - .build(); - final BooleanLiteralPath element = BooleanLiteralPath.fromString("true", collection); + // TODO: implement with columns - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> testOperator(operator, collection, element)); - assertEquals( - "Left operand to " + operator + " operator is not comparable to right operand: " - + (operator.equals("in") - ? "true in foo" - : "foo contains true"), - error.getMessage()); - } + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // ParserContext parserContext; + // + // @BeforeEach + // void setUp() { + // parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // } + // + // static Stream parameters() { + // return Stream.of("in", "contains"); + // } + // + // Collection testOperator(final String operator, final Collection collection, + // final Collection element) { + // final BinaryOperatorInput operatorInput; + // if ("in".equals(operator)) { + // operatorInput = new BinaryOperatorInput(parserContext, element, collection); + // } else if ("contains".equals(operator)) { + // operatorInput = new BinaryOperatorInput(parserContext, collection, element); + // } else { + // throw new IllegalArgumentException("Membership operator '" + operator + "' cannot be tested"); + // } + // + // final Collection result = BinaryOperator.getInstance(operator).invoke(operatorInput); + // assertThat(result) + // .isElementPath(BooleanCollection.class) + // .isSingular(); + // return result; + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void returnsCorrectResultWhenElementIsLiteral(final String operator) { + // final PrimitivePath collection = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(StringPrimitiveRowFixture.createCompleteDataset(spark)) + // .idAndValueColumns() + // .build(); + // final StringLiteralPath element = StringCollection.fromLiteral("'Samuel'", collection); + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(collection.getIdColumn())) + // .build(); + // + // final Collection result = testOperator(operator, collection, element); + // assertThat(result) + // .selectOrderedResult() + // .hasRows( + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_1, false), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_2, true), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_3, false), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_4, false), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_5, false)); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void returnsCorrectResultWhenElementIsExpression(final String operator) { + // final PrimitivePath collection = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(StringPrimitiveRowFixture.createCompleteDataset(spark)) + // .idAndValueColumns() + // .build(); + // final PrimitivePath element = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(StringPrimitiveRowFixture + // .createDataset(spark, RowFactory.create(StringPrimitiveRowFixture.ROW_ID_1, "Eva"), + // StringPrimitiveRowFixture.STRING_2_SAMUEL, + // StringPrimitiveRowFixture.STRING_3_NULL, + // StringPrimitiveRowFixture.STRING_4_ADAM, + // StringPrimitiveRowFixture.STRING_5_NULL)) + // .idAndValueColumns() + // .singular(true) + // .expression("name.family.first()") + // .build(); + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(collection.getIdColumn())) + // .build(); + // + // final Collection result = testOperator(operator, collection, element); + // assertThat(result) + // .selectOrderedResult() + // .hasRows( + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_1, false), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_2, true), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_3, null), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_4, true), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_5, null)); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void resultIsFalseWhenCollectionIsEmpty(final String operator) { + // final PrimitivePath collection = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(StringPrimitiveRowFixture.createNullRowsDataset(spark)) + // .idAndValueColumns() + // .build(); + // final StringLiteralPath element = StringCollection.fromLiteral("'Samuel'", collection); + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(collection.getIdColumn())) + // .build(); + // + // final Collection result = testOperator(operator, collection, element); + // assertThat(result) + // .selectOrderedResult() + // .hasRows( + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_3, false), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_5, false)); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void returnsEmptyWhenElementIsEmpty(final String operator) { + // final PrimitivePath collection = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(StringPrimitiveRowFixture.createCompleteDataset(spark)) + // .idAndValueColumns() + // .build(); + // final PrimitivePath element = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(StringPrimitiveRowFixture.createAllRowsNullDataset(spark)) + // .idAndValueColumns() + // .singular(true) + // .expression("name.family.first()") + // .build(); + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(collection.getIdColumn())) + // .build(); + // + // final Collection result = testOperator(operator, collection, element); + // assertThat(result) + // .selectOrderedResult() + // .hasRows( + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_1, null), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_2, null), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_3, null), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_4, null), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_5, null)); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void worksForCodingLiterals(final String operator) { + // + // final Coding snomedCoding = new Coding(SNOMED_URL, "56459004", null); + // final Coding loincCoding1 = new Coding(LOINC_URL, "56459004", null); + // loincCoding1.setId("fake-id-1"); + // final Coding loincCoding2 = new Coding(LOINC_URL, "56459004", null); + // loincCoding2.setId("fake-id-2"); + // final Coding loincCodingWithVersion = new Coding(LOINC_URL, "56459004", null); + // loincCodingWithVersion.setVersion("version1"); + // + // final Dataset codingDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withStructTypeColumns(codingStructType()) + // .withRow(StringPrimitiveRowFixture.ROW_ID_1, rowFromCoding(snomedCoding)) + // .withRow(StringPrimitiveRowFixture.ROW_ID_1, rowFromCoding(loincCoding1)) + // .withRow(StringPrimitiveRowFixture.ROW_ID_2, rowFromCoding(snomedCoding)) + // .withRow(StringPrimitiveRowFixture.ROW_ID_2, rowFromCoding(loincCoding2)) + // .withRow(StringPrimitiveRowFixture.ROW_ID_3, rowFromCoding(snomedCoding)) + // .withRow(StringPrimitiveRowFixture.ROW_ID_3, rowFromCoding(loincCodingWithVersion)) + // .withRow(StringPrimitiveRowFixture.ROW_ID_4, null) + // .withRow(StringPrimitiveRowFixture.ROW_ID_4, null) + // .buildWithStructValue(); + // + // final PrimitivePath collection = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODING) + // .dataset(codingDataset) + // .idAndValueColumns() + // .build(); + // + // final CodingLiteralPath element = CodingCollection + // .fromLiteral("http://loinc.org|56459004", collection); + // parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(collection.getIdColumn())) + // .build(); + // + // final Collection result = testOperator(operator, collection, element); + // assertThat(result) + // .selectOrderedResult() + // .hasRows( + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_1, true), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_2, true), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_3, false), + // RowFactory.create(StringPrimitiveRowFixture.ROW_ID_4, false)); + // } + // + // @ParameterizedTest + // @MethodSource("parameters") + // void throwExceptionWhenElementIsNotSingular(final String operator) { + // final PrimitivePath collection = new ElementPathBuilder(spark) + // .singular(false) + // .build(); + // final PrimitivePath element = new ElementPathBuilder(spark) + // .singular(false) + // .expression("name.given") + // .build(); + // + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> testOperator(operator, collection, element)); + // assertEquals("Element operand used with " + operator + " operator is not singular: name.given", + // error.getMessage()); + // } + // + // + // @ParameterizedTest + // @MethodSource("parameters") + // void throwExceptionWhenIncompatibleTypes(final String operator) { + // final PrimitivePath collection = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .expression("foo") + // .build(); + // final BooleanLiteralPath element = BooleanLiteralPath.fromString("true", collection); + // + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> testOperator(operator, collection, element)); + // assertEquals( + // "Left operand to " + operator + " operator is not comparable to right operand: " + // + (operator.equals("in") + // ? "true in foo" + // : "foo contains true"), + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperatorTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperatorTest.java index d970738fec..fd963b5ffd 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperatorTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/PathTraversalOperatorTest.java @@ -17,394 +17,354 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.builders.DatasetBuilder.makeEid; -import static au.csiro.pathling.test.fixtures.ExtensionFixture.MANY_EXT_ROW_1; -import static au.csiro.pathling.test.fixtures.ExtensionFixture.MANY_EXT_ROW_2; -import static au.csiro.pathling.test.fixtures.ExtensionFixture.ONE_MY_EXTENSION; -import static au.csiro.pathling.test.fixtures.ExtensionFixture.nullEntryMap; -import static au.csiro.pathling.test.fixtures.ExtensionFixture.oneEntryMap; -import static au.csiro.pathling.test.fixtures.ExtensionFixture.toElementDataset; -import static au.csiro.pathling.utilities.Preconditions.checkPresent; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.when; - -import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.io.source.DataSource; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -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.builders.ResourceDatasetBuilder; -import au.csiro.pathling.test.builders.ResourcePathBuilder; -import au.csiro.pathling.test.helpers.FhirHelpers; -import ca.uhn.fhir.context.FhirContext; -import com.google.common.collect.ImmutableMap; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; -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.hl7.fhir.r4.model.Enumerations.ResourceType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; /** * @author John Grimes */ @SpringBootUnitTest +@NotImplemented class PathTraversalOperatorTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @MockBean - DataSource dataSource; - - ParserContext parserContext; - - @BeforeEach - void setUp() { - parserContext = new ParserContextBuilder(spark, fhirContext).build(); - } - - @Test - void singularTraversalFromSingular() { - final Dataset leftDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn("gender", DataTypes.StringType) - .withRow("patient-1", "female") - .withRow("patient-2", null) - .build(); - when(dataSource.read(ResourceType.PATIENT)).thenReturn(leftDataset); - final ResourceCollection left = new ResourcePathBuilder(spark) - .fhirContext(fhirContext) - .resourceType(ResourceType.PATIENT) - .database(dataSource) - .singular(true) - .build(); - - final PathTraversalInput input = new PathTraversalInput(parserContext, left, "gender"); - final Collection result = new PathTraversalOperator().invoke(input); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", null, "female") - .withRow("patient-2", null, null) - .build(); - assertThat(result) - .isElementPath(StringCollection.class) - .isSingular() - .selectOrderedResultWithEid() - .hasRows(expectedDataset); - } - - @Test - void manyTraversalFromSingular() { - final Dataset leftDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn("name", DataTypes.createArrayType(DataTypes.StringType)) - .withColumn("active", DataTypes.BooleanType) - .withRow("patient-1", Arrays.asList(null, "Marie", null, "Anne"), true) - .withRow("patient-2", Collections.emptyList(), true) - .withRow("patient-3", null, true) - .build(); - when(dataSource.read(ResourceType.PATIENT)).thenReturn(leftDataset); - final ResourceCollection left = new ResourcePathBuilder(spark) - .fhirContext(fhirContext) - .resourceType(ResourceType.PATIENT) - .database(dataSource) - .singular(true) - .build(); - - final PathTraversalInput input = new PathTraversalInput(parserContext, left, "name"); - final Collection result = new PathTraversalOperator().invoke(input); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", makeEid(0), null) - .withRow("patient-1", makeEid(1), "Marie") - .withRow("patient-1", makeEid(2), null) - .withRow("patient-1", makeEid(3), "Anne") - .withRow("patient-2", null, null) - .withRow("patient-3", null, null) - .build(); - assertThat(result) - .isElementPath(PrimitivePath.class) - .hasExpression("Patient.name") - .hasFhirType(FHIRDefinedType.HUMANNAME) - .isNotSingular() - .selectOrderedResultWithEid() - .hasRows(expectedDataset); - } - - @Test - void manyTraversalFromNonSingular() { - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructColumn("given", DataTypes.createArrayType(DataTypes.StringType)) - // patient with two names - .withRow("patient-1", makeEid(1), RowFactory.create(Arrays.asList("Jude", "Adam"))) - .withRow("patient-1", makeEid(0), RowFactory.create(Arrays.asList("Mark", "Alen", null))) - // patient with empty list of given names and null list - .withRow("patient-2", makeEid(1), RowFactory.create(Collections.emptyList())) - .withRow("patient-2", makeEid(0), null) - // no name in the first place - .withRow("patient-5", null, null) - .buildWithStructValue(); - - final Optional definition = FhirHelpers - .getChildOfResource(fhirContext, "Patient", "name"); - - assertTrue(definition.isPresent()); - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("Patient.name") - .definition(definition.get()) - .buildDefined(); - - final PathTraversalInput input = new PathTraversalInput(parserContext, left, "given"); - final Collection result = new PathTraversalOperator().invoke(input); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", makeEid(0, 0), "Mark") - .withRow("patient-1", makeEid(0, 1), "Alen") - .withRow("patient-1", makeEid(0, 2), null) - .withRow("patient-1", makeEid(1, 0), "Jude") - .withRow("patient-1", makeEid(1, 1), "Adam") - .withRow("patient-2", makeEid(0, 0), null) - .withRow("patient-2", makeEid(1, 0), null) - .withRow("patient-5", null, null) - .build(); - - assertThat(result) - .isElementPath(PrimitivePath.class) - .hasExpression("Patient.name.given") - .hasFhirType(FHIRDefinedType.STRING) - .isNotSingular() - .selectOrderedResultWithEid() - .hasRows(expectedDataset); - } - - @Test - void singularTraversalFromNonSingular() { - final Dataset inputDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructColumn("family", DataTypes.StringType) - // patient with two names - .withRow("patient-1", makeEid(1), RowFactory.create("Jude")) - .withRow("patient-1", makeEid(0), RowFactory.create("Mark")) - // patient with some null values - .withRow("patient-2", makeEid(1), RowFactory.create("Adam")) - .withRow("patient-2", makeEid(0), RowFactory.create((String) null)) - // patient with empty list of given names - .withRow("patient-5", null, null) - .buildWithStructValue(); - - final Optional definition = FhirHelpers - .getChildOfResource(fhirContext, "Patient", "name"); - - assertTrue(definition.isPresent()); - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.STRING) - .dataset(inputDataset) - .idAndEidAndValueColumns() - .expression("Patient.name") - .definition(definition.get()) - .buildDefined(); - - final PathTraversalInput input = new PathTraversalInput(parserContext, left, "family"); - final Collection result = new PathTraversalOperator().invoke(input); - - final Dataset expectedDataset = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withColumn(DataTypes.StringType) - .withRow("patient-1", makeEid(0), "Mark") - .withRow("patient-1", makeEid(1), "Jude") - .withRow("patient-2", makeEid(0), null) - .withRow("patient-2", makeEid(1), "Adam") - .withRow("patient-5", null, null) - .build(); - - assertThat(result) - .isElementPath(PrimitivePath.class) - .hasExpression("Patient.name.family") - .hasFhirType(FHIRDefinedType.STRING) - .isNotSingular() - .selectOrderedResultWithEid() - .hasRows(expectedDataset); - } - - @Test - void testExtensionTraversalOnResources() { - final Dataset patientDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withColumn("gender", DataTypes.StringType) - .withColumn("active", DataTypes.BooleanType) - .withFidColumn() - .withExtensionColumn() - .withRow("patient-1", "female", true, 1, - ImmutableMap.builder().put(1, Arrays.asList(MANY_EXT_ROW_1, MANY_EXT_ROW_2)).build()) - .withRow("patient-2", "female", false, 1, - ImmutableMap.builder().put(1, Collections.singletonList(MANY_EXT_ROW_1)) - .put(2, Collections.singletonList(MANY_EXT_ROW_2)).build()) - .withRow("patient-3", "male", false, 1, oneEntryMap(2, ONE_MY_EXTENSION)) - .withRow("patient-4", "male", false, 1, nullEntryMap(1)) - .withRow("patient-5", "male", true, 1, null) - .build(); - when(dataSource.read(ResourceType.PATIENT)) - .thenReturn(patientDataset); - final ResourceCollection left = ResourceCollection - .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient"); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final PathTraversalInput input = new PathTraversalInput(parserContext, left, "extension"); - final Collection result = new PathTraversalOperator().invoke(input); - - assertThat(result) - .hasExpression("Patient.extension") - .isNotSingular() - .isElementPath(PrimitivePath.class) - .hasFhirType(FHIRDefinedType.EXTENSION) - .selectOrderedResult() - .hasRows( - // Many many extensions for this resource _fix - RowFactory.create("patient-1", MANY_EXT_ROW_1), - RowFactory.create("patient-1", MANY_EXT_ROW_2), - // One extension with matching _fid and some with other fids - RowFactory.create("patient-2", MANY_EXT_ROW_1), - // Not extensions with matching _fid - RowFactory.create("patient-3", null), - // Empty list of extension for matching _fix - RowFactory.create("patient-4", null), - // No extensions present all together (empty extension map) - RowFactory.create("patient-5", null) - ); - } - - - @Test - void testExtensionTraversalOnElements() { - final Map manyToFidZeroMap = ImmutableMap.builder() - .put(0, Arrays.asList(MANY_EXT_ROW_1, MANY_EXT_ROW_2)).build(); - - final ImmutableMap onePerFidZeroAndOneMap = ImmutableMap.builder() - .put(0, Collections.singletonList(MANY_EXT_ROW_1)) - .put(1, Collections.singletonList(MANY_EXT_ROW_2)).build(); - - // Construct element dataset from the resource dataset so that the resource path can be used as - // the current resource for this element path. - // Note: this resource path is not singular as this will be a base for elements. - - final Dataset resourceLikeDataset = new ResourceDatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructColumn("name", DataTypes.StringType) - .withStructColumn("_fid", DataTypes.IntegerType) - .withStructValueColumn() - .withExtensionColumn() - .withRow("observation-1", makeEid(0), RowFactory.create("name1", 0), manyToFidZeroMap) - .withRow("observation-2", makeEid(0), RowFactory.create("name2-1", 0), - onePerFidZeroAndOneMap) - .withRow("observation-2", makeEid(1), RowFactory.create("name2-2", 1), - onePerFidZeroAndOneMap) - .withRow("observation-3", makeEid(0), RowFactory.create("name3", 0), - oneEntryMap(2, ONE_MY_EXTENSION)) - .withRow("observation-4", makeEid(0), RowFactory.create("name4", 0), nullEntryMap(1)) - .withRow("observation-5", makeEid(0), RowFactory.create("name5", 0), null) - .build(); - - when(dataSource.read(ResourceType.OBSERVATION)) - .thenReturn(resourceLikeDataset); - final ResourceCollection baseResourceCollection = ResourceCollection - .build(fhirContext, dataSource, ResourceType.OBSERVATION, "Observation"); - - final Dataset elementDataset = toElementDataset(resourceLikeDataset, - baseResourceCollection); - - final ElementDefinition codeDefinition = checkPresent(FhirHelpers - .getChildOfResource(fhirContext, "Observation", "code")); - - final PrimitivePath left = new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.CODEABLECONCEPT) - .definition(codeDefinition) - .dataset(elementDataset) - .idAndEidAndValueColumns() - .expression("code") - .singular(false) - .currentResource(baseResourceCollection) - .buildDefined(); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); - final PathTraversalInput input = new PathTraversalInput(parserContext, left, "extension"); - final Collection result = new PathTraversalOperator().invoke(input); - - final Dataset expectedResult = new DatasetBuilder(spark) - .withIdColumn() - .withEidColumn() - .withStructTypeColumns(DatasetBuilder.SIMPLE_EXTENSION_TYPE) - .withRow("observation-1", makeEid(0, 0), MANY_EXT_ROW_1) - .withRow("observation-1", makeEid(0, 1), MANY_EXT_ROW_2) - .withRow("observation-2", makeEid(0, 0), MANY_EXT_ROW_1) - .withRow("observation-2", makeEid(1, 0), MANY_EXT_ROW_2) - .withRow("observation-3", makeEid(0, 0), null) - .withRow("observation-4", makeEid(0, 0), null) - .withRow("observation-5", makeEid(0, 0), null) - .buildWithStructValue(); - - assertThat(result) - .hasExpression("code.extension") - .isNotSingular() - .isElementPath(PrimitivePath.class) - .hasFhirType(FHIRDefinedType.EXTENSION) - .selectOrderedResultWithEid() - .hasRows(expectedResult); - - } - - @Test - void throwsErrorOnNonExistentChild() { - final ResourceCollection left = new ResourcePathBuilder(spark) - .fhirContext(fhirContext) - .resourceType(ResourceType.ENCOUNTER) - .expression("Encounter") - .build(); - - final PathTraversalInput input = new PathTraversalInput(parserContext, left, "reason"); - final InvalidUserInputError error = assertThrows( - InvalidUserInputError.class, - () -> new PathTraversalOperator().invoke(input)); - assertEquals("No such child: Encounter.reason", - error.getMessage()); - } + // TODO: implement with columns + + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @MockBean + // DataSource dataSource; + // + // ParserContext parserContext; + // + // @BeforeEach + // void setUp() { + // parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // } + // + // @Test + // void singularTraversalFromSingular() { + // final Dataset leftDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn("gender", DataTypes.StringType) + // .withRow("patient-1", "female") + // .withRow("patient-2", null) + // .build(); + // when(dataSource.read(ResourceType.PATIENT)).thenReturn(leftDataset); + // final ResourceCollection left = new ResourcePathBuilder(spark) + // .fhirContext(fhirContext) + // .resourceType(ResourceType.PATIENT) + // .database(dataSource) + // .singular(true) + // .build(); + // + // final PathTraversalInput input = new PathTraversalInput(parserContext, left, "gender"); + // final Collection result = new PathTraversalOperator().invoke(input); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", null, "female") + // .withRow("patient-2", null, null) + // .build(); + // assertThat(result) + // .isElementPath(StringCollection.class) + // .isSingular() + // .selectOrderedResultWithEid() + // .hasRows(expectedDataset); + // } + // + // @Test + // void manyTraversalFromSingular() { + // final Dataset leftDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn("name", DataTypes.createArrayType(DataTypes.StringType)) + // .withColumn("active", DataTypes.BooleanType) + // .withRow("patient-1", Arrays.asList(null, "Marie", null, "Anne"), true) + // .withRow("patient-2", Collections.emptyList(), true) + // .withRow("patient-3", null, true) + // .build(); + // when(dataSource.read(ResourceType.PATIENT)).thenReturn(leftDataset); + // final ResourceCollection left = new ResourcePathBuilder(spark) + // .fhirContext(fhirContext) + // .resourceType(ResourceType.PATIENT) + // .database(dataSource) + // .singular(true) + // .build(); + // + // final PathTraversalInput input = new PathTraversalInput(parserContext, left, "name"); + // final Collection result = new PathTraversalOperator().invoke(input); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", makeEid(0), null) + // .withRow("patient-1", makeEid(1), "Marie") + // .withRow("patient-1", makeEid(2), null) + // .withRow("patient-1", makeEid(3), "Anne") + // .withRow("patient-2", null, null) + // .withRow("patient-3", null, null) + // .build(); + // assertThat(result) + // .isElementPath(PrimitivePath.class) + // .hasExpression("Patient.name") + // .hasFhirType(FHIRDefinedType.HUMANNAME) + // .isNotSingular() + // .selectOrderedResultWithEid() + // .hasRows(expectedDataset); + // } + // + // @Test + // void manyTraversalFromNonSingular() { + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructColumn("given", DataTypes.createArrayType(DataTypes.StringType)) + // // patient with two names + // .withRow("patient-1", makeEid(1), RowFactory.create(Arrays.asList("Jude", "Adam"))) + // .withRow("patient-1", makeEid(0), RowFactory.create(Arrays.asList("Mark", "Alen", null))) + // // patient with empty list of given names and null list + // .withRow("patient-2", makeEid(1), RowFactory.create(Collections.emptyList())) + // .withRow("patient-2", makeEid(0), null) + // // no name in the first place + // .withRow("patient-5", null, null) + // .buildWithStructValue(); + // + // final Optional definition = FhirHelpers + // .getChildOfResource(fhirContext, "Patient", "name"); + // + // assertTrue(definition.isPresent()); + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("Patient.name") + // .definition(definition.get()) + // .buildDefined(); + // + // final PathTraversalInput input = new PathTraversalInput(parserContext, left, "given"); + // final Collection result = new PathTraversalOperator().invoke(input); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", makeEid(0, 0), "Mark") + // .withRow("patient-1", makeEid(0, 1), "Alen") + // .withRow("patient-1", makeEid(0, 2), null) + // .withRow("patient-1", makeEid(1, 0), "Jude") + // .withRow("patient-1", makeEid(1, 1), "Adam") + // .withRow("patient-2", makeEid(0, 0), null) + // .withRow("patient-2", makeEid(1, 0), null) + // .withRow("patient-5", null, null) + // .build(); + // + // assertThat(result) + // .isElementPath(PrimitivePath.class) + // .hasExpression("Patient.name.given") + // .hasFhirType(FHIRDefinedType.STRING) + // .isNotSingular() + // .selectOrderedResultWithEid() + // .hasRows(expectedDataset); + // } + // + // @Test + // void singularTraversalFromNonSingular() { + // final Dataset inputDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructColumn("family", DataTypes.StringType) + // // patient with two names + // .withRow("patient-1", makeEid(1), RowFactory.create("Jude")) + // .withRow("patient-1", makeEid(0), RowFactory.create("Mark")) + // // patient with some null values + // .withRow("patient-2", makeEid(1), RowFactory.create("Adam")) + // .withRow("patient-2", makeEid(0), RowFactory.create((String) null)) + // // patient with empty list of given names + // .withRow("patient-5", null, null) + // .buildWithStructValue(); + // + // final Optional definition = FhirHelpers + // .getChildOfResource(fhirContext, "Patient", "name"); + // + // assertTrue(definition.isPresent()); + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.STRING) + // .dataset(inputDataset) + // .idAndEidAndValueColumns() + // .expression("Patient.name") + // .definition(definition.get()) + // .buildDefined(); + // + // final PathTraversalInput input = new PathTraversalInput(parserContext, left, "family"); + // final Collection result = new PathTraversalOperator().invoke(input); + // + // final Dataset expectedDataset = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", makeEid(0), "Mark") + // .withRow("patient-1", makeEid(1), "Jude") + // .withRow("patient-2", makeEid(0), null) + // .withRow("patient-2", makeEid(1), "Adam") + // .withRow("patient-5", null, null) + // .build(); + // + // assertThat(result) + // .isElementPath(PrimitivePath.class) + // .hasExpression("Patient.name.family") + // .hasFhirType(FHIRDefinedType.STRING) + // .isNotSingular() + // .selectOrderedResultWithEid() + // .hasRows(expectedDataset); + // } + // + // @Test + // void testExtensionTraversalOnResources() { + // final Dataset patientDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withColumn("gender", DataTypes.StringType) + // .withColumn("active", DataTypes.BooleanType) + // .withFidColumn() + // .withExtensionColumn() + // .withRow("patient-1", "female", true, 1, + // ImmutableMap.builder().put(1, Arrays.asList(MANY_EXT_ROW_1, MANY_EXT_ROW_2)).build()) + // .withRow("patient-2", "female", false, 1, + // ImmutableMap.builder().put(1, Collections.singletonList(MANY_EXT_ROW_1)) + // .put(2, Collections.singletonList(MANY_EXT_ROW_2)).build()) + // .withRow("patient-3", "male", false, 1, oneEntryMap(2, ONE_MY_EXTENSION)) + // .withRow("patient-4", "male", false, 1, nullEntryMap(1)) + // .withRow("patient-5", "male", true, 1, null) + // .build(); + // when(dataSource.read(ResourceType.PATIENT)) + // .thenReturn(patientDataset); + // final ResourceCollection left = ResourceCollection + // .build(fhirContext, dataSource, ResourceType.PATIENT, "Patient"); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final PathTraversalInput input = new PathTraversalInput(parserContext, left, "extension"); + // final Collection result = new PathTraversalOperator().invoke(input); + // + // assertThat(result) + // .hasExpression("Patient.extension") + // .isNotSingular() + // .isElementPath(PrimitivePath.class) + // .hasFhirType(FHIRDefinedType.EXTENSION) + // .selectOrderedResult() + // .hasRows( + // // Many many extensions for this resource _fix + // RowFactory.create("patient-1", MANY_EXT_ROW_1), + // RowFactory.create("patient-1", MANY_EXT_ROW_2), + // // One extension with matching _fid and some with other fids + // RowFactory.create("patient-2", MANY_EXT_ROW_1), + // // Not extensions with matching _fid + // RowFactory.create("patient-3", null), + // // Empty list of extension for matching _fix + // RowFactory.create("patient-4", null), + // // No extensions present all together (empty extension map) + // RowFactory.create("patient-5", null) + // ); + // } + // + // + // @Test + // void testExtensionTraversalOnElements() { + // final Map manyToFidZeroMap = ImmutableMap.builder() + // .put(0, Arrays.asList(MANY_EXT_ROW_1, MANY_EXT_ROW_2)).build(); + // + // final ImmutableMap onePerFidZeroAndOneMap = ImmutableMap.builder() + // .put(0, Collections.singletonList(MANY_EXT_ROW_1)) + // .put(1, Collections.singletonList(MANY_EXT_ROW_2)).build(); + // + // // Construct element dataset from the resource dataset so that the resource path can be used as + // // the current resource for this element path. + // // Note: this resource path is not singular as this will be a base for elements. + // + // final Dataset resourceLikeDataset = new ResourceDatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructColumn("name", DataTypes.StringType) + // .withStructColumn("_fid", DataTypes.IntegerType) + // .withStructValueColumn() + // .withExtensionColumn() + // .withRow("observation-1", makeEid(0), RowFactory.create("name1", 0), manyToFidZeroMap) + // .withRow("observation-2", makeEid(0), RowFactory.create("name2-1", 0), + // onePerFidZeroAndOneMap) + // .withRow("observation-2", makeEid(1), RowFactory.create("name2-2", 1), + // onePerFidZeroAndOneMap) + // .withRow("observation-3", makeEid(0), RowFactory.create("name3", 0), + // oneEntryMap(2, ONE_MY_EXTENSION)) + // .withRow("observation-4", makeEid(0), RowFactory.create("name4", 0), nullEntryMap(1)) + // .withRow("observation-5", makeEid(0), RowFactory.create("name5", 0), null) + // .build(); + // + // when(dataSource.read(ResourceType.OBSERVATION)) + // .thenReturn(resourceLikeDataset); + // final ResourceCollection baseResourceCollection = ResourceCollection + // .build(fhirContext, dataSource, ResourceType.OBSERVATION, "Observation"); + // + // final Dataset elementDataset = toElementDataset(resourceLikeDataset, + // baseResourceCollection); + // + // final ElementDefinition codeDefinition = checkPresent(FhirHelpers + // .getChildOfResource(fhirContext, "Observation", "code")); + // + // final PrimitivePath left = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.CODEABLECONCEPT) + // .definition(codeDefinition) + // .dataset(elementDataset) + // .idAndEidAndValueColumns() + // .expression("code") + // .singular(false) + // .currentResource(baseResourceCollection) + // .buildDefined(); + // + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).build(); + // final PathTraversalInput input = new PathTraversalInput(parserContext, left, "extension"); + // final Collection result = new PathTraversalOperator().invoke(input); + // + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn() + // .withEidColumn() + // .withStructTypeColumns(DatasetBuilder.SIMPLE_EXTENSION_TYPE) + // .withRow("observation-1", makeEid(0, 0), MANY_EXT_ROW_1) + // .withRow("observation-1", makeEid(0, 1), MANY_EXT_ROW_2) + // .withRow("observation-2", makeEid(0, 0), MANY_EXT_ROW_1) + // .withRow("observation-2", makeEid(1, 0), MANY_EXT_ROW_2) + // .withRow("observation-3", makeEid(0, 0), null) + // .withRow("observation-4", makeEid(0, 0), null) + // .withRow("observation-5", makeEid(0, 0), null) + // .buildWithStructValue(); + // + // assertThat(result) + // .hasExpression("code.extension") + // .isNotSingular() + // .isElementPath(PrimitivePath.class) + // .hasFhirType(FHIRDefinedType.EXTENSION) + // .selectOrderedResultWithEid() + // .hasRows(expectedResult); + // + // } + // + // @Test + // void throwsErrorOnNonExistentChild() { + // final ResourceCollection left = new ResourcePathBuilder(spark) + // .fhirContext(fhirContext) + // .resourceType(ResourceType.ENCOUNTER) + // .expression("Encounter") + // .build(); + // + // final PathTraversalInput input = new PathTraversalInput(parserContext, left, "reason"); + // final InvalidUserInputError error = assertThrows( + // InvalidUserInputError.class, + // () -> new PathTraversalOperator().invoke(input)); + // assertEquals("No such child: Encounter.reason", + // error.getMessage()); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/QuantityOperatorsPrecisionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/QuantityOperatorsPrecisionTest.java index 92dd57415f..3cb16b7c19 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/QuantityOperatorsPrecisionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/operator/QuantityOperatorsPrecisionTest.java @@ -17,211 +17,189 @@ package au.csiro.pathling.fhirpath.operator; -import static au.csiro.pathling.test.assertions.Assertions.assertThat; -import static au.csiro.pathling.test.helpers.SparkHelpers.quantityStructType; -import static au.csiro.pathling.test.helpers.SparkHelpers.rowForUcumQuantity; - -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.parser.ParserContext; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.test.SpringBootUnitTest; -import au.csiro.pathling.test.builders.DatasetBuilder; -import au.csiro.pathling.test.builders.ElementPathBuilder; -import au.csiro.pathling.test.builders.ParserContextBuilder; -import ca.uhn.fhir.context.FhirContext; -import com.google.common.collect.ImmutableSet; -import java.math.BigDecimal; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import javax.annotation.Nonnull; -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.fhir.ucum.Prefix; -import org.fhir.ucum.UcumService; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; @SpringBootUnitTest +@NotImplemented public class QuantityOperatorsPrecisionTest { - @Autowired - SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @Autowired - UcumService ucumService; - - static final String ID_ALIAS = "_abc123"; - - // Reasonable decimal with units assume above the value of 9999 we would use the next prefix up - // (if available) - - static final String REASONABLE_DECIMAL_01 = createSpanningDecimal(9, 3, 1, - 6).toString(); // 9000.000001 - static final String REASONABLE_DECIMAL_02 = createSpanningDecimal(9, 3, 2, - 6).toString(); // 9000.000002 - - // for Decimal(32,6) - static final String FULL_DECIMAL_01 = createSpanningDecimal(9, 26, 1, - 6).toString(); // 9e26 + 0.00001 - static final String FULL_DECIMAL_02 = createSpanningDecimal(9, 26, 2, - 6).toString(); // 9e26 + 0.00002 - - // These units (prefixes) results in overflow for largest supported decimals (e.g. 9e26 'Tm') - static final Set UNSUPPORTED_FULL_DECIMAL_UNITS = ImmutableSet.of("Ym", "Zm", "Em", "Pm", - "Tm"); - - // These mol prefixes results in overflow for reasonable decimals (e.g. 9e3 'Tmol') - static final Set UNSUPPORTED_REASONABLE_DECIMAL_MOL_UNITS = ImmutableSet.of("Ymol", - "Zmol", - "Emol", "Pmol", "Tmol"); - - - @Nonnull - private static String unitToRowId(@Nonnull final String unit) { - return "unit-" + unit; - } - - @Nonnull - private PrimitivePath buildQuantityPathForUnits(@Nonnull final String value, - final List units) { - DatasetBuilder datasetBuilder = new DatasetBuilder(spark) - .withIdColumn(ID_ALIAS) - .withStructTypeColumns(quantityStructType()); - for (final String unit : units) { - datasetBuilder = datasetBuilder.withRow(unitToRowId(unit), rowForUcumQuantity(value, unit)); - } - final Dataset dataset = datasetBuilder.buildWithStructValue(); - return new ElementPathBuilder(spark) - .fhirType(FHIRDefinedType.QUANTITY) - .singular(true) - .dataset(dataset) - .idAndValueColumns() - .build(); - } - - @Nonnull - private List getAllPrefixedUnits(@Nonnull final String baseUnit) { - return ucumService.getModel().getPrefixes().stream() - .map(Prefix::getCode) - .filter(p -> p.length() == 1) // filter out Ki, Gi etc - .map(p -> p + baseUnit) - .collect(Collectors.toUnmodifiableList()); - } - - @SuppressWarnings("SameParameterValue") - @Nonnull - private static BigDecimal createSpanningDecimal(final int leftValue, final int leftScale, - final int rightValue, final int rightScale) { - return new BigDecimal(leftValue).movePointRight(leftScale) - .add(new BigDecimal(rightValue).movePointLeft(rightScale)); - } - - @SuppressWarnings("SameParameterValue") - @Nonnull - private static List createResult(@Nonnull final List unitRange, - final boolean result) { - return createResult(unitRange, result, Collections.emptySet()); - } - - @Nonnull - private static List createResult(@Nonnull final List unitRange, final boolean result, - @Nonnull final Set outOfRangeUnits) { - return unitRange.stream().map( - unit -> - RowFactory.create(unitToRowId(unit), outOfRangeUnits.contains(unit) - ? null - : result)).collect(Collectors.toList()); - } - - - @Nonnull - private Collection callOperator(@Nonnull final PrimitivePath left, @Nonnull final String operator, - @Nonnull final PrimitivePath right) { - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .groupingColumns(Collections.singletonList(left.getIdColumn())) - .build(); - - final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); - final BinaryOperator equalityOperator = BinaryOperator.getInstance(operator); - return equalityOperator.invoke(input); - } - - @Test - void equalityPrecisionForReasonableDecimals() { - final List unitRange = getAllPrefixedUnits("m"); - final PrimitivePath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); - final PrimitivePath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); - final Collection result = callOperator(left, "=", right); - assertThat(result).selectResult().hasRows(createResult(unitRange, true)); - } - - - @Test - void nonEqualityPrecisionForReasonableDecimals() { - final List unitRange = getAllPrefixedUnits("m"); - final PrimitivePath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); - final PrimitivePath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_02, unitRange); - final Collection result = callOperator(left, "!=", right); - assertThat(result).selectResult().hasRows(createResult(unitRange, true)); - } - - - @Test - void comparisonPrecisionForReasonableDecimals() { - final List unitRange = getAllPrefixedUnits("m"); - final PrimitivePath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); - final PrimitivePath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_02, unitRange); - final Collection result = callOperator(left, "<", right); - assertThat(result).selectResult().hasRows(createResult(unitRange, true)); - } - - - @Test - void equalityPrecisionForFullDecimals() { - final List unitRange = getAllPrefixedUnits("m"); - final PrimitivePath left = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); - final PrimitivePath right = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); - final Collection result = callOperator(left, "=", right); - assertThat(result).selectResult() - .hasRows(createResult(unitRange, true, UNSUPPORTED_FULL_DECIMAL_UNITS)); - } - - @Test - void nonEqualityPrecisionForFullDecimals() { - final List unitRange = getAllPrefixedUnits("m"); - final PrimitivePath left = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); - final PrimitivePath right = buildQuantityPathForUnits(FULL_DECIMAL_02, unitRange); - final Collection result = callOperator(left, "!=", right); - assertThat(result).selectResult() - .hasRows(createResult(unitRange, true, UNSUPPORTED_FULL_DECIMAL_UNITS)); - } - - @Test - void comparisonPrecisionForFullDecimals() { - final List unitRange = getAllPrefixedUnits("m"); - final PrimitivePath left = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); - final PrimitivePath right = buildQuantityPathForUnits(FULL_DECIMAL_02, unitRange); - final Collection result = callOperator(left, "<", right); - assertThat(result).selectResult() - .hasRows(createResult(unitRange, true, UNSUPPORTED_FULL_DECIMAL_UNITS)); - } - - @Test - void equalityPrecisionForReasonableDecimalsWithMoles() { - final List unitRange = getAllPrefixedUnits("mol"); - final PrimitivePath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); - final PrimitivePath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); - final Collection result = callOperator(left, "=", right); - assertThat(result).selectResult() - .hasRows(createResult(unitRange, true, UNSUPPORTED_REASONABLE_DECIMAL_MOL_UNITS)); - } + // TODO: implement with columns + // + // + // @Autowired + // SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @Autowired + // UcumService ucumService; + // + // static final String ID_ALIAS = "_abc123"; + // + // // Reasonable decimal with units assume above the value of 9999 we would use the next prefix up + // // (if available) + // + // static final String REASONABLE_DECIMAL_01 = createSpanningDecimal(9, 3, 1, + // 6).toString(); // 9000.000001 + // static final String REASONABLE_DECIMAL_02 = createSpanningDecimal(9, 3, 2, + // 6).toString(); // 9000.000002 + // + // // for Decimal(32,6) + // static final String FULL_DECIMAL_01 = createSpanningDecimal(9, 26, 1, + // 6).toString(); // 9e26 + 0.00001 + // static final String FULL_DECIMAL_02 = createSpanningDecimal(9, 26, 2, + // 6).toString(); // 9e26 + 0.00002 + // + // // These units (prefixes) results in overflow for largest supported decimals (e.g. 9e26 'Tm') + // static final Set UNSUPPORTED_FULL_DECIMAL_UNITS = ImmutableSet.of("Ym", "Zm", "Em", "Pm", + // "Tm"); + // + // // These mol prefixes results in overflow for reasonable decimals (e.g. 9e3 'Tmol') + // static final Set UNSUPPORTED_REASONABLE_DECIMAL_MOL_UNITS = ImmutableSet.of("Ymol", + // "Zmol", + // "Emol", "Pmol", "Tmol"); + // + // + // @Nonnull + // private static String unitToRowId(@Nonnull final String unit) { + // return "unit-" + unit; + // } + // + // @Nonnull + // private PrimitivePath buildQuantityPathForUnits(@Nonnull final String value, + // final List units) { + // DatasetBuilder datasetBuilder = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withStructTypeColumns(quantityStructType()); + // for (final String unit : units) { + // datasetBuilder = datasetBuilder.withRow(unitToRowId(unit), rowForUcumQuantity(value, unit)); + // } + // final Dataset dataset = datasetBuilder.buildWithStructValue(); + // return new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.QUANTITY) + // .singular(true) + // .dataset(dataset) + // .idAndValueColumns() + // .build(); + // } + // + // @Nonnull + // private List getAllPrefixedUnits(@Nonnull final String baseUnit) { + // return ucumService.getModel().getPrefixes().stream() + // .map(Prefix::getCode) + // .filter(p -> p.length() == 1) // filter out Ki, Gi etc + // .map(p -> p + baseUnit) + // .collect(Collectors.toUnmodifiableList()); + // } + // + // @SuppressWarnings("SameParameterValue") + // @Nonnull + // private static BigDecimal createSpanningDecimal(final int leftValue, final int leftScale, + // final int rightValue, final int rightScale) { + // return new BigDecimal(leftValue).movePointRight(leftScale) + // .add(new BigDecimal(rightValue).movePointLeft(rightScale)); + // } + // + // @SuppressWarnings("SameParameterValue") + // @Nonnull + // private static List createResult(@Nonnull final List unitRange, + // final boolean result) { + // return createResult(unitRange, result, Collections.emptySet()); + // } + // + // @Nonnull + // private static List createResult(@Nonnull final List unitRange, final boolean result, + // @Nonnull final Set outOfRangeUnits) { + // return unitRange.stream().map( + // unit -> + // RowFactory.create(unitToRowId(unit), outOfRangeUnits.contains(unit) + // ? null + // : result)).collect(Collectors.toList()); + // } + // + // + // @Nonnull + // private Collection callOperator(@Nonnull final PrimitivePath left, @Nonnull final String operator, + // @Nonnull final PrimitivePath right) { + // final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(left.getIdColumn())) + // .build(); + // + // final BinaryOperatorInput input = new BinaryOperatorInput(parserContext, left, right); + // final BinaryOperator equalityOperator = BinaryOperator.getInstance(operator); + // return equalityOperator.invoke(input); + // } + // + // @Test + // void equalityPrecisionForReasonableDecimals() { + // final List unitRange = getAllPrefixedUnits("m"); + // final PrimitivePath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); + // final PrimitivePath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); + // final Collection result = callOperator(left, "=", right); + // assertThat(result).selectResult().hasRows(createResult(unitRange, true)); + // } + // + // + // @Test + // void nonEqualityPrecisionForReasonableDecimals() { + // final List unitRange = getAllPrefixedUnits("m"); + // final PrimitivePath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); + // final PrimitivePath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_02, unitRange); + // final Collection result = callOperator(left, "!=", right); + // assertThat(result).selectResult().hasRows(createResult(unitRange, true)); + // } + // + // + // @Test + // void comparisonPrecisionForReasonableDecimals() { + // final List unitRange = getAllPrefixedUnits("m"); + // final PrimitivePath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); + // final PrimitivePath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_02, unitRange); + // final Collection result = callOperator(left, "<", right); + // assertThat(result).selectResult().hasRows(createResult(unitRange, true)); + // } + // + // + // @Test + // void equalityPrecisionForFullDecimals() { + // final List unitRange = getAllPrefixedUnits("m"); + // final PrimitivePath left = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); + // final PrimitivePath right = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); + // final Collection result = callOperator(left, "=", right); + // assertThat(result).selectResult() + // .hasRows(createResult(unitRange, true, UNSUPPORTED_FULL_DECIMAL_UNITS)); + // } + // + // @Test + // void nonEqualityPrecisionForFullDecimals() { + // final List unitRange = getAllPrefixedUnits("m"); + // final PrimitivePath left = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); + // final PrimitivePath right = buildQuantityPathForUnits(FULL_DECIMAL_02, unitRange); + // final Collection result = callOperator(left, "!=", right); + // assertThat(result).selectResult() + // .hasRows(createResult(unitRange, true, UNSUPPORTED_FULL_DECIMAL_UNITS)); + // } + // + // @Test + // void comparisonPrecisionForFullDecimals() { + // final List unitRange = getAllPrefixedUnits("m"); + // final PrimitivePath left = buildQuantityPathForUnits(FULL_DECIMAL_01, unitRange); + // final PrimitivePath right = buildQuantityPathForUnits(FULL_DECIMAL_02, unitRange); + // final Collection result = callOperator(left, "<", right); + // assertThat(result).selectResult() + // .hasRows(createResult(unitRange, true, UNSUPPORTED_FULL_DECIMAL_UNITS)); + // } + // + // @Test + // void equalityPrecisionForReasonableDecimalsWithMoles() { + // final List unitRange = getAllPrefixedUnits("mol"); + // final PrimitivePath left = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); + // final PrimitivePath right = buildQuantityPathForUnits(REASONABLE_DECIMAL_01, unitRange); + // final Collection result = callOperator(left, "=", right); + // assertThat(result).selectResult() + // .hasRows(createResult(unitRange, true, UNSUPPORTED_REASONABLE_DECIMAL_MOL_UNITS)); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java index 8f52dc966b..6ec872d2a1 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java @@ -21,7 +21,6 @@ import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -47,10 +46,10 @@ public static ResourcePathAssertion assertThat(@Nonnull final ResourceCollection return new ResourcePathAssertion(fhirPath); } - @Nonnull - public static ElementPathAssertion assertThat(@Nonnull final PrimitivePath fhirPath) { - return new ElementPathAssertion(fhirPath); - } + // @Nonnull + // public static ElementPathAssertion assertThat(@Nonnull final PrimitivePath fhirPath) { + // return new ElementPathAssertion(fhirPath); + // } @Nonnull public static DatasetAssert assertThat(@Nonnull final Dataset rowDataset) { diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java index c96c3e338d..02ffbf7ac7 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java @@ -17,25 +17,16 @@ package au.csiro.pathling.test.assertions; -import static au.csiro.pathling.utilities.Preconditions.check; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; -import au.csiro.pathling.fhirpath.literal.LiteralPath; -import java.util.ArrayList; -import java.util.List; import javax.annotation.Nonnull; -import org.apache.spark.sql.Column; /** * @author Piotr Szul * @author John Grimes */ @SuppressWarnings("unused") +@NotImplemented public abstract class BaseFhirPathAssertion> { @Nonnull @@ -45,79 +36,82 @@ public abstract class BaseFhirPathAssertion> this.result = result; } - @Nonnull - public DatasetAssert selectResult() { - final Column[] selection = new Column[]{result.getIdColumn(), result.getValueColumn()}; - return new DatasetAssert(result.getDataset().select(selection)); - } - - @Nonnull - public DatasetAssert selectOrderedResult() { - final Column[] selection = new Column[]{result.getIdColumn(), result.getValueColumn()}; - // TODO: Update this to make sure that it is ordered. - return new DatasetAssert(result.getDataset().select(selection)); - } - - @Nonnull - public DatasetAssert selectGroupingResult(@Nonnull final List groupingColumns) { - return selectGroupingResult(groupingColumns, false); - } - - @Nonnull - private DatasetAssert selectGroupingResult(@Nonnull final List groupingColumns, - final boolean preserveOrder) { - check(!groupingColumns.isEmpty()); - final ArrayList allColumnsList = new ArrayList<>(groupingColumns); - allColumnsList.add(result.getValueColumn()); - final Column[] allColumns = allColumnsList.toArray(new Column[0]); - return new DatasetAssert(preserveOrder - ? result.getDataset().select(allColumns) - : result.getDataset().select(allColumns).orderBy(allColumns)); - } - - @Nonnull - public DatasetAssert selectOrderedResultWithEid() { - final Column[] selection = new Column[]{result.getIdColumn(), result.getValueColumn()}; - // TODO: Update this to make sure that it is ordered. - return new DatasetAssert(result.getDataset().select(selection)); - } - - @Nonnull - public T hasExpression(@Nonnull final String expression) { - assertEquals(expression, result.getExpression()); - return self(); - } - - public T isSingular() { - assertTrue(result.isSingular()); - return self(); - } - - public T isNotSingular() { - assertFalse(result.isSingular()); - return self(); - } - - public T preservesCardinalityOf(final Collection otherResult) { - assertEquals(otherResult.isSingular(), result.isSingular()); - return self(); - } - - - public ElementPathAssertion isElementPath(final Class ofType) { - assertTrue(ofType.isAssignableFrom(result.getClass())); - return new ElementPathAssertion((PrimitivePath) result); - } - - public ResourcePathAssertion isResourcePath() { - assertTrue(ResourceCollection.class.isAssignableFrom(result.getClass())); - return new ResourcePathAssertion((ResourceCollection) result); - } - - public LiteralPathAssertion isLiteralPath(final Class ofType) { - assertTrue(ofType.isAssignableFrom(result.getClass())); - return new LiteralPathAssertion((LiteralPath) result); - } + + // TODO: implement with columns + + // @Nonnull + // public DatasetAssert selectResult() { + // final Column[] selection = new Column[]{result.getIdColumn(), result.getValueColumn()}; + // return new DatasetAssert(result.getDataset().select(selection)); + // } + // + // @Nonnull + // public DatasetAssert selectOrderedResult() { + // final Column[] selection = new Column[]{result.getIdColumn(), result.getValueColumn()}; + // // TODO: Update this to make sure that it is ordered. + // return new DatasetAssert(result.getDataset().select(selection)); + // } + // + // @Nonnull + // public DatasetAssert selectGroupingResult(@Nonnull final List groupingColumns) { + // return selectGroupingResult(groupingColumns, false); + // } + // + // @Nonnull + // private DatasetAssert selectGroupingResult(@Nonnull final List groupingColumns, + // final boolean preserveOrder) { + // check(!groupingColumns.isEmpty()); + // final ArrayList allColumnsList = new ArrayList<>(groupingColumns); + // allColumnsList.add(result.getValueColumn()); + // final Column[] allColumns = allColumnsList.toArray(new Column[0]); + // return new DatasetAssert(preserveOrder + // ? result.getDataset().select(allColumns) + // : result.getDataset().select(allColumns).orderBy(allColumns)); + // } + // + // @Nonnull + // public DatasetAssert selectOrderedResultWithEid() { + // final Column[] selection = new Column[]{result.getIdColumn(), result.getValueColumn()}; + // // TODO: Update this to make sure that it is ordered. + // return new DatasetAssert(result.getDataset().select(selection)); + // } + // + // @Nonnull + // public T hasExpression(@Nonnull final String expression) { + // assertEquals(expression, result.getExpression()); + // return self(); + // } + // + // public T isSingular() { + // assertTrue(result.isSingular()); + // return self(); + // } + // + // public T isNotSingular() { + // assertFalse(result.isSingular()); + // return self(); + // } + // + // public T preservesCardinalityOf(final Collection otherResult) { + // assertEquals(otherResult.isSingular(), result.isSingular()); + // return self(); + // } + // + // + // public ElementPathAssertion isElementPath(final Class ofType) { + // assertTrue(ofType.isAssignableFrom(result.getClass())); + // return new ElementPathAssertion((PrimitivePath) result); + // } + // + // public ResourcePathAssertion isResourcePath() { + // assertTrue(ResourceCollection.class.isAssignableFrom(result.getClass())); + // return new ResourcePathAssertion((ResourceCollection) result); + // } + // + // public LiteralPathAssertion isLiteralPath(final Class ofType) { + // assertTrue(ofType.isAssignableFrom(result.getClass())); + // return new LiteralPathAssertion((LiteralPath) result); + // } @SuppressWarnings("unchecked") private T self() { diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java index 35342ad0a4..81b3ca7f63 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java @@ -17,36 +17,42 @@ package au.csiro.pathling.test.assertions; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; +import au.csiro.pathling.fhirpath.collection.Collection; import javax.annotation.Nonnull; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** * @author John Grimes */ @SuppressWarnings("UnusedReturnValue") +@NotImplemented public class ElementPathAssertion extends BaseFhirPathAssertion { - @Nonnull - private final PrimitivePath fhirPath; - - ElementPathAssertion(@Nonnull final PrimitivePath fhirPath) { - super(fhirPath); - this.fhirPath = fhirPath; - } - - @Nonnull - public ElementPathAssertion hasFhirType(@Nonnull final FHIRDefinedType type) { - assertEquals(type, fhirPath.getFhirType()); - return this; + ElementPathAssertion(@Nonnull final Collection result) { + super(result); } - - @Nonnull - public ElementPathAssertion hasDefinition(@Nonnull final ElementDefinition elementDefinition) { - assertTrue(fhirPath.getDefinition().isPresent()); - assertEquals(elementDefinition, fhirPath.getDefinition().get()); - return this; - } + // TODO: check + + // @Nonnull + // private final PrimitivePath fhirPath; + // + // ElementPathAssertion(@Nonnull final PrimitivePath fhirPath) { + // super(fhirPath); + // this.fhirPath = fhirPath; + // } + // + // @Nonnull + // public ElementPathAssertion hasFhirType(@Nonnull final FHIRDefinedType type) { + // assertEquals(type, fhirPath.getFhirType()); + // return this; + // } + // + // + // @Nonnull + // public ElementPathAssertion hasDefinition(@Nonnull final ElementDefinition elementDefinition) { + // assertTrue(fhirPath.getDefinition().isPresent()); + // assertEquals(elementDefinition, fhirPath.getDefinition().get()); + // return this; + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/LiteralPathAssertion.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/LiteralPathAssertion.java index cc9efddfe6..2a7af21630 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/LiteralPathAssertion.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/LiteralPathAssertion.java @@ -17,42 +17,43 @@ package au.csiro.pathling.test.assertions; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; -import au.csiro.pathling.fhirpath.literal.LiteralPath; -import java.util.function.Function; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; +import au.csiro.pathling.fhirpath.collection.Collection; import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.hl7.fhir.r4.model.Coding; /** * @author John Grimes */ @SuppressWarnings("UnusedReturnValue") +@NotImplemented public class LiteralPathAssertion extends BaseFhirPathAssertion { - @Nonnull - private final LiteralPath fhirPath; - - LiteralPathAssertion(@Nonnull final LiteralPath fhirPath) { - super(fhirPath); - this.fhirPath = fhirPath; + LiteralPathAssertion(@Nonnull final Collection result) { + super(result); } - @Nonnull - public LiteralPathAssertion has(@Nullable final Object expected, - @Nonnull final Function function) { - assertEquals(expected, function.apply(fhirPath)); - return this; - } - - @Nonnull - public LiteralPathAssertion hasCodingValue(@Nonnull final Coding expectedCoding) { - assertTrue(fhirPath instanceof CodingLiteralPath); - final Coding actualCoding = ((CodingLiteralPath) fhirPath).getValue(); - assertTrue(expectedCoding.equalsDeep(actualCoding)); - return this; - } + //TODO: LiteralPathAssertion + + // @Nonnull + // private final LiteralPath fhirPath; + // + // LiteralPathAssertion(@Nonnull final LiteralPath fhirPath) { + // super(fhirPath); + // this.fhirPath = fhirPath; + // } + // + // @Nonnull + // public LiteralPathAssertion has(@Nullable final Object expected, + // @Nonnull final Function function) { + // assertEquals(expected, function.apply(fhirPath)); + // return this; + // } + // + // @Nonnull + // public LiteralPathAssertion hasCodingValue(@Nonnull final Coding expectedCoding) { + // assertTrue(fhirPath instanceof CodingLiteralPath); + // final Coding actualCoding = ((CodingLiteralPath) fhirPath).getValue(); + // assertTrue(expectedCoding.equalsDeep(actualCoding)); + // return this; + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java index 18e4984180..d06f0195b9 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java @@ -25,9 +25,7 @@ import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.definition.BasicElementDefinition; import au.csiro.pathling.fhirpath.definition.ElementDefinition; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.test.helpers.SparkHelpers.IdAndValueColumns; -import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.spark.sql.Column; @@ -161,17 +159,19 @@ public ElementPathBuilder definition(@Nonnull final ElementDefinition definition return this; } - @Nonnull - public PrimitivePath build() { - return PrimitivePath.build(expression, dataset, idColumn, valueColumn, Optional.empty(), - singular, - Optional.ofNullable(currentResource), Optional.ofNullable(thisColumn), fhirType); - } - - @Nonnull - public PrimitivePath buildDefined() { - return PrimitivePath.build(expression, dataset, idColumn, valueColumn, Optional.empty(), - singular, - Optional.ofNullable(currentResource), Optional.ofNullable(thisColumn), definition); - } + // TODO: check + + // @Nonnull + // public PrimitivePath build() { + // return PrimitivePath.build(expression, dataset, idColumn, valueColumn, Optional.empty(), + // singular, + // Optional.ofNullable(currentResource), Optional.ofNullable(thisColumn), fhirType); + // } + // + // @Nonnull + // public PrimitivePath buildDefined() { + // return PrimitivePath.build(expression, dataset, idColumn, valueColumn, Optional.empty(), + // singular, + // Optional.ofNullable(currentResource), Optional.ofNullable(thisColumn), definition); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java index 056737c8c3..1f70149734 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java @@ -17,11 +17,12 @@ package au.csiro.pathling.test.builders; -import static org.apache.spark.sql.functions.lit; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.function.registry.StaticFunctionRegistry; import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -44,6 +45,9 @@ */ public class ParserContextBuilder { + @Nonnull + private ResourceCollection rootContext; + @Nonnull private Collection inputContext; @@ -69,9 +73,8 @@ public ParserContextBuilder(@Nonnull final SparkSession spark, @Nonnull final FhirContext fhirContext) { this.fhirContext = fhirContext; this.spark = spark; - inputContext = mock(Collection.class); - when(inputContext.getIdColumn()).thenReturn(lit(null)); - when(inputContext.getDataset()).thenReturn(spark.emptyDataFrame()); + rootContext = mock(ResourceCollection.class); + inputContext = rootContext; dataSource = Mockito.mock(DataSource.class, new DefaultAnswer()); groupingColumns = Collections.emptyList(); nodeIdColumns = new HashMap<>(); @@ -83,17 +86,17 @@ public ParserContextBuilder inputContext(@Nonnull final Collection inputContext) return this; } - @Nonnull - public ParserContextBuilder inputExpression(@Nonnull final String inputExpression) { - when(inputContext.getExpression()).thenReturn(inputExpression); - return this; - } + // @Nonnull + // public ParserContextBuilder inputExpression(@Nonnull final String inputExpression) { + // when(inputContext.getExpression()).thenReturn(inputExpression); + // return this; + // } - @Nonnull - public ParserContextBuilder idColumn(@Nonnull final Column idColumn) { - when(inputContext.getIdColumn()).thenReturn(idColumn); - return this; - } + // @Nonnull + // public ParserContextBuilder idColumn(@Nonnull final Column idColumn) { + // when(inputContext.getIdColumn()).thenReturn(idColumn); + // return this; + // } @Nonnull public ParserContextBuilder database(@Nonnull final DataSource dataSource) { @@ -116,9 +119,9 @@ public ParserContextBuilder groupingColumns(@Nonnull final List grouping @Nonnull public ParserContext build() { - return new ParserContext(inputContext, fhirContext, spark, dataSource, - Optional.ofNullable(terminologyServiceFactory), functionRegistry, groupingColumns, - Optional.empty()); + return new ParserContext(inputContext, rootContext, fhirContext, spark, dataSource, + StaticFunctionRegistry.getInstance(), + Optional.ofNullable(terminologyServiceFactory), Optional.empty()); } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java index 552ab9daca..329a39aff6 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ResourcePathBuilder.java @@ -17,22 +17,13 @@ package au.csiro.pathling.test.builders; -import static au.csiro.pathling.QueryHelpers.createColumn; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import au.csiro.pathling.QueryHelpers.DatasetWithColumn; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.definition.ResourceDefinition; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.test.fixtures.PatientResourceRowFixture; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -159,39 +150,42 @@ public ResourcePathBuilder thisColumn(final Column thisColumn) { return this; } - @Nonnull - public ResourceCollection build() { - return ResourceCollection.build(fhirContext, dataSource, resourceType, expression); - } - - @Nonnull - public ResourceCollection buildCustom() { - final String resourceCode = resourceType.toCode(); - final RuntimeResourceDefinition hapiDefinition = fhirContext - .getResourceDefinition(resourceCode); - final ResourceDefinition definition = new ResourceDefinition(resourceType, hapiDefinition, - Optional.empty()); - // in most cases value column should be the same as id - final DatasetWithColumn datasetWithColumn = createColumn(dataset, valueColumn); - - final Map elementsToColumns = new HashMap<>(); - for (final String columnName : dataset.columns()) { - elementsToColumns.put(columnName, dataset.col(columnName)); - } - - try { - final Constructor constructor = ResourceCollection.class - .getDeclaredConstructor(String.class, Dataset.class, Column.class, Optional.class, - Column.class, boolean.class, Optional.class, ResourceDefinition.class, Map.class); - constructor.setAccessible(true); - return constructor - .newInstance(expression, datasetWithColumn.getDataset(), idColumn, eidColumn, - datasetWithColumn.getColumn(), singular, Optional.ofNullable(thisColumn), definition, - elementsToColumns); - } catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException | - InstantiationException e) { - throw new RuntimeException("Problem building ResourcePath", e); - } - } + // TODO: check + + // + // @Nonnull + // public ResourceCollection build() { + // return ResourceCollection.build(fhirContext, dataSource, resourceType, expression); + // } + // + // @Nonnull + // public ResourceCollection buildCustom() { + // final String resourceCode = resourceType.toCode(); + // final RuntimeResourceDefinition hapiDefinition = fhirContext + // .getResourceDefinition(resourceCode); + // final ResourceDefinition definition = new ResourceDefinition(resourceType, hapiDefinition, + // Optional.empty()); + // // in most cases value column should be the same as id + // final DatasetWithColumn datasetWithColumn = createColumn(dataset, valueColumn); + // + // final Map elementsToColumns = new HashMap<>(); + // for (final String columnName : dataset.columns()) { + // elementsToColumns.put(columnName, dataset.col(columnName)); + // } + // + // try { + // final Constructor constructor = ResourceCollection.class + // .getDeclaredConstructor(String.class, Dataset.class, Column.class, Optional.class, + // Column.class, boolean.class, Optional.class, ResourceDefinition.class, Map.class); + // constructor.setAccessible(true); + // return constructor + // .newInstance(expression, datasetWithColumn.getDataset(), idColumn, eidColumn, + // datasetWithColumn.getColumn(), singular, Optional.ofNullable(thisColumn), definition, + // elementsToColumns); + // } catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException | + // InstantiationException e) { + // throw new RuntimeException("Problem building ResourcePath", e); + // } + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/UntypedResourcePathBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/UntypedResourcePathBuilder.java index 5ce148b81f..d6b0248029 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/UntypedResourcePathBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/UntypedResourcePathBuilder.java @@ -18,12 +18,7 @@ package au.csiro.pathling.test.builders; import static org.apache.spark.sql.functions.col; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import au.csiro.pathling.fhirpath.collection.UntypedResourcePath; -import au.csiro.pathling.fhirpath.collection.ReferencePath; -import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.spark.sql.Column; @@ -32,7 +27,6 @@ import org.apache.spark.sql.SparkSession; import org.apache.spark.sql.functions; import org.apache.spark.sql.types.DataTypes; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** * @author John Grimes @@ -111,17 +105,19 @@ public UntypedResourcePathBuilder thisColumn(@Nonnull final Column thisColumn) { return this; } - @Nonnull - public UntypedResourcePath build() { - final ReferencePath referencePath = mock(ReferencePath.class); - when(referencePath.getIdColumn()).thenReturn(idColumn); - when(referencePath.getValueColumn()).thenReturn(valueColumn); - when(referencePath.getDataset()).thenReturn(dataset); - when(referencePath.isSingular()).thenReturn(singular); - when(referencePath.getCurrentResource()).thenReturn(Optional.empty()); - when(referencePath.getThisColumn()).thenReturn(Optional.ofNullable(thisColumn)); - when(referencePath.getFhirType()).thenReturn(FHIRDefinedType.REFERENCE); - return UntypedResourcePath.build(referencePath, expression); - } + // TODO: check + // + // @Nonnull + // public UntypedResourcePath build() { + // final ReferencePath referencePath = mock(ReferencePath.class); + // when(referencePath.getIdColumn()).thenReturn(idColumn); + // when(referencePath.getValueColumn()).thenReturn(valueColumn); + // when(referencePath.getDataset()).thenReturn(dataset); + // when(referencePath.isSingular()).thenReturn(singular); + // when(referencePath.getCurrentResource()).thenReturn(Optional.empty()); + // when(referencePath.getThisColumn()).thenReturn(Optional.ofNullable(thisColumn)); + // when(referencePath.getFhirType()).thenReturn(FHIRDefinedType.REFERENCE); + // return UntypedResourcePath.build(referencePath, expression); + // } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java index 91736906d3..a33163ce71 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java @@ -42,7 +42,7 @@ public static Optional getChildOfResource( .getResourceDefinition(resourceCode); requireNonNull(hapiDefinition); final ResourceDefinition definition = new ResourceDefinition( - ResourceType.fromCode(resourceCode), hapiDefinition, Optional.empty()); + ResourceType.fromCode(resourceCode), hapiDefinition); return definition.getChildElement(elementName); } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java index 2162ac024c..02df491eec 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java @@ -186,10 +186,12 @@ public static Dataset selectValuesAndNodes(@Nonnull final Dataset data final List columns = new ArrayList<>(); columns.addAll( paths.stream() - .map(Collection::getValueColumn) + .map(Collection::getColumn) .collect(toList()) ); - columns.addAll(new ArrayList<>(context.getNesting().getOrderingColumns())); + + // TODO: review + // columns.addAll(new ArrayList<>(context.getNesting().getOrderingColumns())); return dataset.select(columns.toArray(new Column[0])); } From a86ffb782ecdcf5b9cb6d5ded3f8eab2841da88b Mon Sep 17 00:00:00 2001 From: John Grimes Date: Tue, 5 Sep 2023 15:15:18 +1000 Subject: [PATCH 62/95] Fix Collection object construction --- .../csiro/pathling/fhirpath/FhirPathType.java | 3 +- .../collection/BooleanCollection.java | 29 ++++++++---- .../fhirpath/collection/CodingCollection.java | 19 ++++++-- .../fhirpath/collection/Collection.java | 41 +++++++++++----- .../fhirpath/collection/DateCollection.java | 17 +++++-- .../collection/DateTimeCollection.java | 30 ++++++++---- .../collection/DecimalCollection.java | 9 +++- .../collection/IntegerCollection.java | 21 ++++++--- .../fhirpath/collection/MixedCollection.java | 47 ++++++++++++++++--- .../collection/QuantityCollection.java | 17 +++++-- .../collection/ResourceCollection.java | 40 ++++++++++------ .../fhirpath/collection/StringCollection.java | 18 +++++-- .../fhirpath/collection/TimeCollection.java | 17 +++++-- .../fhirpath/function/WhereFunction.java | 6 +-- .../fhirpath/parser/InvocationVisitor.java | 18 +++---- 15 files changed, 239 insertions(+), 93 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathType.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathType.java index b911529926..f76bb65027 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathType.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathType.java @@ -1,6 +1,7 @@ package au.csiro.pathling.fhirpath; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import lombok.Getter; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; @@ -36,7 +37,7 @@ public enum FhirPathType { * @return the corresponding {@link FhirPathType} according to the rules of automatic conversion * within the FHIR spec */ - @Nonnull + @Nullable public static FhirPathType forFhirType(@Nonnull final FHIRDefinedType fhirType) { return FhirTypeMapping.get(fhirType); } 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 325e784382..f05e54cf89 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 @@ -42,10 +42,25 @@ public class BooleanCollection extends Collection implements Materializable> COMPARABLE_TYPES = ImmutableSet .of(BooleanCollection.class); - public BooleanCollection(@Nonnull final Column column, + protected BooleanCollection(@Nonnull final Column column, + @Nonnull final Optional type, + @Nonnull final Optional fhirType, + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); + } + + /** + * Returns a new instance with the specified column and definition. + * + * @param column The column to use + * @param definition The definition to use + * @return A new instance of {@link BooleanCollection} + */ + @Nonnull + public static BooleanCollection build(@Nonnull final Column column, @Nonnull final Optional definition) { - super(column, Optional.of(FhirPathType.BOOLEAN), Optional.of(FHIRDefinedType.BOOLEAN), - definition); + return new BooleanCollection(column, Optional.of(FhirPathType.BOOLEAN), + Optional.of(FHIRDefinedType.BOOLEAN), definition); } /** @@ -57,13 +72,7 @@ public BooleanCollection(@Nonnull final Column column, @Nonnull public static BooleanCollection fromLiteral(@Nonnull final String literal) { final boolean value = literal.equals("true"); - return new BooleanCollection(lit(value), Optional.empty()); - } - - @Nonnull - public static BooleanCollection build(@Nonnull final Column column, - @Nonnull final Optional definition) { - return new BooleanCollection(column, definition); + return BooleanCollection.build(lit(value), Optional.empty()); } @Nonnull 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 9cb96986da..925725d403 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 @@ -45,16 +45,25 @@ public class CodingCollection extends Collection implements Materializable, Comparable, StringCoercible { - public CodingCollection(@Nonnull final Column column, - @Nonnull final Optional definition) { - super(column, Optional.of(FhirPathType.CODING), Optional.of(FHIRDefinedType.CODING), - definition); + protected CodingCollection(@Nonnull final Column column, + @Nonnull final Optional type, + @Nonnull final Optional fhirType, + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); } + /** + * Returns a new instance with the specified column and definition. + * + * @param column The column to use + * @param definition The definition to use + * @return A new instance of {@link CodingCollection} + */ @Nonnull public static CodingCollection build(@Nonnull final Column column, @Nonnull final Optional definition) { - return new CodingCollection(column, definition); + return new CodingCollection(column, Optional.of(FhirPathType.CODING), + Optional.of(FHIRDefinedType.CODING), definition); } /** 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 b123230fd6..dc82b4ba64 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 @@ -29,6 +29,7 @@ import java.util.Optional; import java.util.function.Function; import javax.annotation.Nonnull; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import org.apache.spark.sql.Column; @@ -43,7 +44,7 @@ * @author John Grimes */ @Getter -@AllArgsConstructor +@AllArgsConstructor(access = AccessLevel.PROTECTED) public class Collection implements Comparable, Numeric { // See https://hl7.org/fhir/fhirpath.html#types. @@ -98,8 +99,26 @@ public class Collection implements Comparable, Numeric { private Optional definition; /** - * Builds the appropriate subtype of {@link Collection} based upon the supplied {@link - * ElementDefinition}. + * Builds a generic {@link Collection} with the specified column, FHIRPath type, FHIR type and + * definition. + * + * @param column a {@link Column} containing the result of the expression + * @param fhirPathType the {@link FhirPathType} that this path should be based upon + * @param fhirType the {@link FHIRDefinedType} that this path should be based upon + * @param definition the {@link ElementDefinition} that this path should be based upon + * @return a new {@link Collection} + */ + @Nonnull + public static Collection build(@Nonnull final Column column, + @Nonnull final Optional fhirPathType, + @Nonnull final Optional fhirType, + @Nonnull final Optional definition) { + return new Collection(column, fhirPathType, fhirType, definition); + } + + /** + * Builds the appropriate subtype of {@link Collection} based upon the supplied + * {@link ElementDefinition}. *

* Use this builder when the path is the child of another path, and will need to be traversable. * @@ -112,16 +131,16 @@ public static Collection build(@Nonnull final Column column, @Nonnull final ElementDefinition definition) { final Optional optionalFhirType = definition.getFhirType(); if (optionalFhirType.isPresent()) { - return getInstance(column, Optional.empty(), Optional.of(definition)); + return getInstance(column, optionalFhirType, Optional.of(definition)); } else { throw new IllegalArgumentException( - "Attempted to build an ElementPath with an ElementDefinition with no fhirType"); + "Attempted to build a Collection with an ElementDefinition with no fhirType"); } } /** - * Builds the appropriate subtype of {@link Collection} based upon the supplied {@link - * FHIRDefinedType}. + * Builds the appropriate subtype of {@link Collection} based upon the supplied + * {@link FHIRDefinedType}. *

* Use this builder when the path is derived, e.g. the result of a function. * @@ -152,10 +171,10 @@ private static Collection getInstance(@Nonnull final Column column, final Constructor constructor = elementPathClass .getDeclaredConstructor(Column.class, Optional.class, Optional.class, Optional.class); return constructor - .newInstance(column, Optional.of(fhirPathType), Optional.of(fhirType), definition); + .newInstance(column, Optional.ofNullable(fhirPathType), fhirType, definition); } catch (final NoSuchMethodException | InstantiationException | IllegalAccessException | - InvocationTargetException e) { - throw new RuntimeException("Problem building an FhirPath object", e); + InvocationTargetException e) { + throw new RuntimeException("Problem building a Collection object", e); } } @@ -264,7 +283,7 @@ public Optional getNumericContextColumn() { /** * Creates a null {@link Collection}. - * + * * @return the null collection. */ @Nonnull 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 c1c683ccf5..1971e7b82a 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 @@ -49,15 +49,24 @@ public class DateCollection extends Collection implements Materializable, Comparable, Temporal, StringCoercible { - public DateCollection(@Nonnull final Column column, - @Nonnull final Optional definition) { - super(column, Optional.of(FhirPathType.DATE), Optional.of(FHIRDefinedType.DATE), definition); + protected DateCollection(@Nonnull final Column column, @Nonnull final Optional type, + @Nonnull final Optional fhirType, + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); } + /** + * Returns a new instance with the specified column and definition. + * + * @param column The column to use + * @param definition The definition to use + * @return A new instance of {@link DateCollection} + */ @Nonnull public static DateCollection build(@Nonnull final Column column, @Nonnull final Optional definition) { - return new DateCollection(column, definition); + return new DateCollection(column, Optional.of(FhirPathType.DATE), + Optional.of(FHIRDefinedType.DATE), definition); } /** 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 84ae2398d4..0dfe964246 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 @@ -54,10 +54,25 @@ public class DateTimeCollection extends Collection implements private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet .of(DateCollection.class, DateTimeCollection.class); - public DateTimeCollection(@Nonnull final Column column, + protected DateTimeCollection(@Nonnull final Column column, + @Nonnull final Optional type, + @Nonnull final Optional fhirType, + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); + } + + /** + * Returns a new instance with the specified column and definition. + * + * @param column The column to use + * @param definition The definition to use + * @return A new instance of {@link DateTimeCollection} + */ + @Nonnull + public static DateTimeCollection build(@Nonnull final Column column, @Nonnull final Optional definition) { - super(column, Optional.of(FhirPathType.DATETIME), Optional.of(FHIRDefinedType.DATETIME), - definition); + return new DateTimeCollection(column, Optional.of(FhirPathType.DATETIME), + Optional.of(FHIRDefinedType.DATETIME), definition); } /** @@ -68,16 +83,11 @@ public DateTimeCollection(@Nonnull final Column column, * @throws ParseException if the literal is malformed */ @Nonnull - public static DateTimeCollection fromLiteral(@Nonnull final String fhirPath) throws ParseException { + public static DateTimeCollection fromLiteral(@Nonnull final String fhirPath) + throws ParseException { final String dateString = fhirPath.replaceFirst("^@", ""); return DateTimeCollection.build(lit(dateString), Optional.empty()); } - - @Nonnull - public static DateTimeCollection build(@Nonnull final Column column, - @Nonnull final Optional definition) { - return new DateTimeCollection(column, definition); - } @Nonnull public static ImmutableSet> getComparableTypes() { 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 3a062287fc..5c8ef1595e 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 @@ -50,13 +50,20 @@ public class DecimalCollection extends Collection implements Materializable fhirPathType, @Nonnull final Optional fhirType, @Nonnull final Optional definition) { super(column, fhirPathType, fhirType, definition); } + /** + * Returns a new instance with the specified column and definition. + * + * @param column The column to use + * @param definition The definition to use + * @return A new instance of {@link DecimalCollection} + */ @Nonnull public static DecimalCollection build(@Nonnull final Column column, @Nonnull final Optional definition) { 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 71a5a99182..c8f210a775 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 @@ -52,16 +52,25 @@ public class IntegerCollection extends Collection implements private static final ImmutableSet> COMPARABLE_TYPES = ImmutableSet .of(IntegerCollection.class, DecimalCollection.class); - public IntegerCollection(@Nonnull final Column column, - @Nonnull final Optional definition) { - super(column, Optional.of(FhirPathType.INTEGER), Optional.of(FHIRDefinedType.INTEGER), - definition); + protected IntegerCollection(@Nonnull final Column column, + @Nonnull final Optional type, + @Nonnull final Optional fhirType, + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); } + /** + * Returns a new instance with the specified column and definition. + * + * @param column The column to use + * @param definition The definition to use + * @return A new instance of {@link IntegerCollection} + */ @Nonnull public static IntegerCollection build(@Nonnull final Column column, @Nonnull final Optional definition) { - return new IntegerCollection(column, definition); + return new IntegerCollection(column, Optional.of(FhirPathType.INTEGER), + Optional.of(FHIRDefinedType.INTEGER), definition); } /** @@ -74,7 +83,7 @@ public static IntegerCollection build(@Nonnull final Column column, public static IntegerCollection fromLiteral(@Nonnull final String fhirPath) throws NumberFormatException { final int value = Integer.parseInt(fhirPath); - return new IntegerCollection(lit(value), Optional.empty()); + return IntegerCollection.build(lit(value), Optional.empty()); } @Nonnull diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java index d5b872adc5..9df20cffc8 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java @@ -1,38 +1,62 @@ package au.csiro.pathling.fhirpath.collection; -import static au.csiro.pathling.utilities.Preconditions.checkPresent; +import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; +import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.definition.ChoiceElementDefinition; import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.NodeDefinition; import java.util.Optional; import javax.annotation.Nonnull; +import lombok.Getter; import org.apache.spark.sql.Column; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** * Represents a choice element, which can be resolved to any one of a number of data types. * * @author John Grimes */ +@Getter public class MixedCollection extends Collection { + /** + * The collection from which this choice element is derived. + */ @Nonnull private final Collection from; + /** + * The definition of this choice element. + */ @Nonnull private final ChoiceElementDefinition choiceDefinition; - public MixedCollection(@Nonnull final Column column, - @Nonnull final Optional definition, @Nonnull final Collection from) { - super(column, Optional.empty(), Optional.empty(), definition); + protected MixedCollection(@Nonnull final Column column, + @Nonnull final Optional type, + @Nonnull final Optional fhirType, + @Nonnull final Optional definition, + @Nonnull final Collection from) { + super(column, type, fhirType, definition); this.from = from; - this.choiceDefinition = checkPresent(definition); + checkArgument(definition.isPresent() && definition.get() instanceof ChoiceElementDefinition, + "definition must be a ChoiceElementDefinition"); + this.choiceDefinition = (ChoiceElementDefinition) definition.get(); } + /** + * Returns a new instance with the specified column and definition. + * + * @param column The column to use + * @param definition The definition to use + * @param from The collection from which this choice element is derived + * @return A new instance of {@link MixedCollection} + */ @Nonnull public static MixedCollection build(@Nonnull final Column column, @Nonnull final Optional definition, @Nonnull final Collection from) { - return new MixedCollection(column, definition, from); + return new MixedCollection(column, Optional.empty(), Optional.empty(), definition, from); } @Nonnull @@ -46,12 +70,21 @@ private Optional resolveChoiceDefinition(@Nonnull final Strin return choiceDefinition.getChildByType(type); } + /** + * Returns a new collection representing just the elements of this collection with the specified + * type. + * + * @param type The type of element to return + * @return A new collection representing just the elements of this collection with the specified + * type + */ @Nonnull public Optional resolveChoice(@Nonnull final String type) { final String elementName = choiceDefinition.getElementName(); final String columnName = ChoiceElementDefinition.getColumnName(elementName, type); if (from instanceof ResourceCollection) { - final Optional elementColumn = ((ResourceCollection) from).getElementColumn(columnName); + final Optional elementColumn = ((ResourceCollection) from).getElementColumn( + columnName); final Optional definition = resolveChoiceDefinition(type); checkUserInput(elementColumn.isPresent() && definition.isPresent(), "No such child: " + columnName); 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 e252713328..2c84cb0c0d 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 @@ -57,15 +57,24 @@ public class QuantityCollection extends Collection implements Comparable, Numeri private static final Pattern UCUM_PATTERN = Pattern.compile("([0-9.]+) ('[^']+')"); public QuantityCollection(@Nonnull final Column column, - @Nonnull final Optional definition) { - super(column, Optional.of(FhirPathType.QUANTITY), Optional.of(FHIRDefinedType.QUANTITY), - definition); + @Nonnull final Optional type, + @Nonnull final Optional fhirType, + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); } + /** + * Returns a new instance with the specified column and definition. + * + * @param column The column to use + * @param definition The definition to use + * @return A new instance of {@link QuantityCollection} + */ @Nonnull public static QuantityCollection build(@Nonnull final Column column, @Nonnull final Optional definition) { - return new QuantityCollection(column, definition); + return new QuantityCollection(column, Optional.of(FhirPathType.QUANTITY), + Optional.of(FHIRDefinedType.QUANTITY), definition); } /** 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 9b170cfb11..7ca0d8c57a 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 @@ -21,6 +21,8 @@ import au.csiro.pathling.encoders.EncoderBuilder; import au.csiro.pathling.encoders.ExtensionSupport; +import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.definition.NodeDefinition; import au.csiro.pathling.fhirpath.definition.ResourceDefinition; import au.csiro.pathling.io.source.DataSource; import ca.uhn.fhir.context.FhirContext; @@ -33,6 +35,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nonnull; +import lombok.Getter; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; @@ -47,6 +50,7 @@ * * @author John Grimes */ +@Getter public class ResourceCollection extends Collection { /** @@ -55,26 +59,29 @@ public class ResourceCollection extends Collection { @Nonnull private final Map elementsToColumns; + /** + * The {@link ResourceDefinition} for this resource type. + */ @Nonnull private final ResourceDefinition resourceDefinition; + /** + * The {@link Dataset} containing the resource data. + */ @Nonnull private final Dataset dataset; - - public ResourceCollection(@Nonnull final Dataset dataset, @Nonnull final Column column, - @Nonnull final ResourceDefinition definition, - @Nonnull final Map elementsToColumns) { - super(column, Optional.empty(), getFhirType(definition.getResourceType()), - Optional.of(definition)); - this.dataset = dataset; + protected ResourceCollection(@Nonnull final Column column, + @Nonnull final Optional type, + @Nonnull final Optional fhirType, + @Nonnull final Optional definition, + @Nonnull final Map elementsToColumns, + @Nonnull final ResourceDefinition resourceDefinition, + @Nonnull final Dataset dataset) { + super(column, type, fhirType, definition); this.elementsToColumns = elementsToColumns; - this.resourceDefinition = definition; - } - - @Nonnull - public Dataset getDataset() { - return dataset; + this.resourceDefinition = resourceDefinition; + this.dataset = dataset; } @Nonnull @@ -112,7 +119,9 @@ public static ResourceCollection build(@Nonnull final FhirContext fhirContext, .collect(Collectors.toMap(Function.identity(), functions::col, (a, b) -> null)); // We use the ID column as the value column for a ResourcePath. - return new ResourceCollection(dataset, functions.col("id"), definition, elementsToColumns); + return new ResourceCollection(functions.col("id"), Optional.empty(), + getFhirType(resourceType), Optional.of(definition), elementsToColumns, definition, + dataset); } /** @@ -152,6 +161,9 @@ public Column getExtensionContainerColumn() { + " Check if extension support was enabled when data were imported!"); } + /** + * @return the {@link ResourceType} of this resource collection + */ public ResourceType getResourceType() { return resourceDefinition.getResourceType(); } 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 ba56c84198..81895ffea8 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 @@ -46,16 +46,24 @@ */ public class StringCollection extends Collection implements Materializable { - public StringCollection(@Nonnull final Column column, - @Nonnull final Optional definition) { - super(column, Optional.of(FhirPathType.STRING), Optional.of(FHIRDefinedType.STRING), - definition); + public StringCollection(@Nonnull final Column column, @Nonnull final Optional type, + @Nonnull final Optional fhirType, + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); } + /** + * Returns a new instance with the specified column and definition. + * + * @param column The column to use + * @param definition The definition to use + * @return A new instance of {@link StringCollection} + */ @Nonnull public static StringCollection build(@Nonnull final Column column, @Nonnull final Optional definition) { - return new StringCollection(column, definition); + return new StringCollection(column, Optional.of(FhirPathType.STRING), + Optional.of(FHIRDefinedType.STRING), definition); } /** 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 b6020ff70f..2f5e7d5c75 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 @@ -40,15 +40,24 @@ public class TimeCollection extends Collection implements Materializable, Comparable, StringCoercible { - public TimeCollection(@Nonnull final Column column, - @Nonnull final Optional definition) { - super(column, Optional.of(FhirPathType.TIME), Optional.of(FHIRDefinedType.TIME), definition); + public TimeCollection(@Nonnull final Column column, @Nonnull final Optional type, + @Nonnull final Optional fhirType, + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); } + /** + * Returns a new instance with the specified column and definition. + * + * @param column The column to use + * @param definition The definition to use + * @return A new instance of {@link TimeCollection} + */ @Nonnull public static TimeCollection build(@Nonnull final Column column, @Nonnull final Optional definition) { - return new TimeCollection(column, definition); + return new TimeCollection(column, Optional.of(FhirPathType.TIME), + Optional.of(FHIRDefinedType.TIME), definition); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java index a6bd098340..d2759871ce 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java @@ -41,7 +41,7 @@ */ @Name("where") public class WhereFunction implements NamedFunction { - + @Nonnull @Override public Collection invoke(@Nonnull final FunctionInput input) { @@ -52,7 +52,7 @@ public Collection invoke(@Nonnull final FunctionInput input) { final Column column = filter(previous.getColumn(), element -> { final Collection result = argument.apply( - new Collection(element, previous.getType(), previous.getFhirType(), + Collection.build(element, previous.getType(), previous.getFhirType(), Optional.empty()), input.getContext()); final SparkSession spark = input.getContext().getSparkSession(); final FhirPathType type = checkPresent(result.getType()); @@ -61,7 +61,7 @@ public Collection invoke(@Nonnull final FunctionInput input) { return result.getColumn(); }); - return new Collection(column, previous.getType(), previous.getFhirType(), Optional.empty()); + return Collection.build(column, previous.getType(), previous.getFhirType(), Optional.empty()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index 7f6d24fc1f..6974c2732c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -79,7 +79,7 @@ public FhirPath visitMemberInvocation( @Nullable final MemberInvocationContext ctx) { @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - + // TODO: refactor to an expression return (input, c) -> { try { @@ -93,7 +93,7 @@ public FhirPath visitMemberInvocation( // TODO: what is this about? // If it is not a valid path traversal, see if it is a valid type specifier. final FHIRDefinedType fhirType = FHIRDefinedType.fromCode(fhirPath); - return new Collection(functions.lit(null), Optional.of(FhirPathType.TYPE_SPECIFIER), + return Collection.build(functions.lit(null), Optional.of(FhirPathType.TYPE_SPECIFIER), Optional.of(fhirType), Optional.empty()); } catch (final FHIRException e2) { @@ -117,7 +117,7 @@ public FhirPath visitFunctionInvocation( @Nullable final FunctionInvocationContext ctx) { final String functionIdentifier = requireNonNull(ctx).function().identifier().getText(); - + @Nullable final ParamListContext paramList = ctx.function().paramList(); final Visitor paramListVisitor = new Visitor(context); final List> arguments = Optional.ofNullable(paramList) @@ -126,8 +126,7 @@ public FhirPath visitFunctionInvocation( .map(paramListVisitor::visit) .collect(toList()) ).orElse(new ArrayList<>()); - - + // TODO: refactor to an expression return (input, c) -> { final NamedFunction function; @@ -143,19 +142,22 @@ public FhirPath visitFunctionInvocation( @Override @Nonnull - public FhirPath visitThisInvocation(@Nullable final ThisInvocationContext ctx) { + public FhirPath visitThisInvocation( + @Nullable final ThisInvocationContext ctx) { return (input, c) -> input; } @Override @Nonnull - public FhirPath visitIndexInvocation(@Nullable final IndexInvocationContext ctx) { + public FhirPath visitIndexInvocation( + @Nullable final IndexInvocationContext ctx) { throw new InvalidUserInputError("$index is not supported"); } @Override @Nonnull - public FhirPath visitTotalInvocation(@Nullable final TotalInvocationContext ctx) { + public FhirPath visitTotalInvocation( + @Nullable final TotalInvocationContext ctx) { throw new InvalidUserInputError("$total is not supported"); } From e1f1fb5aeba1691110a10872d83421c5934ca39a Mon Sep 17 00:00:00 2001 From: John Grimes Date: Wed, 6 Sep 2023 14:53:59 +1000 Subject: [PATCH 63/95] Update tests to use definitions from the spec --- .gitmodules | 3 + .../pathling/views/ConstantDeclaration.java | 20 ++- .../csiro/pathling/views/DirectSelection.java | 32 ++-- .../au/csiro/pathling/views/FhirView.java | 60 ++++--- .../pathling/views/FhirViewExecutor.java | 11 +- .../views/ForEachOrNullSelection.java | 39 +++++ .../pathling/views/ForEachSelection.java | 21 +-- .../csiro/pathling/views/FromSelection.java | 19 ++- .../pathling/views/NestedSelectClause.java | 2 +- .../au/csiro/pathling/views/SelectClause.java | 13 +- .../views/SelectClauseTypeAdapter.java | 8 +- .../csiro/pathling/views/UnionSelection.java | 45 ++++++ .../csiro/pathling/UnitTestDependencies.java | 5 +- .../au/csiro/pathling/views/FhirViewTest.java | 153 +++++++++++++++--- .../views/FhirViewTestDataSource.java | 63 ++++++++ .../views/elementRelatedUnnesting.json | 28 ---- .../views/flattenedBloodPressure.json | 82 ---------- .../requests/views/forEachWithinFrom.json | 33 ---- .../requests/views/nestedSingular.json | 23 --- .../views/resourceRelatedUnnesting.json | 41 ----- .../requests/views/singularNoUnnesting.json | 18 --- .../requests/views/unnestEmptyCollection.json | 19 --- .../results/views/elementRelatedUnnesting.tsv | 6 - .../results/views/flattenedBloodPressure.tsv | 2 - .../results/views/forEachWithinFrom.tsv | 11 -- .../results/views/nestedSingular.tsv | 5 - .../views/resourceRelatedUnnesting.tsv | 7 - .../results/views/singularNoUnnesting.tsv | 3 - .../results/views/unnestEmptyCollection.tsv | 3 - .../_delta_log/00000000000000000000.json | 4 - ...41b2-9f1d-f94a57684570-c000.snappy.parquet | Bin 154746 -> 0 bytes .../_delta_log/00000000000000000000.json | 4 - ...4aa2-b08a-d8980f55947d-c000.snappy.parquet | Bin 44411 -> 0 bytes 33 files changed, 391 insertions(+), 392 deletions(-) create mode 100644 .gitmodules create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/ForEachOrNullSelection.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/UnionSelection.java create mode 100644 fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTestDataSource.java delete mode 100644 fhirpath/src/test/resources/requests/views/elementRelatedUnnesting.json delete mode 100644 fhirpath/src/test/resources/requests/views/flattenedBloodPressure.json delete mode 100644 fhirpath/src/test/resources/requests/views/forEachWithinFrom.json delete mode 100644 fhirpath/src/test/resources/requests/views/nestedSingular.json delete mode 100644 fhirpath/src/test/resources/requests/views/resourceRelatedUnnesting.json delete mode 100644 fhirpath/src/test/resources/requests/views/singularNoUnnesting.json delete mode 100644 fhirpath/src/test/resources/requests/views/unnestEmptyCollection.json delete mode 100644 fhirpath/src/test/resources/results/views/elementRelatedUnnesting.tsv delete mode 100644 fhirpath/src/test/resources/results/views/flattenedBloodPressure.tsv delete mode 100644 fhirpath/src/test/resources/results/views/forEachWithinFrom.tsv delete mode 100644 fhirpath/src/test/resources/results/views/nestedSingular.tsv delete mode 100644 fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.tsv delete mode 100644 fhirpath/src/test/resources/results/views/singularNoUnnesting.tsv delete mode 100644 fhirpath/src/test/resources/results/views/unnestEmptyCollection.tsv delete mode 100644 fhirpath/src/test/resources/test-data/views/Observation.parquet/_delta_log/00000000000000000000.json delete mode 100644 fhirpath/src/test/resources/test-data/views/Observation.parquet/part-00000-a35c6b50-884f-41b2-9f1d-f94a57684570-c000.snappy.parquet delete mode 100644 fhirpath/src/test/resources/test-data/views/Patient.parquet/_delta_log/00000000000000000000.json delete mode 100644 fhirpath/src/test/resources/test-data/views/Patient.parquet/part-00000-52417c46-8b68-4aa2-b08a-d8980f55947d-c000.snappy.parquet diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..4f27704dc2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "sql-on-fhir"] + path = fhirpath/src/test/resources/sql-on-fhir + url = https://github.com/FHIR/sql-on-fhir-v2.git diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ConstantDeclaration.java b/fhirpath/src/main/java/au/csiro/pathling/views/ConstantDeclaration.java index 20d81bd455..9358d29489 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/ConstantDeclaration.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ConstantDeclaration.java @@ -4,24 +4,32 @@ import lombok.Data; /** - * An optional list of constants that can be used in any FHIRPath expression in the view definition. - * These are effectively strings or numbers that can be injected into FHIRPath expressions below by - * having {@code %constantName`} in the expression. + * Constant that can be used in FHIRPath expressions. + *

+ * A constant is a string that is injected into a FHIRPath expression through the use of a FHIRPath + * external constant with the same name. * * @author John Grimes + * @see ViewDefinition.constant */ @Data public class ConstantDeclaration { /** - * The name of the variable that can be referenced by following variables or columns, where users - * would use the {@code %variable_name} syntax. + * Name of constant (referred to in FHIRPath as {@code %[name]}). + * + * @see ViewDefinition.constant.name */ @NotNull String name; /** - * The value of the constant name to be used in expressions below. + * The string that will be substituted in place of the constant reference. + * + * @see ViewDefinition.constant.value */ @NotNull String value; diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java index e593ec3859..0fe01f9df8 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java @@ -1,6 +1,5 @@ package au.csiro.pathling.views; -import com.google.gson.annotations.SerializedName; import javax.annotation.Nullable; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; @@ -18,29 +17,34 @@ public class DirectSelection extends SelectClause { /** - * The name of the column produced in the output. - *

- * The name is limited to letters, numbers, or underscores and cannot start with an underscore -- - * i.e. with a regular expression of {@code ^[^_][A-Za-z0-9_]+$}. This makes it usable as table - * names in a wide variety of databases. + * Name of the column produced in the output, must be in a database-friendly format. + * + * @see ViewDefinition.select.name */ @NotNull @Size(max = 255) - @Pattern(regexp = "^[^_][A-Za-z0-9_]+$") - String name; + @Pattern(regexp = "^[^_][A-Za-z][A-Za-z0-9_]+$") + String alias; /** - * The FHIRPath expression for the column's content. This may be from the resource root or from a - * variable defined above, using a {@code %var_name.rest.of.path} structure. + * A FHIRPath expression that evaluates to the value that will be output in the column for each + * resource. The input context is the collection of resources of the type specified in the + * resource element. Constants defined in {@link FhirView#constants} can be referenced as + * {@code %[name]}. + * + * @see ViewDefinition.select.path */ @NotNull - @SerializedName("expr") - String expression; + String path; /** - * An optional human-readable description of the column. + * A human-readable description of the column. + * + * @see ViewDefinition.select.description */ - @SerializedName("desc") @Nullable String description; diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java index a24525a0c7..78f5a62c8a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java @@ -4,6 +4,7 @@ import java.util.List; import javax.annotation.Nullable; import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import lombok.Data; @@ -14,60 +15,73 @@ * may shift to defining views as true FHIR resources. * * @author John Grimes + * @see ViewDefinition */ @Data public class FhirView { /** - * Name of the FHIR view to be created. View runners can use this in whatever way is appropriate - * for their use case, such as a database view or table name. - *

- * The name is limited to letters, numbers, or underscores and cannot start with an underscore -- - * i.e. with a regular expression of {@code }^[^_][A-Za-z0-9_]+$}. This makes it usable as table - * names in a wide variety of databases. + * Name of the view definition, must be in a database-friendly format. + * + * @see ViewDefinition.name */ - @NotNull + @Nullable + @Pattern(regexp = "^[^_][A-Za-z][A-Za-z0-9_]+$") String name; /** - * An optional human-readable description of the view. + * Natural language description of the view definition. + * + * @see ViewDefinition.description */ @SerializedName("desc") @Nullable String description; /** - * The FHIR resource the view is based on, e.g. 'Patient' or 'Observation'. + * The FHIR resource that the view is based upon, e.g. 'Patient' or 'Observation'. + * + * @see ViewDefinition.resource */ @NotNull String resource; /** - * An optional list of constants that can be used in any FHIRPath expression in the view - * definition. These are effectively strings or numbers that can be injected into FHIRPath - * expressions below by having {@code %constantName`} in the expression. + * Constant that can be used in FHIRPath expressions. + *

+ * A constant is a string that is injected into a FHIRPath expression through the use of a + * FHIRPath external constant with the same name. + * + * @see ViewDefinition.constant */ @Nullable List constants; /** - * The select stanza defines the actual content of the view itself. This stanza is a list where - * each item in is one of: - *

    - *
  • a structure with the column name, expression, and optional description,
  • - *
  • a 'from' structure indicating a relative path to pull fields from in a nested select, or
  • - *
  • a 'forEach' structure, unrolling the specified path and creating a new row for each item.
  • - *
- * See the comments below for details on the semantics. + * Defines the content of a column within the view. + * + * @see ViewDefinition.select */ @NotNull @Size(min = 1) List select; /** - * 'where' filters are FHIRPath expressions joined with an implicit "and". This enables users to - * select a subset of rows that match a specific need. For example, a user may be interested only - * in a subset of observations based on code value and can filter them here. + * FHIRPath expression defining a filter condition. + *

+ * A FHIRPath expression that defines a filter that must evaluate to true for a resource to be + * included in the output. The input context is the collection of resources of the type specified + * in the resource element. Constants defined in {@link #constants} can be referenced as + * {@code %[name]}. The result of the expression must be of type Boolean. + * + * @see ViewDefinition.where */ @Nullable List where; diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index fcf251c27f..6d98db005f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -82,7 +82,7 @@ public Dataset buildQuery(@Nonnull final FhirView view) { .reduce(Column::and); final Dataset subjectDataset = dataSource.read(resourceType); - + return filterCondition .map(subjectDataset::filter) .orElse(subjectDataset) @@ -117,9 +117,9 @@ private List parseSelect(@Nonnull final List selectGroup, @Nonnull private List directSelection(@Nonnull final ParserContext context, @Nonnull final DirectSelection select, @Nonnull final List currentSelection) { - final Collection path = evalExpression(select.getExpression(), context); + final Collection path = evalExpression(select.getPath(), context); final List newColumns = new ArrayList<>(currentSelection); - newColumns.add(path.getColumn().alias(select.getName())); + newColumns.add(path.getColumn().alias(select.getAlias())); return newColumns; } @@ -128,10 +128,11 @@ private List directSelection(@Nonnull final ParserContext context, private List nestedSelection(final @Nonnull ParserContext context, @Nonnull final NestedSelectClause select, @Nonnull final List currentSelection, final boolean unnest) { - final Collection from = evalExpression(select.getExpression(), context); + final Collection from = evalExpression(select.getPath(), context); final Collection nextInputContext = unnest // TODO: FIX FIRST - ? from // .unnest() + ? from + // .unnest() : from; final ParserContext nextContext = context.withInputContext(nextInputContext); return parseSelect(select.getSelect(), nextContext, currentSelection); diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ForEachOrNullSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/ForEachOrNullSelection.java new file mode 100644 index 0000000000..bc73f48854 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ForEachOrNullSelection.java @@ -0,0 +1,39 @@ +package au.csiro.pathling.views; + +import java.util.List; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Same as forEach, but produces a single row with a null value if the collection is empty. + * + * @author John Grimes + * @see ViewDefinition.select.forEachOrNull + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class ForEachOrNullSelection extends NestedSelectClause { + + /** + * Same as forEach, but produces a single row with a null value if the collection is empty. + * + * @see ViewDefinition.select.forEachOrNull + */ + @NotNull + String path; + + /** + * Nested select relative to the {@link #path}. + * + * @see ViewDefinition.select.select + */ + @NotNull + @Size(min = 1) + List select; + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java index 49219c897c..446e0d31b3 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java @@ -1,6 +1,5 @@ package au.csiro.pathling.views; -import com.google.gson.annotations.SerializedName; import java.util.List; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @@ -8,26 +7,30 @@ import lombok.EqualsAndHashCode; /** - * A 'forEach' expression unnests a new row for each item in the specified FHIRPath expression, and - * users will select columns in the nested select. This differs from the 'from' expression above - * because it creates a new row for each item in the matched collection, unrolling that part of the - * resource. + * Same as from, but unnests a new row for each item in the collection. * * @author John Grimes + * @see ViewDefinition.select.forEach */ @Data @EqualsAndHashCode(callSuper = false) public class ForEachSelection extends NestedSelectClause { /** - * The FHIRPath expression to unnest. + * Same as from, but unnests a new row for each item in the collection. + * + * @see ViewDefinition.select.forEach */ @NotNull - @SerializedName("forEach") - String expression; + String path; /** - * The nested select clauses. + * Nested select relative to the {@link #path}. + * + * @see ViewDefinition.select.select */ @NotNull @Size(min = 1) diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FromSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/FromSelection.java index c1f4a071e1..030ab2b038 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FromSelection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FromSelection.java @@ -7,26 +7,31 @@ import lombok.EqualsAndHashCode; /** - * A 'from' expression is a convenience to select values relative to some parent FHIRPath. This does - * not unnest or unroll multiple values. If the 'from' results in a FHIRPath collection, that full - * collection is used in the nested select, so the resulting view would have repeated fields rather - * than a separate row per value. + * Creates a scope for selection relative to a parent FHIRPath expression. * * @author John Grimes + * @see ViewDefinition.select.from */ @Data @EqualsAndHashCode(callSuper = false) public class FromSelection extends NestedSelectClause { /** - * The FHIRPath expression for the parent path to select from. + * Creates a scope for selection relative to a parent FHIRPath expression. + * + * @see ViewDefinition.select.from */ @NotNull @SerializedName("from") - String expression; + String path; /** - * The nested select clauses. + * Nested select relative to the {@link #path}. + * + * @see ViewDefinition.select.select */ @NotNull List select; diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/NestedSelectClause.java b/fhirpath/src/main/java/au/csiro/pathling/views/NestedSelectClause.java index 69f21d2f08..7c54ed42dd 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/NestedSelectClause.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/NestedSelectClause.java @@ -4,7 +4,7 @@ public abstract class NestedSelectClause extends SelectClause { - abstract String getExpression(); + abstract String getPath(); abstract List getSelect(); diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/SelectClause.java b/fhirpath/src/main/java/au/csiro/pathling/views/SelectClause.java index ed9192402f..998b0b2de6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/SelectClause.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/SelectClause.java @@ -1,17 +1,12 @@ package au.csiro.pathling.views; /** - * The select stanza defines the actual content of the view itself. This stanza is a list where each - * item in is one of: - *

    - *
  • a structure with the column name, expression, and optional description,
  • - *
  • a 'from' structure indicating a relative path to pull fields from in a nested select, or
  • - *
  • a 'forEach' structure, unrolling the specified path and creating a new row for each item.
  • - *
- * See the comments below for details on the semantics. + * Defines the content of a column within the view. * * @author John Grimes + * @see ViewDefinition.select */ public abstract class SelectClause { - + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/SelectClauseTypeAdapter.java b/fhirpath/src/main/java/au/csiro/pathling/views/SelectClauseTypeAdapter.java index 17389ceb9b..3f63dee39a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/SelectClauseTypeAdapter.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/SelectClauseTypeAdapter.java @@ -37,15 +37,19 @@ public SelectClause read(final JsonReader in) throws IOException { final JsonElement jsonElement = Streams.parse(in); final JsonObject jsonObject = jsonElement.getAsJsonObject(); - if (jsonObject.has("name")) { + if (jsonObject.has("path")) { return gson.fromJson(jsonObject, DirectSelection.class); } else if (jsonObject.has("from")) { return gson.fromJson(jsonObject, FromSelection.class); } else if (jsonObject.has("forEach")) { return gson.fromJson(jsonObject, ForEachSelection.class); + } else if (jsonObject.has("forEachOrNull")) { + return gson.fromJson(jsonObject, ForEachOrNullSelection.class); + } else if (jsonObject.has("union")) { + return gson.fromJson(jsonObject, UnionSelection.class); } else { throw new JsonParseException( - "Select clause must contain either 'name', 'from', or 'forEach'"); + "Select clause must contain either 'path', 'from', 'forEach', 'forEachOrNull' or 'union'"); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/UnionSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/UnionSelection.java new file mode 100644 index 0000000000..f1e4c72322 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/UnionSelection.java @@ -0,0 +1,45 @@ +package au.csiro.pathling.views; + +import java.util.List; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * The result of each selection within the union will be combined according to the semantics of the + * union operator in FHIRPath. The results of the selected expressions must be of the same type, or + * able to be implicitly converted to a common type according to the FHIRPath data type conversion + * rules. + * + * @author John Grimes + * @see ViewDefinition.select.union + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class UnionSelection extends NestedSelectClause { + + /** + * The result of each selection within the union will be combined according to the semantics of + * the union operator in FHIRPath. The results of the selected expressions must be of the same + * type, or able to be implicitly converted to a common type according to the FHIRPath data type + * conversion rules. + * + * @see ViewDefinition.select.union + */ + @NotNull + String path; + + /** + * Nested select relative to the {@link #path}. + * + * @see ViewDefinition.select.select + */ + @NotNull + @Size(min = 1) + List select; + +} diff --git a/fhirpath/src/test/java/au/csiro/pathling/UnitTestDependencies.java b/fhirpath/src/test/java/au/csiro/pathling/UnitTestDependencies.java index fddbd66db1..de9a7d97e0 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/UnitTestDependencies.java +++ b/fhirpath/src/test/java/au/csiro/pathling/UnitTestDependencies.java @@ -29,6 +29,7 @@ import au.csiro.pathling.views.SelectClauseTypeAdapterFactory; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import javax.annotation.Nonnull; @@ -93,14 +94,14 @@ static SparkSession sparkSession( @Bean @ConditionalOnMissingBean @Nonnull - static FhirContext fhirContext() { + public static FhirContext fhirContext() { return FhirContext.forR4(); } @Bean @ConditionalOnMissingBean @Nonnull - static IParser jsonParser(@Nonnull final FhirContext fhirContext) { + public static IParser jsonParser(@Nonnull final FhirContext fhirContext) { return fhirContext.newJsonParser(); } diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java index 334e8ffaa8..9f91dbbea2 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -1,27 +1,42 @@ package au.csiro.pathling.views; +import static au.csiro.pathling.UnitTestDependencies.fhirContext; +import static au.csiro.pathling.UnitTestDependencies.jsonParser; import static au.csiro.pathling.test.assertions.Assertions.assertThat; +import static au.csiro.pathling.validation.ValidationUtils.ensureValid; import au.csiro.pathling.encoders.FhirEncoders; -import au.csiro.pathling.io.Database; import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import au.csiro.pathling.test.SpringBootUnitTest; import ca.uhn.fhir.context.FhirContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; import java.util.Optional; import java.util.stream.Stream; import javax.annotation.Nonnull; import lombok.Value; +import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; -import org.junit.jupiter.api.BeforeEach; +import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Enumerations.ResourceType; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.params.ParameterizedTest; @@ -36,6 +51,8 @@ @TestInstance(Lifecycle.PER_CLASS) class FhirViewTest { + static Path tempDir; + @Autowired SparkSession spark; @@ -51,58 +68,144 @@ class FhirViewTest { @MockBean TerminologyServiceFactory terminologyServiceFactory; - FhirViewExecutor executor; - - static final Path TEST_DATA_PATH = Path.of( - "src/test/resources/test-data/views").toAbsolutePath().normalize(); - - @BeforeEach - void setUp() { - final DataSource dataSource = Database.forFileSystem(spark, fhirEncoders, - TEST_DATA_PATH.toUri().toString(), false); - executor = new FhirViewExecutor(fhirContext, spark, dataSource, - Optional.of(terminologyServiceFactory)); + @BeforeAll + static void beforeAll() throws IOException { + tempDir = Files.createTempDirectory("pathling-fhir-view-test"); } @Nonnull Stream requests() throws IOException { + final ObjectMapper mapper = new ObjectMapper(); final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - final Resource[] resources = resolver.getResources("classpath:requests/views/*.json"); - return Stream.of(resources).map(resource -> { + final Resource[] resources = resolver.getResources("classpath:sql-on-fhir/input/tests/*.json"); + return Stream.of(resources) + // Get each test file. + .map(resource -> { try { return resource.getFile(); } catch (final IOException e) { throw new RuntimeException(e); } - }).map(File::toPath) + }) + // Map it to a path. + .map(File::toPath) + // Parse the JSON. .map(path -> { try { - final FhirView view = gson.fromJson(new FileReader(path.toFile()), FhirView.class); - return new TestParameters(view, path); - } catch (final FileNotFoundException e) { + return mapper.readTree(new FileReader(path.toFile())); + } catch (final IOException e) { throw new RuntimeException(e); } + }) + // Create a TestParameters object for each test within the file. + .flatMap(testDefinition -> { + final DataSource sourceData = getDataSource(testDefinition); + return toTestParameters(testDefinition, sourceData).stream(); }); } + DataSource getDataSource(@Nonnull final JsonNode testDefinition) { + try { + // Create a parent directory based upon the test name. + final JsonNode resources = testDefinition.get("resource"); + final Path directory = getTempDir(testDefinition); + final FhirViewTestDataSource result = new FhirViewTestDataSource(); + + for (final Iterator it = resources.elements(); it.hasNext(); ) { + final JsonNode resource = it.next(); + + // Append each resource to a file named after its type. + final String resourceType = resource.get("resourceType").asText(); + final Path ndjsonPath = directory.resolve(resourceType + ".ndjson"); + Files.write(ndjsonPath, + (resource + "\n").getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, + StandardOpenOption.APPEND); + + // Read the NDJSON file into a Spark dataset and add it to the data source. + final Dataset jsonStrings = spark.read().text(ndjsonPath.toString()) + .as(Encoders.STRING()); + final ExpressionEncoder encoder = fhirEncoders.of(resourceType); + final Dataset dataset = jsonStrings.map( + (MapFunction) (json) -> jsonParser(fhirContext()) + .parseResource(json), encoder).toDF(); + result.put(ResourceType.fromCode(resourceType), dataset); + } + + return result; + + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + List toTestParameters(@Nonnull final JsonNode testDefinition, + @Nonnull final DataSource sourceData) { + try { + final JsonNode views = testDefinition.get("views"); + final List result = new ArrayList<>(); + + for (final Iterator it = views.elements(); it.hasNext(); ) { + final JsonNode view = it.next(); + + // Serialize a FhirView object from the view definition in the test. + final FhirView fhirView = gson.fromJson(view.get("view").toString(), FhirView.class); + ensureValid(fhirView, "View is not valid"); + + // Write the expected JSON to a file, named after the view. + final Path directory = getTempDir(testDefinition); + final String expectedFileName = + view.get("title").asText().replaceAll("\\W+", "_") + ".json"; + final Path expectedPath = directory.resolve(expectedFileName); + for (final Iterator rowIt = view.get("result").elements(); rowIt.hasNext(); ) { + final JsonNode row = rowIt.next(); + Files.write(expectedPath, (row + "\n").getBytes(StandardCharsets.UTF_8), + StandardOpenOption.CREATE, StandardOpenOption.APPEND); + } + + final String testName = + testDefinition.get("name").asText() + " - " + view.get("title").asText(); + result.add(new TestParameters(testName, sourceData, fhirView, expectedPath)); + } + + return result; + + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + @Nonnull + private static Path getTempDir(final @Nonnull JsonNode testDefinition) throws IOException { + final String directoryName = testDefinition.get("name").asText().replaceAll("\\W+", "_"); + final Path directory = tempDir.resolve(directoryName); + try { + return Files.createDirectory(directory); + } catch (final FileAlreadyExistsException ignored) { + return directory; + } + } + @ParameterizedTest @MethodSource("requests") void test(@Nonnull final TestParameters parameters) { + final FhirViewExecutor executor = new FhirViewExecutor(fhirContext, spark, + parameters.getSourceData(), Optional.ofNullable(terminologyServiceFactory)); final Dataset result = executor.buildQuery(parameters.getView()); - assertThat(result) - .hasRows(spark, "results/views/" + - parameters.getRequestFile().getFileName().toString().replace(".json", ".tsv"), true); + final Dataset expectedResult = spark.read().json(parameters.getExpectedJson().toString()); + assertThat(result).hasRows(expectedResult); } @Value static class TestParameters { + String title; + DataSource sourceData; FhirView view; - Path requestFile; + Path expectedJson; @Override public String toString() { - return view.getName(); + return getTitle(); } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTestDataSource.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTestDataSource.java new file mode 100644 index 0000000000..93e936e408 --- /dev/null +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTestDataSource.java @@ -0,0 +1,63 @@ +/* + * Copyright 2023 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.views; + +import au.csiro.pathling.io.source.DataSource; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.extern.slf4j.Slf4j; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; +import org.hl7.fhir.r4.model.Enumerations.ResourceType; + +/** + * A class for making FHIR data available for the view tests. + * + * @author John Grimes + */ +@Slf4j +public class FhirViewTestDataSource implements DataSource { + + private static final Map> resourceTypeToDataset = new HashMap<>(); + + public void put(@Nonnull final ResourceType resourceType, @Nonnull final Dataset dataset) { + resourceTypeToDataset.put(resourceType, dataset); + } + + @Nonnull + @Override + public Dataset read(@Nullable final ResourceType resourceType) { + return resourceTypeToDataset.get(resourceType); + } + + @Nonnull + @Override + public Dataset read(@Nullable final String resourceCode) { + return resourceTypeToDataset.get(ResourceType.fromCode(resourceCode)); + } + + @Nonnull + @Override + public Set getResourceTypes() { + return resourceTypeToDataset.keySet(); + } + +} diff --git a/fhirpath/src/test/resources/requests/views/elementRelatedUnnesting.json b/fhirpath/src/test/resources/requests/views/elementRelatedUnnesting.json deleted file mode 100644 index e5ee6f4cb8..0000000000 --- a/fhirpath/src/test/resources/requests/views/elementRelatedUnnesting.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "Element-related unnesting", - "resource": "Patient", - "select": [ - { - "name": "id", - "expr": "id" - }, - { - "forEach": "name", - "select": [ - { - "name": "family_name", - "expr": "family" - }, - { - "forEach": "given", - "select": [ - { - "name": "given_name", - "expr": "$this" - } - ] - } - ] - } - ] -} diff --git a/fhirpath/src/test/resources/requests/views/flattenedBloodPressure.json b/fhirpath/src/test/resources/requests/views/flattenedBloodPressure.json deleted file mode 100644 index 912543178c..0000000000 --- a/fhirpath/src/test/resources/requests/views/flattenedBloodPressure.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "name": "Flattened blood pressure", - "resource": "Observation", - "constants": [ - { - "name": "sbp_component", - "value": "component.where(code.coding.exists(system='http://loinc.org' and code='8480-6')).first()" - }, - { - "name": "dbp_component", - "value": "component.where(code.coding.exists(system='http://loinc.org' and code='8462-4')).first()" - } - ], - "select": [ - { - "name": "id", - "expr": "id" - }, - { - "name": "patient_id", - "expr": "subject.getId()" - }, - { - "name": "effective_date_time", - "expr": "effective.ofType(dateTime)" - }, - { - "from": "%sbp_component", - "select": [ - { - "name": "sbp_quantity_system", - "expr": "value.ofType(Quantity).system" - }, - { - "name": "sbp_quantity_code", - "expr": "value.ofType(Quantity).code" - }, - { - "name": "sbp_quantity_display", - "expr": "value.ofType(Quantity).unit" - }, - { - "name": "sbp_quantity_value", - "expr": "value.ofType(Quantity).value" - } - ] - }, - { - "from": "%dbp_component", - "select": [ - { - "name": "dbp_quantity_system", - "expr": "value.ofType(Quantity).system" - }, - { - "name": "dbp_quantity_code", - "expr": "value.ofType(Quantity).code" - }, - { - "name": "dbp_quantity_display", - "expr": "value.ofType(Quantity).unit" - }, - { - "name": "dbp_quantity_value", - "expr": "value.ofType(Quantity).value" - } - ] - } - ], - "where": [ - { - "expr": "code.coding.exists(system='http://loinc.org' and code='85354-9')" - }, - { - "expr": "%sbp_component.dataAbsentReason.empty()" - }, - { - "expr": "%dbp_component.dataAbsentReason.empty()" - } - ] -} - diff --git a/fhirpath/src/test/resources/requests/views/forEachWithinFrom.json b/fhirpath/src/test/resources/requests/views/forEachWithinFrom.json deleted file mode 100644 index f63af31e1c..0000000000 --- a/fhirpath/src/test/resources/requests/views/forEachWithinFrom.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "forEach within from", - "resource": "Patient", - "select": [ - { - "name": "id", - "expr": "id" - }, - { - "from": "name", - "select": [ - { - "forEach": "prefix", - "select": [ - { - "name": "name_prefix", - "expr": "$this" - } - ] - }, - { - "forEach": "given", - "select": [ - { - "name": "given_name", - "expr": "$this" - } - ] - } - ] - } - ] -} diff --git a/fhirpath/src/test/resources/requests/views/nestedSingular.json b/fhirpath/src/test/resources/requests/views/nestedSingular.json deleted file mode 100644 index 095854da20..0000000000 --- a/fhirpath/src/test/resources/requests/views/nestedSingular.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "Nested singular elements", - "resource": "Patient", - "select": [ - { - "name": "id", - "expr": "id" - }, - { - "forEach": "name", - "select": [ - { - "name": "name_use", - "expr": "use" - }, - { - "name": "family_name", - "expr": "family" - } - ] - } - ] -} diff --git a/fhirpath/src/test/resources/requests/views/resourceRelatedUnnesting.json b/fhirpath/src/test/resources/requests/views/resourceRelatedUnnesting.json deleted file mode 100644 index 5102d3381d..0000000000 --- a/fhirpath/src/test/resources/requests/views/resourceRelatedUnnesting.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "Resource-related unnesting", - "resource": "Patient", - "select": [ - { - "name": "id", - "expr": "id" - }, - { - "forEach": "name", - "select": [ - { - "forEach": "prefix", - "select": [ - { - "name": "name_prefix", - "expr": "$this" - } - ] - }, - { - "name": "family_name", - "expr": "family" - } - ] - }, - { - "forEach": "maritalStatus.coding", - "select": [ - { - "name": "marital_status_system", - "expr": "system" - }, - { - "name": "marital_status_code", - "expr": "code" - } - ] - } - ] -} diff --git a/fhirpath/src/test/resources/requests/views/singularNoUnnesting.json b/fhirpath/src/test/resources/requests/views/singularNoUnnesting.json deleted file mode 100644 index 26b60412a7..0000000000 --- a/fhirpath/src/test/resources/requests/views/singularNoUnnesting.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "Singular elements, no unnesting", - "resource": "Patient", - "select": [ - { - "name": "id", - "expr": "id" - }, - { - "name": "gender", - "expr": "gender" - }, - { - "name": "birth_date", - "expr": "birthDate" - } - ] -} diff --git a/fhirpath/src/test/resources/requests/views/unnestEmptyCollection.json b/fhirpath/src/test/resources/requests/views/unnestEmptyCollection.json deleted file mode 100644 index b0c26a251e..0000000000 --- a/fhirpath/src/test/resources/requests/views/unnestEmptyCollection.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "Unnest empty collection", - "resource": "Patient", - "select": [ - { - "name": "id", - "expr": "id" - }, - { - "forEach": "photo", - "select": [ - { - "name": "photo_title", - "expr": "title" - } - ] - } - ] -} diff --git a/fhirpath/src/test/resources/results/views/elementRelatedUnnesting.tsv b/fhirpath/src/test/resources/results/views/elementRelatedUnnesting.tsv deleted file mode 100644 index ce66c21c14..0000000000 --- a/fhirpath/src/test/resources/results/views/elementRelatedUnnesting.tsv +++ /dev/null @@ -1,6 +0,0 @@ -id family_name given_name -1 Wuckert Karina -1 Oberbrunner Karina -2 Towne Guy -2 Cleveland Maponos -2 Cleveland Wilburg diff --git a/fhirpath/src/test/resources/results/views/flattenedBloodPressure.tsv b/fhirpath/src/test/resources/results/views/flattenedBloodPressure.tsv deleted file mode 100644 index 55d1575585..0000000000 --- a/fhirpath/src/test/resources/results/views/flattenedBloodPressure.tsv +++ /dev/null @@ -1,2 +0,0 @@ -id patient_id effective_date_time sbp_quantity_system sbp_quantity_code sbp_quantity_display sbp_quantity_value dbp_quantity_system dbp_quantity_code dbp_quantity_display dbp_quantity_value -blood-pressure example 1999-07-02 http://unitsofmeasure.org mm[Hg] mmHg 109 http://unitsofmeasure.org mm[Hg] mmHg 44 diff --git a/fhirpath/src/test/resources/results/views/forEachWithinFrom.tsv b/fhirpath/src/test/resources/results/views/forEachWithinFrom.tsv deleted file mode 100644 index 3b88f41003..0000000000 --- a/fhirpath/src/test/resources/results/views/forEachWithinFrom.tsv +++ /dev/null @@ -1,11 +0,0 @@ -id name_prefix given_name -1 Mrs. Karina -1 Mrs. Karina -1 Miss. Karina -1 Miss. Karina -2 Mr. Maponos -2 Mr. Wilburg -2 Mr. Guy -2 Prof. Maponos -2 Prof. Wilburg -2 Prof. Guy diff --git a/fhirpath/src/test/resources/results/views/nestedSingular.tsv b/fhirpath/src/test/resources/results/views/nestedSingular.tsv deleted file mode 100644 index acfa4dcb0e..0000000000 --- a/fhirpath/src/test/resources/results/views/nestedSingular.tsv +++ /dev/null @@ -1,5 +0,0 @@ -id name_use family_name -1 maiden Wuckert -1 official Oberbrunner -2 nickname Cleveland -2 official Towne diff --git a/fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.tsv b/fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.tsv deleted file mode 100644 index 157a64ee45..0000000000 --- a/fhirpath/src/test/resources/results/views/resourceRelatedUnnesting.tsv +++ /dev/null @@ -1,7 +0,0 @@ -id name_prefix family_name marital_status_system marital_status_code -1 Miss. Wuckert http://terminology.hl7.org/CodeSystem/v3-MaritalStatus M -1 Miss. Wuckert http://snomed.info/sct 87915002 -1 Mrs. Oberbrunner http://terminology.hl7.org/CodeSystem/v3-MaritalStatus M -1 Mrs. Oberbrunner http://snomed.info/sct 87915002 -2 Mr. Towne -2 Prof. Cleveland diff --git a/fhirpath/src/test/resources/results/views/singularNoUnnesting.tsv b/fhirpath/src/test/resources/results/views/singularNoUnnesting.tsv deleted file mode 100644 index 930d70bb76..0000000000 --- a/fhirpath/src/test/resources/results/views/singularNoUnnesting.tsv +++ /dev/null @@ -1,3 +0,0 @@ -id gender birth_date -1 female 1959-09-27 -2 male 1983-09-06 diff --git a/fhirpath/src/test/resources/results/views/unnestEmptyCollection.tsv b/fhirpath/src/test/resources/results/views/unnestEmptyCollection.tsv deleted file mode 100644 index 260859927b..0000000000 --- a/fhirpath/src/test/resources/results/views/unnestEmptyCollection.tsv +++ /dev/null @@ -1,3 +0,0 @@ -id photo_title -1 -2 diff --git a/fhirpath/src/test/resources/test-data/views/Observation.parquet/_delta_log/00000000000000000000.json b/fhirpath/src/test/resources/test-data/views/Observation.parquet/_delta_log/00000000000000000000.json deleted file mode 100644 index a040d53372..0000000000 --- a/fhirpath/src/test/resources/test-data/views/Observation.parquet/_delta_log/00000000000000000000.json +++ /dev/null @@ -1,4 +0,0 @@ -{"commitInfo":{"timestamp":1690767715961,"operation":"WRITE","operationParameters":{"mode":"Overwrite","partitionBy":"[]"},"isolationLevel":"Serializable","isBlindAppend":false,"operationMetrics":{"numFiles":"1","numOutputRows":"1","numOutputBytes":"154746"},"engineInfo":"Apache-Spark/3.3.2 Delta-Lake/2.3.0","txnId":"5f04ae46-b2c1-456a-8b19-a1a24fc51fb5"}} -{"protocol":{"minReaderVersion":1,"minWriterVersion":2}} -{"metaData":{"id":"f0b52a85-c0e9-48a3-939a-fb0348c124da","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"id_versioned\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"meta\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"versionId\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"versionId_versioned\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"lastUpdated\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}},{\"name\":\"source\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"profile\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"security\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"tag\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"implicitRules\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"language\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"status\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"div\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"identifier\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"use\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"assigner\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"basedOn\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"partOf\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"status\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"category\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"subject\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"focus\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"encounter\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"effectiveDateTime\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"effectiveInstant\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}},{\"name\":\"effectivePeriod\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"effectiveTiming\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"event\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"repeat\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"boundsDuration\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"boundsPeriod\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"boundsRange\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"low\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"high\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"count\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"countMax\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"duration\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"duration_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"durationMax\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"durationMax_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"durationUnit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"frequency\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"frequencyMax\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"periodMax\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"periodMax_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"periodUnit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"dayOfWeek\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"timeOfDay\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"when\",\"type\":{\"type\":\"array\",\"elementType\":\"string\",\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"offset\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"issued\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}},{\"name\":\"performer\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueBoolean\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valueCodeableConcept\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueDateTime\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valueInteger\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valuePeriod\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueQuantity\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueRange\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"low\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"high\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueRatio\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"numerator\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"denominator\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueSampledData\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"origin\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"factor\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"factor_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"lowerLimit\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"lowerLimit_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"upperLimit\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"upperLimit_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"dimensions\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"data\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueString\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valueTime\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"dataAbsentReason\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"interpretation\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"note\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"authorReference\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"authorString\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"time\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"bodySite\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"method\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"specimen\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"device\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"referenceRange\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"low\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"high\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"appliesTo\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"age\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"low\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"high\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"hasMember\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"derivedFrom\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"reference\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"component\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueBoolean\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valueCodeableConcept\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueDateTime\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valueInteger\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valuePeriod\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"start\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueQuantity\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueRange\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"low\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"high\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueRatio\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"numerator\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"denominator\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueSampledData\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"origin\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"period\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"period_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"factor\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"factor_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"lowerLimit\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"lowerLimit_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"upperLimit\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"upperLimit_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"dimensions\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"data\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"valueString\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"valueTime\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"dataAbsentReason\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"interpretation\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"referenceRange\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"low\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"high\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"type\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"appliesTo\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"coding\",\"type\":{\"type\":\"array\",\"elementType\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"version\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"display\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"userSelected\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}},{\"name\":\"age\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"low\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"high\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value\",\"type\":\"decimal(32,6)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"value_scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comparator\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"unit\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"system\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_value_canonicalized\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"value\",\"type\":\"decimal(38,0)\",\"nullable\":true,\"metadata\":{}},{\"name\":\"scale\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"_code_canonicalized\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}},{\"name\":\"text\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}}]},\"containsNull\":true},\"nullable\":true,\"metadata\":{}}]}","partitionColumns":[],"configuration":{},"createdTime":1690767712665}} -{"add":{"path":"part-00000-a35c6b50-884f-41b2-9f1d-f94a57684570-c000.snappy.parquet","partitionValues":{},"size":154746,"modificationTime":1690767715371,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"id\":\"blood-pressure\",\"id_versioned\":\"Observation/blood-pressure\",\"meta\":{},\"text\":{\"status\":\"generated\",\"div\":\"
S#5RY{hz;B({7%{@=z+4%#v)KH@PX#)p?eQurxA?t9*?zFp1|`Q;;6f`D`O?X6=|R@G z+-A46_C7P5BoHoMs}d5UfCyGG9|KcZ!1(8aK^EDT*iQeo=l2V>I)(N$?=o7aP?u4D zj?w^@+pe|k{r%7{nZp41Wt*`5Bv}@KfpV1s#q01ZPN3TJ4qL~iwy*!WZ|D+1mlrRc zd|J&=agoK6>2z(iZ_9TV_-(I4V-+1GVE4StO^WDqVXBMu{%v%2* zjlh1l!wXEVtxASnX!4mWv({%i>;;Z|<>qC2hhMhdp1IPoK651iS+`E+`~2J8@Q~Bx zD|9=yXV_f@ZdU=elI+KBvS&H6AItT6iVB?2c-$UuhW$$Cjmj$J-8b0_9ZsjKAUDH) zL*cfY?D-CFuB#x!zLxxvIlK*#Ay4T@!75Osk z*IQT4eSCSI;>ykQ1Intat>bU0UGUID=~*7X-;A2e#O6hnQf$&AMZW{a%Q!R}@(6mpp9AT{^E>=SK4Q@fI|LJl zdqLoe(aW=;dUHMA?F-(1%Y!bz!)@Q<$}RBOGkt}Qg4J)ov0;9OebW{Hb<2A^Zl_YP z+2ee_;&ywUxXFIae`URBwLg~Uc+3SgvD`Lw*T*yciZ|a?0OrlzzJi3aTc6u>{fbp9 zR;|3<<5ae6_xY9lbWfJ=zQ--;Z`qr^ctxGna{DDevDmFQS$tP4OullfwLdxCXYHSV zP3B7L&n@fU{!b{Pvf%I6Y+CKQ#qI+DsDo^~qrho*x*V4*KKoW{({2=cvo@8UW*e-qqyx)xcqr`hudw>&U3k)UZue9 zDZKT*WQY6CFWxc7n(8acdQ{2wUo+2=dEd^p_kuer1^#qpnOP z+dp673n`Nb5qxKd%zTGy(Vg&SJx=zK)I+dAw z%x=5NcKVfnOjxx@1EfnbG0s>lb4la|R??T9R}YeodFE_rqOop%HeA%8dEa4MW}E)% zj{WmAPlhh`(skH>TZtoMAXb~%$aqy}*k%UK504SJAOHG`&#^8T8pfsT)P{jW8c2ZW z>a4Z~tL=-Q`%}k!jTnBo>vHW$>qV2dKzE%18mQg09J|R-l!xt=5Vo~pc)HeQ#Gp^6 zZN2S>uk3FVwRwl2%?42BVo+u)>4MUIqzeLclB=nF@Ndy_2_07qI%NR=nL`A$}pkrcZ*lGk&P;rCp4%^>afA=bz^g!h-q;~8pX~#|`p=q{v*>->StKSu~ymh0R1+rVTE?+Iq%th%a z{zMe}OkcV8O9KnnH#G`A&aJ~!6Kgr zk43XY6hlV&IU2h8`)ewCRcD_6}vva%i0L_3Kne9n^ zX<){k=!S^mPqX^-dC(lqBD@*)UzoKZ)xvqTW6E%047!2Qv5{fk^XfsP)-h;o^D(Ju zng%fqqV3&*%~!L&%Xa4d?cfWQ6|mPP83vd0Y`h3bxZ~#k;g%gjTKnubKN0S!tMDlj zH=qC8#J^wzFauLJ04LK8z)z5kE6KR&>DZ@^KRY)QzBdAz%Ql7CICm91`kTM}WGw+q zrrQ162HfGL0+s=wWe#X=kehtE^j_NwzwUbmcJVPKucDh80n>WX2MHut!LklFZ&QhE zwg!?)tF6E`{jZnFhEJCHJwoPMlKJ1^|H;02-1d&2Kd_y5sqoVVm&14W{X2fJKt*|b;Zo2--HTlWU;6nxIr)l%d?ZOeor(qeGFz&xW!KJ^ zlU3-2SCPm=KL|07W3heEcDL=9Un_ndHUzV(SRqu!4VFu+m%roH-@LNb_x2r^T)N!y zv2`zOum0yftI+D#!l$6moP3ph*U56H+%H)D2D-=jEu@l?ZEu6E?O*!oFHg=)($dt$`jfzd5^PCJ zQ{cP{^61-y!^EWX10(*ZL0Ffj!oe99@@Ent#`Gu9-;(Izeu+c)W}+xDmHUl)80@`( zomeNzngvLOBzi(!;t)leI&YMn&I4Yk$+>uRlJ;L?Hic{%@t=2;Q06cSf<_k$haTzw z((;gpgky;Ge`ya%wHi;JV2CBEwb&6vOH+fA!Ygo*dcM%oR87*J1?^VGc#=@;IW)51 zVqGn9hdxVaX{xd3G#)6_rPA3#!70MS^R-ijmZmaM`&rUC@}EFFUpoj$5H&3Lc_Bai zNB4*JNMwBT*SN<3rs+)6#W#91J;(GM({tjOc9{*wY&d4aF&j>-%Qa@)Fyn?9H_W&( zYjGo4j~wDpn5hRXuY^bnF>5DakbB;`wd4a66v%}E+LQ28D*0L@)R!bnQ~y28Hzrzc zAu_5wGY2MNp9g<}t;tPC>ObV`1YH98ctJBPJWAfM!UOmpEm<-Gd5U~rp#DdD3X+$G z{a_%{ZvwzP`dI+@i~4;4e{({#pcB<_2^>T!>W>S8Q+OZReAS+FtjNy9RR@|p>&p7v%=P=)#vz1mfW_==2r7Ht!8578uq}*8r!4l1Krm# zW0$UF90O}@e(izlS!&G<%oV3@u(fH-d)Bchrq|i3wdBeSTaJoxD#P}WD)!vO9I=$##6ai^Au0zV$H{S>c<&z?I)Nx^a=JrU9pwP zLxNoQw2&^yb#RrM7LSJnx$YSu9b_%y*2BGPnmhusmbc+qE{_OuL#dE1$PLd4>4Mx) zCZr2;!zVfKs314N5iyz$M+Ld*Q$o5RH&qDfg531HkS@qgaP$qs7v!cFgmjR#{;#o$ z%OiqZ|7k00v&RIvy_%<&eum2fg50!|r%!^caqammPoD-^8*TK|aJl|PE*}@U)^zTGiyk%1@|U4Sn?^D@U7 z6Xd{=*ji;CH;qrhWH+`L_PYUt@xZPc|dBr!lJS510y*&N2 zAa{L}r&q%j@fyykAn*AWPoEa#!);bJAg}*6mrn?CXFE@?euc|pg50*B1PDF`yfxR`2m;51bJ^4PcMFz z%e{hJe~70a6XfeK2>e#F1XWZud`>nS7)%41r^Yl@Wx2fra1LQ7P^^eoP;16{lvD#|2 zA4Y%4nfDBGc^YKRBYJ+Vjmv|Ayz4YiKPAWq|Bt6vO>ucdkX!!F)6WQU_ZgmE^A9eM3G&`)o?iSm?&LCDjm(~J|16iwYB=Tq$lA!O`Nbp_e@+W>cP&q^ z`y7{#3-WeE21vUf;;&6N23NMV?*_OOskG z9TViXFY)xUW-bp2a?6)_`dLBl+r`tHzQW~Gf_x~z(`#Sm@-ac~*v-=`zsluNLEhWK z(@Va_{)!*gvm>{&ussNuI$xcXn&fg`ZP|1=ERf@$i(Jk} zwklp9T*j?vKU|*#`w0}k!?q-3mIdnbcWTcg&|6t*5T0!w@LueO-6ancIvsw+Nnz*t zJVoAYMb!pKFDmqUa$If&PHLUEDHAljJ~=hn?eh6;Ni@erid)H73j93Fe4mnCbJL zf})i}c-`Es4tJ4I(s_l7*X3~rmldmFF4*0xwE|3;qZDYZ5v(!a;q$q23&7~f)T#@- zN{-@H3Rr8yOp+Hv#*4BXKE-)YLA1(>5q2KLA^$x&Gl_}?(EA@Lcs7KWT#q+uI0*9R zbI6B4RSZbM&T(!P;v!#B)}s(DV|CKGIiBny_-Z}4lYsKGfFGlaE`Y}2DJt;C*k;fT z-=gH?Ksj7nl?~v2_q+17hKwEG687tz1<-B_{G@NVlqKGqNJHl^z)IrZd^w5&*IZ>Q z1mRHjqER5IQ=R8k3KfUnc5Mi-#aUn_r*A`%*WrgTgYDnsXKagYm)n4p2DF86=<+Ss zevtf|j5w0WKmdP%QHjH?(98?7J^2tJ9Da{i43o*^m}4;^MBEg6W+>`qk5KSzM}eon z1q`ky6({kp#c&k4pp?b736z3=Yf8-()OqV8q`QAahr#=xMbrQZRolfBV>5KNp}Vs= zZqE}Jo@tZwT)BA{Sh2B5%*D_{o5y^V?{aJ-PD@(Se5cyht`5mA8E~@1MnixjM zOWChlE~4Q=nkwiA&3=K?vHhN$_bJL_F+{lqehAd}B0i?M5IbIIPnt8pKsMxDYC%B}L^d)kz3>{!#jtSWfhom>S^6ay zCbl?WolJ2;YDDd}fCURSQ`LYo7r!_rs>d-HM}*<^wjVi3EC{-h1_=-_Z6 zOo0^dCYaXxL*-e*f0ptU6~dq-d_MlOmXGwc3!N|+EFi1kK3dVquu$y~C!e(Q-QxGc zG+RtxfUY7;`oS@lkj%Gc`C!_;8SdzTY4SO~-n7_7Rz?cF@KOM0{ZZCl&hgSF7kK<3 zUqV3Dvh}5fAp*jsjv{}a$GcfuY>jDU3**ZoiV*B($z--7`klC{40_Ga@;J9|ae?0F zxbf$~o2R@`E8isGo+GRLq&|FwN;ZipG5G)~qf^=H%2r~{wwSH()4^?&78>-n1(?l6 zC-LPd?S;d!e*h93DuW z{(?M*?=B@jYi5_6m`iY`_=O-OE;ns zwGX@SV=lHPPyFwwHD%nIR+pTadPkx+;VG~y(W}f94mj7FOe4|p#=zRCDNSJQh9pZ` z@jT7k523l)M{zoPYo7exl)E8^RPWPReVvD}`g#rFsk`yfB?xA(j9L9-;@cw>v;HC@ zI%*%^G0^&pBt-)VI!!^383Lti#>Cr*n#W|s1Bh7nLo9kb#z^A1kxZ$5IK|X#pD(F7 zfS~OZv~&T43~UaOKy}{_GUU<)67n%bK0=WP0GT#4icCH^3|r{~GE{L7si0F7s%c>q zD7n6y7D}4cAY64f8geokTu@a{%6Kh^*GlmYz*Y1SORBHs0~brm9YnBR3RbZw3RqAD zS1gk71`+Q##Tx=Vv4VrDA#~ji$)G0?wERbC!N4U%N!nfOzt}|QAl*se5=pC)pO6|2 zP=Ij(fPL7>@{Y@S>oD&jnztFQd1f5|`2tjn9)z}eu|(U7S;uJBqjFaDD?Ma>Fy-O2 zql+_zdpHFVVlKa*Q%Dyh-yx*haX86#n;;Z|<>qDjn`W1-w`Z<&tj}CoxE@)3{_SpfNc~zk!|p0@y9$(a z$dvuqP4+BD_G7tT@;z00w#V)9X4tQE-l(in-hGq3(BUNCvu4TJleTEtK zwd9X1j~Cv~W!P62ZnOJ5ZkN-3MN*smv#BVnEbU)J~mZPnGw z)@K&lRlO)~dX~rU_vB~TR~2qswtllhKg$+NY)^Z<$m73BUsjq1<%P0FM7)=N)tzAn zqvgRim7ABr_NJn2KlIic6u6|*MYfcs7x};lUbKzJnGRdcusdIo=g)U9TmQfo`|a?f zonFg$ull21MP?>n3DpnEtk29^PrpniRh41Ki$JNKBvHKA11ijKgau z!@h}_?>!zjY>M6NalT)1!|ubV)$&-L<1rU_#Bx^d2v>_RSd>JcOvI|64E?d9po;&Zp9gCvg532f~q%jmsaoeA8`Sa`!x7(hb=W;vY8)v(x zkT{gXO&v7Y1nOs})C#O_)QdUOm2J4|S2eYCU1x?}yONWrr>bS&vhmjUQ8Mmi>~m6; z2CE^%uHC{JR4?p%Q2IQ-juRoFZnTrjEMjVlq?K)e&8aRoguMrDf8Z`Vu>|0JU}Nrw>^IxrNu_t{n8|hD%&MZS zHWYQ6LO2VDyDsGoH!->3MORQPk6_}pYu^Y9V{kURW77Yd-LYGUS!xePo4?r|HVTIvhuwRz`#+>t=BMyI_R$`E)jt&-j2wiFZ??%EmMIyn zyqCQ9Fa8Ol9kOexJrqtg+|;YR(kF+sh}b|8cV3|(mPJHV&u0i}g}YNWr|rB# zVrfH`4$3l~0E@Ueg)oiFOg+dnM48%`#a|xXz7;~%zDz3AQKXupROMI3PsO*i5T5cY zC7vndsp-W^A5MTrx7&qK4a-!OKPCRPk5cWqO55!cyEg=Hgdu!;u9Aw@jC}o+ulQ=s zgpvK3o>Z>w^D4et!rYITlN7U;VTNtKqD$faHW{!F0V{us_ODOVlne_<&yrw`xIRr% za2)YkDBdxK7q-yq2~1)$J|<&MBj#a>+5S#V$7q=JfN`d3`#U97tNVyQOi zPYJIE@#=qu^)wV2FG#x~8Lb7;_EWULwM09*=S|!;wkhS_w7|8QsdI5|r2YpMuXTTm zo%KOP9R$=hmb58E#qG%KfOuW0#U?a$tyJ7WgevdH;vP!Z%(pExRHQDDl;Tjj1b+hI z0~EeyMI7*Apz5)v^jXttR%E7TMA&R}JIt!+HlvlHwizXo7b@iqViv2vq@Rq{4uMfi zVXQX3G7hWi`gu}w<13|_s|S*ll%4@}jJ8#AaPSWP(G+)D+p0`B;??DMxYNn)4L;o; zVArs}i6!5QN*x2G-V2{3td2t|U6UnxHLV7{g!l5gKZsu8eiQZTM!oj^0`;m~qm3Y9 zXhp$82U07QYouBkL87xj1m9$g0FnA3Lg?no#|(KXkHX_KP|{PVK>ZOcY4>$R0Ssu% zv9`5PL-x^jUnl8a{&V6{j?cvJjo z18Xx=m#ATc?avEN0T(dvl;{j?s96(e_F+p}_w~`sscVd+u-(^7h3!Ge;z2BI%?;5Z zdE5I4d;?W;LuTql>TW|OK*562#9|yp&IaJjfKJ8$r><%3v!=9JAzNnZO6gTow2h51 zXMlptI)`YODKJcG${-kK2)?P>vrcR6;T<`+ag$o!vrekzssYkK_6}h!PXn&5ZSlrj zX-%oNrcJNQOubsNZRpo{1|I`E&M2N}vt3ZM)RaCb+Fr0(Wd<*sc29~lbj(W0fyXja zFO~MF1|@*jQJ)btIea?opQ+2~JlA0#=tPfffIP_*y#!n?Nuc z$N5z;l1?P1PslJe2y=wO?7Ep(D~HkPLhQNgX0WHWRhUb}p24Z2JzJ3M6p(>E&vLTV z;45lyszg4=I16M8`F>+j2Es7Eh@=ORbni%#?G8)Y;ClAmVQSFLH^@n>G`LlZf3yvBv>> zF&#)5#1@uY2>iGVU-nB<(}yX1;C8rbB_??B8Y46|Aqm_rm7xJiCMd}zza@HS3A24Y$@WvS_V+|3(_NV*Hu?7VNX6(yu`xEaS-29-=q1@Mu!$J022*zRtB#674fCL6u9$F zrgJ!75`oFtup)Tpos!nO5WJ6q?|5(2#vy}C4PnQ7CHxM=KSA-k0AG!;MwJg4V!LFP zeq^Z_L)(}BPs}C?6=O^PM=Ht$lI*4=-LaGCgKW19e-`1pDSY{+n9CqX+44=2{yT?= zAB|Ijp4bWaY?=6fj|{&X;Yh{mLrs9 zI1Uzl3>cR2hYbCM#4bTW~o&5$l6O;$AC44Fc5)N9|y){*1gDjoU-oP5_@e$3#fTcG|;t-C^PbXx&5yT5nyukfY@z|}D3^H)Pq~a-rJfx}!km^u7oQmvT zO9nY1Lze%BIMkS`Y2dJQTIgv@6ufh7i&Q0uM=gHX~pg1)KoDC~Zo2hbh-n zhTV^_1FD|ykE$oX1(jh2-Y==zhnOc-RRJ@Ks`A~b40%FE9!KP=KT)eb9M!Lc`&Sug z;9*JKX$0LvK_?Itk5md1toZeTAAzvP3_@bOaE(9k6xl%NkFblGvVcRxT!sY&(vSZ3>f8E^mrcTvFV4~4Hl?7X${ zAZZZQACh!ChEQFqZU{xp8q|FwQnc5R{W5y-D53r_Cy>EL9ZE4$P_I|(LgCW9*MmWd_yf|A<5} zh!jJVVh||k$yLVdA@sYtL7C?S@=Q^lU5+S@$=}@(*WBfhlrH%lah{qJSheE-rk-t> zs$NEn?Ss5o7?*R_k(`#a<}A&Y8~LThx;87l%ptaG&XUwY%#9jz} z>ks5eML3N3rIT0%r{lm6a=6nn#VArVQ;M!!t@RtP<j`elG71UO3ps$8ts)!(tr@>jVe1==xx{r_Uc^g@2E znb8G`;1`s=GD0sR?57BokA_ANRY`)HddV3_j zI}y9&6tyB?hpyMKt0}0k40;4XnCj5)GMRyLA1KRp}{M?%r67%CM4(fuKH-6QKaDjf!<4>8YOeY; zwUM9I+(yab(OMIEms07MbgW14Rtnw+;A(Xm1Lw_&fcMGZdl9^sf>#%XwPeKhR9z&g z+>NNmDQbUcR9tEm8&$uIGJ+`O|A$ts-pZ^hdLE)k4WfFhq|PY>3Q(Ya01D9{5GZ7e z=$GNjCrE7{QuTTwq|Fd6RFkNFLQ<{~(Z*EeBB1HpM8AyFhB!N>sI|6**Gg^_)!QVE zdJw6RBK1c=(zl9!8RsbC98fjd&gxHeT(pysW>LLe(sByXhbj61poeG{5zzJRVn9Z( z{3EI3GZek%$?(>U*f46Il$35p*xJ8iB^}|g%e6oOCzXnA;)o2e9|77ZK+Q*k^#Xte z1x5La*WvegMW~vOO1ktR)PSlBKxrcYK^G1swXq{I+Bl+}R2BOes}9y!b&7>-Xf+>` zG@M4vsx#DDfT{NNp)o_Xwj(lR^)XVfdnjbhQ&AxGO|IrCNxuMs^{Dzq1C!g`5gG0P z!W~m}`*<|nbmP04k4w4@AXwQysOV^N>Td>R^_o!=$m2f zC!|so|A|yr7o`{iiijQ%u`LeC*fofKlw#KvTSJ12Hv@{T37(f!Z2`E{l;UYL_z-~$ z(Vj#&sYO8+39$&ahzCmO4ZWw?4bMLftL;q61b!>VGX zyc)yeNv*e`REF$9$O%_Wt|s+OPRb-yuO zMTKr$=)t&TNM>3MpOiHpMxOfOIpmXsQNp9O;n;ZejTnF=tDi!i{gkJv+t1`GYZz3EdjWCud-d>RdXlt6~p zFUOG$<7BKgGP8%LCH)2vY%c|Cs)(o`1$%^75P^g|66A4&>{In4kYV*(2wQd)?>t=@%czPF5!T(p3lpA)+21i4r)gN8(z<%!onaug8=s-;BE@oR2flE z4k+tcDeKvTu-&Sj5n&fP;mv=653YmoaeD>+f$@@Npi&i^HK+e85xtGF(y3lIQ4_@SRWknHfg zE)^;8Z&n;W59=Wb5IiXXbs^9Q1*-ov>VbPKf^ed}3WUrO z_Ap{kQ|x20VvBtSc}WWZ68@4^^m;-CSQ6Dfq$)2(d)L1hKb5Wl5H0|dxO$K) zK)H@5z{T4FAp#_cXbg!CQKF981PZ0z!G)ohZ~>UaHH}CB#-50Fdy95r3HCANo8~)Bt`^jDzeF`YA*|L(zBE zGxUwY+ApHo3tcm@EaZ_OEB;2RtF{cQ>u5yCWU}KBU5!A&vl3J@LbXw-)-OcTPG8^f ztOV78Py-a|EQO-qsn5^yIJa+c`4ue^lHdwYNB{!}aFPOaG%#HrV)-M5JyJf%Bw-#$ z%&JeKsmfn6h$+T6f(scX+>+Cz3ieRkL8G|3fJT5JrvzM&z&#XrSEE6NdGsQvkWE6} zhp5LW>M5hBLEa5HCEy+eE-Od-9{eJxtM=d#>nu1f5w#(^gg%DoyC`~96BzHglrOO7os*$)IhT#J%hXv@=3762-`tnPaA{P`yynLFi#=o5XJ2JvN2V~PLucw zE{5wlX&1iPQIH#cv;cV|$bATT zm_m+m$cG|9id$redqF-4wg+J+DD2+dpy^_|)jt}nuwF}$A*+NuhRCIrXvgBO0&=qmmcT=P34Z4p;sysPe0_^?c)o~BfFY*@+=9TP z6!=&Sz;d*Nyb^R5LZ7A3?O%&QXZBWx=nOd};9&%=e*x>VbPoViFUgB$>u4*xgbwf| zx>HEEpVAEwI_f%c((&~}LI-#fUBy30RS#0Srmq_}TiA78!UI4OPc!nIraY4dd2q{x z)|nGr$SC1&bG31q?2M~HMg|7GptBL5)AzLvZNCd}`ppOy^ z5ds6P7BjUnGSR`z@=2Fpck%zz{j?ffTktR_k{ZIsBry^zfoiNPSFgxi9+ofP+Q8!JFrq)>~; z%|{xulS9A(OycT7t`W*r|Lp|1M7Jh<04VVdBi}UTJCP`#Zk$gD0Z}46g@kpVL2u}6 zPq2V|20+*VPGYM#OI)IzvQ@v5Ae(NONeBT^B5X#&Bb0EA5Yo9pe7+JqYapxuE3tMU z>nY0Gwx1Os~CcVcywe1{=#i{$$Y9EF8$m*TtM6LRg1tPBE_ z#B>~)S}D^2X9~o@6s_F>yu@DeFXAY@lzmqRE9XqH%PkQoBn|3OgX2_#shQUxsBHp= zq{2Q_q5QL0*@wS7<3`Z8Rv?i0dyqds`RfnNIDb(41rAAtF;w9YRpA6tfwte7@`KPV z8i*u4rcsYEsz>Mdm?`3Ecf5_0mIq)G*UsW(@ckV%SnXAvj4NXFnB&ORb(@e+f^9+A zMhZK^Ve=w2XaXxwks+@H-G$HxD0IvBL1P0$CJkB|VMBHaeHhV)Df$_LZeW0biL2MNIp*G}}z?%`gje?KG2rdRg z((OQg3BCj22PpjBt{8RK4Y(n<1U`V^CnetBh889Wv<49Rmi*?aU zD5*;&K&c;g1Exe-vV&Cr9!gn%C^i#D9C-tn#8r=6J(TNM3|u&^5U0|RS;F3j*vBY# z`wv-dMQtgFEj4z?FTwX9eA(x)W=gwR%@~6Z+1dd`A{j%HU6f>ikkBc$F_KWt9Y7?G zY2@gl98Eupkwf3!A-jaW^BLkzM=APbtmtxs2LK7b1@VjPuo?~=j#YbIi-+tIdKaQM zQ1pr)1G>Q$A7R?7pJoH7#5s(d9h7s3a2jm#5jgd29zZ3|Q^+|)IRia0S}|gy2Q-PS zqJ+4_6eT;&$u>sn-}zXC1b)aRfi@#(&F9e%x_-h+uyH}2!*`dGpQU(pa|-EQ1_6a! z5@-j4?xUd9y-`7R<3s`pc_hdIgzTq~qXHysg!j6(D$YB+p8RNsj3cw^=O(WMBaFLzd32Nu zCC<8|VUFzvI9Y!nfkN^ZxydO9tWZ$|jFYsU<)Ytwv={I@MD&=)4u=()fJvtomHL+(y9PNsVWYkMfSafRndKfStMeE)8cG$ z%6@A~tu+nuC0_IqEP6j^0!1JFB`f;IFh!RS^r0aIGD;m2VhQ&QMlE5;<9W0Y zL!uU7NEB7i5w|){DW*9^xV9gXB3j4+T!}V-wB=vKlJ)+Il`LUeF$9rl2iOvICsGF} zb>q;?Q3pj20GGH2k^2zkJ~@NjJerU~1F}RsiNs@+xa-$5SZFbt5N-gMxXa2gay4OH z*9-%9ME=;k94F3UB;vOvvomBMJ_=CGc7VZ=>KRVgpx?L!!`-SAuRu z=m84dF&b5G{gF0WoFzj!$Sh%ZBlby(UG_V`rZcT@os}M&&DN7sc39yyHUJ>K)?y3$ zXWl2Sza>@pJL#oti`YAlB7ar$9NU9%3)fNLCtj4i)EK`solj2rSW1bNz@$w>QBsqM z8uZ^Vh!g&@EDaj0O`#&=pvVeKTJi5=G_rompPWLB4Iq-StICN3?))-3VCk63VYIN? zls_5Dm{MU)1Aw$1P&73;KYwE`{WmEl|0_f>1E|7YPyveB@rM{y5PPvbcsCqCqzytz zQkSZi@D&xf{60@kK779eL$nnhZ*GL2K;5|Vg(z4j7Hkv>wgc`a>lu%+WW1v)v!+ym zVaKg$l>m}DFF*eS8*@LT{TDQrQbOo5h`Jozg=6A|*FY6@xjRf?*Eg!jE0)PA<<^vs zr2#5wX-jBB``Ee{wpai2o>k!OYRWra{mm;|eQ)0Zzb}Q34gQy8v7}6*Vzpnv8A;s) zC`My;UIL0~YaGcb52t)M4L~zfliz~vj;KN$lK&;@QuZm*7*2yO#1sD*V||5L`$$fK zYxQcZ1TSq0%9pwzcSU~s>J`59HG&9Ag-j1om5r!nb6}3`HkiX5Q?;~&jZfM^C9pG5 z3%sOXhMZC?Xh-020EP+MFb57-6#_7S-$Hu~$Sr~QA$ZBl2)_GIQ5{eM7s5JG9snf# z3B+%r_)`QwvL8wKL1CM)0F1;^TtPgfld>E-&TMbcL~=|fB!D83)FR0UCE5AsSV?$L zCh(A7f^S9mX$pTdW_U5+5&}Sw2)dD=ZZ}qS>xr24S65=GCqaG*eiY%`Dg0Riudc*~ zi5OB%M~h{oOn@qpP9f4rwA#aN~BfK6K^|3N$VzKHDuU;MA!gM zVhbQ!^;falCSqa}-oemXgX|J|C!)7f^tS(HwPv8@=-liDnXMj#HX`LZiNiiS7tkj!+>0U?i5ZN>cOXEm#+ge~p<%9|HhD!f!y~jly%qXm~9y`5CEP;SBrE9DBUSe7j6Qi1z0#-M;u^4 zP6@abfrlyZ!T&Q{4&5PKA_ZVb6x~R1hEh~bF}H{o1#}eBU;|(zmQiG>-Ggp0LRgFq zHgqf@0}g;mTvN!^M!8!49-q-dMIFGBsH&=npAAr|GjUSsLl2-xBmpEjNlCiT#8)0U z{s4|d(}^@yU&kt{`A2*-d}X@ zjYT2@)cSA)*b;Twr-_>!qtwO!jL)A1_I8_fWY zMAMBlLzHHc&=?LRqS=gu5_Y9$9Nk94lAc?0Bd73HD z;WFlb;nqaL`Ja)f1@IF41hOBb?6sd{PB}B|`mhD?5_|DyiPwx$_Hn``Za8l7qXubI0St+v6)Eb!h2FE{Q_QZ0DdYnPDG?w^MBPZVpAz*E zA{xF76U}4C5>g7lkSIoxVvtfaRK!sh>99gd1V|Fm6cU}LM8^qHjAhXuVMu8JN1~~! zAzrt$4eMtA^RbsF)WL?74A3O90Fv#cWM!3cl!>2C$X>1kjzrUmG<}rj2%({)55sP- zgwwD?5NVkJO(GjavJ;f7`GwfY^hYC78o-fgCXuG%+gML02~Fe;13}S2J1~(F0D?qN z_9AhF-ISoSiVszlm~q|=miVunbCozBTA zgcpD%-frZrdj;L8{j*Tm$Xl=sxl{1=D?$$F68R{Sw^Q=68fIz3O@(j0B8>$QB!Vd< zI6?^qIKj3sjV02NdSV*^D2b`+bHrIsQKse>Sy2LUFh$!m3Gfno0NJbeV=bPZd3Ld} zlC69|A!*Qw8njXkx@%{={JM5aQ~(Z1g+WxIm#R?rxfxf1H(|mL1QP!w@*k)C$7h~j z*P4k6z#*wnR!6+B{5$9e2kO8C>bJNvVk{*qyM}X>4^?6nm@w7YM#z_zX zFo~-jxeigTQ3G62Pp_h}5}+ieK4coBOs(~-RN=bbNMH4cJ4lEP0Zw9@K(-wnSeHA# z0Bm%O9WR@(>_G?tQ6enBux*!l303LJPPOZ7tFsptQRh61J+m3_@4| zR$^^M)?vyzl{hP34k3I1DDib8-x7AP`b7+v6_!3a3Gx& zssK`AoJ7VR%2?LSYB_Gkkg*DYCEl_x5bruhd4~uuo#@8R8!A`JNNUug8Vyv9LxIH2A2DPBfyCd6 z{2i3P_T|L*`2l703qin>=(>?^h|(S7bnS6;R#DC^qbdN0q{1kwFhy19*v(2jqbkVl z4tOLrrcjNV@1y@!esy-#2%5bCk)%h}OQap_qk4?ak{I%wSu%$F zVH^-idUT>5lT?qAuYn$P9yg=D5qdHQY?3mAs7&QS^pGP&nFKmL@dP#6G5}E`oJ7JF zN*LI~2m^66&WH!AQ3ZfOQlPAn_~K!zz!|Q<;LIryX^rRJl>5_wRZ^-Em71VR^?sdM zWtNo^BM4Cpm?g#9QL)k=pno*AzFvyyVhYg=s3pz%P_t&L+3D9!GahA#TEHu*HGyg! zq-q`h#_MJ08xN2z8FdqFcAcV^kgf+G=85BMeZYEiwjRK1f#JsOE- zFfj1dCNf+D4oQVpRH42LoxSUuOoe!60OWuyab^W@5?eR2?Wb%t`xsko9DztXj!R?& zNQrS283!rj1YwMSRID*d(>XwuNT-nWG$q~tEg+4546KocoY4WcL|xTHJbve^Sf3Sb zGh*RTlRAKwm;=bXmoksc46}Y-2cQyXCvx^t&em_wNNL3mg0vZcl~@On^#o$chwDNt%>>iFnv!ZbKojcRGp~t$jt5kd zCXJ{`H`Qc{XhOqhBGDw`G!Hl=71~jSajL@M{RyiOv^oWt5@jD!mi!Psq5eC}6XGp( zs1+)Jl~^Z`wTZHxAgqanG-M4K(*RuJE^a0s*GaiMJ7&hnp@JGO`(qNP=6_#St!fJYxMQv55to+nno{aX&QF)6j~4@nb3w=ClWA6-`9$R|I!oRG{PgX?SI?}8$?+mYpl-yx%N z?J^B?S;j}7dg@vs?<1;$N&+YZoar#zDy#b5nSj0wVkQdjhEv!cD1-_VEl92x^!Xk6 zg~qGG=PB}LE6j|f#$&3=La!&s1r@IO0;>my*X!60$%jP8~2ZDf@Mnyx9jwQ| zU6eqhDuk25J$%eP+W`Ou&t4v#4O)|j0HuJdYA|yQRpIdYT)73If@Oqo$oAaDAK2m? zAVw`2c_5LYUzWqCIPW1N%$W*;Z>+Hru?5WJ{rBXUroc6e#Kt_?FsIJ-c;76uOS7kc zN?19%@a8zYchSupV(4BHi7PsL&vx%?y1MD=ba6ReS2wsA;48{{6qc)kyUTNPAjLyO zjwf4~?z8q9_pI>T9S|u6*`6Xe_&&ta!nt0_N=^3>>wu%xLQDc@i5B0HGrKNxD%q}lhkN;TYp%WF8b~(S0U-)K z%!6aO=sg|AQOMO~4k*8e2RUAq1~nmf2XGa@XfeJ0OLTQeH@u8l_eZE1vmFJV0#~-f z?Rrvp8EPczgsaST*RG18GI6g0x1qsmjJiC+qx2)hOpk;vra_~{z(yOE8N<2#sYN!! zs~^6SX~ew}>D1tRSmsi*%@wX0m@!S=0~1qBG-H~PEkLhtOv`iS=9$fjE|VMVF?jeh zhX~Ad_;~LE93sRphQPTux0=Br5qCMvf0tuh0uWAlX29z%wth_mN_e=g0qM#W=LAB) znlZf|NN6gM<5gfUX+buBZ?Y`rYG)Wa6U7W8y)=%u;wenR=A|&Y62)2R3~7dRO02@2 zj_vp4yiZXcV~v!JQ)h9Nfvij2ld}Q0mKu5O%X_;-);6BVQ$j2;%wjP;IXON>n3x-E z12KhYVbdt9xmj8@G2iSwWxmA3&jx1Y%}xw%?}BU0g;k<>*0~`J=XkvNp= z*SY(m1s4x)W1GFzIflXA-+aV}Ft@`x7x{eTc27aJQpm%f5ep`=*kXn!Gd#U6FP_oM z6Mt=oFn6-o1!|i!I7X1#+`Z-wOZHtF+V#F7co7=%>!Mj293d+kM8`W@L6n3i2Ieaz zvR^sOyA~teP`Jvx0N0D8Kx4i?iTqKl*??&mY`#A+-=Bz|jKsNEW;Q3YIT`;v^!0ch z8N(_CI`3w(vx$uhjSM~#nFALT!HR}%FVSo>h8rry_Ik!_=Vm*9bG36m?uGB0J+SN3 zY*pq4MRS8<^z)ZZu+} zJ-!NfK`$K99SOJfbR=k_ws}la6jC)ON_Tf+o z+lb#)%8M4~44y+Jtd$dXjTDdr@r1K*Vm3G&w#K^>zON+WhZZsSTG|H~aqiW`t`9yn z;v3ZsD$09LQp{VkeDL|uX2s!q9Y3ux-@-m+ZW5WjO(NXGNz3;Zdg1uU(4UUQy^AA} z^%v9oP47=^4?-dv91A>t-es_bzc-{IIEwsv9`9x)NAbdT;t*Ozm`%O0+o&Rr1bFzN z1>BNgPfs?&@*x)3B&Ls);@yUqh>vA?oZGj!Lit&Q8Z|?zdfu{mN^Alr2FL5}23s_o zM4k6Ko>FHH70jVRVnYRke(ftH#~XzD^dUmTJ}3HMr?S;`K8`Jz@x$!4dP7EZ=C|ze z57f*xac(=dj(9<@iJRZDn{SiNx5){4e>RVQHaO~tp(UHA_$b#dgKtC~c!CU>MDQrb zkKPF}y)sYn(b>v8#Rq)EV1Hnq;zL%v%(t-T?^{^&6rX5|DyH|JgWjLWUOBeQFU(uI zH)PwG`3{ed?96v~%cPx*=1Ygz-YJ+LM3^5$m>)z$o(09QG+}=Itp;R+t3G_;!raaM z=H1P0e*Ik(e#On46@gl{ap6|7*a}fLL6|2{*SYuzFRpfFz z?eBiKa^0%zb*pZ??z(k1DB0Jq$-XW-$8kgUb!)TUt>j#H-I~?ctyzH`}@BA_S&L$`>buUZ6CKVT0=$dp@KT`` zC=68i{aRoK`SbOPqU~BBL_5bTW#92@bO3zXr|r;u9)B@W>_v66+I+hG$Eo>LPepmS zfmZ1SPr&Q<(2n(N#ZHRZ=qWGrmj!|7^%YhGwvsw#tBRal`uJ6BVF!r(q>`^7nF(d8Rob-%k8w0@{s_8~=Fqq?5xOig# z{@fHOE6E{@2h=R}51;+iPKXr&&XM!kvtf6}_NLw@|*?tj1AP#~+C6_`aH zTfSz+nkCEDEXiHP{#uIiDufO;sG97XfM>7s4ED|1uc}cfJJ?T)7_k4U}rs2Ut7R6K29L}rj z{y|Yz5-;8s3YD*2x-_H(O1=IvU)k23Ioo`za>@c*mp)xqti8N57}83Y?#NrRk%a1y z$M)gt2Ww#DD`nhSb$&IeWbn$F^ z=#77HTH4K~P`k}x6PJ8U8V!;LiO(Q6z8BsO#9P^0qP;&v`~8-!TjsqfnQq5&@_)H% zN~TLuAY&$eaLIII8Xs_Ex)t(2`g|IBKKWX{~+<>{qu9}AEcH}w%nHKPJ`7p zQOj9kSuN8|{=ikW@cMqPIvWf-U2>hw*IfFNjkNrd>0TM%S`PKS6mxjgC1vo%7ZPM-FRWekvGCq=UJZY0#)gUJDrY@UlI8yCsGdoj0Z9wx7f}#=vd@LN2fl#GOCov9jk(pCSmRTfm^*pD`kHQsvtPvALkrYahn{`< z0dZ2FSAhi4yIsBY4;M;Btj?k{7qE(?=GBH8v8%$*R)-Ul(a;UP>8i)2SO=2kt7 z$^M7cLS2XUMd}Vc+58ArbXX)0KZ?b!iDcJTF}HRxCI=T|-CCDm?zl+yhT7?hPuEtipxEgDJ zXbmQZ*5I>kYcV+?lD%KU+=hHi4vS>R*D<$h9VYulvgvWm9eEt>ck&5z?Aj+WIV6(p zPhoEL)0jLblFjQ8Hntu`oc;zT>z~2o1(7`ZEEcOQz+}Hj9()dqT@lHX&tvZ17qHlX zNVaUi+;NfY-H5pd$TkhVg+@fO^CisPw+Tg@7s=LdV(z#|_H0IO!^`-+|9wslohp=`JCMyduc}64~i$s4C$;M)Q_OeJG*D!Z?2}_ch`NX)RV0N(F z6WIQ4uxK0E$)GMHyAU2qai@5TA5u0fAmbqXIon(O?hY*w^p^QG{=Kw#!I$Zq^Qmf z`9#ANY3?+iHyBb=Scw^$Pb($6X+@EYpjK2tK11)cDnpTa33aw*Rh&Winr$@5480!4 zytE?n;oF!oqu3iP_jz{mJcU$nc12JNyiDX3(HaujsUgqSTTLx!syW`$av#}57TR3l z(}KJX)HyQ9-mI+^o~^vH)DuG52O+XU&B}S4LHPD>B%~I5cj$kU!n5f9w2-&Ns|BK2 z;%-X`sl=!PmloPtu7&GD1UX3ydHuJhxxs=)8(DZ=`VNn;!cbFZxfbx26^GXqnjxLo zJ-~Yb-o-dQYY^Rv6Ki_kiC;Jl}M-+ zW`t-kC@Zy6(P%>3p~>VVU>2jDQ%L_!3wZq7Y1<{>KLB=aB@U$pj9xpl&>IMCTTkLQ z&!tiCo?@*?^8~fxr^?EFn#a%lZ7$EHkH6wAM_-&6TATW)@Medi7kwId8DBvyPRLG<~P#A&=o>g8(@_clQlu@4< zMP(KKP=FH?YZ_8|PN{L%Rd{@$0&bUBNd0GiX@xK3E%#|pQSU-0qRwpo={bK$+o}bq zbElVYD+`rHbD(*a4s=&h83Fl2uaJ?%*iQ1u$9#dzRy+)oS`qN^=b@(sy&r1qr?hRJ z;5L>_4|zjAGq)(9kt0B5e&LhECHx^0W}{UgEVPoU&km67(-arn<}Hr~2phC-Bj+Hh zvhgR(3eYm)aX@r?c)*z)U~r7S#ReGCv^WSPqrCneW|+V+|1}N3wC9rzxE{avL+Jf6 z&Fg5dPiP`DhfHEhE65QOCOgWE&?@R+biMrtteJG)sRaV;9q&HUOj^L>+Z3SlW*V5I zzv;v>Cns$;ztrRRY$Y!@FD2jrbRM)lp7A2REVM1w_Xv4n=1R+4kU8;lM>aVmJ3DiQ zQ_0Ar%MfacE6w$yYjbu+{z_$$BbRLEAZznvgEsjjuPCnezjmneU1v+wEG7F5QP8wt z0SLbxT(>8jzzU>QhQ~A34~nE3*hzJGe&qt|!Qk`76l>L7~h5Dd}<@ z->N>KWY>`G=BAQjK?L-nfSMEua1{XO0MI7`tkzBt)hDBN@1we0XS&dB|IDPU7RF^3 zMAW28+B8xGu>mPSqQ1M=k?ShB&vhc@!_t{1aw z(j*;v0Cf~l1BluJC|uStv3Ltn+<=Tb54bacYbJa4nP!o2`fE5;mLi+eB@M3was-g0 zwvc@B2$`cYbMN2K#@p+q2JCj)Q4zdO-==n)h(?lIdP1g+ z0qr2rddUG9qw|F671qeTGbQC}>u4i)0sIZ!OH#2Pm<`)$TU3#AU_8c~*BQYp*1Fb_N2Kz@Q2rQ{?=qAZ zvj!SOXwfdY=mZo!3q>bI(M7P}#N!1G$dhu(x*yRtx&|dX$$3qTgLJ)9U)ThdY3a^6 zlFH3cdY_M0c~UM-*Fdr8q+GNUinc+~PI8n`521#o(kXjE=iQPTXMxoRteShGvglhA zcFvl6B-mwuT?W_y!nRDM7%aVT6GKRrftB^t#CuAqk(%dn?e;_j>WdeaCZKbL=DCvA z`$5PN5Hg;C5Mzv>g2rV*tstly1ohn;e{D?RgG%bVSE|w}kaPhgHO-4(l87u+Ow&9` z%%>ox(odV`Y64OQGr)qhL?V z*^$|24nA3_pD!T~0CE737Z6!6l`9*aiFS<(GVdtx#(;N}92<`k1lYX6HuSV zfSRjhH+_lM$3mX|kxLes;i_@C_BcpcS~<4;+s$@^8>`h1)j9)Wjx3BMhJ6_e7qh{& zDf`GmsWmQv_LV`}8l#Ba5^gP1m|;yjD#IoL)&#KbFLNEIhGBqVyzVbc%GEbO><3<5 zR&<^oS{SV^OQN*^Z2)LPNGsr9_DlvdM$;hzGQF;J0cZ?BZC~MPS%c)kqwOma>>R-M zhiC&#M1e^o$Arup0agdFdLN9em}&ah`=F#%)laD9KLJw1Lkq|r6j&Otv=w}(#Hicw zkc4Ug)Kx$Y6O{h`WQj648Cz-?mU(T!tF530>&UjnLqB84j%-QAZa}sIvg%=5NYf}* z^{@mS0AMcw`vJ%n%JeU>V61msh1vb2phDJq$dx4g%!IMeUj@vi3L0A47g4m zQoyx?{7gxP6BYDy1;4?>$lQH^w(Nl&>N8{>|A-i>p!4&Yh4mWMfwpPRcu`%sdx-f?SLAOg2K=5h#Qx2eSqr+Tu)A9&De)~WcB1o zs$Brq6=3aO8kuGI%6)`AlSg&AqXjmcHp@pzruKJH?0}aQ5 z<_i=~m(S%VMX3!O8{K%y%vgag5Yqk;*~glFc4aL3>w7gxr*gW!?edY*R1QCkM$WDo z#he2%7d}#Rl``)kGVmZ5Ms_x<~}^_e=Ax8hyhJ1ROeYUtBHqoCd&L0(}pyp|d(RpD<(e z%{hoUv_`_T0cQ7~(`pY9jQ-NfcJfR)b`QyzZonJ{OxxN>7~?)445w{tCA9{CcM5nD z5qajdX!=G!A;Yc$YzSbzUyGz%8r@ffTJ?TS(rWk5Xwyvss3AWRi0(>4PD8%LX#`H= zUqHVk9DPP^Ve~Y*n+ZX~GN>IuCjiv(^+*cAUMK{0d|lG02SA?!sA^p#5Zg(Gq^fli z={%4|fz%(7l)-mkA*^49T?bfwrGr|k>2cJH@6?*oL7N_z)Y^NHHeEX-jUed*OOk0b zm!%9k5gURHCh;TL*-UvUF2TNA{bFv19L4)clX!Yjxy!x^BCNY}Q{?xo)s^C~Odn{euvXLd%r$f_$7bPhN zKuXnaXo!&nq!_va6*M9XItqdgGC`d$#a|ul8dOZ@OHzGKgP2YxX5S_*#(K=)i#@zI z&>+8WlSCW@;vgfQw@F0bp~&+x@){t=8M5`8_LMZQd$xX4QghEQsk=1%B{bx?ZKiPn zbX;aO0rMDR_H5>QPT}MFaij<i)Y0|xGR#h$LxB5B7flCq-!ZDY`?SM5QqR!gg1mB=+ms6X^Ea=%S7 zi7jx(3a#5OV-ErLGQ&2##uc_6fO-Ft0uk*KO|MD%9tZHAzhZ$XHn1V~Q1*z-J_GC{ zjD6zun3TqFL&+yzm$beJ&LPdZJD83!SnR+! zC)4-+ih4xlUa;`tZ*g61&`obRRP)2%k~BX8>?X#(7KLr$4%cMhNdTT?z%JOe4$Ftu zhGmB`Bsb_HTiH#Yjz0nB0Atp^8QB23lBVXbeN!S{0^%4W4kEF@e9psiL4ulbQ07en zZ~qUViCW%54I}VyIn6kTwY()MTHj3DzJr0s5WL3@INI{Ym@KRXgnhz<^}fw(5w|eo zY?2Q8y>Cmk>jHsSnZU+(5*8S?kkR;#B=Hoy(fun0I`>t*o#E0zG1N=i}|J`EO0%I<~0JZ zu@)Mvu^25&Cz#t!rZk4W#$rjWcEFrqn9GRS5{_Xfw4|z1<6V}SJ;3~wF^_9pwJDjV z%2MKSP15o_5JwqtcL^sJgllPj57=EIfvy9no*a^TKyeLtTxr=EuD+7_iLf3wDcG?4i%?kc_5O6!o&%;Y4%6n`~Ibo|X`r*vJz{2oP3aw}1&V#@a>U zR!S_6K51!2;d_ZRJiFHNa}~+)Zqhs;xpcG3@G0<5`rAbu z?`_oQZH7s!Ys1mY?JiG=_e2v;Kg}p`vCj$V1?5;CJKeMl+;k|@FFZ$$Vjh(e|1?0q zz6<$dp@@#!5(Z_<#uz z?2cgDAg0%cjkn&TC8k%UDXqWcwSEu?Vi@FyEkc4m)NUc9P*kzbICfCzrJCqc4I%tSj{9iT0o@RFuJ<)z{@h_;M4%ofo?(Ne% zSS0cp+3KHt3pdQ|K2Fk8k>Rz9ySWpIweT<9h3j&=TR5J*7xDG!w|6P-=k-&UOG*O^ z^)rDBm**_c&BYDFt@i}H#}JS&Ux9xGg>mE`5+ iF3-!&En4%~BlMpPT%ah)|J~UZP}-^#Wz& Date: Wed, 6 Sep 2023 14:55:53 +1000 Subject: [PATCH 64/95] Update element names in tests to match likely consensus --- fhirpath/src/test/resources/sql-on-fhir | 1 + 1 file changed, 1 insertion(+) create mode 160000 fhirpath/src/test/resources/sql-on-fhir diff --git a/fhirpath/src/test/resources/sql-on-fhir b/fhirpath/src/test/resources/sql-on-fhir new file mode 160000 index 0000000000..915a45ba57 --- /dev/null +++ b/fhirpath/src/test/resources/sql-on-fhir @@ -0,0 +1 @@ +Subproject commit 915a45ba574fadeb5467d15523655bb8beecf748 From e66aaecbf689315139599fb86969069f7e8f9ccb Mon Sep 17 00:00:00 2001 From: John Grimes Date: Wed, 6 Sep 2023 14:56:56 +1000 Subject: [PATCH 65/95] Fix bug in basic query tests --- fhirpath/src/test/resources/sql-on-fhir | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fhirpath/src/test/resources/sql-on-fhir b/fhirpath/src/test/resources/sql-on-fhir index 915a45ba57..2de0f788f6 160000 --- a/fhirpath/src/test/resources/sql-on-fhir +++ b/fhirpath/src/test/resources/sql-on-fhir @@ -1 +1 @@ -Subproject commit 915a45ba574fadeb5467d15523655bb8beecf748 +Subproject commit 2de0f788f64dcb78215a70ef737bbb9999382304 From eb7798e6d5e36d6fa61ccae24c2f7def7bfad3eb Mon Sep 17 00:00:00 2001 From: John Grimes Date: Fri, 8 Sep 2023 13:36:14 +1000 Subject: [PATCH 66/95] Update FhirViewTest to use new test format --- .../pathling/aggregate/AggregateExecutor.java | 6 +- .../pathling/extract/ExtractExecutor.java | 2 +- .../csiro/pathling/search/SearchExecutor.java | 12 +- .../csiro/pathling/search/SearchProvider.java | 6 +- .../security/ga4gh/PassportScopeEnforcer.java | 3 +- .../security/ga4gh/ScopeAwareDatabase.java | 3 +- .../aggregate/AggregateExecutorTest.java | 5 +- .../aggregate/AggregateQueryExecutorTest.java | 3 +- .../pathling/extract/ExtractQueryTest.java | 3 +- .../fhirpath/parser/AbstractParserTest.java | 155 ++++++++---------- .../parser/DateTimeArithmeticParserTest.java | 15 +- .../pathling/fhirpath/parser/ParserTest.java | 11 +- .../fhirpath/parser/QuantityParserTest.java | 11 +- .../search/SearchExecutorBuilder.java | 4 +- .../test/benchmark/AggregateBenchmark.java | 4 +- .../test/benchmark/SearchDevBenchmark.java | 4 +- .../benchmark/TerminologyBenchmarkTest.java | 5 +- .../benchmark/TerminologyDevBenchmark.java | 5 +- .../pathling/test/integration/AsyncTest.java | 5 +- .../test/integration/ExtractTest.java | 5 +- .../pathling/test/integration/SearchTest.java | 5 +- .../pathling/views/FhirViewExecutorTest.java | 5 +- .../java/au/csiro/pathling/QueryExecutor.java | 13 +- .../aggregate/AggregateQueryExecutor.java | 3 +- .../extract/ExtractQueryExecutor.java | 10 +- ...serContext.java => EvaluationContext.java} | 34 ++-- .../au/csiro/pathling/fhirpath/FhirPath.java | 18 +- .../pathling/fhirpath/FhirPathAndContext.java | 5 +- .../pathling/fhirpath/FunctionInput.java | 3 +- .../fhirpath/collection/Collection.java | 32 +++- .../fhirpath/collection/MixedCollection.java | 10 +- .../collection/ResourceCollection.java | 11 +- .../fhirpath/function/FirstFunction.java | 33 ++-- .../fhirpath/function/NamedFunction.java | 4 +- .../fhirpath/function/WhereFunction.java | 2 +- .../operator/BinaryOperatorInput.java | 4 +- .../fhirpath/operator/BooleanOperator.java | 10 +- .../fhirpath/parser/InvocationVisitor.java | 28 +--- .../fhirpath/parser/LiteralTermVisitor.java | 18 +- .../pathling/fhirpath/parser/Parser.java | 27 ++- .../pathling/fhirpath/parser/TermVisitor.java | 16 +- .../pathling/fhirpath/parser/Visitor.java | 25 +-- .../fhirpath/path/EvalOperatorPath.java | 16 +- .../fhirpath/path/ExtConsFhirPath.java | 7 +- .../pathling/views/FhirViewExecutor.java | 39 ++--- ...der.java => EvaluationContextBuilder.java} | 90 +++++----- .../pathling/test/helpers/SparkHelpers.java | 6 +- .../pathling/test/helpers/TestHelpers.java | 30 ++-- .../au/csiro/pathling/views/FhirViewTest.java | 35 ++-- fhirpath/src/test/resources/sql-on-fhir | 2 +- .../library/io/source/AbstractSource.java | 5 +- 51 files changed, 402 insertions(+), 411 deletions(-) rename fhirpath/src/main/java/au/csiro/pathling/fhirpath/{parser/ParserContext.java => EvaluationContext.java} (82%) rename fhirpath/src/test/java/au/csiro/pathling/test/builders/{ParserContextBuilder.java => EvaluationContextBuilder.java} (50%) diff --git a/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java index 15c25f7c55..8fe43ba7fb 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java @@ -20,10 +20,9 @@ import static java.util.stream.Collectors.toList; import au.csiro.pathling.config.QueryConfiguration; -import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.io.Database; -import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.ArrayList; @@ -32,6 +31,7 @@ import java.util.function.Function; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; +import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.hl7.fhir.r4.model.Type; @@ -59,7 +59,7 @@ public class AggregateExecutor extends AggregateQueryExecutor { */ public AggregateExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final DataSource dataSource, + @Nonnull final Dataset dataSource, @Nonnull final Optional terminologyServiceFactory) { super(configuration, fhirContext, sparkSession, dataSource, terminologyServiceFactory); diff --git a/fhir-server/src/main/java/au/csiro/pathling/extract/ExtractExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/extract/ExtractExecutor.java index d8aee6c58d..37d4afa27e 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/extract/ExtractExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/extract/ExtractExecutor.java @@ -64,7 +64,7 @@ public class ExtractExecutor extends ExtractQueryExecutor { */ public ExtractExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final Database database, + @Nonnull final Dataset database, @Nonnull final Optional terminologyClientFactory, @Nonnull final ResultWriter resultWriter, @Nonnull final ResultRegistry resultRegistry) { diff --git a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java index 36248d019c..b7a581318d 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java @@ -17,20 +17,13 @@ package au.csiro.pathling.search; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.toList; import static org.apache.spark.sql.functions.col; -import static org.apache.spark.sql.functions.row_number; import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; import au.csiro.pathling.fhirpath.annotations.NotImplemented; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.function.registry.StaticFunctionRegistry; -import au.csiro.pathling.fhirpath.parser.ParserContext; import au.csiro.pathling.io.Database; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; @@ -39,7 +32,6 @@ import ca.uhn.fhir.rest.param.StringAndListParam; import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringParam; -import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Optional; @@ -52,8 +44,6 @@ import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder; -import org.apache.spark.sql.expressions.Window; -import org.apache.spark.sql.expressions.WindowSpec; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -98,7 +88,7 @@ public class SearchExecutor extends QueryExecutor implements IBundleProvider { */ public SearchExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final Database database, + @Nonnull final Dataset database, @Nonnull final Optional terminologyServiceFactory, @Nonnull final FhirEncoders fhirEncoders, @Nonnull final ResourceType subjectResource, @Nonnull final Optional filters) { diff --git a/fhir-server/src/main/java/au/csiro/pathling/search/SearchProvider.java b/fhir-server/src/main/java/au/csiro/pathling/search/SearchProvider.java index 5454931bcf..3b373d430c 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/search/SearchProvider.java +++ b/fhir-server/src/main/java/au/csiro/pathling/search/SearchProvider.java @@ -35,6 +35,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.extern.slf4j.Slf4j; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -74,7 +76,7 @@ public class SearchProvider implements IResourceProvider { private final SparkSession sparkSession; @Nonnull - private final Database database; + private final Dataset database; @Nonnull private final Optional terminologyServiceFactory; @@ -103,7 +105,7 @@ public class SearchProvider implements IResourceProvider { */ public SearchProvider(@Nonnull final ServerConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final Database database, + @Nonnull final Dataset database, @Nonnull final Optional terminologyServiceFactory, @Nonnull final FhirEncoders fhirEncoders, @Nonnull final Class resourceClass) { diff --git a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java index 6b638242c8..fef27b3114 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java +++ b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java @@ -21,7 +21,6 @@ import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.io.Database; -import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.Collection; @@ -61,7 +60,7 @@ public PassportScopeEnforcer( @Nonnull final QueryConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final DataSource dataSource, + @Nonnull final Dataset dataSource, @Nonnull final Optional terminologyServiceFactory, @Nonnull final PassportScope passportScope) { super(configuration, fhirContext, sparkSession, dataSource, terminologyServiceFactory); diff --git a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/ScopeAwareDatabase.java b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/ScopeAwareDatabase.java index 44813408c2..7690724aff 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/ScopeAwareDatabase.java +++ b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/ScopeAwareDatabase.java @@ -21,7 +21,6 @@ import au.csiro.pathling.encoders.FhirEncoders; import au.csiro.pathling.io.CacheableDatabase; import au.csiro.pathling.io.Database; -import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.Optional; @@ -99,7 +98,7 @@ public Dataset read(@Nullable final ResourceType resourceType) { .map(scope -> { // We need to create a non-scope-aware reader here for the parsing of the filters, so that // we don't have recursive application of the filters. - final DataSource dataSource = Database.forConfiguration(spark, fhirEncoders, + final Dataset dataSource = Database.forConfiguration(spark, fhirEncoders, configuration.getStorage()); final PassportScopeEnforcer scopeEnforcer = new PassportScopeEnforcer( configuration.getQuery(), diff --git a/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateExecutorTest.java b/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateExecutorTest.java index 0fafd10d6d..2b6763241d 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateExecutorTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateExecutorTest.java @@ -23,7 +23,6 @@ import au.csiro.pathling.aggregate.AggregateResponse.Grouping; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; -import au.csiro.pathling.io.CacheableDatabase; import au.csiro.pathling.search.SearchExecutor; import au.csiro.pathling.terminology.TerminologyService; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -38,6 +37,8 @@ import java.util.List; import java.util.Optional; import javax.annotation.Nonnull; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -75,7 +76,7 @@ abstract class AggregateExecutorTest { FhirEncoders fhirEncoders; @MockBean - CacheableDatabase database; + Dataset database; AggregateExecutor executor; ResourceType subjectResource; diff --git a/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryExecutorTest.java b/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryExecutorTest.java index d8bd8db44b..048f056fdc 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryExecutorTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryExecutorTest.java @@ -23,7 +23,6 @@ import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; -import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.query.ExpressionWithLabel; import au.csiro.pathling.terminology.TerminologyServiceFactory; import au.csiro.pathling.test.SharedMocks; @@ -75,7 +74,7 @@ class AggregateQueryExecutorTest { FhirEncoders fhirEncoders; @MockBean - DataSource dataSource; + Dataset dataSource; AggregateQueryExecutor executor; diff --git a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java index 8c5d5c20aa..f0970f8c08 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java @@ -28,7 +28,6 @@ import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.query.ExpressionWithLabel; import au.csiro.pathling.terminology.TerminologyServiceFactory; import au.csiro.pathling.test.SharedMocks; @@ -80,7 +79,7 @@ class ExtractQueryTest { FhirEncoders fhirEncoders; @MockBean - DataSource dataSource; + Dataset dataSource; ResourceType subjectResource; diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java index 72c293a1e2..41215bbfbe 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java @@ -17,101 +17,80 @@ package au.csiro.pathling.fhirpath.parser; -import static org.mockito.Mockito.when; - -import au.csiro.pathling.encoders.FhirEncoders; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.io.source.DataSource; -import au.csiro.pathling.terminology.TerminologyService; -import au.csiro.pathling.terminology.TerminologyServiceFactory; -import au.csiro.pathling.test.SharedMocks; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.TimingExtension; -import au.csiro.pathling.test.assertions.FhirPathAssertion; -import au.csiro.pathling.test.builders.ParserContextBuilder; -import au.csiro.pathling.test.helpers.TestHelpers; -import ca.uhn.fhir.context.FhirContext; -import java.util.Collections; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; @SpringBootUnitTest @ExtendWith(TimingExtension.class) public class AbstractParserTest { - @Autowired - protected SparkSession spark; - - @Autowired - FhirContext fhirContext; - - @Autowired - TerminologyService terminologyService; - - @Autowired - FhirEncoders fhirEncoders; - - @Autowired - TerminologyServiceFactory terminologyServiceFactory; - - @MockBean - protected DataSource dataSource; - - Parser parser; - - @BeforeEach - void setUp() { - SharedMocks.resetAll(); - mockResource(ResourceType.PATIENT, ResourceType.CONDITION, ResourceType.ENCOUNTER, - ResourceType.PROCEDURE, ResourceType.MEDICATIONREQUEST, ResourceType.OBSERVATION, - ResourceType.DIAGNOSTICREPORT, ResourceType.ORGANIZATION, ResourceType.QUESTIONNAIRE, - ResourceType.CAREPLAN); - - final ResourceCollection subjectResource = ResourceCollection - .build(fhirContext, dataSource, ResourceType.PATIENT, ResourceType.PATIENT.toCode()); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(terminologyServiceFactory) - .database(dataSource) - .inputContext(subjectResource) - .groupingColumns(Collections.singletonList(subjectResource.getIdColumn())) - .build(); - parser = new Parser(parserContext); - } - - void mockResource(final ResourceType... resourceTypes) { - for (final ResourceType resourceType : resourceTypes) { - final Dataset dataset = TestHelpers.getDatasetForResourceType(spark, resourceType); - when(dataSource.read(resourceType)).thenReturn(dataset); - } - } - - @SuppressWarnings("SameParameterValue") - @Nonnull - protected FhirPathAssertion assertThatResultOf(@Nonnull final ResourceType resourceType, - @Nonnull final String expression) { - final ResourceCollection subjectResource = ResourceCollection - .build(fhirContext, dataSource, resourceType, resourceType.toCode()); - - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) - .terminologyClientFactory(terminologyServiceFactory) - .database(dataSource) - .inputContext(subjectResource) - .build(); - final Parser resourceParser = new Parser(parserContext); - return assertThat(resourceParser.evaluate(expression)); - } - - @SuppressWarnings("SameParameterValue") - FhirPathAssertion assertThatResultOf(final String expression) { - return assertThat(parser.evaluate(expression)); - } + // @Autowired + // protected SparkSession spark; + // + // @Autowired + // FhirContext fhirContext; + // + // @Autowired + // TerminologyService terminologyService; + // + // @Autowired + // FhirEncoders fhirEncoders; + // + // @Autowired + // TerminologyServiceFactory terminologyServiceFactory; + // + // @MockBean + // protected DataSource dataSource; + // + // Parser parser; + // + // @BeforeEach + // void setUp() { + // SharedMocks.resetAll(); + // mockResource(ResourceType.PATIENT, ResourceType.CONDITION, ResourceType.ENCOUNTER, + // ResourceType.PROCEDURE, ResourceType.MEDICATIONREQUEST, ResourceType.OBSERVATION, + // ResourceType.DIAGNOSTICREPORT, ResourceType.ORGANIZATION, ResourceType.QUESTIONNAIRE, + // ResourceType.CAREPLAN); + // + // final ResourceCollection subjectResource = ResourceCollection + // .build(fhirContext, dataSource, ResourceType.PATIENT, ResourceType.PATIENT.toCode()); + // + // final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) + // .terminologyServiceFactory(terminologyServiceFactory) + // .dataset(dataSource) + // .inputContext(subjectResource) + // .groupingColumns(Collections.singletonList(subjectResource.getIdColumn())) + // .build(); + // parser = new Parser(evaluationContext); + // } + // + // void mockResource(final ResourceType... resourceTypes) { + // for (final ResourceType resourceType : resourceTypes) { + // final Dataset dataset = TestHelpers.getDatasetForResourceType(spark, resourceType); + // when(dataSource.read(resourceType)).thenReturn(dataset); + // } + // } + // + // @SuppressWarnings("SameParameterValue") + // @Nonnull + // protected FhirPathAssertion assertThatResultOf(@Nonnull final ResourceType resourceType, + // @Nonnull final String expression) { + // final ResourceCollection subjectResource = ResourceCollection + // .build(fhirContext, dataSource, resourceType, resourceType.toCode()); + // + // final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) + // .terminologyClientFactory(terminologyServiceFactory) + // .database(dataSource) + // .inputContext(subjectResource) + // .build(); + // final Parser resourceParser = new Parser(evaluationContext); + // return assertThat(resourceParser.evaluate(expression, context)); + // } + // + // @SuppressWarnings("SameParameterValue") + // FhirPathAssertion assertThatResultOf(final String expression) { + // return assertThat(parser.evaluate(expression, context)); + // } } diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java index 8ad68715bc..d99c4c43ee 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java @@ -17,9 +17,10 @@ package au.csiro.pathling.fhirpath.parser; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.collection.BooleanCollection; import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.test.builders.ParserContextBuilder; +import au.csiro.pathling.test.builders.EvaluationContextBuilder; import java.util.Collections; import org.hl7.fhir.r4.model.Enumerations.ResourceType; import org.junit.jupiter.api.Test; @@ -29,15 +30,15 @@ public class DateTimeArithmeticParserTest extends AbstractParserTest { @Test void lengthOfEncounter() { final ResourceCollection subjectResource = ResourceCollection - .build(fhirContext, dataSource, ResourceType.ENCOUNTER, ResourceType.ENCOUNTER.toCode() - ); - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + .build(fhirContext, dataSource.read(ResourceType.ENCOUNTER), ResourceType.ENCOUNTER); + + final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) .terminologyClientFactory(terminologyServiceFactory) .database(dataSource) .inputContext(subjectResource) .groupingColumns(Collections.singletonList(subjectResource.getIdColumn())) .build(); - parser = new Parser(parserContext); + parser = new Parser(evaluationContext); assertThatResultOf("(period.start + 20 minutes) > period.end") .isElementPath(BooleanCollection.class) @@ -50,13 +51,13 @@ void ageAtTimeOfEncounter() { final ResourceCollection subjectResource = ResourceCollection .build(fhirContext, dataSource, ResourceType.ENCOUNTER, ResourceType.ENCOUNTER.toCode() ); - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) .terminologyClientFactory(terminologyServiceFactory) .database(dataSource) .inputContext(subjectResource) .groupingColumns(Collections.singletonList(subjectResource.getIdColumn())) .build(); - parser = new Parser(parserContext); + parser = new Parser(evaluationContext); assertThatResultOf("period.start > (subject.resolve().ofType(Patient).birthDate + 60 years)") .isElementPath(BooleanCollection.class) diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java index 24a3280386..750d1c1159 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java @@ -37,18 +37,19 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.collection.BooleanCollection; import au.csiro.pathling.fhirpath.collection.DateCollection; import au.csiro.pathling.fhirpath.collection.DecimalCollection; import au.csiro.pathling.fhirpath.collection.IntegerCollection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; import au.csiro.pathling.fhirpath.literal.DateLiteralPath; import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; import au.csiro.pathling.fhirpath.literal.TimeLiteralPath; import au.csiro.pathling.test.builders.DatasetBuilder; -import au.csiro.pathling.test.builders.ParserContextBuilder; +import au.csiro.pathling.test.builders.EvaluationContextBuilder; import au.csiro.pathling.test.helpers.TerminologyServiceHelpers; import au.csiro.pathling.test.helpers.TerminologyServiceHelpers.TranslateExpectations; import java.util.Collections; @@ -66,7 +67,7 @@ public class ParserTest extends AbstractParserTest { @SuppressWarnings("SameParameterValue") private T assertThrows(final Class errorType, final String expression) { - return Assertions.assertThrows(errorType, () -> parser.evaluate(expression)); + return Assertions.assertThrows(errorType, () -> parser.evaluate(expression, context)); } private TranslateExpectations setupMockTranslationFor_195662009_444814009( @@ -794,13 +795,13 @@ private void setSubjectResource(@Nonnull final ResourceType resourceType) { .build(fhirContext, dataSource, resourceType, resourceType.toCode() ); - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) .terminologyClientFactory(terminologyServiceFactory) .database(dataSource) .inputContext(subjectResource) .groupingColumns(Collections.singletonList(subjectResource.getIdColumn())) .build(); - parser = new Parser(parserContext); + parser = new Parser(evaluationContext); } @Test diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java index 1faf547ccf..f751fd29fe 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java @@ -17,9 +17,10 @@ package au.csiro.pathling.fhirpath.parser; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.collection.BooleanCollection; import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.test.builders.ParserContextBuilder; +import au.csiro.pathling.test.builders.EvaluationContextBuilder; import java.util.Collections; import org.hl7.fhir.r4.model.Enumerations.ResourceType; import org.junit.jupiter.api.Test; @@ -31,13 +32,13 @@ void lengthObservationComparison() { final ResourceCollection subjectResource = ResourceCollection .build(fhirContext, dataSource, ResourceType.OBSERVATION, ResourceType.OBSERVATION.toCode() ); - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) .terminologyClientFactory(terminologyServiceFactory) .database(dataSource) .inputContext(subjectResource) .groupingColumns(Collections.singletonList(subjectResource.getIdColumn())) .build(); - parser = new Parser(parserContext); + parser = new Parser(evaluationContext); assertThatResultOf("valueQuantity < 1.5 'm'") .isElementPath(BooleanCollection.class) @@ -50,13 +51,13 @@ void lengthObservationSubtraction() { final ResourceCollection subjectResource = ResourceCollection .build(fhirContext, dataSource, ResourceType.OBSERVATION, ResourceType.OBSERVATION.toCode() ); - final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext) + final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) .terminologyClientFactory(terminologyServiceFactory) .database(dataSource) .inputContext(subjectResource) .groupingColumns(Collections.singletonList(subjectResource.getIdColumn())) .build(); - parser = new Parser(parserContext); + parser = new Parser(evaluationContext); assertThatResultOf("valueQuantity > (valueQuantity - 2 'g/dL')") .isElementPath(BooleanCollection.class) diff --git a/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorBuilder.java b/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorBuilder.java index 750aafa613..c4a457826b 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorBuilder.java +++ b/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorBuilder.java @@ -31,6 +31,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.Getter; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -53,7 +55,7 @@ class SearchExecutorBuilder { final FhirEncoders fhirEncoders; @Nonnull - final Database database; + final Dataset database; @Nonnull final TerminologyServiceFactory terminologyServiceFactory; diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/AggregateBenchmark.java b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/AggregateBenchmark.java index 0ce44e2684..404ba9893f 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/AggregateBenchmark.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/AggregateBenchmark.java @@ -35,6 +35,8 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.hl7.fhir.r4.model.Enumerations.ResourceType; import org.junit.jupiter.api.Tag; @@ -84,7 +86,7 @@ public static class AggregateState extends AbstractJmhSpringBootState { FhirEncoders fhirEncoders; AggregateExecutor executor; - Database database; + Dataset database; void mockResource(final ResourceType... resourceTypes) { TestHelpers.mockResource(database, spark, resourceTypes); diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/SearchDevBenchmark.java b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/SearchDevBenchmark.java index 3f4b4cf882..35946276cf 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/SearchDevBenchmark.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/SearchDevBenchmark.java @@ -34,6 +34,8 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -87,7 +89,7 @@ public static class SearchState extends AbstractJmhSpringBootState { FhirEncoders fhirEncoders; @Autowired - Database database; + Dataset database; @Bean @ConditionalOnMissingBean diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyBenchmarkTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyBenchmarkTest.java index dafef78995..94f33bb80d 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyBenchmarkTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyBenchmarkTest.java @@ -23,7 +23,6 @@ import au.csiro.pathling.aggregate.AggregateResponse; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; -import au.csiro.pathling.io.CacheableDatabase; import au.csiro.pathling.terminology.TerminologyServiceFactory; import au.csiro.pathling.test.SharedMocks; import au.csiro.pathling.test.helpers.TestHelpers; @@ -31,6 +30,8 @@ import ca.uhn.fhir.parser.IParser; import java.util.Optional; import javax.annotation.Nonnull; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.hl7.fhir.r4.model.Enumerations.ResourceType; import org.junit.jupiter.api.BeforeEach; @@ -71,7 +72,7 @@ public class TerminologyBenchmarkTest { FhirEncoders fhirEncoders; @MockBean - CacheableDatabase database; + Dataset database; AggregateExecutor defaultExecutor; diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyDevBenchmark.java b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyDevBenchmark.java index d35c01ff47..a4253bf176 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyDevBenchmark.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyDevBenchmark.java @@ -23,7 +23,6 @@ import au.csiro.pathling.aggregate.AggregateResponse; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; -import au.csiro.pathling.io.CacheableDatabase; import au.csiro.pathling.jmh.AbstractJmhSpringBootState; import au.csiro.pathling.terminology.DefaultTerminologyServiceFactory; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -33,6 +32,8 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.hl7.fhir.r4.model.Enumerations.ResourceType; import org.openjdk.jmh.annotations.Benchmark; @@ -86,7 +87,7 @@ public static class TerminologyState extends AbstractJmhSpringBootState { FhirEncoders fhirEncoders; @MockBean - CacheableDatabase database; + Dataset database; AggregateExecutor defaultExecutor; diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/integration/AsyncTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/integration/AsyncTest.java index 0045f24ec4..b9ec411548 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/integration/AsyncTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/integration/AsyncTest.java @@ -21,7 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import au.csiro.pathling.io.CacheableDatabase; import au.csiro.pathling.test.helpers.TestHelpers; import java.net.MalformedURLException; import java.net.URI; @@ -31,6 +30,8 @@ import java.util.List; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.hl7.fhir.r4.model.Enumerations.ResourceType; import org.junit.jupiter.api.Tag; @@ -60,7 +61,7 @@ class AsyncTest extends IntegrationTest { SparkSession spark; @MockBean - CacheableDatabase database; + Dataset database; @LocalServerPort int port; diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/integration/ExtractTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/integration/ExtractTest.java index 19ce0889bb..91f0aba01a 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/integration/ExtractTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/integration/ExtractTest.java @@ -22,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import au.csiro.pathling.io.CacheableDatabase; import au.csiro.pathling.test.helpers.TestHelpers; import ca.uhn.fhir.parser.IParser; import java.io.IOException; @@ -33,6 +32,8 @@ import java.net.URISyntaxException; import java.net.URL; import org.apache.commons.io.IOUtils; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.hl7.fhir.r4.model.Enumerations.ResourceType; import org.hl7.fhir.r4.model.Parameters; @@ -60,7 +61,7 @@ class ExtractTest extends IntegrationTest { IParser jsonParser; @MockBean - CacheableDatabase database; + Dataset database; @LocalServerPort int port; diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/integration/SearchTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/integration/SearchTest.java index a7a01f1307..a990d1eb78 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/integration/SearchTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/integration/SearchTest.java @@ -19,10 +19,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -import au.csiro.pathling.io.CacheableDatabase; import au.csiro.pathling.test.helpers.TestHelpers; import java.net.URI; import java.net.URISyntaxException; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.hl7.fhir.r4.model.Enumerations.ResourceType; import org.junit.jupiter.api.Tag; @@ -45,7 +46,7 @@ class SearchTest extends IntegrationTest { SparkSession spark; @MockBean - CacheableDatabase database; + Dataset database; @LocalServerPort int port; diff --git a/fhir-server/src/test/java/au/csiro/pathling/views/FhirViewExecutorTest.java b/fhir-server/src/test/java/au/csiro/pathling/views/FhirViewExecutorTest.java index 81d5a83d55..357db54f07 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/views/FhirViewExecutorTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/views/FhirViewExecutorTest.java @@ -17,11 +17,12 @@ package au.csiro.pathling.views; -import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import au.csiro.pathling.test.SpringBootUnitTest; import ca.uhn.fhir.context.FhirContext; import java.util.Optional; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; @@ -36,7 +37,7 @@ class FhirViewExecutorTest { SparkSession sparkSession; @Autowired - DataSource dataSource; + Dataset dataSource; @Autowired TerminologyServiceFactory terminologyServiceFactory; diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index f696ff1f93..23b5ba2715 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -19,11 +19,10 @@ import au.csiro.pathling.QueryHelpers.DatasetWithColumn; import au.csiro.pathling.config.QueryConfiguration; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import com.google.common.collect.Streams; @@ -58,14 +57,14 @@ public abstract class QueryExecutor { protected final SparkSession sparkSession; @Nonnull - protected final DataSource dataSource; + protected final Dataset dataSource; @Nonnull protected final Optional terminologyServiceFactory; protected QueryExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final DataSource dataSource, + @Nonnull final Dataset dataSource, @Nonnull final Optional terminologyServiceFactory) { this.configuration = configuration; this.fhirContext = fhirContext; @@ -76,14 +75,14 @@ protected QueryExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull protected List parseExpressions( - @Nonnull final ParserContext parserContext, + @Nonnull final EvaluationContext evaluationContext, @Nonnull final java.util.Collection expressions) { - return parseExpressions(parserContext, expressions, Optional.empty()); + return parseExpressions(evaluationContext, expressions, Optional.empty()); } @Nonnull protected List parseExpressions( - @Nonnull final ParserContext parserContext, + @Nonnull final EvaluationContext evaluationContext, @Nonnull final java.util.Collection expressions, @Nonnull final Optional> contextDataset) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java index e7398956a6..e1dbd0d2dd 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java @@ -22,7 +22,6 @@ import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.io.Database; -import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.List; @@ -55,7 +54,7 @@ public class AggregateQueryExecutor extends QueryExecutor { */ public AggregateQueryExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final DataSource dataSource, + @Nonnull final Dataset dataSource, @Nonnull final Optional terminologyServiceFactory) { super(configuration, fhirContext, sparkSession, dataSource, terminologyServiceFactory); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java index ab307aae05..5db7bfc303 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java @@ -1,18 +1,10 @@ package au.csiro.pathling.extract; -import static au.csiro.pathling.utilities.Preconditions.checkArgument; -import static java.util.stream.Collectors.toList; - import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.config.QueryConfiguration; -import au.csiro.pathling.fhirpath.AbstractPath; import au.csiro.pathling.fhirpath.annotations.NotImplemented; -import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.StringCoercible; -import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; -import java.util.List; import java.util.Optional; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; @@ -31,7 +23,7 @@ public class ExtractQueryExecutor extends QueryExecutor { public ExtractQueryExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final DataSource dataSource, + @Nonnull final Dataset dataSource, @Nonnull final Optional terminologyServiceFactory) { super(configuration, fhirContext, sparkSession, dataSource, terminologyServiceFactory); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/EvaluationContext.java similarity index 82% rename from fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java rename to fhirpath/src/main/java/au/csiro/pathling/fhirpath/EvaluationContext.java index 39b7e69f51..35bd245074 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/ParserContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/EvaluationContext.java @@ -15,29 +15,30 @@ * limitations under the License. */ -package au.csiro.pathling.fhirpath.parser; +package au.csiro.pathling.fhirpath; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.function.NamedFunction; import au.csiro.pathling.fhirpath.function.registry.FunctionRegistry; -import au.csiro.pathling.io.source.DataSource; +import au.csiro.pathling.fhirpath.parser.ConstantReplacer; import au.csiro.pathling.terminology.TerminologyService; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.Optional; import javax.annotation.Nonnull; import lombok.Getter; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; /** - * Context and dependencies used in the parsing of a FHIRPath expression. + * Context and dependencies used in the evaluation of a FHIRPath expression. * * @author John Grimes */ -// TODO: Split into two classes, one for parsing and one for evaluation @Getter -public class ParserContext { +public class EvaluationContext { /** * The input context from which the FHIRPath is to be evaluated, referred to through @@ -79,7 +80,7 @@ public class ParserContext { * A table resolver for retrieving Datasets for resource references. */ @Nonnull - private final DataSource dataSource; + private final Dataset dataset; /** * A registry of FHIRPath function implementations. @@ -106,35 +107,36 @@ public class ParserContext { * @param fhirContext a {@link FhirContext} that can be used to do FHIR stuff * @param sparkSession a {@link SparkSession} that can be used to resolve Spark queries required * for this expression - * @param dataSource for retrieving data relating to resource references + * @param dataset for retrieving data relating to resource references * @param terminologyServiceFactory a factory for {@link TerminologyService} objects, used for * parallel processing * @param constantReplacer a list of constants and the expressions that they should be replaced * with */ - public ParserContext(@Nonnull final Collection inputContext, @Nonnull final ResourceCollection resource, + public EvaluationContext(@Nonnull final Collection inputContext, + @Nonnull final ResourceCollection resource, @Nonnull final FhirContext fhirContext, - @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataSource, - @Nonnull final FunctionRegistry functionRegistry, + @Nonnull final SparkSession sparkSession, @Nonnull final Dataset dataset, + @Nonnull final FunctionRegistry functionRegistry, @Nonnull final Optional terminologyServiceFactory, @Nonnull final Optional constantReplacer) { this.inputContext = inputContext; this.resource = resource; this.fhirContext = fhirContext; this.sparkSession = sparkSession; - this.dataSource = dataSource; + this.dataset = dataset; this.functionRegistry = functionRegistry; this.terminologyServiceFactory = terminologyServiceFactory; this.constantReplacer = constantReplacer; } /** - * @return a new {@link ParserContext} with the same properties as this one, but with a different - * input context + * @return a new {@link EvaluationContext} with the same properties as this one, but with a + * different input context */ @Nonnull - public ParserContext withInputContext(@Nonnull final Collection inputContext) { - return new ParserContext(inputContext, resource, fhirContext, sparkSession, dataSource, + public EvaluationContext withInputContext(@Nonnull final Collection inputContext) { + return new EvaluationContext(inputContext, resource, fhirContext, sparkSession, dataset, functionRegistry, terminologyServiceFactory, constantReplacer); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java index 00194820fa..63ce9e9517 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java @@ -1,9 +1,9 @@ package au.csiro.pathling.fhirpath; import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import javax.annotation.Nonnull; import java.util.function.Function; +import javax.annotation.Nonnull; +import org.apache.spark.sql.Column; /** * A description of how to take one {@link Collection} and transform it into another. @@ -14,5 +14,17 @@ */ @FunctionalInterface public interface FhirPath { - O apply(@Nonnull final I input, @Nonnull final ParserContext context); + + O apply(@Nonnull final I input, @Nonnull final EvaluationContext context); + + static Column applyOperation(@Nonnull final EvaluationContext context, + @Nonnull final Collection input, @Nonnull final Function singularOperation, + @Nonnull final Function collectionOperation) { + if (input.isSingular(context)) { + return singularOperation.apply(input.getColumn()); + } else { + return collectionOperation.apply(input.getColumn()); + } + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java index 028074ef67..9b2064c2ea 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPathAndContext.java @@ -1,12 +1,11 @@ package au.csiro.pathling.fhirpath; import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.parser.ParserContext; import javax.annotation.Nonnull; import lombok.Value; /** - * Holds the value of a {@link Collection} and an associated {@link ParserContext}. + * Holds the value of a {@link Collection} and an associated {@link EvaluationContext}. * * @author John Grimes */ @@ -17,6 +16,6 @@ public class FhirPathAndContext { Collection result; @Nonnull - ParserContext context; + EvaluationContext context; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FunctionInput.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FunctionInput.java index 3d1a9ec247..cf818976fe 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FunctionInput.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FunctionInput.java @@ -18,7 +18,6 @@ package au.csiro.pathling.fhirpath; import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.parser.ParserContext; import java.util.List; import javax.annotation.Nonnull; import lombok.Value; @@ -35,7 +34,7 @@ public class FunctionInput { * Context and dependencies for use in evaluating the function. */ @Nonnull - ParserContext context; + EvaluationContext context; /** * The collection that is the input to the function, i.e. the result of the evaluation of the 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 dc82b4ba64..9d797f4ca4 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 @@ -18,6 +18,7 @@ package au.csiro.pathling.fhirpath.collection; import au.csiro.pathling.fhirpath.Comparable; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.Numeric; import au.csiro.pathling.fhirpath.definition.ElementDefinition; @@ -33,9 +34,9 @@ import lombok.AllArgsConstructor; import lombok.Getter; import org.apache.spark.sql.Column; -import org.apache.spark.sql.SparkSession; import org.apache.spark.sql.functions; import org.apache.spark.sql.types.ArrayType; +import org.apache.spark.sql.types.DataType; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** @@ -189,22 +190,37 @@ public static Optional> classForType( } /** - * @param spark a {@link SparkSession} to help with determining the schema of the result + * @param context an {@link EvaluationContext} * @return whether the result of evaluating this path is a non-singular collection */ - public boolean isSingular(@Nonnull final SparkSession spark) { - return spark.emptyDataFrame().select(column).schema() + public boolean isSingular(@Nonnull final EvaluationContext context) { + return context.getDataset().select(column).schema() .fields()[0].dataType() instanceof ArrayType; } + private static boolean needsFlattening(@Nonnull final Column column, + @Nonnull final EvaluationContext context) { + final boolean arrayOfArrays; + final DataType dataType = context.getDataset().select(column).schema().fields()[0].dataType(); + if (dataType instanceof ArrayType) { + final ArrayType arrayType = (ArrayType) dataType; + arrayOfArrays = arrayType.elementType() instanceof ArrayType; + } else { + arrayOfArrays = false; + } + return arrayOfArrays; + } + /** * Return the child {@link Collection} that results from traversing to the given expression. * * @param expression the name of the child element + * @param context an {@link EvaluationContext} * @return a new {@link Collection} representing the child element */ @Nonnull - public Optional traverse(@Nonnull final String expression) { + public Optional traverse(@Nonnull final String expression, + @Nonnull final EvaluationContext context) { // It is only possible to traverse to a child with an element definition. return definition.filter(def -> def instanceof ElementDefinition) .map(def -> (ElementDefinition) def) @@ -213,7 +229,11 @@ public Optional traverse(@Nonnull final String expression) { .map(def -> { // Build a new FhirPath object, with a column that uses `getField` to extract the // appropriate child. - return Collection.build(column.getField(expression), def); + final Column traversed = column.getField(expression); + final Column flattened = needsFlattening(traversed, context) + ? functions.flatten(traversed) + : traversed; + return Collection.build(flattened, def); }); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java index 9df20cffc8..d00c159c7b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java @@ -3,6 +3,7 @@ import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.definition.ChoiceElementDefinition; import au.csiro.pathling.fhirpath.definition.ElementDefinition; @@ -61,7 +62,8 @@ public static MixedCollection build(@Nonnull final Column column, @Nonnull @Override - public Optional traverse(@Nonnull final String expression) { + public Optional traverse(@Nonnull final String expression, + final EvaluationContext context) { return Optional.empty(); } @@ -75,11 +77,13 @@ private Optional resolveChoiceDefinition(@Nonnull final Strin * type. * * @param type The type of element to return + * @param context The {@link EvaluationContext} to use * @return A new collection representing just the elements of this collection with the specified * type */ @Nonnull - public Optional resolveChoice(@Nonnull final String type) { + public Optional resolveChoice(@Nonnull final String type, + @Nonnull final EvaluationContext context) { final String elementName = choiceDefinition.getElementName(); final String columnName = ChoiceElementDefinition.getColumnName(elementName, type); if (from instanceof ResourceCollection) { @@ -90,7 +94,7 @@ public Optional resolveChoice(@Nonnull final String type) { "No such child: " + columnName); return elementColumn.map(column -> Collection.build(column, definition.get())); } - return from.traverse(columnName); + return from.traverse(columnName, context); } } 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 7ca0d8c57a..d9e5222f50 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 @@ -21,6 +21,7 @@ import au.csiro.pathling.encoders.EncoderBuilder; import au.csiro.pathling.encoders.ExtensionSupport; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.definition.NodeDefinition; import au.csiro.pathling.fhirpath.definition.ResourceDefinition; @@ -97,13 +98,13 @@ private static Optional getFhirType(@Nonnull final ResourceType * Build a new ResourcePath using the supplied {@link FhirContext} and {@link DataSource}. * * @param fhirContext the {@link FhirContext} to use for sourcing the resource definition - * @param dataSource the {@link DataSource} to use for retrieving the Dataset + * @param dataset the {@link Dataset} that contains the resource data * @param resourceType the type of the resource * @return A shiny new ResourcePath */ @Nonnull public static ResourceCollection build(@Nonnull final FhirContext fhirContext, - @Nonnull final DataSource dataSource, @Nonnull final ResourceType resourceType) { + @Nonnull final Dataset dataset, @Nonnull final ResourceType resourceType) { // Get the resource definition from HAPI. final String resourceCode = resourceType.toCode(); @@ -111,9 +112,6 @@ public static ResourceCollection build(@Nonnull final FhirContext fhirContext, resourceCode); final ResourceDefinition definition = new ResourceDefinition(resourceType, hapiDefinition); - // Retrieve the dataset for the resource type using the supplied resource reader. - final Dataset dataset = dataSource.read(resourceType); - //noinspection ReturnOfNull final Map elementsToColumns = Stream.of(dataset.columns()) .collect(Collectors.toMap(Function.identity(), functions::col, (a, b) -> null)); @@ -170,7 +168,8 @@ public ResourceType getResourceType() { @Nonnull @Override - public Optional traverse(@Nonnull final String expression) { + public Optional traverse(@Nonnull final String expression, + final EvaluationContext context) { // Get the child column from the map of elements to columns. return getElementColumn(expression).flatMap(value -> // Get the child element definition from the resource definition. diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java index c0ae61167c..f8f2bf3063 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java @@ -17,8 +17,16 @@ package au.csiro.pathling.fhirpath.function; +import static au.csiro.pathling.fhirpath.FhirPath.applyOperation; +import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; + +import au.csiro.pathling.fhirpath.FunctionInput; import au.csiro.pathling.fhirpath.annotations.Name; import au.csiro.pathling.fhirpath.annotations.NotImplemented; +import au.csiro.pathling.fhirpath.collection.Collection; +import java.util.function.Function; +import javax.annotation.Nonnull; +import org.apache.spark.sql.Column; /** * This function allows the selection of only the first element of a collection. @@ -31,19 +39,16 @@ @NotImplemented public class FirstFunction implements NamedFunction { - // TODO: implement as columns + @Nonnull + @Override + public Collection invoke(@Nonnull final FunctionInput input) { + checkNoArguments(getName(), input); + + final Collection inputCollection = input.getInput(); + final Column result = applyOperation(input.getContext(), + inputCollection, Function.identity(), column -> column.getItem(0)); - // @Nonnull - // @Override - // public Collection invoke(@Nonnull final FunctionInput input) { - // checkNoArguments(getName(), input); - // - // final NonLiteralPath inputPath = input.getInput(); - // final Dataset dataset = inputPath.getOrderedDataset(nesting); - // final String expression = expressionFromInput(input, NAME, input.getInput()); - // final Column aggregateColumn = first(inputPath.getValueColumn(), true); - // - // return buildAggregateResult(dataset, input.getContext(), inputPath, aggregateColumn, - // expression); - // } + return Collection.build(result, inputCollection.getType(), + inputCollection.getFhirType(), inputCollection.getDefinition()); + } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java index 90f3453289..3cf9a1bb4b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/NamedFunction.java @@ -17,9 +17,7 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.utilities.Preconditions.check; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import static java.util.Objects.nonNull; import static java.util.Objects.requireNonNull; import au.csiro.pathling.fhirpath.FunctionInput; @@ -49,7 +47,7 @@ default String getName() { * @return a {@link Collection} object representing the resulting expression */ @Nonnull - default O invoke(@Nonnull FunctionInput input) { + default O invoke(@Nonnull final FunctionInput input) { throw new UnsupportedOperationException("Not implemented: " + getName()); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java index d2759871ce..c2f4340b38 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java @@ -56,7 +56,7 @@ public Collection invoke(@Nonnull final FunctionInput input) { Optional.empty()), input.getContext()); final SparkSession spark = input.getContext().getSparkSession(); final FhirPathType type = checkPresent(result.getType()); - checkUserInput(type.equals(FhirPathType.BOOLEAN) && result.isSingular(spark), + checkUserInput(type.equals(FhirPathType.BOOLEAN) && result.isSingular(input.getContext()), "Argument to " + getName() + " function must be a singular Boolean"); return result.getColumn(); }); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperatorInput.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperatorInput.java index 6076875c04..61652d5065 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperatorInput.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperatorInput.java @@ -17,8 +17,8 @@ package au.csiro.pathling.fhirpath.operator; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.parser.ParserContext; import javax.annotation.Nonnull; import lombok.Value; @@ -34,7 +34,7 @@ public class BinaryOperatorInput { * Context and dependencies for use in evaluating the function. */ @Nonnull - ParserContext context; + EvaluationContext context; /** * An expression representing the left operand. diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java index ab832d1ed1..f4f346551a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java @@ -20,12 +20,12 @@ import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static org.apache.spark.sql.functions.when; -import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.Collection; import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; -import org.apache.spark.sql.SparkSession; /** * Provides the functionality of the family of boolean operators within FHIRPath, i.e. and, or, xor @@ -51,15 +51,15 @@ public BooleanOperator(final BooleanOperatorType type) { public Collection invoke(@Nonnull final BinaryOperatorInput input) { final Collection left = input.getLeft(); final Collection right = input.getRight(); - final SparkSession spark = input.getContext().getSparkSession(); + final EvaluationContext context = input.getContext(); checkUserInput(left instanceof BooleanCollection, "Left operand to " + type + " operator must be Boolean"); - checkUserInput(left.isSingular(spark), + checkUserInput(left.isSingular(context), "Left operand to " + type + " operator must be singular"); checkUserInput(right instanceof BooleanCollection, "Right operand to " + type + " operator must be Boolean"); - checkUserInput(right.isSingular(spark), + checkUserInput(right.isSingular(context), "Right operand to " + type + " operator must be singular"); final Column leftValue = left.getColumn(); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index 6974c2732c..1b948f039a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -52,20 +52,6 @@ */ class InvocationVisitor extends FhirPathBaseVisitor> { - @Nonnull - private final ParserContext context; - - /** - * This constructor is used when there is no explicit invoker, i.e. an invocation is made without - * an expression on the left-hand side of the dot notation. In this case, the invoker is taken to - * be either the root node, or the `$this` node in the context of functions that support it. - * - * @param context The {@link ParserContext} to use when parsing the invocation - */ - InvocationVisitor(@Nonnull final ParserContext context) { - this.context = context; - } - /** * This method gets called when an element is on the right-hand side of the invocation expression, * or when an identifier is referred to as a term (e.g. "Encounter" or "type"). @@ -81,10 +67,10 @@ public FhirPath visitMemberInvocation( requireNonNull(fhirPath); // TODO: refactor to an expression - return (input, c) -> { + return (input, context) -> { try { // Attempt path traversal. - final Optional result = input.traverse(fhirPath); + final Optional result = input.traverse(fhirPath, context); checkUserInput(result.isPresent(), "No such child: " + fhirPath); return result.get(); @@ -119,7 +105,7 @@ public FhirPath visitFunctionInvocation( final String functionIdentifier = requireNonNull(ctx).function().identifier().getText(); @Nullable final ParamListContext paramList = ctx.function().paramList(); - final Visitor paramListVisitor = new Visitor(context); + final Visitor paramListVisitor = new Visitor(); final List> arguments = Optional.ofNullable(paramList) .map(ParamListContext::expression) .map(p -> p.stream() @@ -128,14 +114,14 @@ public FhirPath visitFunctionInvocation( ).orElse(new ArrayList<>()); // TODO: refactor to an expression - return (input, c) -> { + return (input, context) -> { final NamedFunction function; try { - function = c.getFunctionRegistry().getInstance(functionIdentifier); + function = context.getFunctionRegistry().getInstance(functionIdentifier); } catch (final NoSuchFunctionException e) { throw new InvalidUserInputError(e.getMessage()); } - final FunctionInput functionInput = new FunctionInput(c, input, arguments); + final FunctionInput functionInput = new FunctionInput(context, input, arguments); return function.invoke(functionInput); }; } @@ -144,7 +130,7 @@ public FhirPath visitFunctionInvocation( @Nonnull public FhirPath visitThisInvocation( @Nullable final ThisInvocationContext ctx) { - return (input, c) -> input; + return (input, context) -> input; } @Override diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java index 9dc35634c3..69fadbc782 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java @@ -62,7 +62,7 @@ public FhirPath visitStringLiteral( @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return (input, c) -> StringCollection.fromLiteral(fhirPath); + return (input, context) -> StringCollection.fromLiteral(fhirPath); } @Override @@ -70,7 +70,7 @@ public FhirPath visitDateLiteral(@Nullable final DateLit @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return (input, c) -> { + return (input, context) -> { try { return DateCollection.fromLiteral(fhirPath); } catch (final ParseException e) { @@ -86,7 +86,7 @@ public FhirPath visitDateTimeLiteral( @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return (input, c) -> { + return (input, context) -> { try { return DateTimeCollection.fromLiteral(fhirPath); } catch (final ParseException e) { @@ -101,7 +101,7 @@ public FhirPath visitTimeLiteral(@Nullable final TimeLit @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return (input, c) -> TimeCollection.fromLiteral(fhirPath); + return (input, context) -> TimeCollection.fromLiteral(fhirPath); } @Override @@ -111,7 +111,7 @@ public FhirPath visitNumberLiteral( @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return (input, c) -> { + return (input, context) -> { // The FHIRPath grammar lumps these two types together, so we tease them apart by trying to // parse them. try { @@ -134,13 +134,13 @@ public FhirPath visitBooleanLiteral( @Nullable final String fhirPath = ctx.getText(); requireNonNull(fhirPath); - return (input, c) -> BooleanCollection.fromLiteral(fhirPath); + return (input, context) -> BooleanCollection.fromLiteral(fhirPath); } @Override @Nonnull public FhirPath visitNullLiteral(@Nullable final NullLiteralContext ctx) { - return (input, c) -> Collection.nullCollection(); + return (input, context) -> Collection.nullCollection(); } @Override @@ -152,7 +152,7 @@ public FhirPath visitQuantityLiteral( requireNonNull(number); @Nullable final TerminalNode ucumUnit = ctx.quantity().unit().STRING(); - return (input, c) -> { + return (input, context) -> { if (ucumUnit == null) { // Create a calendar duration literal. final String fhirPath = String.format("%s %s", number, ctx.quantity().unit().getText()); @@ -176,7 +176,7 @@ public FhirPath visitCodingLiteral( @Nullable final String fhirPath = requireNonNull(ctx).getText(); requireNonNull(fhirPath); - return (input, c) -> { + return (input, context) -> { try { return CodingCollection.fromLiteral(fhirPath); } catch (final IllegalArgumentException e) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java index 46d01b0666..0fbcec15f7 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Parser.java @@ -17,6 +17,7 @@ package au.csiro.pathling.fhirpath.parser; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.parser.generated.FhirPathLexer; @@ -36,29 +37,22 @@ @Getter public class Parser { - @Nonnull - private final ParserContext context; - - /** - * @param context The {@link ParserContext} that this parser should use when parsing expressions - */ - public Parser(@Nonnull final ParserContext context) { - this.context = context; - } - /** * Evaluates a FHIRPath expression. * * @param expression The String representation of the FHIRPath expression + * @param context An {@link EvaluationContext} that provides dependencies and context for the * + * evaluation of the expression * @return a new {@link Collection} object */ @Nonnull - public Collection evaluate(@Nonnull final String expression) { - final FhirPath fhirPath = parse(expression); + public Collection evaluate(@Nonnull final String expression, + @Nonnull final EvaluationContext context) { + final FhirPath fhirPath = parse(expression); return fhirPath.apply(context.getInputContext(), context); } - + /** * Parses a FHIRPath expression. * @@ -66,13 +60,12 @@ public Collection evaluate(@Nonnull final String expression) { * @return a new {@link Collection} object */ @Nonnull - public FhirPath parse(@Nonnull final String expression) { + public FhirPath parse(@Nonnull final String expression) { final FhirPathParser parser = buildParser(expression); - final Visitor visitor = new Visitor(context); - return visitor.visit(parser.expression()); + return new Visitor().visit(parser.expression()); } - + @Nonnull static FhirPathParser buildParser(final @Nonnull String expression) { final FhirPathLexer lexer = new FhirPathLexer(CharStreams.fromString(expression)); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java index 3f31760698..c58e8c85b1 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TermVisitor.java @@ -37,17 +37,11 @@ */ class TermVisitor extends FhirPathBaseVisitor> { - @Nonnull - private final ParserContext context; - - TermVisitor(@Nonnull final ParserContext context) { - this.context = context; - } - @Override @Nonnull - public FhirPath visitInvocationTerm(@Nullable final InvocationTermContext ctx) { - return new InvocationVisitor(context).visit(requireNonNull(ctx).invocation()); + public FhirPath visitInvocationTerm( + @Nullable final InvocationTermContext ctx) { + return new InvocationVisitor().visit(requireNonNull(ctx).invocation()); } @Override @@ -71,8 +65,8 @@ public FhirPath visitParenthesizedTerm( @Nullable final ParenthesizedTermContext ctx) { // TODO: maybe we do not need that and just use the subExpression directly? // Parentheses are ignored in the standalone term case. - final FhirPath subExpression = new Visitor(context).visit( + final FhirPath subExpression = new Visitor().visit( requireNonNull(ctx).expression()); - return (input, c) -> subExpression.apply(input, c); + return (input, context) -> subExpression.apply(input, context); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java index ed4c876fff..b738ce6e63 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java @@ -22,8 +22,6 @@ import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.operator.BinaryOperator; -import au.csiro.pathling.fhirpath.operator.BinaryOperatorInput; import au.csiro.pathling.fhirpath.operator.BinaryOperatorType; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.AdditiveExpressionContext; @@ -41,9 +39,9 @@ import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.TermExpressionContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.TypeExpressionContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.UnionExpressionContext; +import au.csiro.pathling.fhirpath.path.EvalOperatorPath; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import au.csiro.pathling.fhirpath.path.EvalOperatorPath; import org.antlr.v4.runtime.tree.ParseTree; /** @@ -54,13 +52,6 @@ */ class Visitor extends FhirPathBaseVisitor> { - @Nonnull - private final ParserContext context; - - Visitor(@Nonnull final ParserContext context) { - this.context = context; - } - /** * A term is typically a standalone literal or function invocation. * @@ -71,7 +62,7 @@ class Visitor extends FhirPathBaseVisitor> { @Nonnull public FhirPath visitTermExpression( @Nullable final TermExpressionContext ctx) { - return requireNonNull(ctx).term().accept(new TermVisitor(context)); + return requireNonNull(ctx).term().accept(new TermVisitor()); } /** @@ -87,13 +78,13 @@ public FhirPath visitInvocationExpression( // TODO: Is this really OK (now I am a bit confused of what the context is vs input) - final FhirPath invocationSubject = new Visitor(context).visit( + final FhirPath invocationSubject = new Visitor().visit( requireNonNull(ctx).expression()); final FhirPath invocationVerb = ctx.invocation() - .accept(new InvocationVisitor(context)); - return (input, c) -> { + .accept(new InvocationVisitor()); + return (input, context) -> { // TODO: perhpas we should also create the new cotext here (with different %context) - return invocationVerb.apply(invocationSubject.apply(input, c), c); + return invocationVerb.apply(invocationSubject.apply(input, context), context); }; } @@ -102,8 +93,8 @@ private FhirPath visitBinaryOperator( @Nullable final ParseTree leftContext, @Nullable final ParseTree rightContext, @Nullable final String operatorName) { requireNonNull(operatorName); - return new EvalOperatorPath(new Visitor(context).visit(leftContext), - new Visitor(context).visit(rightContext), + return new EvalOperatorPath(new Visitor().visit(leftContext), + new Visitor().visit(rightContext), BinaryOperatorType.fromSymbol(operatorName).getInstance()); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/EvalOperatorPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/EvalOperatorPath.java index d61b8fae4d..70bd8b43c2 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/EvalOperatorPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/EvalOperatorPath.java @@ -17,23 +17,25 @@ package au.csiro.pathling.fhirpath.path; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.operator.BinaryOperator; import au.csiro.pathling.fhirpath.operator.BinaryOperatorInput; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import lombok.Value; import javax.annotation.Nonnull; +import lombok.Value; @Value public class EvalOperatorPath implements FhirPath { - FhirPath leftPath; - FhirPath rightPath; + FhirPath leftPath; + FhirPath rightPath; BinaryOperator operator; - + @Override - public Collection apply(@Nonnull final Collection input, @Nonnull final ParserContext context) { - return operator.invoke(new BinaryOperatorInput(context, leftPath.apply(input, context), rightPath.apply(input, context))); + public Collection apply(@Nonnull final Collection input, + @Nonnull final EvaluationContext context) { + return operator.invoke(new BinaryOperatorInput(context, leftPath.apply(input, context), + rightPath.apply(input, context))); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/ExtConsFhirPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/ExtConsFhirPath.java index 4254a8a4f4..a466f688d5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/ExtConsFhirPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/ExtConsFhirPath.java @@ -17,11 +17,11 @@ package au.csiro.pathling.fhirpath.path; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import lombok.Value; import javax.annotation.Nonnull; +import lombok.Value; @Value public class ExtConsFhirPath implements FhirPath { @@ -29,7 +29,8 @@ public class ExtConsFhirPath implements FhirPath { String name; @Override - public Collection apply(@Nonnull final Collection input, @Nonnull final ParserContext context) { + public Collection apply(@Nonnull final Collection input, + @Nonnull final EvaluationContext context) { if (name.equals("%context")) { return context.getInputContext(); } else if (name.equals("%resource") || name.equals("%rootResource")) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index 6d98db005f..06e735e7e7 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -3,14 +3,13 @@ import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.function.registry.StaticFunctionRegistry; import au.csiro.pathling.fhirpath.parser.ConstantReplacer; import au.csiro.pathling.fhirpath.parser.Parser; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.ArrayList; @@ -38,18 +37,18 @@ public class FhirViewExecutor { private final SparkSession sparkSession; @Nonnull - private final DataSource dataSource; + private final Dataset dataset; @Nonnull private final Optional terminologyServiceFactory; public FhirViewExecutor(@Nonnull final FhirContext fhirContext, - @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataSource, + @Nonnull final SparkSession sparkSession, @Nonnull final Dataset dataset, @Nonnull final Optional terminologyServiceFactory) { this.fhirContext = fhirContext; this.sparkSession = sparkSession; - this.dataSource = dataSource; + this.dataset = dataset; this.terminologyServiceFactory = terminologyServiceFactory; } @@ -63,13 +62,14 @@ public Dataset buildQuery(@Nonnull final FhirView view) { // Build a new expression parser, and parse all the column expressions within the query. final ResourceType resourceType = ResourceType.fromCode(view.getResource()); - final ResourceCollection inputContext = ResourceCollection.build(fhirContext, dataSource, + final ResourceCollection inputContext = ResourceCollection.build(fhirContext, dataset, resourceType); - final ParserContext parserContext = new ParserContext(inputContext, inputContext, fhirContext, - sparkSession, dataSource, StaticFunctionRegistry.getInstance(), terminologyServiceFactory, + final EvaluationContext evaluationContext = new EvaluationContext(inputContext, inputContext, + fhirContext, + sparkSession, dataset, StaticFunctionRegistry.getInstance(), terminologyServiceFactory, constantReplacer); - final List select = parseSelect(view.getSelect(), parserContext, + final List select = parseSelect(view.getSelect(), evaluationContext, Collections.emptyList()); final List where = view.getWhere() == null ? Collections.emptyList() @@ -77,21 +77,19 @@ public Dataset buildQuery(@Nonnull final FhirView view) { .map(WhereClause::getExpression) .collect(toList()); final Optional filterCondition = where.stream() - .map(e -> evalExpression(e, parserContext)) + .map(e -> evalExpression(e, evaluationContext)) .map(Collection::getColumn) .reduce(Column::and); - final Dataset subjectDataset = dataSource.read(resourceType); - return filterCondition - .map(subjectDataset::filter) - .orElse(subjectDataset) + .map(dataset::filter) + .orElse(dataset) .select(select.toArray(new Column[0])); } @Nonnull private List parseSelect(@Nonnull final List selectGroup, - @Nonnull final ParserContext context, @Nonnull final List currentSelection) { + @Nonnull final EvaluationContext context, @Nonnull final List currentSelection) { final List newSelection = new ArrayList<>(currentSelection); for (final SelectClause select : selectGroup) { @@ -115,7 +113,7 @@ private List parseSelect(@Nonnull final List selectGroup, } @Nonnull - private List directSelection(@Nonnull final ParserContext context, + private List directSelection(@Nonnull final EvaluationContext context, @Nonnull final DirectSelection select, @Nonnull final List currentSelection) { final Collection path = evalExpression(select.getPath(), context); final List newColumns = new ArrayList<>(currentSelection); @@ -125,7 +123,7 @@ private List directSelection(@Nonnull final ParserContext context, @Nonnull @NotImplemented - private List nestedSelection(final @Nonnull ParserContext context, + private List nestedSelection(final @Nonnull EvaluationContext context, @Nonnull final NestedSelectClause select, @Nonnull final List currentSelection, final boolean unnest) { final Collection from = evalExpression(select.getPath(), context); @@ -134,18 +132,17 @@ private List nestedSelection(final @Nonnull ParserContext context, ? from // .unnest() : from; - final ParserContext nextContext = context.withInputContext(nextInputContext); + final EvaluationContext nextContext = context.withInputContext(nextInputContext); return parseSelect(select.getSelect(), nextContext, currentSelection); } @Nonnull private Collection evalExpression(@Nonnull final String expression, - @Nonnull final ParserContext context) { - final Parser parser = new Parser(context); + @Nonnull final EvaluationContext context) { final String updatedExpression = context.getConstantReplacer() .map(replacer -> replacer.execute(expression)) .orElse(expression); - return parser.evaluate(updatedExpression); + return new Parser().evaluate(updatedExpression, context); } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/EvaluationContextBuilder.java similarity index 50% rename from fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java rename to fhirpath/src/test/java/au/csiro/pathling/test/builders/EvaluationContextBuilder.java index 1f70149734..4a9558b88d 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ParserContextBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/EvaluationContextBuilder.java @@ -18,38 +18,34 @@ package au.csiro.pathling.test.builders; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.fhirpath.function.NamedFunction; +import au.csiro.pathling.fhirpath.function.registry.FunctionRegistry; import au.csiro.pathling.fhirpath.function.registry.StaticFunctionRegistry; -import au.csiro.pathling.fhirpath.parser.ParserContext; -import au.csiro.pathling.io.source.DataSource; +import au.csiro.pathling.fhirpath.parser.ConstantReplacer; import au.csiro.pathling.terminology.TerminologyServiceFactory; import au.csiro.pathling.test.DefaultAnswer; import ca.uhn.fhir.context.FhirContext; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.apache.spark.sql.Column; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; -import org.mockito.Mockito; /** * @author John Grimes */ -public class ParserContextBuilder { +public class EvaluationContextBuilder { @Nonnull - private ResourceCollection rootContext; + private Collection inputContext; @Nonnull - private Collection inputContext; + private ResourceCollection resource; @Nonnull private final FhirContext fhirContext; @@ -58,70 +54,74 @@ public class ParserContextBuilder { private final SparkSession spark; @Nonnull - private DataSource dataSource; + private Dataset dataset; + + @Nonnull + private FunctionRegistry functionRegistry; @Nullable private TerminologyServiceFactory terminologyServiceFactory; - @Nonnull - private List groupingColumns; - - @Nonnull - private final Map> nodeIdColumns; + @Nullable + private ConstantReplacer constantReplacer; - public ParserContextBuilder(@Nonnull final SparkSession spark, + public EvaluationContextBuilder(@Nonnull final SparkSession spark, @Nonnull final FhirContext fhirContext) { + inputContext = resource; + resource = mock(ResourceCollection.class); this.fhirContext = fhirContext; this.spark = spark; - rootContext = mock(ResourceCollection.class); - inputContext = rootContext; - dataSource = Mockito.mock(DataSource.class, new DefaultAnswer()); - groupingColumns = Collections.emptyList(); - nodeIdColumns = new HashMap<>(); + //noinspection unchecked + dataset = mock(Dataset.class, new DefaultAnswer()); + functionRegistry = new StaticFunctionRegistry(); + terminologyServiceFactory = mock(TerminologyServiceFactory.class); + constantReplacer = mock(ConstantReplacer.class); } @Nonnull - public ParserContextBuilder inputContext(@Nonnull final Collection inputContext) { + public EvaluationContextBuilder inputContext(@Nonnull final Collection inputContext) { this.inputContext = inputContext; return this; } - // @Nonnull - // public ParserContextBuilder inputExpression(@Nonnull final String inputExpression) { - // when(inputContext.getExpression()).thenReturn(inputExpression); - // return this; - // } + @Nonnull + public EvaluationContextBuilder resource(@Nonnull final ResourceCollection resource) { + this.resource = resource; + return this; + } - // @Nonnull - // public ParserContextBuilder idColumn(@Nonnull final Column idColumn) { - // when(inputContext.getIdColumn()).thenReturn(idColumn); - // return this; - // } + @Nonnull + public EvaluationContextBuilder dataset(@Nonnull final Dataset dataset) { + this.dataset = dataset; + return this; + } @Nonnull - public ParserContextBuilder database(@Nonnull final DataSource dataSource) { - this.dataSource = dataSource; + public EvaluationContextBuilder functionRegistry( + @Nonnull final FunctionRegistry functionRegistry) { + this.functionRegistry = functionRegistry; return this; } @Nonnull - public ParserContextBuilder terminologyClientFactory( + public EvaluationContextBuilder terminologyServiceFactory( @Nonnull final TerminologyServiceFactory terminologyServiceFactory) { this.terminologyServiceFactory = terminologyServiceFactory; return this; } @Nonnull - public ParserContextBuilder groupingColumns(@Nonnull final List groupingColumns) { - this.groupingColumns = groupingColumns; + public EvaluationContextBuilder constantReplacer( + @Nonnull final ConstantReplacer constantReplacer) { + this.constantReplacer = constantReplacer; return this; } @Nonnull - public ParserContext build() { - return new ParserContext(inputContext, rootContext, fhirContext, spark, dataSource, - StaticFunctionRegistry.getInstance(), - Optional.ofNullable(terminologyServiceFactory), Optional.empty()); + public EvaluationContext build() { + return new EvaluationContext(inputContext, resource, fhirContext, spark, dataset, + functionRegistry, Optional.ofNullable(terminologyServiceFactory), + Optional.ofNullable(constantReplacer)); } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java index 02df491eec..b60b3a5036 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java @@ -22,9 +22,9 @@ import static org.apache.spark.sql.functions.col; import au.csiro.pathling.encoders.datatypes.DecimalCustomCoder; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.encoding.QuantityEncoding; -import au.csiro.pathling.fhirpath.parser.ParserContext; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; @@ -182,14 +182,14 @@ public static Row rowForUcumQuantity(@Nonnull final String value, @Nonnull public static Dataset selectValuesAndNodes(@Nonnull final Dataset dataset, - @Nonnull final List paths, @Nonnull final ParserContext context) { + @Nonnull final List paths, @Nonnull final EvaluationContext context) { final List columns = new ArrayList<>(); columns.addAll( paths.stream() .map(Collection::getColumn) .collect(toList()) ); - + // TODO: review // columns.addAll(new ArrayList<>(context.getNesting().getOrderingColumns())); return dataset.select(columns.toArray(new Column[0])); diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/TestHelpers.java b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/TestHelpers.java index eb018cb483..1b5286e6a7 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/TestHelpers.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/TestHelpers.java @@ -48,21 +48,21 @@ public abstract class TestHelpers { public static final String UCUM_URL = "http://unitsofmeasure.org"; public static final MediaType FHIR_MEDIA_TYPE = new MediaType("application", "fhir+json"); - public static void mockResource(@Nonnull final DataSource dataSource, - @Nonnull final SparkSession spark, @Nonnull final ResourceType... resourceTypes) { - for (final ResourceType resourceType : resourceTypes) { - final Dataset dataset = getDatasetForResourceType(spark, resourceType); - when(dataSource.read(resourceType)).thenReturn(dataset); - } - } - - public static void mockCachedResource(@Nonnull final DataSource dataSource, - @Nonnull final SparkSession spark, @Nonnull final ResourceType... resourceTypes) { - for (final ResourceType resourceType : resourceTypes) { - final Dataset dataset = getDatasetForResourceType(spark, resourceType).cache(); - when(dataSource.read(resourceType)).thenReturn(dataset); - } - } + // public static void mockResource(@Nonnull final Dataset dataSource, + // @Nonnull final SparkSession spark, @Nonnull final ResourceType... resourceTypes) { + // for (final ResourceType resourceType : resourceTypes) { + // final Dataset dataset = getDatasetForResourceType(spark, resourceType); + // when(dataSource.read(resourceType)).thenReturn(dataset); + // } + // } + // + // public static void mockCachedResource(@Nonnull final Dataset dataSource, + // @Nonnull final SparkSession spark, @Nonnull final ResourceType... resourceTypes) { + // for (final ResourceType resourceType : resourceTypes) { + // final Dataset dataset = getDatasetForResourceType(spark, resourceType).cache(); + // when(dataSource.read(resourceType)).thenReturn(dataset); + // } + // } public static void mockResource(@Nonnull final DataSource dataSource, @Nonnull final SparkSession spark, final int numPartitions, diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java index 9f91dbbea2..f9ba4c4bc3 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -77,7 +77,7 @@ static void beforeAll() throws IOException { Stream requests() throws IOException { final ObjectMapper mapper = new ObjectMapper(); final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - final Resource[] resources = resolver.getResources("classpath:sql-on-fhir/input/tests/*.json"); + final Resource[] resources = resolver.getResources("classpath:sql-on-fhir/tests/v1/*.json"); return Stream.of(resources) // Get each test file. .map(resource -> { @@ -107,7 +107,7 @@ Stream requests() throws IOException { DataSource getDataSource(@Nonnull final JsonNode testDefinition) { try { // Create a parent directory based upon the test name. - final JsonNode resources = testDefinition.get("resource"); + final JsonNode resources = testDefinition.get("resources"); final Path directory = getTempDir(testDefinition); final FhirViewTestDataSource result = new FhirViewTestDataSource(); @@ -141,7 +141,7 @@ DataSource getDataSource(@Nonnull final JsonNode testDefinition) { List toTestParameters(@Nonnull final JsonNode testDefinition, @Nonnull final DataSource sourceData) { try { - final JsonNode views = testDefinition.get("views"); + final JsonNode views = testDefinition.get("tests"); final List result = new ArrayList<>(); for (final Iterator it = views.elements(); it.hasNext(); ) { @@ -156,15 +156,26 @@ List toTestParameters(@Nonnull final JsonNode testDefinition, final String expectedFileName = view.get("title").asText().replaceAll("\\W+", "_") + ".json"; final Path expectedPath = directory.resolve(expectedFileName); - for (final Iterator rowIt = view.get("result").elements(); rowIt.hasNext(); ) { + List expectedColumns = null; + for (final Iterator rowIt = view.get("expected").elements(); rowIt.hasNext(); ) { final JsonNode row = rowIt.next(); + + // Get the columns from the first row. + if (expectedColumns == null) { + final List columns = new ArrayList<>(); + row.fields().forEachRemaining(field -> columns.add(field.getKey())); + expectedColumns = columns; + } + + // Append the row to the file. Files.write(expectedPath, (row + "\n").getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.APPEND); } final String testName = - testDefinition.get("name").asText() + " - " + view.get("title").asText(); - result.add(new TestParameters(testName, sourceData, fhirView, expectedPath)); + testDefinition.get("title").asText() + " - " + view.get("title").asText(); + result.add( + new TestParameters(testName, sourceData, fhirView, expectedPath, expectedColumns)); } return result; @@ -176,7 +187,7 @@ List toTestParameters(@Nonnull final JsonNode testDefinition, @Nonnull private static Path getTempDir(final @Nonnull JsonNode testDefinition) throws IOException { - final String directoryName = testDefinition.get("name").asText().replaceAll("\\W+", "_"); + final String directoryName = testDefinition.get("title").asText().replaceAll("\\W+", "_"); final Path directory = tempDir.resolve(directoryName); try { return Files.createDirectory(directory); @@ -188,10 +199,13 @@ private static Path getTempDir(final @Nonnull JsonNode testDefinition) throws IO @ParameterizedTest @MethodSource("requests") void test(@Nonnull final TestParameters parameters) { + final FhirView view = parameters.getView(); final FhirViewExecutor executor = new FhirViewExecutor(fhirContext, spark, - parameters.getSourceData(), Optional.ofNullable(terminologyServiceFactory)); - final Dataset result = executor.buildQuery(parameters.getView()); - final Dataset expectedResult = spark.read().json(parameters.getExpectedJson().toString()); + parameters.getSourceData().read(view.getResource()), + Optional.ofNullable(terminologyServiceFactory)); + final Dataset result = executor.buildQuery(view); + final Dataset expectedResult = spark.read().json(parameters.getExpectedJson().toString()) + .selectExpr(parameters.getExpectedColumns().toArray(new String[0])); assertThat(result).hasRows(expectedResult); } @@ -202,6 +216,7 @@ static class TestParameters { DataSource sourceData; FhirView view; Path expectedJson; + List expectedColumns; @Override public String toString() { diff --git a/fhirpath/src/test/resources/sql-on-fhir b/fhirpath/src/test/resources/sql-on-fhir index 2de0f788f6..24b2132e87 160000 --- a/fhirpath/src/test/resources/sql-on-fhir +++ b/fhirpath/src/test/resources/sql-on-fhir @@ -1 +1 @@ -Subproject commit 2de0f788f64dcb78215a70ef737bbb9999382304 +Subproject commit 24b2132e871db35492c90478fa97a404b4023466 diff --git a/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java b/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java index 0ae6f5d7af..3f72a3708d 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java +++ b/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java @@ -23,7 +23,6 @@ import au.csiro.pathling.aggregate.AggregateQueryExecutor; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.extract.ExtractQueryExecutor; -import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.library.PathlingContext; import au.csiro.pathling.library.io.sink.DataSinkBuilder; import au.csiro.pathling.library.query.AggregateQuery; @@ -34,6 +33,8 @@ import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.hl7.fhir.r4.model.Enumerations.ResourceType; /** @@ -56,7 +57,7 @@ protected AbstractSource(@Nonnull final PathlingContext context) { @Nonnull private QueryDispatcher buildDispatcher(final @Nonnull PathlingContext context, - final DataSource dataSource) { + final Dataset dataSource) { // Use the default query configuration. final QueryConfiguration queryConfiguration = QueryConfiguration.builder().build(); From 36028aeb71b09f63fe663741d84f6c2e5add253f Mon Sep 17 00:00:00 2001 From: John Grimes Date: Fri, 8 Sep 2023 14:15:48 +1000 Subject: [PATCH 67/95] Get tests passing up until implementation of comparison --- .../pathling/fhirpath/collection/Collection.java | 12 ++++++------ .../pathling/fhirpath/function/WhereFunction.java | 4 +--- 2 files changed, 7 insertions(+), 9 deletions(-) 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 9d797f4ca4..bc74788a00 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 @@ -194,8 +194,8 @@ public static Optional> classForType( * @return whether the result of evaluating this path is a non-singular collection */ public boolean isSingular(@Nonnull final EvaluationContext context) { - return context.getDataset().select(column).schema() - .fields()[0].dataType() instanceof ArrayType; + return !(context.getDataset().select(column).schema() + .fields()[0].dataType() instanceof ArrayType); } private static boolean needsFlattening(@Nonnull final Column column, @@ -230,10 +230,10 @@ public Optional traverse(@Nonnull final String expression, // Build a new FhirPath object, with a column that uses `getField` to extract the // appropriate child. final Column traversed = column.getField(expression); - final Column flattened = needsFlattening(traversed, context) - ? functions.flatten(traversed) - : traversed; - return Collection.build(flattened, def); + // final Column flattened = needsFlattening(traversed, context) + // ? functions.flatten(traversed) + // : traversed; + return Collection.build(traversed, def); }); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java index c2f4340b38..2074300508 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java @@ -29,7 +29,6 @@ import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; -import org.apache.spark.sql.SparkSession; /** * Describes a function which can scope down the previous invocation within a FHIRPath expression, @@ -53,8 +52,7 @@ public Collection invoke(@Nonnull final FunctionInput input) { final Column column = filter(previous.getColumn(), element -> { final Collection result = argument.apply( Collection.build(element, previous.getType(), previous.getFhirType(), - Optional.empty()), input.getContext()); - final SparkSession spark = input.getContext().getSparkSession(); + previous.getDefinition()), input.getContext()); final FhirPathType type = checkPresent(result.getType()); checkUserInput(type.equals(FhirPathType.BOOLEAN) && result.isSingular(input.getContext()), "Argument to " + getName() + " function must be a singular Boolean"); From c81c945e53eab542890cf9dfa922f8e2c3b54221 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Fri, 8 Sep 2023 21:40:21 +1000 Subject: [PATCH 68/95] Get all basic tests passing --- .../security/ga4gh/PassportScopeEnforcer.java | 3 +- .../au/csiro/pathling/fhirpath/FhirPath.java | 6 +- .../au/csiro/pathling/fhirpath/Temporal.java | 6 +- .../collection/BooleanCollection.java | 11 +-- .../fhirpath/collection/CodingCollection.java | 13 +-- .../fhirpath/collection/Collection.java | 82 ++++++++++--------- .../fhirpath/collection/DateCollection.java | 14 ++-- .../collection/DateTimeCollection.java | 13 +-- .../collection/DecimalCollection.java | 17 ++-- .../collection/IntegerCollection.java | 17 ++-- .../fhirpath/collection/MixedCollection.java | 14 ++-- .../collection/QuantityCollection.java | 16 ++-- .../collection/ResourceCollection.java | 17 ++-- .../fhirpath/collection/StringCollection.java | 11 +-- .../fhirpath/collection/TimeCollection.java | 14 ++-- .../fhirpath/function/CountFunction.java | 2 +- .../fhirpath/function/EmptyFunction.java | 2 +- .../fhirpath/function/ExistsFunction.java | 2 +- .../fhirpath/function/FirstFunction.java | 4 +- .../fhirpath/function/WhereFunction.java | 8 +- .../fhirpath/operator/BooleanOperator.java | 6 +- .../fhirpath/operator/ComparisonOperator.java | 39 ++++----- .../fhirpath/parser/InvocationVisitor.java | 2 +- .../pathling/views/FhirViewExecutor.java | 2 +- 24 files changed, 170 insertions(+), 151 deletions(-) diff --git a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java index fef27b3114..838b8c253e 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java +++ b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java @@ -83,7 +83,8 @@ public Dataset enforce(@Nonnull final ResourceType subjectResource, // Build a new expression parser, and parse all the column expressions within the query. final ResourceCollection inputContext = ResourceCollection - .build(getFhirContext(), getDataSource(), subjectResource); + .build(getFhirContext(), getDataSource(), subjectResource, + ResourceCollection.isSingular()); return filterDataset(inputContext, filters, dataset, dataset.col("id"), Column::or); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java index 63ce9e9517..5e1b80da14 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java @@ -17,10 +17,10 @@ public interface FhirPath { O apply(@Nonnull final I input, @Nonnull final EvaluationContext context); - static Column applyOperation(@Nonnull final EvaluationContext context, - @Nonnull final Collection input, @Nonnull final Function singularOperation, + static Column applyOperation(@Nonnull final Collection input, + @Nonnull final Function singularOperation, @Nonnull final Function collectionOperation) { - if (input.isSingular(context)) { + if (input.isSingular()) { return singularOperation.apply(input.getColumn()); } else { return collectionOperation.apply(input.getColumn()); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java index 15f5a59c57..62fbe5673d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java @@ -38,8 +38,8 @@ public interface Temporal { /** * Gets a function that can take the {@link QuantityCollection} representing a time duration and * return a {@link Collection} that contains the result of date arithmetic operation for this path - * and the provided duration. The type of operation is controlled by supplying a {@link - * MathOperation}. + * and the provided duration. The type of operation is controlled by supplying a + * {@link MathOperation}. * * @param operation The {@link MathOperation} type to retrieve a result for * @return A {@link Function} that takes a {@link QuantityCollection} as its parameter, and @@ -84,7 +84,7 @@ static Function buildDateArithmeticOperation( final Column valueColumn = functions.callUDF(functionName, source.getColumn(), target.getColumn()); - return DateTimeCollection.build(valueColumn, Optional.empty()); + return DateTimeCollection.build(valueColumn, Optional.empty(), true); }; } 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 f05e54cf89..c038019b5a 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 @@ -45,8 +45,8 @@ public class BooleanCollection extends Collection implements Materializable type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition) { - super(column, type, fhirType, definition); + @Nonnull final Optional definition, final boolean singular) { + super(column, type, fhirType, definition, singular); } /** @@ -54,13 +54,14 @@ protected BooleanCollection(@Nonnull final Column column, * * @param column The column to use * @param definition The definition to use + * @param singular Whether the collection is singular * @return A new instance of {@link BooleanCollection} */ @Nonnull public static BooleanCollection build(@Nonnull final Column column, - @Nonnull final Optional definition) { + @Nonnull final Optional definition, final boolean singular) { return new BooleanCollection(column, Optional.of(FhirPathType.BOOLEAN), - Optional.of(FHIRDefinedType.BOOLEAN), definition); + Optional.of(FHIRDefinedType.BOOLEAN), definition, singular); } /** @@ -72,7 +73,7 @@ public static BooleanCollection build(@Nonnull final Column column, @Nonnull public static BooleanCollection fromLiteral(@Nonnull final String literal) { final boolean value = literal.equals("true"); - return BooleanCollection.build(lit(value), Optional.empty()); + return BooleanCollection.build(lit(value), Optional.empty(), true); } @Nonnull 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 925725d403..d15d450be6 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 @@ -48,8 +48,8 @@ public class CodingCollection extends Collection implements Materializable type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition) { - super(column, type, fhirType, definition); + @Nonnull final Optional definition, final boolean singular) { + super(column, type, fhirType, definition, singular); } /** @@ -57,13 +57,14 @@ protected CodingCollection(@Nonnull final Column column, * * @param column The column to use * @param definition The definition to use + * @param singular Whether the collection is singular * @return A new instance of {@link CodingCollection} */ @Nonnull public static CodingCollection build(@Nonnull final Column column, - @Nonnull final Optional definition) { + @Nonnull final Optional definition, final boolean singular) { return new CodingCollection(column, Optional.of(FhirPathType.CODING), - Optional.of(FHIRDefinedType.CODING), definition); + Optional.of(FHIRDefinedType.CODING), definition, singular); } /** @@ -78,7 +79,7 @@ public static CodingCollection fromLiteral(@Nonnull final String fhirPath) throws IllegalArgumentException { final Coding coding = CodingLiteral.fromString(fhirPath); final Column column = buildColumn(coding); - return CodingCollection.build(column, Optional.empty()); + return CodingCollection.build(column, Optional.empty(), true); } @Nonnull @@ -131,7 +132,7 @@ public Function getComparison(@Nonnull final ComparisonOpera @Override public Collection asStringPath() { final Column valueColumn = callUDF(CodingToLiteral.FUNCTION_NAME, getColumn()); - return CodingCollection.build(valueColumn, Optional.empty()); + return CodingCollection.build(valueColumn, Optional.empty(), isSingular()); } } 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 bc74788a00..62aa0ba4b9 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 @@ -17,6 +17,8 @@ package au.csiro.pathling.fhirpath.collection; +import static org.apache.spark.sql.functions.flatten; + import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.FhirPathType; @@ -35,8 +37,6 @@ import lombok.Getter; import org.apache.spark.sql.Column; import org.apache.spark.sql.functions; -import org.apache.spark.sql.types.ArrayType; -import org.apache.spark.sql.types.DataType; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** @@ -99,6 +99,11 @@ public class Collection implements Comparable, Numeric { @Nonnull private Optional definition; + /** + * Whether the result of evaluating this expression is a singleton collection. + */ + private boolean singular; + /** * Builds a generic {@link Collection} with the specified column, FHIRPath type, FHIR type and * definition. @@ -113,8 +118,8 @@ public class Collection implements Comparable, Numeric { public static Collection build(@Nonnull final Column column, @Nonnull final Optional fhirPathType, @Nonnull final Optional fhirType, - @Nonnull final Optional definition) { - return new Collection(column, fhirPathType, fhirType, definition); + @Nonnull final Optional definition, final boolean singular) { + return new Collection(column, fhirPathType, fhirType, definition, singular); } /** @@ -125,14 +130,15 @@ public static Collection build(@Nonnull final Column column, * * @param column a {@link Column} containing the result of the expression * @param definition the {@link ElementDefinition} that this path should be based upon + * @param singular whether the result of evaluating this expression is a singleton collection * @return a new {@link Collection} */ @Nonnull public static Collection build(@Nonnull final Column column, - @Nonnull final ElementDefinition definition) { + @Nonnull final ElementDefinition definition, final boolean singular) { final Optional optionalFhirType = definition.getFhirType(); if (optionalFhirType.isPresent()) { - return getInstance(column, optionalFhirType, Optional.of(definition)); + return getInstance(column, optionalFhirType, Optional.of(definition), singular); } else { throw new IllegalArgumentException( "Attempted to build a Collection with an ElementDefinition with no fhirType"); @@ -147,18 +153,19 @@ public static Collection build(@Nonnull final Column column, * * @param column a {@link Column} containing the result of the expression * @param fhirType the {@link FHIRDefinedType} that this path should be based upon + * @param singular whether the result of evaluating this expression is a singleton collection * @return a new {@link Collection} */ @Nonnull public static Collection build(@Nonnull final Column column, - @Nonnull final FHIRDefinedType fhirType) { - return getInstance(column, Optional.of(fhirType), Optional.empty()); + @Nonnull final FHIRDefinedType fhirType, final boolean singular) { + return getInstance(column, Optional.of(fhirType), Optional.empty(), singular); } @Nonnull private static Collection getInstance(@Nonnull final Column column, @Nonnull final Optional fhirType, - @Nonnull final Optional definition) { + @Nonnull final Optional definition, final boolean singular) { // Look up the class that represents an element with the specified FHIR type. final FHIRDefinedType resolvedType = fhirType .or(() -> definition.flatMap(ElementDefinition::getFhirType)) @@ -170,9 +177,10 @@ private static Collection getInstance(@Nonnull final Column column, try { // Call its constructor and return. final Constructor constructor = elementPathClass - .getDeclaredConstructor(Column.class, Optional.class, Optional.class, Optional.class); + .getDeclaredConstructor(Column.class, Optional.class, Optional.class, Optional.class, + boolean.class); return constructor - .newInstance(column, Optional.ofNullable(fhirPathType), fhirType, definition); + .newInstance(column, Optional.ofNullable(fhirPathType), fhirType, definition, singular); } catch (final NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Problem building a Collection object", e); @@ -189,28 +197,6 @@ public static Optional> classForType( return Optional.ofNullable(FHIR_TYPE_TO_ELEMENT_PATH_CLASS.get(fhirType)); } - /** - * @param context an {@link EvaluationContext} - * @return whether the result of evaluating this path is a non-singular collection - */ - public boolean isSingular(@Nonnull final EvaluationContext context) { - return !(context.getDataset().select(column).schema() - .fields()[0].dataType() instanceof ArrayType); - } - - private static boolean needsFlattening(@Nonnull final Column column, - @Nonnull final EvaluationContext context) { - final boolean arrayOfArrays; - final DataType dataType = context.getDataset().select(column).schema().fields()[0].dataType(); - if (dataType instanceof ArrayType) { - final ArrayType arrayType = (ArrayType) dataType; - arrayOfArrays = arrayType.elementType() instanceof ArrayType; - } else { - arrayOfArrays = false; - } - return arrayOfArrays; - } - /** * Return the child {@link Collection} that results from traversing to the given expression. * @@ -221,19 +207,35 @@ private static boolean needsFlattening(@Nonnull final Column column, @Nonnull public Optional traverse(@Nonnull final String expression, @Nonnull final EvaluationContext context) { + final Optional elementDefinition = definition + .filter(def -> def instanceof ElementDefinition) + .map(def -> (ElementDefinition) def); + final Optional parentCardinality = elementDefinition.flatMap( + ElementDefinition::getMaxCardinality); + // It is only possible to traverse to a child with an element definition. - return definition.filter(def -> def instanceof ElementDefinition) - .map(def -> (ElementDefinition) def) + return elementDefinition // Get the named child definition. .flatMap(def -> def.getChildElement(expression)) .map(def -> { // Build a new FhirPath object, with a column that uses `getField` to extract the // appropriate child. final Column traversed = column.getField(expression); - // final Column flattened = needsFlattening(traversed, context) - // ? functions.flatten(traversed) - // : traversed; - return Collection.build(traversed, def); + + // Detect if flattening is required by examining the cardinality of the parent and the + // child within the traversal operation. If both will result in arrays, flattening will be + // required. + final Optional childCardinality = def.getMaxCardinality(); + final Column flattened = + parentCardinality.orElse(0) != 1 && childCardinality.orElse(0) != 1 + ? flatten(traversed) + : traversed; + + // Determine whether the new collection is singular. + final boolean singular = isSingular() && childCardinality.isPresent() + && childCardinality.get() == 1; + + return Collection.build(flattened, def, singular); }); } @@ -309,7 +311,7 @@ public Optional getNumericContextColumn() { @Nonnull public static Collection nullCollection() { return new Collection(functions.lit(null), Optional.empty(), Optional.empty(), - Optional.empty()); + Optional.empty(), false); } } 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 1971e7b82a..6549388e95 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 @@ -51,8 +51,8 @@ public class DateCollection extends Collection implements Materializable type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition) { - super(column, type, fhirType, definition); + @Nonnull final Optional definition, final boolean singular) { + super(column, type, fhirType, definition, singular); } /** @@ -60,13 +60,14 @@ protected DateCollection(@Nonnull final Column column, @Nonnull final Optional definition) { + @Nonnull final Optional definition, final boolean singular) { return new DateCollection(column, Optional.of(FhirPathType.DATE), - Optional.of(FHIRDefinedType.DATE), definition); + Optional.of(FHIRDefinedType.DATE), definition, singular); } /** @@ -79,7 +80,7 @@ public static DateCollection build(@Nonnull final Column column, @Nonnull public static DateCollection fromLiteral(@Nonnull final String fhirPath) throws ParseException { final String dateString = fhirPath.replaceFirst("^@", ""); - return DateCollection.build(lit(dateString), Optional.empty()); + return DateCollection.build(lit(dateString), Optional.empty(), true); } @Nonnull @@ -114,7 +115,8 @@ public Function getDateArithmeticOperation( @Nonnull @Override public Collection asStringPath() { - return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty()); + return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty(), + isSingular()); } } 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 0dfe964246..8dc1209c44 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 @@ -57,8 +57,8 @@ public class DateTimeCollection extends Collection implements protected DateTimeCollection(@Nonnull final Column column, @Nonnull final Optional type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition) { - super(column, type, fhirType, definition); + @Nonnull final Optional definition, final boolean singular) { + super(column, type, fhirType, definition, singular); } /** @@ -66,13 +66,14 @@ protected DateTimeCollection(@Nonnull final Column column, * * @param column The column to use * @param definition The definition to use + * @param singular Whether the collection is singular * @return A new instance of {@link DateTimeCollection} */ @Nonnull public static DateTimeCollection build(@Nonnull final Column column, - @Nonnull final Optional definition) { + @Nonnull final Optional definition, final boolean singular) { return new DateTimeCollection(column, Optional.of(FhirPathType.DATETIME), - Optional.of(FHIRDefinedType.DATETIME), definition); + Optional.of(FHIRDefinedType.DATETIME), definition, singular); } /** @@ -86,7 +87,7 @@ public static DateTimeCollection build(@Nonnull final Column column, public static DateTimeCollection fromLiteral(@Nonnull final String fhirPath) throws ParseException { final String dateString = fhirPath.replaceFirst("^@", ""); - return DateTimeCollection.build(lit(dateString), Optional.empty()); + return DateTimeCollection.build(lit(dateString), Optional.empty(), true); } @Nonnull @@ -138,7 +139,7 @@ public Collection asStringPath() { } else { valueColumn = getColumn(); } - return StringCollection.build(valueColumn, Optional.empty()); + return StringCollection.build(valueColumn, Optional.empty(), isSingular()); } } 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 5c8ef1595e..4b89235a7b 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 @@ -53,8 +53,8 @@ public class DecimalCollection extends Collection implements Materializable fhirPathType, @Nonnull final Optional fhirType, - @Nonnull final Optional definition) { - super(column, fhirPathType, fhirType, definition); + @Nonnull final Optional definition, final boolean singular) { + super(column, fhirPathType, fhirType, definition, singular); } /** @@ -62,13 +62,14 @@ protected DecimalCollection(@Nonnull final Column column, * * @param column The column to use * @param definition The definition to use + * @param singular Whether the collection is singular * @return A new instance of {@link DecimalCollection} */ @Nonnull public static DecimalCollection build(@Nonnull final Column column, - @Nonnull final Optional definition) { + @Nonnull final Optional definition, final boolean singular) { return new DecimalCollection(column, Optional.of(FhirPathType.DECIMAL), - Optional.of(FHIRDefinedType.DECIMAL), definition); + Optional.of(FHIRDefinedType.DECIMAL), definition, singular); } /** @@ -82,7 +83,7 @@ public static DecimalCollection build(@Nonnull final Column column, public static DecimalCollection fromLiteral(@Nonnull final String literal) throws NumberFormatException { final BigDecimal value = parseLiteral(literal); - return DecimalCollection.build(lit(value), Optional.empty()); + return DecimalCollection.build(lit(value), Optional.empty(), true); } @Nonnull @@ -130,10 +131,10 @@ public Function getMathOperation(@Nonnull final MathOperati case MULTIPLICATION: case DIVISION: result = result.cast(getDecimalType()); - return DecimalCollection.build(result, Optional.empty()); + return DecimalCollection.build(result, Optional.empty(), isSingular()); case MODULUS: result = result.cast(DataTypes.LongType); - return IntegerCollection.build(result, Optional.empty()); + return IntegerCollection.build(result, Optional.empty(), isSingular()); default: throw new AssertionError("Unsupported math operation encountered: " + operation); } @@ -183,7 +184,7 @@ public Optional getFhirValueFromRow(@Nonnull final Row row, final i @Override @Nonnull public Collection asStringPath() { - return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty()); + return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty(), 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 c8f210a775..62d92cb3bb 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 @@ -55,8 +55,8 @@ public class IntegerCollection extends Collection implements protected IntegerCollection(@Nonnull final Column column, @Nonnull final Optional type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition) { - super(column, type, fhirType, definition); + @Nonnull final Optional definition, final boolean singular) { + super(column, type, fhirType, definition, singular); } /** @@ -64,13 +64,14 @@ protected IntegerCollection(@Nonnull final Column column, * * @param column The column to use * @param definition The definition to use + * @param singular Whether the collection is singular * @return A new instance of {@link IntegerCollection} */ @Nonnull public static IntegerCollection build(@Nonnull final Column column, - @Nonnull final Optional definition) { + @Nonnull final Optional definition, final boolean singular) { return new IntegerCollection(column, Optional.of(FhirPathType.INTEGER), - Optional.of(FHIRDefinedType.INTEGER), definition); + Optional.of(FHIRDefinedType.INTEGER), definition, singular); } /** @@ -83,7 +84,7 @@ public static IntegerCollection build(@Nonnull final Column column, public static IntegerCollection fromLiteral(@Nonnull final String fhirPath) throws NumberFormatException { final int value = Integer.parseInt(fhirPath); - return IntegerCollection.build(lit(value), Optional.empty()); + return IntegerCollection.build(lit(value), Optional.empty(), true); } @Nonnull @@ -171,11 +172,11 @@ public static Function buildMathOperation(@Nonnull final Nu if (target instanceof DecimalCollection) { valueColumn = valueColumn.cast(DataTypes.LongType); } - return IntegerCollection.build(valueColumn, Optional.empty()); + return IntegerCollection.build(valueColumn, Optional.empty(), true); case DIVISION: final Column numerator = source.getColumn().cast(DecimalCollection.getDecimalType()); valueColumn = operation.getSparkFunction().apply(numerator, targetNumeric); - return DecimalCollection.build(valueColumn, Optional.empty()); + return DecimalCollection.build(valueColumn, Optional.empty(), true); default: throw new AssertionError("Unsupported math operation encountered: " + operation); } @@ -185,7 +186,7 @@ public static Function buildMathOperation(@Nonnull final Nu @Override @Nonnull public Collection asStringPath() { - return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty()); + return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty(), true); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java index d00c159c7b..dbf6dbe553 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java @@ -38,8 +38,8 @@ protected MixedCollection(@Nonnull final Column column, @Nonnull final Optional type, @Nonnull final Optional fhirType, @Nonnull final Optional definition, - @Nonnull final Collection from) { - super(column, type, fhirType, definition); + final boolean singular, @Nonnull final Collection from) { + super(column, type, fhirType, definition, singular); this.from = from; checkArgument(definition.isPresent() && definition.get() instanceof ChoiceElementDefinition, "definition must be a ChoiceElementDefinition"); @@ -51,13 +51,16 @@ protected MixedCollection(@Nonnull final Column column, * * @param column The column to use * @param definition The definition to use + * @param singular Whether the collection is singular * @param from The collection from which this choice element is derived * @return A new instance of {@link MixedCollection} */ @Nonnull public static MixedCollection build(@Nonnull final Column column, - @Nonnull final Optional definition, @Nonnull final Collection from) { - return new MixedCollection(column, Optional.empty(), Optional.empty(), definition, from); + @Nonnull final Optional definition, final boolean singular, + @Nonnull final Collection from) { + return new MixedCollection(column, Optional.empty(), Optional.empty(), definition, singular, + from); } @Nonnull @@ -92,7 +95,8 @@ public Optional resolveChoice(@Nonnull final String type, final Optional definition = resolveChoiceDefinition(type); checkUserInput(elementColumn.isPresent() && definition.isPresent(), "No such child: " + columnName); - return elementColumn.map(column -> Collection.build(column, definition.get())); + return elementColumn.map(column -> Collection.build(column, definition.get(), + isSingular())); } return from.traverse(columnName, context); } 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 2c84cb0c0d..52a61f42be 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 @@ -59,8 +59,8 @@ public class QuantityCollection extends Collection implements Comparable, Numeri public QuantityCollection(@Nonnull final Column column, @Nonnull final Optional type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition) { - super(column, type, fhirType, definition); + @Nonnull final Optional definition, final boolean singular) { + super(column, type, fhirType, definition, singular); } /** @@ -68,13 +68,14 @@ public QuantityCollection(@Nonnull final Column column, * * @param column The column to use * @param definition The definition to use + * @param singular Whether the collection is singular * @return A new instance of {@link QuantityCollection} */ @Nonnull public static QuantityCollection build(@Nonnull final Column column, - @Nonnull final Optional definition) { + @Nonnull final Optional definition, final boolean singular) { return new QuantityCollection(column, Optional.of(FhirPathType.QUANTITY), - Optional.of(FHIRDefinedType.QUANTITY), definition); + Optional.of(FHIRDefinedType.QUANTITY), definition, singular); } /** @@ -121,7 +122,7 @@ public static QuantityCollection fromUcumString(@Nonnull final String fhirPath, public static QuantityCollection fromCalendarDurationString(@Nonnull final String fhirPath) { final Column column = QuantityEncoding.encodeLiteral(parseCalendarDuration(fhirPath)); - return QuantityCollection.build(column, Optional.empty()); + return QuantityCollection.build(column, Optional.empty(), true); } @Nonnull @@ -205,7 +206,8 @@ private static QuantityCollection buildLiteralPath(@Nonnull final BigDecimal dec quantity.setCode(unit); display.ifPresent(quantity::setUnit); - return QuantityCollection.build(QuantityEncoding.encodeLiteral(quantity), Optional.empty()); + return QuantityCollection.build(QuantityEncoding.encodeLiteral(quantity), Optional.empty(), + true); } @Nonnull @@ -263,7 +265,7 @@ public Function getMathOperation(@Nonnull final MathOperati final Column resultQuantityColumn = when(sourceContext.isNull().or(targetContext.isNull()), null).otherwise(validResult); - return QuantityCollection.build(resultQuantityColumn, Optional.empty()); + return QuantityCollection.build(resultQuantityColumn, Optional.empty(), true); }; } 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 d9e5222f50..71ba9f18cd 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 @@ -76,10 +76,10 @@ protected ResourceCollection(@Nonnull final Column column, @Nonnull final Optional type, @Nonnull final Optional fhirType, @Nonnull final Optional definition, - @Nonnull final Map elementsToColumns, + final boolean singular, @Nonnull final Map elementsToColumns, @Nonnull final ResourceDefinition resourceDefinition, @Nonnull final Dataset dataset) { - super(column, type, fhirType, definition); + super(column, type, fhirType, definition, singular); this.elementsToColumns = elementsToColumns; this.resourceDefinition = resourceDefinition; this.dataset = dataset; @@ -100,11 +100,13 @@ private static Optional getFhirType(@Nonnull final ResourceType * @param fhirContext the {@link FhirContext} to use for sourcing the resource definition * @param dataset the {@link Dataset} that contains the resource data * @param resourceType the type of the resource + * @param singular whether the resource is singular * @return A shiny new ResourcePath */ @Nonnull public static ResourceCollection build(@Nonnull final FhirContext fhirContext, - @Nonnull final Dataset dataset, @Nonnull final ResourceType resourceType) { + @Nonnull final Dataset dataset, @Nonnull final ResourceType resourceType, + final boolean singular) { // Get the resource definition from HAPI. final String resourceCode = resourceType.toCode(); @@ -118,7 +120,7 @@ public static ResourceCollection build(@Nonnull final FhirContext fhirContext, // We use the ID column as the value column for a ResourcePath. return new ResourceCollection(functions.col("id"), Optional.empty(), - getFhirType(resourceType), Optional.of(definition), elementsToColumns, definition, + getFhirType(resourceType), Optional.of(definition), singular, elementsToColumns, definition, dataset); } @@ -173,8 +175,11 @@ public Optional traverse(@Nonnull final String expression, // Get the child column from the map of elements to columns. return getElementColumn(expression).flatMap(value -> // Get the child element definition from the resource definition. - resourceDefinition.getChildElement(expression).map(definition -> - Collection.build(value, definition))); + resourceDefinition.getChildElement(expression).map(definition -> { + final boolean singular = isSingular() && definition.getMaxCardinality().isPresent() + && definition.getMaxCardinality().get() == 1; + return Collection.build(value, definition, singular); + })); } } 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 81895ffea8..c551a39ce4 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 @@ -48,8 +48,8 @@ public class StringCollection extends Collection implements Materializable type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition) { - super(column, type, fhirType, definition); + @Nonnull final Optional definition, final boolean singular) { + super(column, type, fhirType, definition, singular); } /** @@ -57,13 +57,14 @@ public StringCollection(@Nonnull final Column column, @Nonnull final Optional definition) { + @Nonnull final Optional definition, final boolean singular) { return new StringCollection(column, Optional.of(FhirPathType.STRING), - Optional.of(FHIRDefinedType.STRING), definition); + Optional.of(FHIRDefinedType.STRING), definition, singular); } /** @@ -75,7 +76,7 @@ public static StringCollection build(@Nonnull final Column column, @Nonnull public static StringCollection fromLiteral(@Nonnull final String fhirPath) { final String value = parseStringLiteral(fhirPath); - return StringCollection.build(lit(value), Optional.empty()); + return StringCollection.build(lit(value), Optional.empty(), true); } @Nonnull 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 2f5e7d5c75..420aac687d 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 @@ -42,8 +42,8 @@ public class TimeCollection extends Collection implements Materializable type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition) { - super(column, type, fhirType, definition); + @Nonnull final Optional definition, final boolean singular) { + super(column, type, fhirType, definition, singular); } /** @@ -51,13 +51,14 @@ public TimeCollection(@Nonnull final Column column, @Nonnull final Optional definition) { + @Nonnull final Optional definition, final boolean singular) { return new TimeCollection(column, Optional.of(FhirPathType.TIME), - Optional.of(FHIRDefinedType.TIME), definition); + Optional.of(FHIRDefinedType.TIME), definition, singular); } /** @@ -69,7 +70,7 @@ public static TimeCollection build(@Nonnull final Column column, @Nonnull public static TimeCollection fromLiteral(@Nonnull final String literal) { final String timeString = literal.replaceFirst("^@T", ""); - return TimeCollection.build(lit(timeString), Optional.empty()); + return TimeCollection.build(lit(timeString), Optional.empty(), true); } @Nonnull @@ -84,7 +85,8 @@ public Optional getFhirValueFromRow(@Nonnull final Row row, final int @Nonnull @Override public Collection asStringPath() { - return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty()); + return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty(), + isSingular()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java index 0a8f6d46a5..0279bdf9e6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java @@ -43,7 +43,7 @@ public Collection invoke(@Nonnull final FunctionInput input) { checkNoArguments(getName(), input); final Collection inputPath = input.getInput(); final Column valueColumn = size(inputPath.getColumn()); - return IntegerCollection.build(valueColumn, Optional.empty()); + return IntegerCollection.build(valueColumn, Optional.empty(), true); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java index d7f4688a74..e1537c5057 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java @@ -43,7 +43,7 @@ public Collection invoke(@Nonnull final FunctionInput input) { // We use the count function to determine whether there are zero items in the input collection. final Collection countResult = new CountFunction().invoke(input); final Column valueColumn = countResult.getColumn().equalTo(0); - return BooleanCollection.build(valueColumn, Optional.empty()); + return BooleanCollection.build(valueColumn, Optional.empty(), true); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java index 7b98cf112d..a12e3c40f3 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java @@ -61,7 +61,7 @@ public Collection invoke(@Nonnull final FunctionInput input) { column = existsResult.getColumn(); } - return BooleanCollection.build(column, Optional.empty()); + return BooleanCollection.build(column, Optional.empty(), true); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java index f8f2bf3063..05966d93e5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java @@ -45,10 +45,10 @@ public Collection invoke(@Nonnull final FunctionInput input) { checkNoArguments(getName(), input); final Collection inputCollection = input.getInput(); - final Column result = applyOperation(input.getContext(), + final Column result = applyOperation( inputCollection, Function.identity(), column -> column.getItem(0)); return Collection.build(result, inputCollection.getType(), - inputCollection.getFhirType(), inputCollection.getDefinition()); + inputCollection.getFhirType(), inputCollection.getDefinition(), true); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java index 2074300508..2b32b8faca 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java @@ -26,7 +26,6 @@ import au.csiro.pathling.fhirpath.FunctionInput; import au.csiro.pathling.fhirpath.annotations.Name; import au.csiro.pathling.fhirpath.collection.Collection; -import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -52,14 +51,15 @@ public Collection invoke(@Nonnull final FunctionInput input) { final Column column = filter(previous.getColumn(), element -> { final Collection result = argument.apply( Collection.build(element, previous.getType(), previous.getFhirType(), - previous.getDefinition()), input.getContext()); + previous.getDefinition(), true), input.getContext()); final FhirPathType type = checkPresent(result.getType()); - checkUserInput(type.equals(FhirPathType.BOOLEAN) && result.isSingular(input.getContext()), + checkUserInput(type.equals(FhirPathType.BOOLEAN) && result.isSingular(), "Argument to " + getName() + " function must be a singular Boolean"); return result.getColumn(); }); - return Collection.build(column, previous.getType(), previous.getFhirType(), Optional.empty()); + return Collection.build(column, previous.getType(), previous.getFhirType(), + previous.getDefinition(), previous.isSingular()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java index f4f346551a..a53a6f419f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java @@ -55,11 +55,11 @@ public Collection invoke(@Nonnull final BinaryOperatorInput input) { checkUserInput(left instanceof BooleanCollection, "Left operand to " + type + " operator must be Boolean"); - checkUserInput(left.isSingular(context), + checkUserInput(left.isSingular(), "Left operand to " + type + " operator must be singular"); checkUserInput(right instanceof BooleanCollection, "Right operand to " + type + " operator must be Boolean"); - checkUserInput(right.isSingular(context), + checkUserInput(right.isSingular(), "Right operand to " + type + " operator must be singular"); final Column leftValue = left.getColumn(); @@ -97,7 +97,7 @@ public Collection invoke(@Nonnull final BinaryOperatorInput input) { throw new AssertionError("Unsupported boolean operator encountered: " + type); } - return BooleanCollection.build(valueColumn, Optional.empty()); + return BooleanCollection.build(valueColumn, Optional.empty(), true); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java index a4a23fd511..40473b37af 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java @@ -17,8 +17,13 @@ package au.csiro.pathling.fhirpath.operator; +import static au.csiro.pathling.utilities.Preconditions.checkUserInput; + import au.csiro.pathling.fhirpath.Comparable.ComparisonOperation; import au.csiro.pathling.fhirpath.annotations.NotImplemented; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.Collection; +import java.util.Optional; import javax.annotation.Nonnull; /** @@ -42,28 +47,18 @@ public ComparisonOperator(@Nonnull final ComparisonOperation type) { this.type = type; } - // TODO: implement with columns + @Nonnull + @Override + public Collection invoke(@Nonnull final BinaryOperatorInput input) { + final Collection left = input.getLeft(); + final Collection right = input.getRight(); + + checkUserInput(left.isSingular(), "Left operand must be singular"); + checkUserInput(right.isSingular(), "Right operand must be singular"); + checkUserInput(left.isComparableTo(right), "Operands must be comparable"); - // @Nonnull - // @Override - // public Collection invoke(@Nonnull final BinaryOperatorInput input) { - // final Collection left = input.getLeft(); - // final Collection right = input.getRight(); - // final SparkSession spark = input.getContext().getSparkSession(); - // checkUserInput(left.isSingular(spark), - // "Left operand must be singular: " + left.getExpression()); - // - // checkUserInput(right.isSingular(spark), - // "Right operand must be singular: " + right.getExpression()); - // checkArgumentsAreComparable(input, type.toString()); - // - // final String expression = buildExpression(input, type.toString()); - // - // final Column valueColumn = left.getComparison(type).apply(right); - // - // return PrimitivePath.build(expression, datasetWithColumn.getDataset(), idColumn, - // datasetWithColumn.getColumn(), Optional.empty(), true, Optional.empty(), thisColumn, - // FHIRDefinedType.BOOLEAN); - // } + return BooleanCollection.build(left.getComparison(type).apply(right), Optional.empty(), + true); + } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index 1b948f039a..999d355918 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -80,7 +80,7 @@ public FhirPath visitMemberInvocation( // If it is not a valid path traversal, see if it is a valid type specifier. final FHIRDefinedType fhirType = FHIRDefinedType.fromCode(fhirPath); return Collection.build(functions.lit(null), Optional.of(FhirPathType.TYPE_SPECIFIER), - Optional.of(fhirType), Optional.empty()); + Optional.of(fhirType), Optional.empty(), true); } catch (final FHIRException e2) { throw new InvalidUserInputError( diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index 06e735e7e7..bd7ac351ca 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -63,7 +63,7 @@ public Dataset buildQuery(@Nonnull final FhirView view) { // Build a new expression parser, and parse all the column expressions within the query. final ResourceType resourceType = ResourceType.fromCode(view.getResource()); final ResourceCollection inputContext = ResourceCollection.build(fhirContext, dataset, - resourceType); + resourceType, false); final EvaluationContext evaluationContext = new EvaluationContext(inputContext, inputContext, fhirContext, sparkSession, dataset, StaticFunctionRegistry.getInstance(), terminologyServiceFactory, From 6361302a19239030504a0e05d35fae3e83043c6e Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sat, 9 Sep 2023 00:21:43 +1000 Subject: [PATCH 69/95] Get Python view querying functional --- .../aggregate/AggregateQueryExecutorTest.java | 3 +- .../pathling/views/FhirViewExecutorTest.java | 5 +- .../java/au/csiro/pathling/QueryExecutor.java | 5 +- .../aggregate/AggregateQueryExecutor.java | 3 +- .../extract/ExtractQueryExecutor.java | 3 +- .../pathling/views/FhirViewExecutor.java | 13 +-- .../pathling/test/helpers/TestHelpers.java | 30 +++---- .../au/csiro/pathling/views/FhirViewTest.java | 3 +- lib/python/pathling/datasource.py | 40 ++++----- lib/python/pathling/query.py | 82 ----------------- .../pathling/library/PathlingContext.java | 10 +++ .../library/io/source/AbstractSource.java | 13 ++- .../io/source/QueryableDataSource.java | 6 +- .../library/query/FhirViewBuilder.java | 88 ------------------- .../pathling/library/query/FhirViewQuery.java | 66 ++++++++++++++ 15 files changed, 137 insertions(+), 233 deletions(-) delete mode 100644 library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java create mode 100644 library-api/src/main/java/au/csiro/pathling/library/query/FhirViewQuery.java diff --git a/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryExecutorTest.java b/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryExecutorTest.java index 048f056fdc..d8bd8db44b 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryExecutorTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateQueryExecutorTest.java @@ -23,6 +23,7 @@ import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; +import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.query.ExpressionWithLabel; import au.csiro.pathling.terminology.TerminologyServiceFactory; import au.csiro.pathling.test.SharedMocks; @@ -74,7 +75,7 @@ class AggregateQueryExecutorTest { FhirEncoders fhirEncoders; @MockBean - Dataset dataSource; + DataSource dataSource; AggregateQueryExecutor executor; diff --git a/fhir-server/src/test/java/au/csiro/pathling/views/FhirViewExecutorTest.java b/fhir-server/src/test/java/au/csiro/pathling/views/FhirViewExecutorTest.java index 357db54f07..81d5a83d55 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/views/FhirViewExecutorTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/views/FhirViewExecutorTest.java @@ -17,12 +17,11 @@ package au.csiro.pathling.views; +import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import au.csiro.pathling.test.SpringBootUnitTest; import ca.uhn.fhir.context.FhirContext; import java.util.Optional; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; @@ -37,7 +36,7 @@ class FhirViewExecutorTest { SparkSession sparkSession; @Autowired - Dataset dataSource; + DataSource dataSource; @Autowired TerminologyServiceFactory terminologyServiceFactory; diff --git a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java index 23b5ba2715..11dffc37ee 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/QueryExecutor.java @@ -23,6 +23,7 @@ import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import com.google.common.collect.Streams; @@ -57,14 +58,14 @@ public abstract class QueryExecutor { protected final SparkSession sparkSession; @Nonnull - protected final Dataset dataSource; + protected final DataSource dataSource; @Nonnull protected final Optional terminologyServiceFactory; protected QueryExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final Dataset dataSource, + @Nonnull final DataSource dataSource, @Nonnull final Optional terminologyServiceFactory) { this.configuration = configuration; this.fhirContext = fhirContext; diff --git a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java index e1dbd0d2dd..e7398956a6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/aggregate/AggregateQueryExecutor.java @@ -22,6 +22,7 @@ import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.io.Database; +import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.List; @@ -54,7 +55,7 @@ public class AggregateQueryExecutor extends QueryExecutor { */ public AggregateQueryExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final Dataset dataSource, + @Nonnull final DataSource dataSource, @Nonnull final Optional terminologyServiceFactory) { super(configuration, fhirContext, sparkSession, dataSource, terminologyServiceFactory); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java index 5db7bfc303..2d4c69a6c5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java @@ -3,6 +3,7 @@ import au.csiro.pathling.QueryExecutor; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.annotations.NotImplemented; +import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.Optional; @@ -23,7 +24,7 @@ public class ExtractQueryExecutor extends QueryExecutor { public ExtractQueryExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final Dataset dataSource, + @Nonnull final DataSource dataSource, @Nonnull final Optional terminologyServiceFactory) { super(configuration, fhirContext, sparkSession, dataSource, terminologyServiceFactory); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index bd7ac351ca..141874a8f4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -10,6 +10,7 @@ import au.csiro.pathling.fhirpath.function.registry.StaticFunctionRegistry; import au.csiro.pathling.fhirpath.parser.ConstantReplacer; import au.csiro.pathling.fhirpath.parser.Parser; +import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.ArrayList; @@ -37,18 +38,18 @@ public class FhirViewExecutor { private final SparkSession sparkSession; @Nonnull - private final Dataset dataset; + private final DataSource dataSource; @Nonnull private final Optional terminologyServiceFactory; public FhirViewExecutor(@Nonnull final FhirContext fhirContext, - @Nonnull final SparkSession sparkSession, @Nonnull final Dataset dataset, + @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataset, @Nonnull final Optional terminologyServiceFactory) { this.fhirContext = fhirContext; this.sparkSession = sparkSession; - this.dataset = dataset; + this.dataSource = dataset; this.terminologyServiceFactory = terminologyServiceFactory; } @@ -62,12 +63,12 @@ public Dataset buildQuery(@Nonnull final FhirView view) { // Build a new expression parser, and parse all the column expressions within the query. final ResourceType resourceType = ResourceType.fromCode(view.getResource()); + final Dataset dataset = dataSource.read(resourceType); final ResourceCollection inputContext = ResourceCollection.build(fhirContext, dataset, resourceType, false); final EvaluationContext evaluationContext = new EvaluationContext(inputContext, inputContext, - fhirContext, - sparkSession, dataset, StaticFunctionRegistry.getInstance(), terminologyServiceFactory, - constantReplacer); + fhirContext, sparkSession, dataset, StaticFunctionRegistry.getInstance(), + terminologyServiceFactory, constantReplacer); final List select = parseSelect(view.getSelect(), evaluationContext, Collections.emptyList()); diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/TestHelpers.java b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/TestHelpers.java index 1b5286e6a7..eb018cb483 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/TestHelpers.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/TestHelpers.java @@ -48,21 +48,21 @@ public abstract class TestHelpers { public static final String UCUM_URL = "http://unitsofmeasure.org"; public static final MediaType FHIR_MEDIA_TYPE = new MediaType("application", "fhir+json"); - // public static void mockResource(@Nonnull final Dataset dataSource, - // @Nonnull final SparkSession spark, @Nonnull final ResourceType... resourceTypes) { - // for (final ResourceType resourceType : resourceTypes) { - // final Dataset dataset = getDatasetForResourceType(spark, resourceType); - // when(dataSource.read(resourceType)).thenReturn(dataset); - // } - // } - // - // public static void mockCachedResource(@Nonnull final Dataset dataSource, - // @Nonnull final SparkSession spark, @Nonnull final ResourceType... resourceTypes) { - // for (final ResourceType resourceType : resourceTypes) { - // final Dataset dataset = getDatasetForResourceType(spark, resourceType).cache(); - // when(dataSource.read(resourceType)).thenReturn(dataset); - // } - // } + public static void mockResource(@Nonnull final DataSource dataSource, + @Nonnull final SparkSession spark, @Nonnull final ResourceType... resourceTypes) { + for (final ResourceType resourceType : resourceTypes) { + final Dataset dataset = getDatasetForResourceType(spark, resourceType); + when(dataSource.read(resourceType)).thenReturn(dataset); + } + } + + public static void mockCachedResource(@Nonnull final DataSource dataSource, + @Nonnull final SparkSession spark, @Nonnull final ResourceType... resourceTypes) { + for (final ResourceType resourceType : resourceTypes) { + final Dataset dataset = getDatasetForResourceType(spark, resourceType).cache(); + when(dataSource.read(resourceType)).thenReturn(dataset); + } + } public static void mockResource(@Nonnull final DataSource dataSource, @Nonnull final SparkSession spark, final int numPartitions, diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java index f9ba4c4bc3..9eae9dcccb 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -201,8 +201,7 @@ private static Path getTempDir(final @Nonnull JsonNode testDefinition) throws IO void test(@Nonnull final TestParameters parameters) { final FhirView view = parameters.getView(); final FhirViewExecutor executor = new FhirViewExecutor(fhirContext, spark, - parameters.getSourceData().read(view.getResource()), - Optional.ofNullable(terminologyServiceFactory)); + parameters.getSourceData(), Optional.ofNullable(terminologyServiceFactory)); final Dataset result = executor.buildQuery(view); final Dataset expectedResult = spark.read().json(parameters.getExpectedJson().toString()) .selectExpr(parameters.getExpectedColumns().toArray(new String[0])); diff --git a/lib/python/pathling/datasource.py b/lib/python/pathling/datasource.py index af6f58762c..168685834f 100644 --- a/lib/python/pathling/datasource.py +++ b/lib/python/pathling/datasource.py @@ -15,6 +15,7 @@ from typing import Dict, Sequence, Optional, Callable +import json from py4j.java_collections import SetConverter from py4j.java_gateway import JavaObject @@ -50,6 +51,9 @@ def read(self, resource_code: str) -> DataFrame: """ return self._wrap_df(self._jds.read(resource_code)) + def resource_types(self): + return [r.toCode() for r in self._jds.getResourceTypes()] + @property def write(self) -> "DataSinks": """ @@ -118,29 +122,21 @@ def aggregate( def view( self, - resource_type: str, - columns: Sequence[Expression], - variables: Optional[Sequence[Expression]] = None, - filters: Optional[Sequence[Expression]] = None, + resource: str, + select: Sequence[Dict], + constants: Optional[Sequence[Dict]] = None, + where: Optional[Sequence[Dict]] = None, ) -> DataFrame: - """ - Runs a view query for the given resource type, using the specified columns, variables and - filters. - - :param resource_type: A string representing the type of FHIR resource to base the view upon. - :param columns: A sequence of FHIRPath expressions that define the columns to include in the - view. - :param variables: A sequence of FHIRPath expressions that define variables that can be used - in the view. - :param filters: An optional sequence of FHIRPath expressions that can be evaluated against - each resource in the data set to determine whether it is included within the result. - The expression must evaluate to a Boolean value. Multiple filters are combined using - AND logic. - :return: A Spark DataFrame object that represents the view query. - """ - from pathling.query import FhirView - - return FhirView(resource_type, columns, variables, filters).execute(self) + args = locals() + query = { + key: args[key] + for key in ["resource", "select", "constants", "where"] + if args[key] is not None + } + query_json = json.dumps(query) + jquery = self._jds.view(resource) + jquery.json(query_json) + return self._wrap_df(jquery.execute()) class DataSources(SparkConversionsMixin): diff --git a/lib/python/pathling/query.py b/lib/python/pathling/query.py index 1d9bb3e8da..8985881a75 100644 --- a/lib/python/pathling/query.py +++ b/lib/python/pathling/query.py @@ -161,88 +161,6 @@ def _create_jquery(self, data_source: DataSource) -> JavaObject: return jquery -class FhirView(QueryWithFilters): - """ - Represents a FHIR view that describes a way of flattening the data from a specified resource - type into a tabular format. - - :param subject_resource: A string representing the type of FHIR resource to base the view on. - :param columns: A sequence of FHIRPath expressions that define the columns to include in the - view. - :param variables: A sequence of FHIRPath expressions that define the variables that can be used - in the view. - :param filters: An optional sequence of FHIRPath expressions that can be evaluated against each - resource in the data set to determine whether it is included within the result. The - expression must evaluate to a Boolean value. Multiple filters are combined using AND - logic. - :param data_source: An optional DataSource instance to use for executing the query. - """ - - def __init__( - self, - subject_resource: str, - columns: Sequence[Expression], - variables: Optional[Sequence[VariableExpression]] = None, - filters: Optional[Sequence[str]] = None, - data_source: DataSource = None, - ): - """ - Initializes a new instance of the FhirView class. - - :param subject_resource: A string representing the type of FHIR resource to base the view - on. - :param columns: A sequence of FHIRPath expressions that define the columns to include in - the view. - :param variables: A sequence of FHIRPath expressions that define the variables that can be - used in the view. - :param filters: An optional sequence of FHIRPath expressions that can be evaluated against - each resource in the data set to determine whether it is included within the - result. The expression must evaluate to a Boolean value. Multiple filters are - combined using AND logic. - :param data_source: An optional DataSource instance to use for executing the query. - """ - super().__init__(subject_resource, filters, data_source) - self._columns = columns - self._variables = variables - - @property - def columns(self) -> Sequence[Expression]: - """ - Gets the columns to include in the view. - - :return: A sequence of Expression objects representing the columns to include in the view. - """ - return self._columns - - @property - def variables(self) -> Sequence[VariableExpression]: - """ - Gets the variables that can be used in the view. - - :return: A sequence of Expression objects representing the variables that can be used in - the view. - """ - return self._variables - - def _create_jquery(self, data_source: DataSource) -> JavaObject: - """ - Creates a new instance of a Java-based view object. - - :param data_source: The DataSource instance to use for executing the query. - :return: A new instance of a Java-based view object. - """ - jquery = data_source._jds.view(self._subject_resource) - for column in self._columns: - jquery.column(column.expression, column.label) - if self._variables: - for variable in self._variables: - jquery.variable(variable.expression, variable.label, variable.when_many) - if self._filters: - for f in self._filters: - jquery.filter(f) - return jquery - - class AggregateQuery(QueryWithFilters): """ Represents an aggregate query for FHIR data. The query calculates summary values based diff --git a/library-api/src/main/java/au/csiro/pathling/library/PathlingContext.java b/library-api/src/main/java/au/csiro/pathling/library/PathlingContext.java index 8b8b3f5b80..ea53f8e659 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/PathlingContext.java +++ b/library-api/src/main/java/au/csiro/pathling/library/PathlingContext.java @@ -34,10 +34,13 @@ import au.csiro.pathling.terminology.TerminologyFunctions; import au.csiro.pathling.terminology.TerminologyServiceFactory; import au.csiro.pathling.validation.ValidationUtils; +import au.csiro.pathling.views.SelectClauseTypeAdapterFactory; import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeResourceDefinition; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.Getter; @@ -82,6 +85,10 @@ public class PathlingContext { @Nonnull private final TerminologyFunctions terminologyFunctions; + @Nonnull + @Getter + private final Gson gson; + private PathlingContext(@Nonnull final SparkSession spark, @Nonnull final FhirEncoders fhirEncoders, @Nonnull final TerminologyServiceFactory terminologyServiceFactory) { @@ -92,6 +99,9 @@ private PathlingContext(@Nonnull final SparkSession spark, this.terminologyFunctions = TerminologyFunctions.build(); TerminologyUdfRegistrar.registerUdfs(spark, terminologyServiceFactory); FhirpathUDFRegistrar.registerUDFs(spark); + final GsonBuilder builder = new GsonBuilder(); + builder.registerTypeAdapterFactory(new SelectClauseTypeAdapterFactory()); + gson = builder.create(); } diff --git a/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java b/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java index 3f72a3708d..a7632374e4 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java +++ b/library-api/src/main/java/au/csiro/pathling/library/io/source/AbstractSource.java @@ -23,18 +23,17 @@ import au.csiro.pathling.aggregate.AggregateQueryExecutor; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.extract.ExtractQueryExecutor; +import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.library.PathlingContext; import au.csiro.pathling.library.io.sink.DataSinkBuilder; import au.csiro.pathling.library.query.AggregateQuery; import au.csiro.pathling.library.query.ExtractQuery; -import au.csiro.pathling.library.query.FhirViewBuilder; +import au.csiro.pathling.library.query.FhirViewQuery; import au.csiro.pathling.library.query.QueryDispatcher; import au.csiro.pathling.views.FhirViewExecutor; import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; import org.hl7.fhir.r4.model.Enumerations.ResourceType; /** @@ -57,7 +56,7 @@ protected AbstractSource(@Nonnull final PathlingContext context) { @Nonnull private QueryDispatcher buildDispatcher(final @Nonnull PathlingContext context, - final Dataset dataSource) { + final DataSource dataSource) { // Use the default query configuration. final QueryConfiguration queryConfiguration = QueryConfiguration.builder().build(); @@ -109,13 +108,13 @@ public ExtractQuery extract(@Nullable final String subjectResource) { @Nonnull @Override - public FhirViewBuilder view(@Nullable final ResourceType subjectResource) { - return new FhirViewBuilder(dispatcher, requireNonNull(subjectResource)); + public FhirViewQuery view(@Nullable final ResourceType subjectResource) { + return new FhirViewQuery(dispatcher, requireNonNull(subjectResource), context.getGson()); } @Nonnull @Override - public FhirViewBuilder view(@Nullable final String subjectResource) { + public FhirViewQuery view(@Nullable final String subjectResource) { return view(getResourceType(subjectResource)); } diff --git a/library-api/src/main/java/au/csiro/pathling/library/io/source/QueryableDataSource.java b/library-api/src/main/java/au/csiro/pathling/library/io/source/QueryableDataSource.java index 1441aed363..06a544d5e4 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/io/source/QueryableDataSource.java +++ b/library-api/src/main/java/au/csiro/pathling/library/io/source/QueryableDataSource.java @@ -20,7 +20,7 @@ import au.csiro.pathling.library.io.sink.DataSinkBuilder; import au.csiro.pathling.library.query.AggregateQuery; import au.csiro.pathling.library.query.ExtractQuery; -import au.csiro.pathling.library.query.FhirViewBuilder; +import au.csiro.pathling.library.query.FhirViewQuery; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -59,7 +59,7 @@ public interface QueryableDataSource extends DataSource { * @return a query builder for the view operation */ @Nonnull - FhirViewBuilder view(@Nullable ResourceType subjectResource); + FhirViewQuery view(@Nullable ResourceType subjectResource); /** * @param subjectResource the subject resource code @@ -80,6 +80,6 @@ public interface QueryableDataSource extends DataSource { * @return a query builder for the view operation */ @Nonnull - FhirViewBuilder view(@Nullable String subjectResource); + FhirViewQuery view(@Nullable String subjectResource); } diff --git a/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java b/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java deleted file mode 100644 index becfebe839..0000000000 --- a/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewBuilder.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2023 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.library.query; - -import au.csiro.pathling.fhirpath.annotations.NotImplemented; -import javax.annotation.Nonnull; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; - -/** - * Represents a FHIR view. - * - * @author John Grimes - */ -@NotImplemented -public class FhirViewBuilder extends QueryBuilder { - - public FhirViewBuilder(@Nonnull final QueryDispatcher dispatcher, - @Nonnull final ResourceType subjectResource) { - super(dispatcher, subjectResource); - } - - // @Nonnull - // private final List columns = new ArrayList<>(); - // - // @Nonnull - // private final List variables = new ArrayList<>(); - // - // public FhirViewBuilder(@Nonnull final QueryDispatcher dispatcher, - // @Nonnull final ResourceType subjectResource) { - // super(dispatcher, subjectResource); - // } - // - // /** - // * Adds an expression that represents a column to be included in the view. - // * - // * @param expression the column expression - // * @param name the name of the column - // * @return this query - // */ - // @Nonnull - // public FhirViewBuilder column(@Nullable final String expression, @Nullable final String name) { - // columns.add( - // new NamedExpression(requireNonBlank(expression, "Column expression cannot be blank"), - // requireNonBlank(name, "Column name cannot be blank"))); - // return this; - // } - // - // /** - // * Adds an expression that represents a variable to be defined within the view. - // * - // * @param expression the variable expression - // * @param name the name of the variable - // * @return this query - // */ - // @Nonnull - // public FhirViewBuilder variable(@Nullable final String expression, @Nullable final String name, - // @Nullable final String whenMany) { - // variables.add( - // new VariableExpression(requireNonBlank(expression, "Variable expression cannot be blank"), - // requireNonBlank(name, "Variable name cannot be blank"), WhenMany.fromCode(whenMany))); - // return this; - // } - - @Nonnull - @Override - public Dataset execute() { - throw new UnsupportedOperationException("Not implemented yet"); - // final FhirView view = new FhirView(subjectResource, columns, variables, filters); - // return dispatcher.dispatch(view); - } -} diff --git a/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewQuery.java b/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewQuery.java new file mode 100644 index 0000000000..94c642a704 --- /dev/null +++ b/library-api/src/main/java/au/csiro/pathling/library/query/FhirViewQuery.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 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.library.query; + +import static au.csiro.pathling.validation.ValidationUtils.ensureValid; + +import au.csiro.pathling.views.FhirView; +import com.google.gson.Gson; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; +import org.hl7.fhir.r4.model.Enumerations.ResourceType; + +/** + * Represents a FHIR view. + * + * @author John Grimes + */ +public class FhirViewQuery extends QueryBuilder { + + @Nonnull + private final Gson gson; + + @Nullable + private FhirView fhirView; + + public FhirViewQuery(@Nonnull final QueryDispatcher dispatcher, + @Nonnull final ResourceType subjectResource, @Nonnull final Gson gson) { + super(dispatcher, subjectResource); + this.gson = gson; + } + + @Nonnull + public FhirViewQuery json(@Nullable final String json) { + if (json != null) { + fhirView = gson.fromJson(json, FhirView.class); + ensureValid(fhirView, "View is not valid"); + } + return this; + } + + @Nonnull + @Override + public Dataset execute() { + if (fhirView == null) { + throw new IllegalStateException("No view has been set"); + } + return dispatcher.dispatch(fhirView); + } +} From 193b9c64d28cc7330d8e34fc3ce478526293847a Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sun, 10 Sep 2023 21:44:58 +1000 Subject: [PATCH 70/95] Move SQL on FHIR submodule location --- .gitmodules | 2 +- .../src/test/java/au/csiro/pathling/views/FhirViewTest.java | 2 +- fhirpath/src/test/resources/sql-on-fhir | 1 - fhirpath/src/test/resources/tests/sql-on-fhir | 1 + sql-on-fhir | 1 + 5 files changed, 4 insertions(+), 3 deletions(-) delete mode 160000 fhirpath/src/test/resources/sql-on-fhir create mode 120000 fhirpath/src/test/resources/tests/sql-on-fhir create mode 160000 sql-on-fhir diff --git a/.gitmodules b/.gitmodules index 4f27704dc2..5c39511761 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "sql-on-fhir"] - path = fhirpath/src/test/resources/sql-on-fhir + path = sql-on-fhir url = https://github.com/FHIR/sql-on-fhir-v2.git diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java index 9eae9dcccb..7c87258591 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -77,7 +77,7 @@ static void beforeAll() throws IOException { Stream requests() throws IOException { final ObjectMapper mapper = new ObjectMapper(); final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - final Resource[] resources = resolver.getResources("classpath:sql-on-fhir/tests/v1/*.json"); + final Resource[] resources = resolver.getResources("classpath:tests/sql-on-fhir/v1/*.json"); return Stream.of(resources) // Get each test file. .map(resource -> { diff --git a/fhirpath/src/test/resources/sql-on-fhir b/fhirpath/src/test/resources/sql-on-fhir deleted file mode 160000 index 24b2132e87..0000000000 --- a/fhirpath/src/test/resources/sql-on-fhir +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 24b2132e871db35492c90478fa97a404b4023466 diff --git a/fhirpath/src/test/resources/tests/sql-on-fhir b/fhirpath/src/test/resources/tests/sql-on-fhir new file mode 120000 index 0000000000..02e49f3b4c --- /dev/null +++ b/fhirpath/src/test/resources/tests/sql-on-fhir @@ -0,0 +1 @@ +../../../../../sql-on-fhir/tests \ No newline at end of file diff --git a/sql-on-fhir b/sql-on-fhir new file mode 160000 index 0000000000..21e11429ca --- /dev/null +++ b/sql-on-fhir @@ -0,0 +1 @@ +Subproject commit 21e11429ca12c946f1d28a3ad7a43b0d5a25abff From b9ec589d9a3649bbf9335a94ac37ef80896bfa2b Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sun, 10 Sep 2023 21:55:57 +1000 Subject: [PATCH 71/95] Use expect instead of expected --- .../src/test/java/au/csiro/pathling/views/FhirViewTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java index 7c87258591..47d45c9d6a 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -157,7 +157,7 @@ List toTestParameters(@Nonnull final JsonNode testDefinition, view.get("title").asText().replaceAll("\\W+", "_") + ".json"; final Path expectedPath = directory.resolve(expectedFileName); List expectedColumns = null; - for (final Iterator rowIt = view.get("expected").elements(); rowIt.hasNext(); ) { + for (final Iterator rowIt = view.get("expect").elements(); rowIt.hasNext(); ) { final JsonNode row = rowIt.next(); // Get the columns from the first row. From 1274efcedc47a714955df5678b9993c534937468 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sun, 10 Sep 2023 22:08:05 +1000 Subject: [PATCH 72/95] Add formatting to pre-commit hook --- sql-on-fhir | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql-on-fhir b/sql-on-fhir index 21e11429ca..6fb2f18ee0 160000 --- a/sql-on-fhir +++ b/sql-on-fhir @@ -1 +1 @@ -Subproject commit 21e11429ca12c946f1d28a3ad7a43b0d5a25abff +Subproject commit 6fb2f18ee072c29246c62cb507014c61a121f376 From f0d79d7799cb94220ffcb145dd409d1955468ce0 Mon Sep 17 00:00:00 2001 From: John Grimes Date: Sun, 10 Sep 2023 22:41:59 +1000 Subject: [PATCH 73/95] Make column aliases optional --- .../main/java/au/csiro/pathling/views/DirectSelection.java | 2 +- .../main/java/au/csiro/pathling/views/FhirViewExecutor.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java index 0fe01f9df8..4e23c991f2 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java @@ -22,7 +22,7 @@ public class DirectSelection extends SelectClause { * @see ViewDefinition.select.name */ - @NotNull + @Nullable @Size(max = 255) @Pattern(regexp = "^[^_][A-Za-z][A-Za-z0-9_]+$") String alias; diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index 141874a8f4..e4e5f931f0 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -118,7 +118,9 @@ private List directSelection(@Nonnull final EvaluationContext context, @Nonnull final DirectSelection select, @Nonnull final List currentSelection) { final Collection path = evalExpression(select.getPath(), context); final List newColumns = new ArrayList<>(currentSelection); - newColumns.add(path.getColumn().alias(select.getAlias())); + final Column column = path.getColumn(); + final Optional alias = Optional.ofNullable(select.getAlias()); + newColumns.add(alias.map(column::alias).orElse(column)); return newColumns; } From a9fec446561a201dbb4559405175f797b07dbb15 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Mon, 11 Sep 2023 15:25:14 +1000 Subject: [PATCH 74/95] Updating the pointer to sql-on-fhir --- sql-on-fhir | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql-on-fhir b/sql-on-fhir index 6fb2f18ee0..03cc442cad 160000 --- a/sql-on-fhir +++ b/sql-on-fhir @@ -1 +1 @@ -Subproject commit 6fb2f18ee072c29246c62cb507014c61a121f376 +Subproject commit 03cc442cadc72afee14bbb91e98275838610b6a4 From afb652bf240b7faf74134bcf89a0ea30f1e3be53 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Thu, 14 Sep 2023 12:34:19 +1000 Subject: [PATCH 75/95] Delegating singularity tracking for column representation of FHIR elements to Spark/Catalyst. Initial concept of annotation fhipath based function definition. --- .../csiro/pathling/encoders/Expressions.scala | 86 ++++++++ .../pathling/encoders/ExpressionsTest.java | 102 +++++++++ .../pathling/config/QueryConfiguration.java | 8 +- .../pathling/fhirpath/ColumnHelpers.java | 59 ++++++ .../csiro/pathling/fhirpath/Comparable.java | 4 +- .../au/csiro/pathling/fhirpath/FhirPath.java | 11 +- .../au/csiro/pathling/fhirpath/Referrer.java | 2 +- .../collection/BooleanCollection.java | 16 +- .../fhirpath/collection/CodingCollection.java | 13 +- .../fhirpath/collection/Collection.java | 86 ++++---- .../fhirpath/collection/DateCollection.java | 14 +- .../collection/DateTimeCollection.java | 8 +- .../collection/DecimalCollection.java | 17 +- .../collection/IntegerCollection.java | 23 +- .../fhirpath/collection/MixedCollection.java | 11 +- .../collection/QuantityCollection.java | 2 +- .../collection/ResourceCollection.java | 20 +- .../fhirpath/collection/StringCollection.java | 11 +- .../fhirpath/collection/TimeCollection.java | 9 +- .../pathling/fhirpath/column/ColumnCtx.java | 96 +++++++++ .../fhirpath/definition/NodeDefinition.java | 3 +- .../definition/ResolvedChoiceDefinition.java | 5 +- .../fhirpath/function/ColumnFunction0.java | 81 +++++++ .../fhirpath/function/ColumnFunctions.java | 91 ++++++++ .../fhirpath/function/CountFunction.java | 6 +- .../fhirpath/function/EmptyFunction.java | 2 +- .../fhirpath/function/ExistsFunction.java | 2 +- .../fhirpath/function/ExtensionFunction.java | 3 - .../fhirpath/function/FirstFunction.java | 11 +- .../fhirpath/function/GetIdFunction.java | 2 +- .../function/ReverseResolveFunction.java | 3 +- .../fhirpath/function/SumFunction.java | 2 +- .../fhirpath/function/WhereFunction.java | 8 +- .../registry/InMemoryFunctionRegistry.java | 2 +- .../fhirpath/operator/BooleanOperator.java | 8 +- .../fhirpath/operator/ComparisonOperator.java | 5 +- .../fhirpath/operator/MembershipOperator.java | 3 +- .../fhirpath/parser/InvocationVisitor.java | 4 +- .../fhirpath/validation/Codeable.java | 22 ++ .../fhirpath/validation/Expression.java | 22 ++ .../fhirpath/validation/FhirpathFunction.java | 28 +++ .../validation/FunctionConstraints.java | 32 +++ .../pathling/fhirpath/validation/Numeric.java | 22 ++ .../fhirpath/validation/ReturnType.java | 25 +++ .../pathling/query/ExpressionWithLabel.java | 5 +- .../function/ColumnFunction0Test.java | 32 +++ .../function/ColumnFunctionsTest.java | 197 ++++++++++++++++++ .../au/csiro/pathling/views/FhirViewTest.java | 5 +- 48 files changed, 1047 insertions(+), 182 deletions(-) create mode 100644 encoders/src/test/java/au/csiro/pathling/encoders/ExpressionsTest.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/ColumnHelpers.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunction0.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunctions.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/Codeable.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/Expression.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/FhirpathFunction.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/FunctionConstraints.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/Numeric.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/ReturnType.java create mode 100644 fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ColumnFunction0Test.java create mode 100644 fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ColumnFunctionsTest.java diff --git a/encoders/src/main/scala/au/csiro/pathling/encoders/Expressions.scala b/encoders/src/main/scala/au/csiro/pathling/encoders/Expressions.scala index 30e326a826..9b43012ef7 100644 --- a/encoders/src/main/scala/au/csiro/pathling/encoders/Expressions.scala +++ b/encoders/src/main/scala/au/csiro/pathling/encoders/Expressions.scala @@ -29,7 +29,9 @@ package au.csiro.pathling.encoders +import org.apache.spark.sql.{Column, functions} import org.apache.spark.sql.catalyst.InternalRow +import org.apache.spark.sql.catalyst.analysis.{UnresolvedException, UnresolvedExtractValue} import org.apache.spark.sql.catalyst.expressions._ import org.apache.spark.sql.catalyst.expressions.codegen.Block._ import org.apache.spark.sql.catalyst.expressions.codegen.{CodeGenerator, CodegenContext, ExprCode, FalseLiteral} @@ -304,3 +306,87 @@ case class AttachExtensions(targetObject: Expression, AttachExtensions(newChildren.head, newChildren.tail.head) } } + + +case class UnresolvedIfArray(value: Expression, arrayExpressions: Expression => Expression, elseExpression: Expression => Expression) + extends Expression with Unevaluable with NonSQLExpression { + + override def mapChildren(f: Expression => Expression): Expression = { + + val newValue = f(value) + + if (newValue.resolved) { + newValue.dataType match { + case ArrayType(_, _) => f(arrayExpressions(newValue)) + case _ => f(elseExpression(newValue)) + } + } + else { + copy(value = newValue) + } + } + + override def dataType: DataType = throw new UnresolvedException("dataType") + + override def nullable: Boolean = throw new UnresolvedException("nullable") + + override lazy val resolved = false + + override def toString: String = s"$value" + + override def children: Seq[Expression] = value :: Nil + + override def withNewChildrenInternal(newChildren: IndexedSeq[Expression]): Expression = { + UnresolvedIfArray(newChildren.head, arrayExpressions, elseExpression) + } +} + + +case class UnresolvedUnnest(value: Expression) + extends Expression with Unevaluable with NonSQLExpression { + + override def mapChildren(f: Expression => Expression): Expression = { + + val newValue = f(value) + + if (newValue.resolved) { + newValue.dataType match { + case ArrayType(ArrayType(_, _), _) => Flatten(newValue) + case _ => newValue + } + } + else { + copy(value = newValue) + } + } + + override def dataType: DataType = throw new UnresolvedException("dataType") + + override def nullable: Boolean = throw new UnresolvedException("nullable") + + override lazy val resolved = false + + override def toString: String = s"$value" + + override def children: Seq[Expression] = value :: Nil + + override def withNewChildrenInternal(newChildren: IndexedSeq[Expression]): Expression = { + UnresolvedUnnest(newChildren.head) + } +} + + +object ValueFunctions { + def ifArray(value: Column, arrayExpressions: Column => Column, elseExpression: Column => Column): Column = { + val expr = UnresolvedIfArray(value.alias(value.hashCode().toString).expr, + e => arrayExpressions(new Column(e)).expr, e => elseExpression(new Column(e)).expr) + new Column(expr) + } + + def unnest(value: Column): Column = { + val expr = UnresolvedUnnest(value.alias(value.hashCode().toString).expr) + new Column(expr) + } + +} + diff --git a/encoders/src/test/java/au/csiro/pathling/encoders/ExpressionsTest.java b/encoders/src/test/java/au/csiro/pathling/encoders/ExpressionsTest.java new file mode 100644 index 0000000000..cc1621381f --- /dev/null +++ b/encoders/src/test/java/au/csiro/pathling/encoders/ExpressionsTest.java @@ -0,0 +1,102 @@ +/* + * This is a modified version of the Bunsen library, originally published at + * https://github.com/cerner/bunsen. + * + * Bunsen is copyright 2017 Cerner Innovation, Inc., and is licensed under + * the Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0). + * + * These modifications are copyright 2023 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.encoders; + +import org.apache.spark.sql.*; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static au.csiro.pathling.encoders.ValueFunctions.*; + +/** + * Test for FHIR encoders. + */ +public class ExpressionsTest { + + + private static SparkSession spark; + + /** + * Set up Spark. + */ + @BeforeAll + public static void setUp() { + spark = SparkSession.builder() + .master("local[*]") + .appName("testing") + .config("spark.driver.bindAddress", "localhost") + .config("spark.driver.host", "localhost") + .getOrCreate(); + + + } + + /** + * Tear down Spark. + */ + @AfterAll + public static void tearDown() { + spark.stop(); + } + + public static Column size(Column arr) { + return ifArray(arr, functions::size, x -> functions.when(arr.isNotNull(), functions.lit(1)).otherwise(functions.lit(0))); + } + + @Test + public void testIfArray() { + Dataset ds = spark.range(2).toDF(); + Dataset resultDs = ds + .withColumn("id_array", functions.array(functions.col("id"), functions.lit(22))) + .withColumn("test_single", ifArray(ds.col("id"), x -> functions.array(functions.lit(20)), x -> functions.lit(10))) + .withColumn("test_array", ifArray(functions.col("id_array"), x -> functions.array(functions.lit(20)), x -> functions.lit(10))) + .withColumn("test_array_with_unresolved", ifArray(functions.col("id_array"), x -> functions.array(functions.col("id_array")), x -> functions.lit(10))) + .withColumn("test_lit", ifArray(functions.lit("a1"), x -> functions.array(functions.lit(20)), x -> functions.lit(10))) + .withColumn("test_array_lit", ifArray(functions.array(functions.lit("a1")), x -> functions.array(ds.col("id")), x -> ds.col("id"))) + .withColumn("test_array_lit_lambda", ifArray(functions.filter(functions.array(functions.lit("a1")), c -> c.equalTo("a1")), x -> functions.array(functions.lit(20)), x -> functions.lit(10))) + .withColumn("test_array_size", size(functions.col("id_array"))) + .withColumn("test_array_where", ifArray(functions.col("id_array"), x -> functions.filter(x, c -> size(c).equalTo(functions.col("id"))), x -> functions.lit(0))); + resultDs.show(); + resultDs.collectAsList().forEach(System.out::println); + } + + @Test + public void testFlatten() { + Dataset ds = spark.range(2).toDF(); + Dataset resultDs = ds + .withColumn("id_array", functions.array(functions.col("id"), functions.lit(22))) + .withColumn("id_array_of_arrays", functions.array(functions.array(functions.col("id"), functions.lit(22)), + functions.array(functions.col("id"), functions.lit(33)))) + .withColumn("test_unnest_single", unnest(ds.col("id"))) + .withColumn("test_unnest_array", unnest(functions.col("id_array"))) + .withColumn("test_unnest_array_of_arrays", unnest(functions.col("id_array_of_arrays"))) + .withColumn("test_unnest_array_of_arrays_if_array", ifArray(functions.col("id_array"), x -> unnest(functions.col("id_array_of_arrays")), x -> x)) + .withColumn("test_unnest_array_of_arrays_if_id", ifArray(functions.col("id"), x -> unnest(functions.col("id_array_of_arrays")), x -> x)); + + resultDs.show(); + resultDs.collectAsList().forEach(System.out::println); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/config/QueryConfiguration.java b/fhirpath/src/main/java/au/csiro/pathling/config/QueryConfiguration.java index b4b8a47be5..2e8cbc537d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/config/QueryConfiguration.java +++ b/fhirpath/src/main/java/au/csiro/pathling/config/QueryConfiguration.java @@ -24,6 +24,7 @@ /** * Represents configuration that controls the behaviour of query executors. + * * @author Piotr Szul */ @Data @@ -37,11 +38,10 @@ public class QueryConfiguration { @NotNull @Builder.Default private Boolean explainQueries = false; - + /** - * This controls whether the built-in caching within Spark is used for search results. - * It may be useful to turn this off for large datasets in memory-constrained - * environments. + * This controls whether the built-in caching within Spark is used for search results. It may be + * useful to turn this off for large datasets in memory-constrained environments. */ @NotNull @Builder.Default diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ColumnHelpers.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ColumnHelpers.java new file mode 100644 index 0000000000..64b0872003 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/ColumnHelpers.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 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; + +import au.csiro.pathling.encoders.ValueFunctions; +import java.util.function.BiFunction; +import java.util.function.Function; +import javax.annotation.Nonnull; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.functions; +import org.apache.spark.sql.types.ArrayType; + +// TODO: Review of this class/functions are needed +public class ColumnHelpers { + + @Nonnull + public static Column singular(@Nonnull final Column column) { + + return ValueFunctions.ifArray(column, + c -> functions.when(functions.size(c).leq(1), c.getItem(0)) + .otherwise(functions.raise_error( + functions.lit("Expected a single value, but found multiple values"))), + x -> x); + } + + @Nonnull + public static Column aggregate(@Nonnull final Column column, @Nonnull final Object zeroValue, + BiFunction aggregator) { + + return (column.expr().dataType() instanceof ArrayType) + ? functions.when(column.isNull(), zeroValue) + .otherwise(functions.aggregate(column, functions.lit(zeroValue), aggregator::apply)) + : functions.when(column.isNull(), zeroValue).otherwise(column); + // this is OK because aggregator(zero, x) == x + } + + public static Column map(@Nonnull final Column column, + @Nonnull final Function mapper) { + return (column.expr().dataType() instanceof ArrayType) + ? functions.when(column.isNull(), null) + .otherwise(functions.transform(column, mapper::apply)) + : functions.when(column.isNotNull(), mapper.apply(column)); + } +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Comparable.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Comparable.java index 36c231093a..ef2c1939f7 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Comparable.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Comparable.java @@ -49,8 +49,10 @@ static Function buildComparison(@Nonnull final Comparable so final TriFunction compFunction = operation.compFunction; + // TODO: Move to the interface (asking for the singular column) return target -> compFunction - .apply(comparator, source.getColumn(), target.getColumn()); + .apply(comparator, ColumnHelpers.singular(source.getColumn()), + ColumnHelpers.singular(target.getColumn())); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java index 5e1b80da14..c5ca028489 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/FhirPath.java @@ -1,8 +1,11 @@ package au.csiro.pathling.fhirpath; +import au.csiro.pathling.encoders.ValueFunctions; import au.csiro.pathling.fhirpath.collection.Collection; + import java.util.function.Function; import javax.annotation.Nonnull; + import org.apache.spark.sql.Column; /** @@ -20,11 +23,7 @@ public interface FhirPath { static Column applyOperation(@Nonnull final Collection input, @Nonnull final Function singularOperation, @Nonnull final Function collectionOperation) { - if (input.isSingular()) { - return singularOperation.apply(input.getColumn()); - } else { - return collectionOperation.apply(input.getColumn()); - } + return ValueFunctions.ifArray(input.getColumn(), collectionOperation::apply, + singularOperation::apply); } - } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java index c02bd12807..813db1ffe5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Referrer.java @@ -69,7 +69,7 @@ static Column referenceIdColumnFor(@Nonnull final Column referenceColumn) { @NotImplemented static Column resourceEqualityFor(@Nonnull final Referrer referrer, @Nonnull final ResourceCollection resourceCollection) { - + // TODO: implement // final Column targetId = resourceCollection.getCurrentResource() // .map(ResourceCollection::getIdColumn) 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 c038019b5a..0d87bcdcfb 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 @@ -45,8 +45,8 @@ public class BooleanCollection extends Collection implements Materializable type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition, final boolean singular) { - super(column, type, fhirType, definition, singular); + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); } /** @@ -54,14 +54,13 @@ protected BooleanCollection(@Nonnull final Column column, * * @param column The column to use * @param definition The definition to use - * @param singular Whether the collection is singular * @return A new instance of {@link BooleanCollection} */ @Nonnull public static BooleanCollection build(@Nonnull final Column column, - @Nonnull final Optional definition, final boolean singular) { + @Nonnull final Optional definition) { return new BooleanCollection(column, Optional.of(FhirPathType.BOOLEAN), - Optional.of(FHIRDefinedType.BOOLEAN), definition, singular); + Optional.of(FHIRDefinedType.BOOLEAN), definition); } /** @@ -73,7 +72,12 @@ public static BooleanCollection build(@Nonnull final Column column, @Nonnull public static BooleanCollection fromLiteral(@Nonnull final String literal) { final boolean value = literal.equals("true"); - return BooleanCollection.build(lit(value), Optional.empty(), true); + return BooleanCollection.build(lit(value), Optional.empty()); + } + + @Nonnull + public static BooleanCollection build(@Nonnull final Column column) { + return BooleanCollection.build(column, Optional.empty()); } @Nonnull 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 d15d450be6..925725d403 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 @@ -48,8 +48,8 @@ public class CodingCollection extends Collection implements Materializable type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition, final boolean singular) { - super(column, type, fhirType, definition, singular); + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); } /** @@ -57,14 +57,13 @@ protected CodingCollection(@Nonnull final Column column, * * @param column The column to use * @param definition The definition to use - * @param singular Whether the collection is singular * @return A new instance of {@link CodingCollection} */ @Nonnull public static CodingCollection build(@Nonnull final Column column, - @Nonnull final Optional definition, final boolean singular) { + @Nonnull final Optional definition) { return new CodingCollection(column, Optional.of(FhirPathType.CODING), - Optional.of(FHIRDefinedType.CODING), definition, singular); + Optional.of(FHIRDefinedType.CODING), definition); } /** @@ -79,7 +78,7 @@ public static CodingCollection fromLiteral(@Nonnull final String fhirPath) throws IllegalArgumentException { final Coding coding = CodingLiteral.fromString(fhirPath); final Column column = buildColumn(coding); - return CodingCollection.build(column, Optional.empty(), true); + return CodingCollection.build(column, Optional.empty()); } @Nonnull @@ -132,7 +131,7 @@ public Function getComparison(@Nonnull final ComparisonOpera @Override public Collection asStringPath() { final Column valueColumn = callUDF(CodingToLiteral.FUNCTION_NAME, getColumn()); - return CodingCollection.build(valueColumn, Optional.empty(), isSingular()); + return CodingCollection.build(valueColumn, Optional.empty()); } } 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 62aa0ba4b9..0f07bbc297 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 @@ -17,12 +17,11 @@ package au.csiro.pathling.fhirpath.collection; -import static org.apache.spark.sql.functions.flatten; - +import au.csiro.pathling.fhirpath.ColumnHelpers; import au.csiro.pathling.fhirpath.Comparable; -import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.Numeric; +import au.csiro.pathling.fhirpath.column.ColumnCtx; import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.definition.NodeDefinition; import com.google.common.collect.ImmutableMap; @@ -99,11 +98,6 @@ public class Collection implements Comparable, Numeric { @Nonnull private Optional definition; - /** - * Whether the result of evaluating this expression is a singleton collection. - */ - private boolean singular; - /** * Builds a generic {@link Collection} with the specified column, FHIRPath type, FHIR type and * definition. @@ -118,8 +112,8 @@ public class Collection implements Comparable, Numeric { public static Collection build(@Nonnull final Column column, @Nonnull final Optional fhirPathType, @Nonnull final Optional fhirType, - @Nonnull final Optional definition, final boolean singular) { - return new Collection(column, fhirPathType, fhirType, definition, singular); + @Nonnull final Optional definition) { + return new Collection(column, fhirPathType, fhirType, definition); } /** @@ -130,15 +124,14 @@ public static Collection build(@Nonnull final Column column, * * @param column a {@link Column} containing the result of the expression * @param definition the {@link ElementDefinition} that this path should be based upon - * @param singular whether the result of evaluating this expression is a singleton collection * @return a new {@link Collection} */ @Nonnull public static Collection build(@Nonnull final Column column, - @Nonnull final ElementDefinition definition, final boolean singular) { + @Nonnull final ElementDefinition definition) { final Optional optionalFhirType = definition.getFhirType(); if (optionalFhirType.isPresent()) { - return getInstance(column, optionalFhirType, Optional.of(definition), singular); + return getInstance(column, optionalFhirType, Optional.of(definition)); } else { throw new IllegalArgumentException( "Attempted to build a Collection with an ElementDefinition with no fhirType"); @@ -153,19 +146,18 @@ public static Collection build(@Nonnull final Column column, * * @param column a {@link Column} containing the result of the expression * @param fhirType the {@link FHIRDefinedType} that this path should be based upon - * @param singular whether the result of evaluating this expression is a singleton collection * @return a new {@link Collection} */ @Nonnull public static Collection build(@Nonnull final Column column, - @Nonnull final FHIRDefinedType fhirType, final boolean singular) { - return getInstance(column, Optional.of(fhirType), Optional.empty(), singular); + @Nonnull final FHIRDefinedType fhirType) { + return getInstance(column, Optional.of(fhirType), Optional.empty()); } @Nonnull private static Collection getInstance(@Nonnull final Column column, @Nonnull final Optional fhirType, - @Nonnull final Optional definition, final boolean singular) { + @Nonnull final Optional definition) { // Look up the class that represents an element with the specified FHIR type. final FHIRDefinedType resolvedType = fhirType .or(() -> definition.flatMap(ElementDefinition::getFhirType)) @@ -177,10 +169,9 @@ private static Collection getInstance(@Nonnull final Column column, try { // Call its constructor and return. final Constructor constructor = elementPathClass - .getDeclaredConstructor(Column.class, Optional.class, Optional.class, Optional.class, - boolean.class); + .getDeclaredConstructor(Column.class, Optional.class, Optional.class, Optional.class); return constructor - .newInstance(column, Optional.ofNullable(fhirPathType), fhirType, definition, singular); + .newInstance(column, Optional.ofNullable(fhirPathType), fhirType, definition); } catch (final NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Problem building a Collection object", e); @@ -198,45 +189,21 @@ public static Optional> classForType( } /** - * Return the child {@link Collection} that results from traversing to the given expression. + * Return the child {@link Collection} that results from traversing to the given elementName. * - * @param expression the name of the child element - * @param context an {@link EvaluationContext} + * @param elementName the name of the child element * @return a new {@link Collection} representing the child element */ @Nonnull - public Optional traverse(@Nonnull final String expression, - @Nonnull final EvaluationContext context) { + public Optional traverse(@Nonnull final String elementName) { final Optional elementDefinition = definition .filter(def -> def instanceof ElementDefinition) .map(def -> (ElementDefinition) def); - final Optional parentCardinality = elementDefinition.flatMap( - ElementDefinition::getMaxCardinality); - // It is only possible to traverse to a child with an element definition. return elementDefinition // Get the named child definition. - .flatMap(def -> def.getChildElement(expression)) - .map(def -> { - // Build a new FhirPath object, with a column that uses `getField` to extract the - // appropriate child. - final Column traversed = column.getField(expression); - - // Detect if flattening is required by examining the cardinality of the parent and the - // child within the traversal operation. If both will result in arrays, flattening will be - // required. - final Optional childCardinality = def.getMaxCardinality(); - final Column flattened = - parentCardinality.orElse(0) != 1 && childCardinality.orElse(0) != 1 - ? flatten(traversed) - : traversed; - - // Determine whether the new collection is singular. - final boolean singular = isSingular() && childCardinality.isPresent() - && childCardinality.get() == 1; - - return Collection.build(flattened, def, singular); - }); + .flatMap(def -> def.getChildElement(elementName)) + .map(def -> Collection.build(getCtx().traverse(elementName).getValue(), def)); } // @Nonnull @@ -303,6 +270,7 @@ public Optional getNumericContextColumn() { return Optional.empty(); } + /** * Creates a null {@link Collection}. * @@ -311,7 +279,25 @@ public Optional getNumericContextColumn() { @Nonnull public static Collection nullCollection() { return new Collection(functions.lit(null), Optional.empty(), Optional.empty(), - Optional.empty(), false); + Optional.empty()); + } + + @Nonnull + public Collection copyWith(Column newValue) { + // TODO: This is very very suspicious + // Really need to understand what the relationships between all the different types + // some of them seem redundant + return getInstance(newValue, getFhirType(), (Optional) definition); + } + + public Column getSingleton() { + return ColumnHelpers.singular(getColumn()); + } + + @Nonnull + // TODO: Find a better name + public ColumnCtx getCtx() { + return ColumnCtx.of(getColumn()); } } 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 6549388e95..1971e7b82a 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 @@ -51,8 +51,8 @@ public class DateCollection extends Collection implements Materializable type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition, final boolean singular) { - super(column, type, fhirType, definition, singular); + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); } /** @@ -60,14 +60,13 @@ protected DateCollection(@Nonnull final Column column, @Nonnull final Optional definition, final boolean singular) { + @Nonnull final Optional definition) { return new DateCollection(column, Optional.of(FhirPathType.DATE), - Optional.of(FHIRDefinedType.DATE), definition, singular); + Optional.of(FHIRDefinedType.DATE), definition); } /** @@ -80,7 +79,7 @@ public static DateCollection build(@Nonnull final Column column, @Nonnull public static DateCollection fromLiteral(@Nonnull final String fhirPath) throws ParseException { final String dateString = fhirPath.replaceFirst("^@", ""); - return DateCollection.build(lit(dateString), Optional.empty(), true); + return DateCollection.build(lit(dateString), Optional.empty()); } @Nonnull @@ -115,8 +114,7 @@ public Function getDateArithmeticOperation( @Nonnull @Override public Collection asStringPath() { - return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty(), - isSingular()); + return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty()); } } 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 8dc1209c44..1b281a2d18 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 @@ -57,8 +57,8 @@ public class DateTimeCollection extends Collection implements protected DateTimeCollection(@Nonnull final Column column, @Nonnull final Optional type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition, final boolean singular) { - super(column, type, fhirType, definition, singular); + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); } /** @@ -73,7 +73,7 @@ protected DateTimeCollection(@Nonnull final Column column, public static DateTimeCollection build(@Nonnull final Column column, @Nonnull final Optional definition, final boolean singular) { return new DateTimeCollection(column, Optional.of(FhirPathType.DATETIME), - Optional.of(FHIRDefinedType.DATETIME), definition, singular); + Optional.of(FHIRDefinedType.DATETIME), definition); } /** @@ -139,7 +139,7 @@ public Collection asStringPath() { } else { valueColumn = getColumn(); } - return StringCollection.build(valueColumn, Optional.empty(), isSingular()); + return StringCollection.build(valueColumn, Optional.empty()); } } 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 4b89235a7b..5c8ef1595e 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 @@ -53,8 +53,8 @@ public class DecimalCollection extends Collection implements Materializable fhirPathType, @Nonnull final Optional fhirType, - @Nonnull final Optional definition, final boolean singular) { - super(column, fhirPathType, fhirType, definition, singular); + @Nonnull final Optional definition) { + super(column, fhirPathType, fhirType, definition); } /** @@ -62,14 +62,13 @@ protected DecimalCollection(@Nonnull final Column column, * * @param column The column to use * @param definition The definition to use - * @param singular Whether the collection is singular * @return A new instance of {@link DecimalCollection} */ @Nonnull public static DecimalCollection build(@Nonnull final Column column, - @Nonnull final Optional definition, final boolean singular) { + @Nonnull final Optional definition) { return new DecimalCollection(column, Optional.of(FhirPathType.DECIMAL), - Optional.of(FHIRDefinedType.DECIMAL), definition, singular); + Optional.of(FHIRDefinedType.DECIMAL), definition); } /** @@ -83,7 +82,7 @@ public static DecimalCollection build(@Nonnull final Column column, public static DecimalCollection fromLiteral(@Nonnull final String literal) throws NumberFormatException { final BigDecimal value = parseLiteral(literal); - return DecimalCollection.build(lit(value), Optional.empty(), true); + return DecimalCollection.build(lit(value), Optional.empty()); } @Nonnull @@ -131,10 +130,10 @@ public Function getMathOperation(@Nonnull final MathOperati case MULTIPLICATION: case DIVISION: result = result.cast(getDecimalType()); - return DecimalCollection.build(result, Optional.empty(), isSingular()); + return DecimalCollection.build(result, Optional.empty()); case MODULUS: result = result.cast(DataTypes.LongType); - return IntegerCollection.build(result, Optional.empty(), isSingular()); + return IntegerCollection.build(result, Optional.empty()); default: throw new AssertionError("Unsupported math operation encountered: " + operation); } @@ -184,7 +183,7 @@ public Optional getFhirValueFromRow(@Nonnull final Row row, final i @Override @Nonnull public Collection asStringPath() { - return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty(), true); + return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty()); } 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 62d92cb3bb..10954effdb 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 @@ -55,8 +55,8 @@ public class IntegerCollection extends Collection implements protected IntegerCollection(@Nonnull final Column column, @Nonnull final Optional type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition, final boolean singular) { - super(column, type, fhirType, definition, singular); + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); } /** @@ -64,16 +64,21 @@ protected IntegerCollection(@Nonnull final Column column, * * @param column The column to use * @param definition The definition to use - * @param singular Whether the collection is singular * @return A new instance of {@link IntegerCollection} */ @Nonnull public static IntegerCollection build(@Nonnull final Column column, - @Nonnull final Optional definition, final boolean singular) { + @Nonnull final Optional definition) { return new IntegerCollection(column, Optional.of(FhirPathType.INTEGER), - Optional.of(FHIRDefinedType.INTEGER), definition, singular); + Optional.of(FHIRDefinedType.INTEGER), definition); } + @Nonnull + public static IntegerCollection build(@Nonnull final Column column) { + return build(column, Optional.empty()); + } + + /** * Returns a new instance, parsed from a FHIRPath literal. * @@ -84,7 +89,7 @@ public static IntegerCollection build(@Nonnull final Column column, public static IntegerCollection fromLiteral(@Nonnull final String fhirPath) throws NumberFormatException { final int value = Integer.parseInt(fhirPath); - return IntegerCollection.build(lit(value), Optional.empty(), true); + return IntegerCollection.build(lit(value)); } @Nonnull @@ -172,11 +177,11 @@ public static Function buildMathOperation(@Nonnull final Nu if (target instanceof DecimalCollection) { valueColumn = valueColumn.cast(DataTypes.LongType); } - return IntegerCollection.build(valueColumn, Optional.empty(), true); + return IntegerCollection.build(valueColumn, Optional.empty()); case DIVISION: final Column numerator = source.getColumn().cast(DecimalCollection.getDecimalType()); valueColumn = operation.getSparkFunction().apply(numerator, targetNumeric); - return DecimalCollection.build(valueColumn, Optional.empty(), true); + return DecimalCollection.build(valueColumn, Optional.empty()); default: throw new AssertionError("Unsupported math operation encountered: " + operation); } @@ -186,7 +191,7 @@ public static Function buildMathOperation(@Nonnull final Nu @Override @Nonnull public Collection asStringPath() { - return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty(), true); + return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java index dbf6dbe553..1b561452ae 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java @@ -39,7 +39,7 @@ protected MixedCollection(@Nonnull final Column column, @Nonnull final Optional fhirType, @Nonnull final Optional definition, final boolean singular, @Nonnull final Collection from) { - super(column, type, fhirType, definition, singular); + super(column, type, fhirType, definition); this.from = from; checkArgument(definition.isPresent() && definition.get() instanceof ChoiceElementDefinition, "definition must be a ChoiceElementDefinition"); @@ -65,8 +65,7 @@ public static MixedCollection build(@Nonnull final Column column, @Nonnull @Override - public Optional traverse(@Nonnull final String expression, - final EvaluationContext context) { + public Optional traverse(@Nonnull final String elementName) { return Optional.empty(); } @@ -95,10 +94,8 @@ public Optional resolveChoice(@Nonnull final String type, final Optional definition = resolveChoiceDefinition(type); checkUserInput(elementColumn.isPresent() && definition.isPresent(), "No such child: " + columnName); - return elementColumn.map(column -> Collection.build(column, definition.get(), - isSingular())); + return elementColumn.map(column -> Collection.build(column, definition.get())); } - return from.traverse(columnName, context); + return from.traverse(columnName); } - } 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 52a61f42be..cc9f8a3dac 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 @@ -60,7 +60,7 @@ public QuantityCollection(@Nonnull final Column column, @Nonnull final Optional type, @Nonnull final Optional fhirType, @Nonnull final Optional definition, final boolean singular) { - super(column, type, fhirType, definition, singular); + super(column, type, fhirType, definition); } /** 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 71ba9f18cd..994d6b2f7d 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 @@ -21,7 +21,6 @@ import au.csiro.pathling.encoders.EncoderBuilder; import au.csiro.pathling.encoders.ExtensionSupport; -import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.definition.NodeDefinition; import au.csiro.pathling.fhirpath.definition.ResourceDefinition; @@ -79,7 +78,7 @@ protected ResourceCollection(@Nonnull final Column column, final boolean singular, @Nonnull final Map elementsToColumns, @Nonnull final ResourceDefinition resourceDefinition, @Nonnull final Dataset dataset) { - super(column, type, fhirType, definition, singular); + super(column, type, fhirType, definition); this.elementsToColumns = elementsToColumns; this.resourceDefinition = resourceDefinition; this.dataset = dataset; @@ -116,10 +115,10 @@ public static ResourceCollection build(@Nonnull final FhirContext fhirContext, //noinspection ReturnOfNull final Map elementsToColumns = Stream.of(dataset.columns()) - .collect(Collectors.toMap(Function.identity(), functions::col, (a, b) -> null)); + .collect(Collectors.toUnmodifiableMap(Function.identity(), dataset::col)); // We use the ID column as the value column for a ResourcePath. - return new ResourceCollection(functions.col("id"), Optional.empty(), + return new ResourceCollection(dataset.col("id"), Optional.empty(), getFhirType(resourceType), Optional.of(definition), singular, elementsToColumns, definition, dataset); } @@ -170,15 +169,14 @@ public ResourceType getResourceType() { @Nonnull @Override - public Optional traverse(@Nonnull final String expression, - final EvaluationContext context) { + public Optional traverse(@Nonnull final String elementName) { // Get the child column from the map of elements to columns. - return getElementColumn(expression).flatMap(value -> + return getElementColumn(elementName).flatMap(value -> // Get the child element definition from the resource definition. - resourceDefinition.getChildElement(expression).map(definition -> { - final boolean singular = isSingular() && definition.getMaxCardinality().isPresent() - && definition.getMaxCardinality().get() == 1; - return Collection.build(value, definition, singular); + resourceDefinition.getChildElement(elementName).map(definition -> { + // final boolean singular = isSingular() && definition.getMaxCardinality().isPresent() + // && definition.getMaxCardinality().get() == 1; + return Collection.build(value, definition); })); } 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 c551a39ce4..81895ffea8 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 @@ -48,8 +48,8 @@ public class StringCollection extends Collection implements Materializable type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition, final boolean singular) { - super(column, type, fhirType, definition, singular); + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); } /** @@ -57,14 +57,13 @@ public StringCollection(@Nonnull final Column column, @Nonnull final Optional definition, final boolean singular) { + @Nonnull final Optional definition) { return new StringCollection(column, Optional.of(FhirPathType.STRING), - Optional.of(FHIRDefinedType.STRING), definition, singular); + Optional.of(FHIRDefinedType.STRING), definition); } /** @@ -76,7 +75,7 @@ public static StringCollection build(@Nonnull final Column column, @Nonnull public static StringCollection fromLiteral(@Nonnull final String fhirPath) { final String value = parseStringLiteral(fhirPath); - return StringCollection.build(lit(value), Optional.empty(), true); + return StringCollection.build(lit(value), Optional.empty()); } @Nonnull 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 420aac687d..68593c9d0a 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 @@ -42,8 +42,8 @@ public class TimeCollection extends Collection implements Materializable type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition, final boolean singular) { - super(column, type, fhirType, definition, singular); + @Nonnull final Optional definition) { + super(column, type, fhirType, definition); } /** @@ -58,7 +58,7 @@ public TimeCollection(@Nonnull final Column column, @Nonnull final Optional definition, final boolean singular) { return new TimeCollection(column, Optional.of(FhirPathType.TIME), - Optional.of(FHIRDefinedType.TIME), definition, singular); + Optional.of(FHIRDefinedType.TIME), definition); } /** @@ -85,8 +85,7 @@ public Optional getFhirValueFromRow(@Nonnull final Row row, final int @Nonnull @Override public Collection asStringPath() { - return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty(), - isSingular()); + return StringCollection.build(getColumn().cast(DataTypes.StringType), Optional.empty()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java new file mode 100644 index 0000000000..4a698e0f66 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java @@ -0,0 +1,96 @@ +/* + * Copyright 2023 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.column; + +import au.csiro.pathling.encoders.ValueFunctions; +import java.util.function.BiFunction; +import java.util.function.Function; +import javax.annotation.Nonnull; +import lombok.Value; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.functions; +import org.apache.spark.sql.types.ArrayType; + +@Value(staticConstructor = "of") +public class ColumnCtx { + + Column value; + + @Nonnull + public ColumnCtx vectorize(@Nonnull final Function arrayExpression, + @Nonnull final Function singularExpression) { + return of(ValueFunctions.ifArray(value, arrayExpression::apply, singularExpression::apply)); + } + + @Nonnull + public ColumnCtx vectorize(@Nonnull final Function arrayExpression) { + // the default implementation just wraps the element info array if needed + return vectorize(arrayExpression, + c -> arrayExpression.apply(functions.when(c.isNotNull(), functions.array(c)))); + } + + @Nonnull + public ColumnCtx traverse(@Nonnull final String fieldName) { + return of(ValueFunctions.unnest(value.getField(fieldName))); + } + + @Nonnull + public ColumnCtx singular() { + return vectorize( + c -> functions.when(functions.size(c).leq(1), c.getItem(0)) + .otherwise(functions.raise_error( + functions.lit("Expected a single value, but found multiple values"))), + Function.identity() + ); + } + + + @Nonnull + public ColumnCtx aggregate(@Nonnull final Object zeroValue, + BiFunction aggregator) { + + return vectorize( + c -> functions.when(c.isNull(), zeroValue) + .otherwise(functions.aggregate(c, functions.lit(zeroValue), aggregator::apply)), + c -> functions.when(c.isNull(), zeroValue).otherwise(c) + ); + // this is OK because aggregator(zero, x) == x + } + + + @Nonnull + public ColumnCtx first() { + return vectorize(c -> c.getItem(0), Function.identity()); + } + + @Nonnull + public ColumnCtx count() { + return vectorize( + c -> functions.when(c.isNull(), 0).otherwise(functions.size(c)), + c -> functions.when(c.isNull(), 0).otherwise(1) + ); + } + + @Nonnull + public ColumnCtx empty() { + return vectorize( + c -> functions.when(c.isNotNull(), functions.size(c).equalTo(0)).otherwise(true), + Column::isNull); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/NodeDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/NodeDefinition.java index 5883c4ed68..50b56618b4 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/NodeDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/NodeDefinition.java @@ -4,8 +4,7 @@ import javax.annotation.Nonnull; /** - * - * @param + * */ public interface NodeDefinition { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResolvedChoiceDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResolvedChoiceDefinition.java index 09a16d3969..24472137aa 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResolvedChoiceDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResolvedChoiceDefinition.java @@ -11,12 +11,13 @@ public class ResolvedChoiceDefinition implements ElementDefinition { @Nonnull private final BaseRuntimeElementDefinition elementDefinition; - + @Nonnull @Getter private final Optional maxCardinality; - public ResolvedChoiceDefinition(@Nonnull final BaseRuntimeElementDefinition definition, @Nonnull final + public ResolvedChoiceDefinition(@Nonnull final BaseRuntimeElementDefinition definition, + @Nonnull final ChoiceElementDefinition parent) { this.elementDefinition = definition; this.maxCardinality = parent.getMaxCardinality(); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunction0.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunction0.java new file mode 100644 index 0000000000..f8bead3fd0 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunction0.java @@ -0,0 +1,81 @@ +/* + * Copyright 2023 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.function; + +import au.csiro.pathling.fhirpath.FunctionInput; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.validation.FhirpathFunction; +import au.csiro.pathling.fhirpath.validation.ReturnType; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import lombok.Value; +import org.apache.spark.sql.Column; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +@Value +public class ColumnFunction0 implements NamedFunction { + + String name; + Function columnFunction; + Optional returnType; + + + @Override + @Nonnull + public String getName() { + return name; + } + + + @Override + @Nonnull + public Collection invoke(@Nonnull final FunctionInput functionInput) { + final Column columnResult = columnFunction.apply(functionInput.getInput().getColumn()); + // if the result type is not defined pass the input collection type through + return returnType.map(rt -> Collection.build(columnResult, rt)) + .orElse((functionInput.getInput().copyWith(columnResult))); + } + + public static Function from(Method method) { + return column -> { + try { + return (Column) method.invoke(null, column); + } catch (final ReflectiveOperationException e) { + throw new RuntimeException(e); + } + }; + } + + public static ColumnFunction0 of(@Nonnull final Method method) { + + return new ColumnFunction0(method.getName(), from(method), + Optional.ofNullable(method.getAnnotation(ReturnType.class)).map(ReturnType::value)); + } + + public static List of(@Nonnull final Class clazz) { + return Stream.of(clazz.getDeclaredMethods()) + .filter(m -> m.getAnnotation(FhirpathFunction.class) != null) + .map(ColumnFunction0::of).collect(Collectors.toUnmodifiableList()); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunctions.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunctions.java new file mode 100644 index 0000000000..ca3d571e30 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunctions.java @@ -0,0 +1,91 @@ +/* + * Copyright 2023 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.function; + +import au.csiro.pathling.fhirpath.column.ColumnCtx; +import au.csiro.pathling.fhirpath.validation.FhirpathFunction; +import au.csiro.pathling.fhirpath.validation.Numeric; +import au.csiro.pathling.fhirpath.validation.ReturnType; +import au.csiro.pathling.sql.misc.TemporalDifferenceFunction; +import java.util.function.Function; +import javax.annotation.Nonnull; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.functions; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +public class ColumnFunctions { + + @FhirpathFunction + @ReturnType(FHIRDefinedType.BOOLEAN) + public static Column not(@Nonnull final Column column) { + return ColumnCtx.of(column).vectorize( + c -> functions.when(c.isNotNull(), functions.transform(c, functions::not)), + c -> functions.when(c.isNotNull(), functions.not(c)) + ).getValue(); + } + + + @FhirpathFunction + @ReturnType(FHIRDefinedType.BOOLEAN) + public static Column empty(@Nonnull final Column column) { + return ColumnCtx.of(column).empty().getValue(); + } + + @FhirpathFunction + @ReturnType(FHIRDefinedType.INTEGER) + public static Column count(@Nonnull final Column column) { + return ColumnCtx.of(column).count().getValue(); + } + + @FhirpathFunction + public static Column first(@Nonnull final Column column) { + // how to deal with nulls inside the expressioss? + // technically should use filter to remove nulls, but that's expensive + return ColumnCtx.of(column).first().getValue(); + } + + @FhirpathFunction + public static Column last(@Nonnull final Column column) { + // we need to use `element_at()` here are `getItem()` does not support column arguments + // NOTE: `element_at()` is 1-indexed as opposed to `getItem()` which is 0-indexed + return ColumnCtx.of(column).vectorize( + c -> functions.when(c.isNull().or(functions.size(c).equalTo(0)), null) + .otherwise(functions.element_at(c, functions.size(c))), + Function.identity() + ).getValue(); + } + + @FhirpathFunction + public static Column singular(@Nonnull final Column column) { + return ColumnCtx.of(column).singular().getValue(); + } + + @FhirpathFunction + public static Column sum(@Nonnull @Numeric final Column column) { + return ColumnCtx.of(column).aggregate(0, Column::plus).getValue(); + } + + @ReturnType(FHIRDefinedType.INTEGER) + public static Column until(@Nonnull final Column from, @Nonnull final Column to, + @Nonnull final Column calendarDuration) { + // the singularity is not strictly necessary here for from as we could lift it with transform + return + functions.call_udf(TemporalDifferenceFunction.FUNCTION_NAME, singular(from), singular(to), + singular(calendarDuration)); + } +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java index 0279bdf9e6..c1ed4c832f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CountFunction.java @@ -41,9 +41,7 @@ public class CountFunction implements NamedFunction { @Override public Collection invoke(@Nonnull final FunctionInput input) { checkNoArguments(getName(), input); - final Collection inputPath = input.getInput(); - final Column valueColumn = size(inputPath.getColumn()); - return IntegerCollection.build(valueColumn, Optional.empty(), true); + return IntegerCollection.build( + input.getInput().getCtx().count().getValue()); } - } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java index e1537c5057..d7f4688a74 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/EmptyFunction.java @@ -43,7 +43,7 @@ public Collection invoke(@Nonnull final FunctionInput input) { // We use the count function to determine whether there are zero items in the input collection. final Collection countResult = new CountFunction().invoke(input); final Column valueColumn = countResult.getColumn().equalTo(0); - return BooleanCollection.build(valueColumn, Optional.empty(), true); + return BooleanCollection.build(valueColumn, Optional.empty()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java index a12e3c40f3..7b98cf112d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExistsFunction.java @@ -61,7 +61,7 @@ public Collection invoke(@Nonnull final FunctionInput input) { column = existsResult.getColumn(); } - return BooleanCollection.build(column, Optional.empty(), true); + return BooleanCollection.build(column, Optional.empty()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java index d57d8773aa..b5f73b7c4b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ExtensionFunction.java @@ -31,9 +31,6 @@ public class ExtensionFunction implements NamedFunction { // TODO: implement as columns - - // private static final String NAME = "extension"; - // // @Nonnull // @Override // public String getName() { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java index 05966d93e5..050d2d596b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/FirstFunction.java @@ -17,14 +17,12 @@ package au.csiro.pathling.fhirpath.function; -import static au.csiro.pathling.fhirpath.FhirPath.applyOperation; import static au.csiro.pathling.fhirpath.function.NamedFunction.checkNoArguments; import au.csiro.pathling.fhirpath.FunctionInput; import au.csiro.pathling.fhirpath.annotations.Name; import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; -import java.util.function.Function; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -43,12 +41,7 @@ public class FirstFunction implements NamedFunction { @Override public Collection invoke(@Nonnull final FunctionInput input) { checkNoArguments(getName(), input); - - final Collection inputCollection = input.getInput(); - final Column result = applyOperation( - inputCollection, Function.identity(), column -> column.getItem(0)); - - return Collection.build(result, inputCollection.getType(), - inputCollection.getFhirType(), inputCollection.getDefinition(), true); + final Column result = input.getInput().getCtx().first().getValue(); + return input.getInput().copyWith(result); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java index fe71aa1934..97e80ea35d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/GetIdFunction.java @@ -8,7 +8,7 @@ public class GetIdFunction implements NamedFunction { // TODO: impelement as columns - + // private static final String NAME = "getId"; // // @Nonnull diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java index 2c7d92afab..d3bfccb8ff 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ReverseResolveFunction.java @@ -25,7 +25,8 @@ * referring element is supplied as an argument. * * @author John Grimes - * @see reverseResolve + * @see reverseResolve */ @Name("reverseResolve") @NotImplemented diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java index c365883fe4..a60e1e3174 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/SumFunction.java @@ -31,7 +31,7 @@ public class SumFunction implements NamedFunction { // TODO: implement as columns - + // private static final String NAME = "sum"; // // public SumFunction() { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java index 2b32b8faca..17a81acd61 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java @@ -51,15 +51,15 @@ public Collection invoke(@Nonnull final FunctionInput input) { final Column column = filter(previous.getColumn(), element -> { final Collection result = argument.apply( Collection.build(element, previous.getType(), previous.getFhirType(), - previous.getDefinition(), true), input.getContext()); + previous.getDefinition()), input.getContext()); final FhirPathType type = checkPresent(result.getType()); - checkUserInput(type.equals(FhirPathType.BOOLEAN) && result.isSingular(), + checkUserInput(type.equals(FhirPathType.BOOLEAN), "Argument to " + getName() + " function must be a singular Boolean"); - return result.getColumn(); + return result.getSingleton(); }); return Collection.build(column, previous.getType(), previous.getFhirType(), - previous.getDefinition(), previous.isSingular()); + previous.getDefinition()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/InMemoryFunctionRegistry.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/InMemoryFunctionRegistry.java index dcc8be56ce..7cc2c02223 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/InMemoryFunctionRegistry.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/InMemoryFunctionRegistry.java @@ -21,7 +21,7 @@ public InMemoryFunctionRegistry(@Nonnull final Map functions) { @Nonnull @Override - public T getInstance(@Nonnull final String name) throws NoSuchFunctionException { + public T getInstance(@Nonnull final String name) throws NoSuchFunctionException { final T function = functions.get(name); if (function == null) { throw new NoSuchFunctionException("Unsupported function: " + name); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java index a53a6f419f..e4e26cd95a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BooleanOperator.java @@ -55,13 +55,9 @@ public Collection invoke(@Nonnull final BinaryOperatorInput input) { checkUserInput(left instanceof BooleanCollection, "Left operand to " + type + " operator must be Boolean"); - checkUserInput(left.isSingular(), - "Left operand to " + type + " operator must be singular"); checkUserInput(right instanceof BooleanCollection, "Right operand to " + type + " operator must be Boolean"); - checkUserInput(right.isSingular(), - "Right operand to " + type + " operator must be singular"); - + final Column leftValue = left.getColumn(); final Column rightValue = right.getColumn(); @@ -97,7 +93,7 @@ public Collection invoke(@Nonnull final BinaryOperatorInput input) { throw new AssertionError("Unsupported boolean operator encountered: " + type); } - return BooleanCollection.build(valueColumn, Optional.empty(), true); + return BooleanCollection.build(valueColumn, Optional.empty()); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java index 40473b37af..13cf8eae3a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/ComparisonOperator.java @@ -53,12 +53,9 @@ public Collection invoke(@Nonnull final BinaryOperatorInput input) { final Collection left = input.getLeft(); final Collection right = input.getRight(); - checkUserInput(left.isSingular(), "Left operand must be singular"); - checkUserInput(right.isSingular(), "Right operand must be singular"); checkUserInput(left.isComparableTo(right), "Operands must be comparable"); - return BooleanCollection.build(left.getComparison(type).apply(right), Optional.empty(), - true); + return BooleanCollection.build(left.getComparison(type).apply(right), Optional.empty()); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java index 1cb3bcebce..a66b406466 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/MembershipOperator.java @@ -38,9 +38,8 @@ public MembershipOperator(final MembershipOperatorType type) { this.type = type; } - // TODO: implement with columns - + // @Nonnull // @Override // public Collection invoke(@Nonnull final BinaryOperatorInput input) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index 999d355918..aab05a5094 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -70,7 +70,7 @@ public FhirPath visitMemberInvocation( return (input, context) -> { try { // Attempt path traversal. - final Optional result = input.traverse(fhirPath, context); + final Optional result = input.traverse(fhirPath); checkUserInput(result.isPresent(), "No such child: " + fhirPath); return result.get(); @@ -80,7 +80,7 @@ public FhirPath visitMemberInvocation( // If it is not a valid path traversal, see if it is a valid type specifier. final FHIRDefinedType fhirType = FHIRDefinedType.fromCode(fhirPath); return Collection.build(functions.lit(null), Optional.of(FhirPathType.TYPE_SPECIFIER), - Optional.of(fhirType), Optional.empty(), true); + Optional.of(fhirType), Optional.empty()); } catch (final FHIRException e2) { throw new InvalidUserInputError( diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/Codeable.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/Codeable.java new file mode 100644 index 0000000000..f1d9963bf6 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/Codeable.java @@ -0,0 +1,22 @@ +/* + * Copyright 2023 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.validation; + +public @interface Codeable { + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/Expression.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/Expression.java new file mode 100644 index 0000000000..7fc8b87352 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/Expression.java @@ -0,0 +1,22 @@ +/* + * Copyright 2023 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.validation; + +public @interface Expression { + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/FhirpathFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/FhirpathFunction.java new file mode 100644 index 0000000000..aa87b7a665 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/FhirpathFunction.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 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.validation; + +import javax.annotation.Nonnull; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@Nonnull +public @interface FhirpathFunction { + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/FunctionConstraints.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/FunctionConstraints.java new file mode 100644 index 0000000000..197b6964c4 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/FunctionConstraints.java @@ -0,0 +1,32 @@ +/* + * Copyright 2023 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.validation; + +import au.csiro.pathling.fhirpath.collection.Collection; +import lombok.Value; +import javax.annotation.Nonnull; + +@Value +public class FunctionConstraints { + + Class inputType; + + public void validate(@Nonnull final Collection input) { + assert (inputType.isAssignableFrom(input.getClass())); + } +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/Numeric.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/Numeric.java new file mode 100644 index 0000000000..9f393e2543 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/Numeric.java @@ -0,0 +1,22 @@ +/* + * Copyright 2023 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.validation; + +public @interface Numeric { + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/ReturnType.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/ReturnType.java new file mode 100644 index 0000000000..0104f9b76b --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/ReturnType.java @@ -0,0 +1,25 @@ +/* + * Copyright 2023 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.validation; + +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +public @interface ReturnType { + + FHIRDefinedType value(); +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/query/ExpressionWithLabel.java b/fhirpath/src/main/java/au/csiro/pathling/query/ExpressionWithLabel.java index 558b7527ea..63d3d4148a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/query/ExpressionWithLabel.java +++ b/fhirpath/src/main/java/au/csiro/pathling/query/ExpressionWithLabel.java @@ -84,7 +84,7 @@ public static Stream> labelsAsStream( return expressionWithLabels.stream().map(ExpressionWithLabel::getLabel) .map(Optional::ofNullable); } - + /** * Returns a list of {@link ExpressionWithLabel} objects, where each expression has no label. * @@ -92,7 +92,8 @@ public static Stream> labelsAsStream( * @return a list of {@link ExpressionWithLabel} objects */ @Nonnull - public static List fromUnlabelledExpressions(@Nonnull final List expressions) { + public static List fromUnlabelledExpressions( + @Nonnull final List expressions) { return expressions.stream().map(ExpressionWithLabel::withNoLabel) .collect(Collectors.toUnmodifiableList()); } diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ColumnFunction0Test.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ColumnFunction0Test.java new file mode 100644 index 0000000000..5b23edb5b0 --- /dev/null +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ColumnFunction0Test.java @@ -0,0 +1,32 @@ +/* + * Copyright 2023 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.function; + +import au.csiro.pathling.fhirpath.ColumnHelpers; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class ColumnFunction0Test { + + @Test + void testFrom() { + final List functions = ColumnFunction0.of(ColumnHelpers.class); + functions.forEach(System.out::println); + } +} diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ColumnFunctionsTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ColumnFunctionsTest.java new file mode 100644 index 0000000000..78445f2129 --- /dev/null +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/ColumnFunctionsTest.java @@ -0,0 +1,197 @@ +/* + * Copyright 2023 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.function; + +import static au.csiro.pathling.fhirpath.function.ColumnFunctions.count; +import static au.csiro.pathling.fhirpath.function.ColumnFunctions.empty; +import static au.csiro.pathling.fhirpath.function.ColumnFunctions.first; +import static au.csiro.pathling.fhirpath.function.ColumnFunctions.last; +import static au.csiro.pathling.fhirpath.function.ColumnFunctions.not; +import static au.csiro.pathling.fhirpath.function.ColumnFunctions.singular; +import static au.csiro.pathling.fhirpath.function.ColumnFunctions.sum; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import au.csiro.pathling.test.SpringBootUnitTest; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.SparkSession; +import org.apache.spark.sql.functions; +import org.apache.spark.sql.types.DataTypes; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + + +@SpringBootUnitTest +class ColumnFunctionsTest { + + + // TODO: improve to check the actual values returned. + class ColumnAsserts { + + final List tests = new ArrayList<>(); + + ColumnAsserts assertNull(Column column) { + tests.add(column.isNull()); + return this; + } + + ColumnAsserts assertEquals(Object value, Column column) { + tests.add(column.equalTo(value)); + return this; + } + + void check() { + final Row result = spark.range(1).select(tests.toArray(Column[]::new)) + .first(); + for (int i = 0; i < result.size(); i++) { + assertTrue(result.getBoolean(i), "Test " + i + " failed: " + tests.get(i)); + } + System.out.println(result); + } + } + + @Autowired + SparkSession spark; + + @Nonnull + public static Column nullValue() { + return functions.lit(null); + } + + @Nonnull + public static Column valueOf(Object value) { + return functions.lit(value); + } + + + @Nonnull + public static Column nullArray() { + return functions.lit(null).cast(DataTypes.createArrayType(DataTypes.NullType)); + } + + @Nonnull + public static Column emptyArray() { + return functions.array(); + } + + @Nonnull + public static Column arrayOf(Object... values) { + return functions.array( + Stream.of(values).map(ColumnFunctionsTest::valueOf).toArray(Column[]::new)); + } + + @Nonnull + public static Column arrayOfOne(Object value) { + return functions.array(valueOf(value)); + } + + + @Test + void testSingular() { + + new ColumnAsserts() + .assertNull(singular(nullValue())) + .assertNull(singular(emptyArray())) + .assertEquals(13, singular(valueOf(13))) + .assertEquals("a", singular(arrayOfOne("a"))) + .check(); + + // final SparkException ex = assertThrows(SparkException.class, () -> + // spark.range(1).select( + // ColumnHelpers.singular(functions.array(functions.lit("a"), functions.lit("b"))) + // ).collect()); + // System.out.println(ex.getCause().getMessage()); + + } + + @Test + void testFirst() { + new ColumnAsserts() + .assertNull(first(nullValue())) + .assertEquals(13, first(valueOf(13))) + .assertNull(first(nullArray())) + .assertNull(first(emptyArray())) + .assertEquals("a", first(arrayOf("a", "b"))) + .check(); + } + + @Test + void testLast() { + new ColumnAsserts() + .assertNull(last(nullValue())) + .assertEquals(17, last(valueOf(17))) + .assertNull(last(nullArray())) + .assertNull(last(emptyArray())) + .assertEquals("b", last(arrayOf("a", "b"))) + .check(); + } + + @Test + void testCount() { + new ColumnAsserts() + .assertEquals(0, count(nullValue())) + .assertEquals(1, count(valueOf(17))) + .assertEquals(0, count(nullArray())) + .assertEquals(0, count(emptyArray())) + .assertEquals(2, count(arrayOf("a", "b"))) + .check(); + } + + @Test + void testSum() { + new ColumnAsserts() + .assertEquals(0, sum(nullValue())) + .assertEquals(17, sum(valueOf(17))) + .assertEquals(0, sum(nullArray())) + .assertEquals(0, sum(emptyArray())) + .assertEquals(6, sum(arrayOf(1, 2, 3))) + .check(); + } + + + @Test + void testNot() { + new ColumnAsserts() + .assertNull(not(nullValue())) + .assertEquals(true, not(valueOf(false))) + .assertEquals(false, not(valueOf(true))) + .assertNull(not(nullArray())) + .assertEquals(emptyArray(), not(emptyArray())) + .assertEquals(arrayOf(false, true), not(arrayOf(true, false))) + .check(); + } + + @Test + void testEmpty() { + new ColumnAsserts() + .assertEquals(true, empty(nullValue())) + .assertEquals(false, empty(valueOf(17))) + .assertEquals(true, empty(nullArray())) + .assertEquals(true, empty(emptyArray())) + .assertEquals(false, empty(arrayOf(1, 2, 3))) + .check(); + } + + +} + + diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java index 47d45c9d6a..88e51c6bbd 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java @@ -144,6 +144,7 @@ List toTestParameters(@Nonnull final JsonNode testDefinition, final JsonNode views = testDefinition.get("tests"); final List result = new ArrayList<>(); + int testNumber = 0; for (final Iterator it = views.elements(); it.hasNext(); ) { final JsonNode view = it.next(); @@ -154,7 +155,8 @@ List toTestParameters(@Nonnull final JsonNode testDefinition, // Write the expected JSON to a file, named after the view. final Path directory = getTempDir(testDefinition); final String expectedFileName = - view.get("title").asText().replaceAll("\\W+", "_") + ".json"; + String.format("%02d_%s.json", testNumber, + view.get("title").asText().replaceAll("\\W+", "_")); final Path expectedPath = directory.resolve(expectedFileName); List expectedColumns = null; for (final Iterator rowIt = view.get("expect").elements(); rowIt.hasNext(); ) { @@ -176,6 +178,7 @@ List toTestParameters(@Nonnull final JsonNode testDefinition, testDefinition.get("title").asText() + " - " + view.get("title").asText(); result.add( new TestParameters(testName, sourceData, fhirView, expectedPath, expectedColumns)); + testNumber++; } return result; From b478a2b1ff9f4933200eeda96130e6dbbf9621ef Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Thu, 14 Sep 2023 14:39:38 +1000 Subject: [PATCH 76/95] Updating the sql-on-fire test cases --- sql-on-fhir | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql-on-fhir b/sql-on-fhir index 03cc442cad..7d353da07d 160000 --- a/sql-on-fhir +++ b/sql-on-fhir @@ -1 +1 @@ -Subproject commit 03cc442cadc72afee14bbb91e98275838610b6a4 +Subproject commit 7d353da07dbe42a0fd73f922180faa75756d4eeb From af744ee3e58eb8236a4d63032c7d3274eff453cd Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Thu, 14 Sep 2023 16:35:22 +1000 Subject: [PATCH 77/95] Making the entire code compilable. Refactoring of ParserTest and related assertion classes, to work with column based approach (WIP). --- .../pathling/aggregate/AggregateExecutor.java | 3 +- .../pathling/extract/ExtractExecutor.java | 3 +- .../csiro/pathling/search/SearchExecutor.java | 3 +- .../csiro/pathling/search/SearchProvider.java | 7 +- .../security/ga4gh/PassportScopeEnforcer.java | 8 +- .../security/ga4gh/ScopeAwareDatabase.java | 3 +- .../aggregate/AggregateExecutorTest.java | 3 +- .../aggregate/DrillDownBuilderTest.java | 34 +-- .../pathling/extract/ExtractQueryTest.java | 6 +- .../fhirpath/parser/AbstractParserTest.java | 162 +++++++------ .../parser/DateTimeArithmeticParserTest.java | 32 +-- .../pathling/fhirpath/parser/ParserTest.java | 224 ++++++++---------- .../fhirpath/parser/QuantityParserTest.java | 26 +- .../search/SearchExecutorBuilder.java | 2 +- .../test/benchmark/AggregateBenchmark.java | 2 +- .../test/benchmark/SearchDevBenchmark.java | 4 +- .../benchmark/TerminologyBenchmarkTest.java | 3 +- .../benchmark/TerminologyDevBenchmark.java | 3 +- .../pathling/test/integration/AsyncTest.java | 3 +- .../test/integration/ExtractTest.java | 5 +- .../pathling/test/integration/SearchTest.java | 5 +- .../fhirpath/function/ColumnFunction0.java | 9 + .../fhirpath/function/ColumnFunctions.java | 18 +- .../registry/StaticFunctionRegistry.java | 18 +- .../pathling/test/assertions/Assertions.java | 11 +- .../assertions/BaseFhirPathAssertion.java | 123 ++++++---- .../test/assertions/ElementPathAssertion.java | 6 +- .../test/assertions/FhirPathAssertion.java | 6 +- .../test/assertions/LiteralPathAssertion.java | 36 +-- .../assertions/ResourcePathAssertion.java | 6 +- 30 files changed, 396 insertions(+), 378 deletions(-) diff --git a/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java index 8fe43ba7fb..7372d3bbbf 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java @@ -23,6 +23,7 @@ import au.csiro.pathling.fhirpath.Materializable; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.io.Database; +import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.ArrayList; @@ -59,7 +60,7 @@ public class AggregateExecutor extends AggregateQueryExecutor { */ public AggregateExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final Dataset dataSource, + @Nonnull final DataSource dataSource, @Nonnull final Optional terminologyServiceFactory) { super(configuration, fhirContext, sparkSession, dataSource, terminologyServiceFactory); diff --git a/fhir-server/src/main/java/au/csiro/pathling/extract/ExtractExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/extract/ExtractExecutor.java index 37d4afa27e..065488cce9 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/extract/ExtractExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/extract/ExtractExecutor.java @@ -22,6 +22,7 @@ import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.io.Database; import au.csiro.pathling.io.ResultWriter; +import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.Optional; @@ -64,7 +65,7 @@ public class ExtractExecutor extends ExtractQueryExecutor { */ public ExtractExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final Dataset database, + @Nonnull final DataSource database, @Nonnull final Optional terminologyClientFactory, @Nonnull final ResultWriter resultWriter, @Nonnull final ResultRegistry resultRegistry) { diff --git a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java index b7a581318d..ad2146064c 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java @@ -25,6 +25,7 @@ import au.csiro.pathling.encoders.FhirEncoders; import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.io.Database; +import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IQueryParameterAnd; @@ -88,7 +89,7 @@ public class SearchExecutor extends QueryExecutor implements IBundleProvider { */ public SearchExecutor(@Nonnull final QueryConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final Dataset database, + @Nonnull final DataSource database, @Nonnull final Optional terminologyServiceFactory, @Nonnull final FhirEncoders fhirEncoders, @Nonnull final ResourceType subjectResource, @Nonnull final Optional filters) { diff --git a/fhir-server/src/main/java/au/csiro/pathling/search/SearchProvider.java b/fhir-server/src/main/java/au/csiro/pathling/search/SearchProvider.java index 3b373d430c..0f0053e213 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/search/SearchProvider.java +++ b/fhir-server/src/main/java/au/csiro/pathling/search/SearchProvider.java @@ -23,6 +23,7 @@ import au.csiro.pathling.encoders.FhirEncoders; import au.csiro.pathling.fhirpath.definition.ResourceDefinition; import au.csiro.pathling.io.Database; +import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.security.OperationAccess; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; @@ -35,8 +36,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.extern.slf4j.Slf4j; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -76,7 +75,7 @@ public class SearchProvider implements IResourceProvider { private final SparkSession sparkSession; @Nonnull - private final Dataset database; + private final DataSource database; @Nonnull private final Optional terminologyServiceFactory; @@ -105,7 +104,7 @@ public class SearchProvider implements IResourceProvider { */ public SearchProvider(@Nonnull final ServerConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final Dataset database, + @Nonnull final DataSource database, @Nonnull final Optional terminologyServiceFactory, @Nonnull final FhirEncoders fhirEncoders, @Nonnull final Class resourceClass) { diff --git a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java index 838b8c253e..b13093444f 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java +++ b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java @@ -21,6 +21,7 @@ import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.io.Database; +import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.Collection; @@ -60,7 +61,7 @@ public PassportScopeEnforcer( @Nonnull final QueryConfiguration configuration, @Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, - @Nonnull final Dataset dataSource, + @Nonnull final DataSource dataSource, @Nonnull final Optional terminologyServiceFactory, @Nonnull final PassportScope passportScope) { super(configuration, fhirContext, sparkSession, dataSource, terminologyServiceFactory); @@ -83,9 +84,8 @@ public Dataset enforce(@Nonnull final ResourceType subjectResource, // Build a new expression parser, and parse all the column expressions within the query. final ResourceCollection inputContext = ResourceCollection - .build(getFhirContext(), getDataSource(), subjectResource, - ResourceCollection.isSingular()); - + .build(getFhirContext(), getDataSource().read(subjectResource), subjectResource, + false); return filterDataset(inputContext, filters, dataset, dataset.col("id"), Column::or); } } diff --git a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/ScopeAwareDatabase.java b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/ScopeAwareDatabase.java index 7690724aff..44813408c2 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/ScopeAwareDatabase.java +++ b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/ScopeAwareDatabase.java @@ -21,6 +21,7 @@ import au.csiro.pathling.encoders.FhirEncoders; import au.csiro.pathling.io.CacheableDatabase; import au.csiro.pathling.io.Database; +import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import java.util.Optional; @@ -98,7 +99,7 @@ public Dataset read(@Nullable final ResourceType resourceType) { .map(scope -> { // We need to create a non-scope-aware reader here for the parsing of the filters, so that // we don't have recursive application of the filters. - final Dataset dataSource = Database.forConfiguration(spark, fhirEncoders, + final DataSource dataSource = Database.forConfiguration(spark, fhirEncoders, configuration.getStorage()); final PassportScopeEnforcer scopeEnforcer = new PassportScopeEnforcer( configuration.getQuery(), diff --git a/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateExecutorTest.java b/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateExecutorTest.java index 2b6763241d..b320f8b3a8 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateExecutorTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/aggregate/AggregateExecutorTest.java @@ -23,6 +23,7 @@ import au.csiro.pathling.aggregate.AggregateResponse.Grouping; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; +import au.csiro.pathling.io.CacheableDatabase; import au.csiro.pathling.search.SearchExecutor; import au.csiro.pathling.terminology.TerminologyService; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -76,7 +77,7 @@ abstract class AggregateExecutorTest { FhirEncoders fhirEncoders; @MockBean - Dataset database; + CacheableDatabase database; AggregateExecutor executor; ResourceType subjectResource; diff --git a/fhir-server/src/test/java/au/csiro/pathling/aggregate/DrillDownBuilderTest.java b/fhir-server/src/test/java/au/csiro/pathling/aggregate/DrillDownBuilderTest.java index 87b3ee8fd4..e296db7d11 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/aggregate/DrillDownBuilderTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/aggregate/DrillDownBuilderTest.java @@ -20,8 +20,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.PrimitivePath; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.builders.ElementPathBuilder; import au.csiro.pathling.test.helpers.TestHelpers; @@ -58,6 +58,7 @@ import org.springframework.beans.factory.annotation.Autowired; @SpringBootUnitTest +@NotImplemented class DrillDownBuilderTest { @Autowired @@ -144,20 +145,21 @@ static Stream parameters() { ); } - @ParameterizedTest - @MethodSource("parameters") - void labelTypes(@Nonnull final TestParameters parameters) { - final List> labels = List.of(Optional.of(parameters.getLabel())); - final PrimitivePath grouping = new ElementPathBuilder(spark) - .expression("someElement") - .singular(true) - .build(); - final List groupings = List.of(grouping); - final DrillDownBuilder builder = new DrillDownBuilder(labels, groupings, - Collections.emptyList()); - final Optional drillDown = builder.build(); - assertTrue(drillDown.isPresent()); - assertEquals(parameters.getExpectedResult(), drillDown.get()); - } + // TODO: Implement + // @ParameterizedTest + // @MethodSource("parameters") + // void labelTypes(@Nonnull final TestParameters parameters) { + // final List> labels = List.of(Optional.of(parameters.getLabel())); + // final PrimitivePath grouping = new ElementPathBuilder(spark) + // .expression("someElement") + // .singular(true) + // .build(); + // final List groupings = List.of(grouping); + // final DrillDownBuilder builder = new DrillDownBuilder(labels, groupings, + // Collections.emptyList()); + // final Optional drillDown = builder.build(); + // assertTrue(drillDown.isPresent()); + // assertEquals(parameters.getExpectedResult(), drillDown.get()); + // } } diff --git a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java index f0970f8c08..fc79adb7d0 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractQueryTest.java @@ -28,6 +28,7 @@ import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; import au.csiro.pathling.errors.InvalidUserInputError; +import au.csiro.pathling.io.CacheableDatabase; import au.csiro.pathling.query.ExpressionWithLabel; import au.csiro.pathling.terminology.TerminologyServiceFactory; import au.csiro.pathling.test.SharedMocks; @@ -79,9 +80,8 @@ class ExtractQueryTest { FhirEncoders fhirEncoders; @MockBean - Dataset dataSource; - - + CacheableDatabase dataSource; + ResourceType subjectResource; diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java index 41215bbfbe..ad20685c5e 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java @@ -17,80 +17,108 @@ package au.csiro.pathling.fhirpath.parser; +import static au.csiro.pathling.test.assertions.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import au.csiro.pathling.encoders.FhirEncoders; +import au.csiro.pathling.fhirpath.EvaluationContext; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import au.csiro.pathling.io.source.DataSource; +import au.csiro.pathling.terminology.TerminologyService; +import au.csiro.pathling.terminology.TerminologyServiceFactory; +import au.csiro.pathling.test.SharedMocks; import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.TimingExtension; +import au.csiro.pathling.test.assertions.FhirPathAssertion; +import au.csiro.pathling.test.builders.EvaluationContextBuilder; +import au.csiro.pathling.test.helpers.TestHelpers; +import ca.uhn.fhir.context.FhirContext; +import javax.annotation.Nonnull; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.SparkSession; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.Enumerations.ResourceType; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import java.util.Collections; @SpringBootUnitTest @ExtendWith(TimingExtension.class) public class AbstractParserTest { - // @Autowired - // protected SparkSession spark; - // - // @Autowired - // FhirContext fhirContext; - // - // @Autowired - // TerminologyService terminologyService; - // - // @Autowired - // FhirEncoders fhirEncoders; - // - // @Autowired - // TerminologyServiceFactory terminologyServiceFactory; - // - // @MockBean - // protected DataSource dataSource; - // - // Parser parser; - // - // @BeforeEach - // void setUp() { - // SharedMocks.resetAll(); - // mockResource(ResourceType.PATIENT, ResourceType.CONDITION, ResourceType.ENCOUNTER, - // ResourceType.PROCEDURE, ResourceType.MEDICATIONREQUEST, ResourceType.OBSERVATION, - // ResourceType.DIAGNOSTICREPORT, ResourceType.ORGANIZATION, ResourceType.QUESTIONNAIRE, - // ResourceType.CAREPLAN); - // - // final ResourceCollection subjectResource = ResourceCollection - // .build(fhirContext, dataSource, ResourceType.PATIENT, ResourceType.PATIENT.toCode()); - // - // final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) - // .terminologyServiceFactory(terminologyServiceFactory) - // .dataset(dataSource) - // .inputContext(subjectResource) - // .groupingColumns(Collections.singletonList(subjectResource.getIdColumn())) - // .build(); - // parser = new Parser(evaluationContext); - // } - // - // void mockResource(final ResourceType... resourceTypes) { - // for (final ResourceType resourceType : resourceTypes) { - // final Dataset dataset = TestHelpers.getDatasetForResourceType(spark, resourceType); - // when(dataSource.read(resourceType)).thenReturn(dataset); - // } - // } - // - // @SuppressWarnings("SameParameterValue") - // @Nonnull - // protected FhirPathAssertion assertThatResultOf(@Nonnull final ResourceType resourceType, - // @Nonnull final String expression) { - // final ResourceCollection subjectResource = ResourceCollection - // .build(fhirContext, dataSource, resourceType, resourceType.toCode()); - // - // final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) - // .terminologyClientFactory(terminologyServiceFactory) - // .database(dataSource) - // .inputContext(subjectResource) - // .build(); - // final Parser resourceParser = new Parser(evaluationContext); - // return assertThat(resourceParser.evaluate(expression, context)); - // } - // - // @SuppressWarnings("SameParameterValue") - // FhirPathAssertion assertThatResultOf(final String expression) { - // return assertThat(parser.evaluate(expression, context)); - // } + @Autowired + protected SparkSession spark; + + @Autowired + FhirContext fhirContext; + + @Autowired + TerminologyService terminologyService; + + @Autowired + FhirEncoders fhirEncoders; + + @Autowired + TerminologyServiceFactory terminologyServiceFactory; + + @MockBean + protected DataSource dataSource; + + Parser parser; + + EvaluationContext evaluationContext; + + @BeforeEach + void setUp() { + SharedMocks.resetAll(); + mockResource(ResourceType.PATIENT, ResourceType.CONDITION, ResourceType.ENCOUNTER, + ResourceType.PROCEDURE, ResourceType.MEDICATIONREQUEST, ResourceType.OBSERVATION, + ResourceType.DIAGNOSTICREPORT, ResourceType.ORGANIZATION, ResourceType.QUESTIONNAIRE, + ResourceType.CAREPLAN); + + parser = new Parser(); + setSubjectResource(ResourceType.PATIENT); + } + + + @SuppressWarnings("SameParameterValue") + void setSubjectResource(@Nonnull final ResourceType resourceType) { + final ResourceCollection subjectResource = ResourceCollection + .build(fhirContext, dataSource.read(resourceType), resourceType, false); + + evaluationContext = new EvaluationContextBuilder(spark, fhirContext) + .dataset(dataSource.read(resourceType)) + .inputContext(subjectResource) + .build(); + } + + void mockResource(final ResourceType... resourceTypes) { + for (final ResourceType resourceType : resourceTypes) { + final Dataset dataset = TestHelpers.getDatasetForResourceType(spark, resourceType); + when(dataSource.read(resourceType)).thenReturn(dataset); + } + } + + @SuppressWarnings("SameParameterValue") + @Nonnull + protected FhirPathAssertion assertThatResultOf(@Nonnull final ResourceType resourceType, + @Nonnull final String expression) { + final ResourceCollection subjectResource = ResourceCollection + .build(fhirContext, dataSource.read(resourceType), resourceType, false); + + final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) + .dataset(dataSource.read(resourceType)) + .inputContext(subjectResource) + .build(); + return assertThat(parser.evaluate(expression, evaluationContext), evaluationContext); + } + + @SuppressWarnings("SameParameterValue") + FhirPathAssertion assertThatResultOf(final String expression) { + return assertThat(parser.evaluate(expression, evaluationContext), evaluationContext); + } } diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java index d99c4c43ee..f27ead1c06 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/DateTimeArithmeticParserTest.java @@ -17,11 +17,7 @@ package au.csiro.pathling.fhirpath.parser; -import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.collection.BooleanCollection; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.test.builders.EvaluationContextBuilder; -import java.util.Collections; import org.hl7.fhir.r4.model.Enumerations.ResourceType; import org.junit.jupiter.api.Test; @@ -29,18 +25,8 @@ public class DateTimeArithmeticParserTest extends AbstractParserTest { @Test void lengthOfEncounter() { - final ResourceCollection subjectResource = ResourceCollection - .build(fhirContext, dataSource.read(ResourceType.ENCOUNTER), ResourceType.ENCOUNTER); - - final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) - .terminologyClientFactory(terminologyServiceFactory) - .database(dataSource) - .inputContext(subjectResource) - .groupingColumns(Collections.singletonList(subjectResource.getIdColumn())) - .build(); - parser = new Parser(evaluationContext); - - assertThatResultOf("(period.start + 20 minutes) > period.end") + assertThatResultOf(ResourceType.ENCOUNTER, + "(period.start + 20 minutes) > period.end") .isElementPath(BooleanCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/lengthOfEncounter.tsv"); @@ -48,18 +34,8 @@ void lengthOfEncounter() { @Test void ageAtTimeOfEncounter() { - final ResourceCollection subjectResource = ResourceCollection - .build(fhirContext, dataSource, ResourceType.ENCOUNTER, ResourceType.ENCOUNTER.toCode() - ); - final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) - .terminologyClientFactory(terminologyServiceFactory) - .database(dataSource) - .inputContext(subjectResource) - .groupingColumns(Collections.singletonList(subjectResource.getIdColumn())) - .build(); - parser = new Parser(evaluationContext); - - assertThatResultOf("period.start > (subject.resolve().ofType(Patient).birthDate + 60 years)") + assertThatResultOf(ResourceType.ENCOUNTER, + "period.start > (subject.resolve().ofType(Patient).birthDate + 60 years)") .isElementPath(BooleanCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/ageAtTimeOfEncounter.tsv"); diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java index 750d1c1159..c36538246c 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java @@ -37,25 +37,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import au.csiro.pathling.errors.InvalidUserInputError; -import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.collection.BooleanCollection; import au.csiro.pathling.fhirpath.collection.DateCollection; import au.csiro.pathling.fhirpath.collection.DecimalCollection; import au.csiro.pathling.fhirpath.collection.IntegerCollection; -import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.collection.StringCollection; -import au.csiro.pathling.fhirpath.literal.CodingLiteralPath; -import au.csiro.pathling.fhirpath.literal.DateLiteralPath; -import au.csiro.pathling.fhirpath.literal.DateTimeLiteralPath; -import au.csiro.pathling.fhirpath.literal.TimeLiteralPath; import au.csiro.pathling.test.builders.DatasetBuilder; -import au.csiro.pathling.test.builders.EvaluationContextBuilder; import au.csiro.pathling.test.helpers.TerminologyServiceHelpers; import au.csiro.pathling.test.helpers.TerminologyServiceHelpers.TranslateExpectations; -import java.util.Collections; -import javax.annotation.Nonnull; import org.apache.spark.sql.types.DataTypes; -import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Enumerations.ResourceType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -67,7 +57,7 @@ public class ParserTest extends AbstractParserTest { @SuppressWarnings("SameParameterValue") private T assertThrows(final Class errorType, final String expression) { - return Assertions.assertThrows(errorType, () -> parser.evaluate(expression, context)); + return Assertions.assertThrows(errorType, () -> parser.evaluate(expression, evaluationContext)); } private TranslateExpectations setupMockTranslationFor_195662009_444814009( @@ -156,103 +146,104 @@ void testCodingOperations() { .hasRows(allPatientsWithValue(spark, false)); } - @Test - void testDateTimeLiterals() { - // Milliseconds precision. - assertThatResultOf("@2015-02-04T14:34:28.350Z") - .isLiteralPath(DateTimeLiteralPath.class) - .hasExpression("@2015-02-04T14:34:28.350Z") - .has("2015-02-04T14:34:28.350Z", - dateTime -> dateTime.getValue().dateTimeValue().getValueAsString()); - - // Milliseconds precision, no timezone. - assertThatResultOf("@2015-02-04T14:34:28.350") - .isLiteralPath(DateTimeLiteralPath.class) - .hasExpression("@2015-02-04T14:34:28.350") - .has("2015-02-04T14:34:28.350", - dateTime -> dateTime.getValue().dateTimeValue().getValueAsString()) - .has(null, dateTime -> dateTime.getValue().dateTimeValue().getTimeZone()); - - // Seconds precision. - assertThatResultOf("@2015-02-04T14:34:28-05:00") - .isLiteralPath(DateTimeLiteralPath.class) - .hasExpression("@2015-02-04T14:34:28-05:00") - .has("2015-02-04T14:34:28-05:00", - dateTime -> dateTime.getValue().dateTimeValue().getValueAsString()); - - // Seconds precision, no timezone. - assertThatResultOf("@2015-02-04T14:34:28") - .isLiteralPath(DateTimeLiteralPath.class) - .hasExpression("@2015-02-04T14:34:28") - .has("2015-02-04T14:34:28", - dateTime -> dateTime.getValue().dateTimeValue().getValueAsString()) - .has(null, dateTime -> dateTime.getValue().dateTimeValue().getTimeZone()); - } - - @Test - void testDateLiterals() { - // Year, month and day. - assertThatResultOf("@2015-02-04") - .isLiteralPath(DateLiteralPath.class) - .hasExpression("@2015-02-04") - .has("2015-02-04", date -> date.getValue().castToDate(date.getValue()).getValueAsString()) - .has(null, date -> date.getValue().castToDate(date.getValue()).getTimeZone()); - - // Year and month. - assertThatResultOf("@2015-02") - .isLiteralPath(DateLiteralPath.class) - .hasExpression("@2015-02") - .has("2015-02", date -> date.getValue().castToDate(date.getValue()).getValueAsString()) - .has(null, date -> date.getValue().castToDate(date.getValue()).getTimeZone()); - - // Year only. - assertThatResultOf("@2015") - .isLiteralPath(DateLiteralPath.class) - .hasExpression("@2015") - .has("2015", date -> date.getValue().castToDate(date.getValue()).getValueAsString()) - .has(null, date -> date.getValue().castToDate(date.getValue()).getTimeZone()); - } - - @Test - void testTimeLiterals() { - // Hours, minutes and seconds. - assertThatResultOf("@T14:34:28") - .isLiteralPath(TimeLiteralPath.class) - .hasExpression("@T14:34:28") - .has("14:34:28", time -> time.getValue().castToTime(time.getValue()).getValueAsString()); - - // Hours and minutes. - assertThatResultOf("@T14:34") - .isLiteralPath(TimeLiteralPath.class) - .hasExpression("@T14:34") - .has("14:34", time -> time.getValue().castToTime(time.getValue()).getValueAsString()); - - // Hour only. - assertThatResultOf("@T14") - .isLiteralPath(TimeLiteralPath.class) - .hasExpression("@T14") - .has("14", time -> time.getValue().castToTime(time.getValue()).getValueAsString()); - } - - @Test - void testCodingLiterals() { - // Coding literal form [system]|[code] - final Coding expectedCoding = - new Coding("http://terminology.hl7.org/CodeSystem/v3-MaritalStatus", "S", null); - assertThatResultOf("http://terminology.hl7.org/CodeSystem/v3-MaritalStatus|S") - .isLiteralPath(CodingLiteralPath.class) - .hasExpression("http://terminology.hl7.org/CodeSystem/v3-MaritalStatus|S") - .hasCodingValue(expectedCoding); - - // Coding literal form [system]|[code]|[version] - final Coding expectedCodingWithVersion = - new Coding("http://terminology.hl7.org/CodeSystem/v3-MaritalStatus", "S", null); - expectedCodingWithVersion.setVersion("v1"); - assertThatResultOf("http://terminology.hl7.org/CodeSystem/v3-MaritalStatus|S|v1") - .isLiteralPath(CodingLiteralPath.class) - .hasExpression("http://terminology.hl7.org/CodeSystem/v3-MaritalStatus|S|v1") - .hasCodingValue(expectedCodingWithVersion); - } + // TODO: Implement + // @Test + // void testDateTimeLiterals() { + // // Milliseconds precision. + // assertThatResultOf("@2015-02-04T14:34:28.350Z") + // .isLiteralPath(DateTimeCollection.class) + // .hasExpression("@2015-02-04T14:34:28.350Z") + // .has("2015-02-04T14:34:28.350Z", + // dateTime -> dateTime.getValue().dateTimeValue().getValueAsString()); + // + // // Milliseconds precision, no timezone. + // assertThatResultOf("@2015-02-04T14:34:28.350") + // .isLiteralPath(DateTimeCollection.class) + // .hasExpression("@2015-02-04T14:34:28.350") + // .has("2015-02-04T14:34:28.350", + // dateTime -> dateTime.getValue().dateTimeValue().getValueAsString()) + // .has(null, dateTime -> dateTime.getValue().dateTimeValue().getTimeZone()); + // + // // Seconds precision. + // assertThatResultOf("@2015-02-04T14:34:28-05:00") + // .isLiteralPath(DateTimeCollection.class) + // .hasExpression("@2015-02-04T14:34:28-05:00") + // .has("2015-02-04T14:34:28-05:00", + // dateTime -> dateTime.getValue().dateTimeValue().getValueAsString()); + // + // // Seconds precision, no timezone. + // assertThatResultOf("@2015-02-04T14:34:28") + // .isLiteralPath(DateTimeCollection.class) + // .hasExpression("@2015-02-04T14:34:28") + // .has("2015-02-04T14:34:28", + // dateTime -> dateTime.getValue().dateTimeValue().getValueAsString()) + // .has(null, dateTime -> dateTime.getValue().dateTimeValue().getTimeZone()); + // } + // + // @Test + // void testDateLiterals() { + // // Year, month and day. + // assertThatResultOf("@2015-02-04") + // .isLiteralPath(DateCollection.class) + // .hasExpression("@2015-02-04") + // .has("2015-02-04", date -> date.getValue().castToDate(date.getValue()).getValueAsString()) + // .has(null, date -> date.getValue().castToDate(date.getValue()).getTimeZone()); + // + // // Year and month. + // assertThatResultOf("@2015-02") + // .isLiteralPath(DateCollection.class) + // .hasExpression("@2015-02") + // .has("2015-02", date -> date.getValue().castToDate(date.getValue()).getValueAsString()) + // .has(null, date -> date.getValue().castToDate(date.getValue()).getTimeZone()); + // + // // Year only. + // assertThatResultOf("@2015") + // .isLiteralPath(DateCollection.class) + // .hasExpression("@2015") + // .has("2015", date -> date.getValue().castToDate(date.getValue()).getValueAsString()) + // .has(null, date -> date.getValue().castToDate(date.getValue()).getTimeZone()); + // } + // + // @Test + // void testTimeLiterals() { + // // Hours, minutes and seconds. + // assertThatResultOf("@T14:34:28") + // .isLiteralPath(TimeCollection.class) + // .hasExpression("@T14:34:28") + // .has("14:34:28", time -> time.getValue().castToTime(time.getValue()).getValueAsString()); + // + // // Hours and minutes. + // assertThatResultOf("@T14:34") + // .isLiteralPath(TimeCollection.class) + // .hasExpression("@T14:34") + // .has("14:34", time -> time.getValue().castToTime(time.getValue()).getValueAsString()); + // + // // Hour only. + // assertThatResultOf("@T14") + // .isLiteralPath(TimeCollection.class) + // .hasExpression("@T14") + // .has("14", time -> time.getValue().castToTime(time.getValue()).getValueAsString()); + // } + // + // @Test + // void testCodingLiterals() { + // // Coding literal form [system]|[code] + // final Coding expectedCoding = + // new Coding("http://terminology.hl7.org/CodeSystem/v3-MaritalStatus", "S", null); + // assertThatResultOf("http://terminology.hl7.org/CodeSystem/v3-MaritalStatus|S") + // .isLiteralPath(CodingCollection.class) + // .hasExpression("http://terminology.hl7.org/CodeSystem/v3-MaritalStatus|S") + // .hasCodingValue(expectedCoding); + // + // // Coding literal form [system]|[code]|[version] + // final Coding expectedCodingWithVersion = + // new Coding("http://terminology.hl7.org/CodeSystem/v3-MaritalStatus", "S", null); + // expectedCodingWithVersion.setVersion("v1"); + // assertThatResultOf("http://terminology.hl7.org/CodeSystem/v3-MaritalStatus|S|v1") + // .isLiteralPath(CodingCollection.class) + // .hasExpression("http://terminology.hl7.org/CodeSystem/v3-MaritalStatus|S|v1") + // .hasCodingValue(expectedCodingWithVersion); + // } @Test void testCountWithReverseResolve() { @@ -788,22 +779,7 @@ void testUntilFunction() { .selectResult() .hasRows(spark, "responses/ParserTest/testUntilFunction.csv"); } - - @SuppressWarnings("SameParameterValue") - private void setSubjectResource(@Nonnull final ResourceType resourceType) { - final ResourceCollection subjectResource = ResourceCollection - .build(fhirContext, dataSource, resourceType, resourceType.toCode() - ); - - final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) - .terminologyClientFactory(terminologyServiceFactory) - .database(dataSource) - .inputContext(subjectResource) - .groupingColumns(Collections.singletonList(subjectResource.getIdColumn())) - .build(); - parser = new Parser(evaluationContext); - } - + @Test void testQuantityMultiplicationAndDivision() { assertThatResultOf( diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java index f751fd29fe..7c0a9b2949 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/QuantityParserTest.java @@ -29,18 +29,7 @@ public class QuantityParserTest extends AbstractParserTest { @Test void lengthObservationComparison() { - final ResourceCollection subjectResource = ResourceCollection - .build(fhirContext, dataSource, ResourceType.OBSERVATION, ResourceType.OBSERVATION.toCode() - ); - final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) - .terminologyClientFactory(terminologyServiceFactory) - .database(dataSource) - .inputContext(subjectResource) - .groupingColumns(Collections.singletonList(subjectResource.getIdColumn())) - .build(); - parser = new Parser(evaluationContext); - - assertThatResultOf("valueQuantity < 1.5 'm'") + assertThatResultOf(ResourceType.OBSERVATION, "valueQuantity < 1.5 'm'") .isElementPath(BooleanCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/lengthObservationComparison.tsv"); @@ -48,18 +37,7 @@ void lengthObservationComparison() { @Test void lengthObservationSubtraction() { - final ResourceCollection subjectResource = ResourceCollection - .build(fhirContext, dataSource, ResourceType.OBSERVATION, ResourceType.OBSERVATION.toCode() - ); - final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) - .terminologyClientFactory(terminologyServiceFactory) - .database(dataSource) - .inputContext(subjectResource) - .groupingColumns(Collections.singletonList(subjectResource.getIdColumn())) - .build(); - parser = new Parser(evaluationContext); - - assertThatResultOf("valueQuantity > (valueQuantity - 2 'g/dL')") + assertThatResultOf(ResourceType.OBSERVATION, "valueQuantity > (valueQuantity - 2 'g/dL')") .isElementPath(BooleanCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/lengthObservationSubtraction.tsv"); diff --git a/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorBuilder.java b/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorBuilder.java index c4a457826b..6f1b5b6c79 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorBuilder.java +++ b/fhir-server/src/test/java/au/csiro/pathling/search/SearchExecutorBuilder.java @@ -55,7 +55,7 @@ class SearchExecutorBuilder { final FhirEncoders fhirEncoders; @Nonnull - final Dataset database; + final Database database; @Nonnull final TerminologyServiceFactory terminologyServiceFactory; diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/AggregateBenchmark.java b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/AggregateBenchmark.java index 404ba9893f..1f2c83a156 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/AggregateBenchmark.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/AggregateBenchmark.java @@ -86,7 +86,7 @@ public static class AggregateState extends AbstractJmhSpringBootState { FhirEncoders fhirEncoders; AggregateExecutor executor; - Dataset database; + Database database; void mockResource(final ResourceType... resourceTypes) { TestHelpers.mockResource(database, spark, resourceTypes); diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/SearchDevBenchmark.java b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/SearchDevBenchmark.java index 35946276cf..3f4b4cf882 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/SearchDevBenchmark.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/SearchDevBenchmark.java @@ -34,8 +34,6 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -89,7 +87,7 @@ public static class SearchState extends AbstractJmhSpringBootState { FhirEncoders fhirEncoders; @Autowired - Dataset database; + Database database; @Bean @ConditionalOnMissingBean diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyBenchmarkTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyBenchmarkTest.java index 94f33bb80d..9d5d7900bc 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyBenchmarkTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyBenchmarkTest.java @@ -23,6 +23,7 @@ import au.csiro.pathling.aggregate.AggregateResponse; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; +import au.csiro.pathling.io.CacheableDatabase; import au.csiro.pathling.terminology.TerminologyServiceFactory; import au.csiro.pathling.test.SharedMocks; import au.csiro.pathling.test.helpers.TestHelpers; @@ -72,7 +73,7 @@ public class TerminologyBenchmarkTest { FhirEncoders fhirEncoders; @MockBean - Dataset database; + CacheableDatabase database; AggregateExecutor defaultExecutor; diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyDevBenchmark.java b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyDevBenchmark.java index a4253bf176..f323ee4360 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyDevBenchmark.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/TerminologyDevBenchmark.java @@ -23,6 +23,7 @@ import au.csiro.pathling.aggregate.AggregateResponse; import au.csiro.pathling.config.QueryConfiguration; import au.csiro.pathling.encoders.FhirEncoders; +import au.csiro.pathling.io.CacheableDatabase; import au.csiro.pathling.jmh.AbstractJmhSpringBootState; import au.csiro.pathling.terminology.DefaultTerminologyServiceFactory; import au.csiro.pathling.terminology.TerminologyServiceFactory; @@ -87,7 +88,7 @@ public static class TerminologyState extends AbstractJmhSpringBootState { FhirEncoders fhirEncoders; @MockBean - Dataset database; + CacheableDatabase database; AggregateExecutor defaultExecutor; diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/integration/AsyncTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/integration/AsyncTest.java index b9ec411548..7c230450df 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/integration/AsyncTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/integration/AsyncTest.java @@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import au.csiro.pathling.io.CacheableDatabase; import au.csiro.pathling.test.helpers.TestHelpers; import java.net.MalformedURLException; import java.net.URI; @@ -61,7 +62,7 @@ class AsyncTest extends IntegrationTest { SparkSession spark; @MockBean - Dataset database; + CacheableDatabase database; @LocalServerPort int port; diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/integration/ExtractTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/integration/ExtractTest.java index 91f0aba01a..19ce0889bb 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/integration/ExtractTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/integration/ExtractTest.java @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import au.csiro.pathling.io.CacheableDatabase; import au.csiro.pathling.test.helpers.TestHelpers; import ca.uhn.fhir.parser.IParser; import java.io.IOException; @@ -32,8 +33,6 @@ import java.net.URISyntaxException; import java.net.URL; import org.apache.commons.io.IOUtils; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.hl7.fhir.r4.model.Enumerations.ResourceType; import org.hl7.fhir.r4.model.Parameters; @@ -61,7 +60,7 @@ class ExtractTest extends IntegrationTest { IParser jsonParser; @MockBean - Dataset database; + CacheableDatabase database; @LocalServerPort int port; diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/integration/SearchTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/integration/SearchTest.java index a990d1eb78..a7a01f1307 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/integration/SearchTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/integration/SearchTest.java @@ -19,11 +19,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; +import au.csiro.pathling.io.CacheableDatabase; import au.csiro.pathling.test.helpers.TestHelpers; import java.net.URI; import java.net.URISyntaxException; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.hl7.fhir.r4.model.Enumerations.ResourceType; import org.junit.jupiter.api.Tag; @@ -46,7 +45,7 @@ class SearchTest extends IntegrationTest { SparkSession spark; @MockBean - Dataset database; + CacheableDatabase database; @LocalServerPort int port; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunction0.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunction0.java index f8bead3fd0..8c0f6292ee 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunction0.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunction0.java @@ -23,6 +23,7 @@ import au.csiro.pathling.fhirpath.validation.ReturnType; import java.lang.reflect.Method; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; @@ -72,10 +73,18 @@ public static ColumnFunction0 of(@Nonnull final Method method) { Optional.ofNullable(method.getAnnotation(ReturnType.class)).map(ReturnType::value)); } + + @Nonnull public static List of(@Nonnull final Class clazz) { return Stream.of(clazz.getDeclaredMethods()) .filter(m -> m.getAnnotation(FhirpathFunction.class) != null) .map(ColumnFunction0::of).collect(Collectors.toUnmodifiableList()); } + @Nonnull + public static Map mapOf(@Nonnull final Class clazz) { + return of(clazz).stream().collect(Collectors.toUnmodifiableMap(NamedFunction::getName, + Function.identity())); + } + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunctions.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunctions.java index ca3d571e30..8403897976 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunctions.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunctions.java @@ -79,13 +79,13 @@ public static Column singular(@Nonnull final Column column) { public static Column sum(@Nonnull @Numeric final Column column) { return ColumnCtx.of(column).aggregate(0, Column::plus).getValue(); } - - @ReturnType(FHIRDefinedType.INTEGER) - public static Column until(@Nonnull final Column from, @Nonnull final Column to, - @Nonnull final Column calendarDuration) { - // the singularity is not strictly necessary here for from as we could lift it with transform - return - functions.call_udf(TemporalDifferenceFunction.FUNCTION_NAME, singular(from), singular(to), - singular(calendarDuration)); - } + // + // @ReturnType(FHIRDefinedType.INTEGER) + // public static Column until(@Nonnull final Column from, @Nonnull final Column to, + // @Nonnull final Column calendarDuration) { + // // the singularity is not strictly necessary here for from as we could lift it with transform + // return + // functions.call_udf(TemporalDifferenceFunction.FUNCTION_NAME, singular(from), singular(to), + // singular(calendarDuration)); + // } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java index 5d5e5d531d..59459641ae 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java @@ -6,6 +6,8 @@ import static au.csiro.pathling.fhirpath.function.BooleansTestFunction.BooleansTestType.ANY_TRUE; import au.csiro.pathling.fhirpath.function.BooleansTestFunction; +import au.csiro.pathling.fhirpath.function.ColumnFunction0; +import au.csiro.pathling.fhirpath.function.ColumnFunctions; import au.csiro.pathling.fhirpath.function.CountFunction; import au.csiro.pathling.fhirpath.function.EmptyFunction; import au.csiro.pathling.fhirpath.function.ExistsFunction; @@ -29,6 +31,7 @@ import au.csiro.pathling.fhirpath.function.terminology.SubsumesFunction; import au.csiro.pathling.fhirpath.function.terminology.TranslateFunction; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; /** * A static registry of FHIRPath function implementations, for use in environments where dependency @@ -42,8 +45,8 @@ public class StaticFunctionRegistry extends InMemoryFunctionRegistry() - .put("count", new CountFunction()) + super(new Builder() + //.put("count", new CountFunction()) .put("resolve", new ResolveFunction()) .put("ofType", new OfTypeFunction()) .put("reverseResolve", new ReverseResolveFunction()) @@ -51,24 +54,25 @@ public StaticFunctionRegistry() { .put("where", new WhereFunction()) .put("subsumes", new SubsumesFunction()) .put("subsumedBy", new SubsumesFunction(true)) - .put("empty", new EmptyFunction()) - .put("first", new FirstFunction()) - .put("not", new NotFunction()) + //.put("empty", new EmptyFunction()) + //.put("first", new FirstFunction()) + //.put("not", new NotFunction()) .put("iif", new IifFunction()) .put("translate", new TranslateFunction()) - .put("sum", new SumFunction()) + //.put("sum", new SumFunction()) .put("anyTrue", new BooleansTestFunction(ANY_TRUE)) .put("anyFalse", new BooleansTestFunction(ANY_FALSE)) .put("allTrue", new BooleansTestFunction(ALL_TRUE)) .put("allFalse", new BooleansTestFunction(ALL_FALSE)) .put("extension", new ExtensionFunction()) .put("until", new UntilFunction()) - .put("exists", new ExistsFunction()) + //.put("exists", new ExistsFunction()) .put("display", new DisplayFunction()) .put("property", new PropertyFunction()) .put("designation", new DesignationFunction()) .put("toString", new ToStringFunction()) .put("getId", new GetIdFunction()) + .putAll(ColumnFunction0.mapOf(ColumnFunctions.class)) .build()); } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java index 6ec872d2a1..484e888c57 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java @@ -19,6 +19,7 @@ import static au.csiro.pathling.test.TestResources.getResourceAsUrl; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.collection.ResourceCollection; import java.net.URL; @@ -37,13 +38,15 @@ public abstract class Assertions { @Nonnull - public static FhirPathAssertion assertThat(@Nonnull final Collection result) { - return new FhirPathAssertion(result); + public static FhirPathAssertion assertThat(@Nonnull final Collection result, + @Nonnull final EvaluationContext evaluationContext) { + return new FhirPathAssertion(result, evaluationContext); } @Nonnull - public static ResourcePathAssertion assertThat(@Nonnull final ResourceCollection fhirPath) { - return new ResourcePathAssertion(fhirPath); + public static ResourcePathAssertion assertThat(@Nonnull final ResourceCollection fhirPath, + @Nonnull final EvaluationContext evaluationContext) { + return new ResourcePathAssertion(fhirPath, evaluationContext); } // @Nonnull diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java index 02ffbf7ac7..e3e52d713c 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java @@ -17,10 +17,17 @@ package au.csiro.pathling.test.assertions; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.catalyst.expressions.Literal; import javax.annotation.Nonnull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + /** * @author Piotr Szul * @author John Grimes @@ -30,27 +37,44 @@ public abstract class BaseFhirPathAssertion> { @Nonnull - private final Collection result; + protected final Collection result; + + + @Nonnull + private final EvaluationContext evaluationContext; - BaseFhirPathAssertion(@Nonnull final Collection result) { + BaseFhirPathAssertion(@Nonnull final Collection result, + @Nonnull final EvaluationContext evaluationContext) { this.result = result; + this.evaluationContext = evaluationContext; } - // TODO: implement with columns - - // @Nonnull - // public DatasetAssert selectResult() { - // final Column[] selection = new Column[]{result.getIdColumn(), result.getValueColumn()}; - // return new DatasetAssert(result.getDataset().select(selection)); - // } - // - // @Nonnull - // public DatasetAssert selectOrderedResult() { - // final Column[] selection = new Column[]{result.getIdColumn(), result.getValueColumn()}; - // // TODO: Update this to make sure that it is ordered. - // return new DatasetAssert(result.getDataset().select(selection)); - // } + + @Nonnull + public DatasetAssert selectResult() { + final Column[] selection = new Column[]{ + evaluationContext.getResource().traverse("id").get().getColumn(), + result.getColumn() + }; + // TODO: Update this to make sure that it is ordered + // and exploded is needed + return DatasetAssert.of(evaluationContext.getDataset().select(selection)); + } + + @Nonnull + public DatasetAssert selectOrderedResult() { + + // + final Column[] selection = new Column[]{ + evaluationContext.getResource().traverse("id").get().getColumn(), + result.getColumn() + }; + // TODO: Update this to make sure that it is ordered + // and exploded is needed + return DatasetAssert.of(evaluationContext.getDataset().select(selection)); + } + // // @Nonnull // public DatasetAssert selectGroupingResult(@Nonnull final List groupingColumns) { @@ -76,42 +100,49 @@ public abstract class BaseFhirPathAssertion> // return new DatasetAssert(result.getDataset().select(selection)); // } // - // @Nonnull - // public T hasExpression(@Nonnull final String expression) { - // assertEquals(expression, result.getExpression()); - // return self(); - // } - // - // public T isSingular() { - // assertTrue(result.isSingular()); - // return self(); - // } - // - // public T isNotSingular() { - // assertFalse(result.isSingular()); - // return self(); - // } + @Nonnull + public T hasExpression(@Nonnull final String expression) { + // TODO: not implemented + fail("Not implemented: hasExpression"); + // assertEquals(expression, result.getExpression()); + return self(); + } + + public T isSingular() { + // TODO: not implemented + fail("Not implemented: isSingluar"); + // assertTrue(result.isSingular()); + return self(); + } + + public T isNotSingular() { + // TODO: not implemented + fail("Not implemented: isNotSingular"); + // assertFalse(result.isSingular()); + return self(); + } // // public T preservesCardinalityOf(final Collection otherResult) { // assertEquals(otherResult.isSingular(), result.isSingular()); // return self(); // } // - // - // public ElementPathAssertion isElementPath(final Class ofType) { - // assertTrue(ofType.isAssignableFrom(result.getClass())); - // return new ElementPathAssertion((PrimitivePath) result); - // } - // - // public ResourcePathAssertion isResourcePath() { - // assertTrue(ResourceCollection.class.isAssignableFrom(result.getClass())); - // return new ResourcePathAssertion((ResourceCollection) result); - // } - // - // public LiteralPathAssertion isLiteralPath(final Class ofType) { - // assertTrue(ofType.isAssignableFrom(result.getClass())); - // return new LiteralPathAssertion((LiteralPath) result); - // } + + public ElementPathAssertion isElementPath(final Class ofType) { + assertTrue(ofType.isAssignableFrom(result.getClass())); + return new ElementPathAssertion(result, evaluationContext); + } + + public ResourcePathAssertion isResourcePath() { + assertTrue(ResourceCollection.class.isAssignableFrom(result.getClass())); + return new ResourcePathAssertion((ResourceCollection) result, evaluationContext); + } + + public LiteralPathAssertion isLiteralPath(final Class ofType) { + assertTrue(ofType.isAssignableFrom(result.getClass())); + assertTrue(result.getColumn().expr() instanceof Literal); + return new LiteralPathAssertion(result, evaluationContext); + } @SuppressWarnings("unchecked") private T self() { diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java index 81b3ca7f63..89c1e3c8c4 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ElementPathAssertion.java @@ -17,6 +17,7 @@ package au.csiro.pathling.test.assertions; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; import javax.annotation.Nonnull; @@ -28,8 +29,9 @@ @NotImplemented public class ElementPathAssertion extends BaseFhirPathAssertion { - ElementPathAssertion(@Nonnull final Collection result) { - super(result); + ElementPathAssertion(@Nonnull final Collection result, + @Nonnull final EvaluationContext evaluationContext) { + super(result, evaluationContext); } // TODO: check diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/FhirPathAssertion.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/FhirPathAssertion.java index c79c46dfb7..d37c6469a1 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/FhirPathAssertion.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/FhirPathAssertion.java @@ -17,6 +17,7 @@ package au.csiro.pathling.test.assertions; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.collection.Collection; import javax.annotation.Nonnull; @@ -25,8 +26,9 @@ */ public class FhirPathAssertion extends BaseFhirPathAssertion { - FhirPathAssertion(@Nonnull final Collection result) { - super(result); + FhirPathAssertion(@Nonnull final Collection result, + @Nonnull final EvaluationContext evaluationContext) { + super(result, evaluationContext); } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/LiteralPathAssertion.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/LiteralPathAssertion.java index 2a7af21630..08d5b875e0 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/LiteralPathAssertion.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/LiteralPathAssertion.java @@ -17,9 +17,16 @@ package au.csiro.pathling.test.assertions; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; +import org.apache.spark.sql.catalyst.expressions.Expression; +import org.apache.spark.sql.catalyst.expressions.Literal; import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author John Grimes @@ -28,26 +35,21 @@ @NotImplemented public class LiteralPathAssertion extends BaseFhirPathAssertion { - LiteralPathAssertion(@Nonnull final Collection result) { - super(result); + LiteralPathAssertion(@Nonnull final Collection result, + @Nonnull final EvaluationContext evaluationContext) { + super(result, evaluationContext); } //TODO: LiteralPathAssertion - - // @Nonnull - // private final LiteralPath fhirPath; - // - // LiteralPathAssertion(@Nonnull final LiteralPath fhirPath) { - // super(fhirPath); - // this.fhirPath = fhirPath; - // } - // - // @Nonnull - // public LiteralPathAssertion has(@Nullable final Object expected, - // @Nonnull final Function function) { - // assertEquals(expected, function.apply(fhirPath)); - // return this; - // } + + @Nonnull + public LiteralPathAssertion has(@Nullable final Object expected) { + // TODO: consider this implementation - we may want to evaluate the expression instead + final Expression maybeLiteral = result.getColumn().expr(); + assertTrue(maybeLiteral instanceof Literal); + assertEquals(expected, ((Literal) maybeLiteral).value()); + return this; + } // // @Nonnull // public LiteralPathAssertion hasCodingValue(@Nonnull final Coding expectedCoding) { diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ResourcePathAssertion.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ResourcePathAssertion.java index 8cf6388c5c..c69afa7b63 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ResourcePathAssertion.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/ResourcePathAssertion.java @@ -19,6 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.collection.ResourceCollection; import javax.annotation.Nonnull; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -31,8 +32,9 @@ public class ResourcePathAssertion extends BaseFhirPathAssertion Date: Fri, 15 Sep 2023 11:55:27 +1000 Subject: [PATCH 78/95] Reverting test data for Patient resource to be compatible with ParserTest expectations. --- .../au/csiro/pathling/fhirpath/parser/AbstractParserTest.java | 2 ++ .../test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java | 2 +- fhir-server/src/test/resources/test-data/fhir/Patient.ndjson | 2 +- .../java/au/csiro/pathling/fhirpath/function/WhereFunction.java | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java index ad20685c5e..4a52120ac7 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java @@ -91,6 +91,7 @@ void setSubjectResource(@Nonnull final ResourceType resourceType) { evaluationContext = new EvaluationContextBuilder(spark, fhirContext) .dataset(dataSource.read(resourceType)) + .resource(subjectResource) .inputContext(subjectResource) .build(); } @@ -111,6 +112,7 @@ protected FhirPathAssertion assertThatResultOf(@Nonnull final ResourceType resou final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) .dataset(dataSource.read(resourceType)) + .resource(subjectResource) .inputContext(subjectResource) .build(); return assertThat(parser.evaluate(expression, evaluationContext), evaluationContext); diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java index c36538246c..0cc3050a2c 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java @@ -779,7 +779,7 @@ void testUntilFunction() { .selectResult() .hasRows(spark, "responses/ParserTest/testUntilFunction.csv"); } - + @Test void testQuantityMultiplicationAndDivision() { assertThatResultOf( diff --git a/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson b/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson index 1a1d13782a..ed940b44e4 100644 --- a/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson +++ b/fhir-server/src/test/resources/test-data/fhir/Patient.ndjson @@ -1,4 +1,4 @@ -{"resourceType":"Patient","id":"8ee183e2-b3c0-4151-be94-b945d6aa8c6d","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -1116549638004693619 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Onie555 Tremblay80"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Lawrence","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":2.442019549037137},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":45.557980450962866}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-21-1297"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99916275"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X27195897X"}],"name":[{"use":"official","family":"Krajcik437","given":["Seymour882","Moyna"],"prefix":["Mr."],"suffix":["MD"]},{"use":"nickname","family":"Burchard","given":["Arianne"],"prefix":["Ms."],"suffix":["MD"]}],"telecom":[{"system":"phone","value":"555-757-3815","use":"home"}],"gender":"male","birthDate":"1970-11-22","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.27362325267794},{"url":"longitude","valueDecimal":-70.91799558593002}]}],"line":["855 Senger Union Suite 12"],"city":"Quincy","state":"Massachusetts","postalCode":"02169","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} +{"resourceType":"Patient","id":"8ee183e2-b3c0-4151-be94-b945d6aa8c6d","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -1116549638004693619 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Onie555 Tremblay80"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Lawrence","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":2.442019549037137},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":45.557980450962866}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"0dc85075-4f59-4e4f-b75d-a2f601d0cf24"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-21-1297"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99916275"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X27195897X"}],"name":[{"use":"official","family":"Krajcik437","given":["Seymour882"],"prefix":["Mr."],"suffix":["MD"]}],"telecom":[{"system":"phone","value":"555-757-3815","use":"home"}],"gender":"male","birthDate":"1970-11-22","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.27362325267794},{"url":"longitude","valueDecimal":-70.91799558593002}]}],"line":["855 Senger Union Suite 12"],"city":"Quincy","state":"Massachusetts","postalCode":"02169","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} {"resourceType":"Patient","id":"beff242e-580b-47c0-9844-c1a68c36c5bf","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: 2489887534555043489 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Germaine912 Berge125"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Boston","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":0.11924342173460653},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":34.88075657826539}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"1f276fc3-7e91-4fc9-a287-be19228e8807"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"1f276fc3-7e91-4fc9-a287-be19228e8807"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-56-3056"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99940301"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X51286458X"}],"name":[{"use":"official","family":"Towne435","given":["Guy979"],"prefix":["Mr."]}],"telecom":[{"system":"phone","value":"555-273-5273","use":"home"}],"gender":"male","birthDate":"1983-09-06","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.416135340079045},{"url":"longitude","valueDecimal":-71.06798157703605}]}],"line":["598 Boyer Ramp"],"city":"Somerville","state":"Massachusetts","postalCode":"02138","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} {"resourceType":"Patient","id":"e62e52ae-2d75-4070-a0ae-3cc78d35ed08","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: -4398174870245759556 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Idella49 Monahan736"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"F"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Chicopee","state":"Massachusetts","country":"US"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":10.028345047642997},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":38.971654952357}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"2cc8ffdd-6233-4dd4-ba71-36eccb8204e2"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"2cc8ffdd-6233-4dd4-ba71-36eccb8204e2"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-43-1135"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99956022"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X85674863X"}],"name":[{"use":"official","family":"Ebert178","given":["Su690"],"prefix":["Ms."]}],"telecom":[{"system":"phone","value":"555-491-7400","use":"home"}],"gender":"female","birthDate":"1959-09-27","deceasedDateTime":"2009-09-27T09:11:55+00:00","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.39138295331103},{"url":"longitude","valueDecimal":-71.00439277761001}]}],"line":["460 Douglas Camp"],"city":"Boston","state":"Massachusetts","postalCode":"02108","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"S","display":"S"}],"text":"S"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en-US","display":"English"}],"text":"English"}}]} {"resourceType":"Patient","id":"2b36c1e2-bbe1-45ae-8124-4adad2677702","text":{"status":"generated","div":"
Generated by Synthea.Version identifier: v2.4.0-373-g9417ce01\n . Person seed: 346614667352111003 Population seed: 1567659637983
"},"extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2135-2","display":"Other"}},{"url":"text","valueString":"Other"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"María del Carmen27 Lozano749"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Ponce","state":"Puerto Rico","country":"PR"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":0.019152156118766007},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":19.980847843881232}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"2f9fd91d-9096-40e8-8307-825f68780599"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"2f9fd91d-9096-40e8-8307-825f68780599"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-42-8004"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99979084"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X28589103X"}],"name":[{"use":"official","family":"Ulibarri312","given":["Pedro316"],"prefix":["Mr."]}],"telecom":[{"system":"phone","value":"555-590-2206","use":"home"}],"gender":"male","birthDate":"1998-12-26","address":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/geolocation","extension":[{"url":"latitude","valueDecimal":42.53265430498948},{"url":"longitude","valueDecimal":-73.25721481498516}]}],"line":["879 Hettinger Gardens"],"city":"Pittsfield","state":"Massachusetts","postalCode":"01201","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"S","display":"Never Married"}],"text":"Never Married"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"es","display":"Spanish"}],"text":"Spanish"}}]} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java index 17a81acd61..a6da718b37 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WhereFunction.java @@ -50,6 +50,8 @@ public Collection invoke(@Nonnull final FunctionInput input) { final Column column = filter(previous.getColumn(), element -> { final Collection result = argument.apply( + + // TOOD: This does not work on Resource collections Collection.build(element, previous.getType(), previous.getFhirType(), previous.getDefinition()), input.getContext()); final FhirPathType type = checkPresent(result.getType()); From 5174a7fcb5f70dc215fc28e4648765c5e70a9719 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Fri, 15 Sep 2023 13:21:16 +1000 Subject: [PATCH 79/95] Implemented first(), empty(), where(), and count() as collection functions. --- .../collection/BooleanCollection.java | 7 ++ .../fhirpath/collection/Collection.java | 11 +- .../collection/IntegerCollection.java | 6 + .../pathling/fhirpath/column/ColumnCtx.java | 9 +- .../function/CollectionExpression.java | 45 +++++++ .../fhirpath/function/ColumnFunctions.java | 6 +- .../fhirpath/function/StandardFunctions.java | 68 +++++++++++ .../fhirpath/function/WrappedFunction.java | 110 ++++++++++++++++++ .../registry/StaticFunctionRegistry.java | 7 +- 9 files changed, 262 insertions(+), 7 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CollectionExpression.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/StandardFunctions.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WrappedFunction.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 0d87bcdcfb..924601f04a 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 @@ -22,6 +22,7 @@ import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.column.ColumnCtx; import au.csiro.pathling.fhirpath.definition.NodeDefinition; import com.google.common.collect.ImmutableSet; import java.util.Optional; @@ -80,6 +81,12 @@ public static BooleanCollection build(@Nonnull final Column column) { return BooleanCollection.build(column, Optional.empty()); } + @Nonnull + public static BooleanCollection build(@Nonnull final ColumnCtx value) { + return BooleanCollection.build(value.getValue(), Optional.empty()); + } + + @Nonnull @Override public Optional getFhirValueFromRow(@Nonnull final Row row, final int columnNumber) { 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 0f07bbc297..1d64e12297 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 @@ -283,13 +283,22 @@ public static Collection nullCollection() { } @Nonnull - public Collection copyWith(Column newValue) { + public Collection copyWith(@Nonnull final Column newValue) { // TODO: This is very very suspicious // Really need to understand what the relationships between all the different types // some of them seem redundant return getInstance(newValue, getFhirType(), (Optional) definition); } + @Nonnull + public Collection copyWith(@Nonnull final ColumnCtx newValue) { + // TODO: This is very very suspicious + // Really need to understand what the relationships between all the different types + // some of them seem redundant + return copyWith(newValue.getValue()); + } + + public Column getSingleton() { return ColumnHelpers.singular(getColumn()); } 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 10954effdb..d9e835748c 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 @@ -26,6 +26,7 @@ import au.csiro.pathling.fhirpath.Materializable; import au.csiro.pathling.fhirpath.Numeric; import au.csiro.pathling.fhirpath.StringCoercible; +import au.csiro.pathling.fhirpath.column.ColumnCtx; import au.csiro.pathling.fhirpath.definition.NodeDefinition; import com.google.common.collect.ImmutableSet; import java.util.Optional; @@ -79,6 +80,11 @@ public static IntegerCollection build(@Nonnull final Column column) { } + @Nonnull + public static IntegerCollection build(ColumnCtx count) { + return build(count.getValue()); + } + /** * Returns a new instance, parsed from a FHIRPath literal. * diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java index 4a698e0f66..459c787448 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java @@ -58,7 +58,14 @@ public ColumnCtx singular() { Function.identity() ); } - + + @Nonnull + public ColumnCtx filter(Function lambda) { + return vectorize( + c -> functions.filter(c, lambda::apply), + c -> functions.when(c.isNotNull(), functions.when(lambda.apply(c), c)) + ); + } @Nonnull public ColumnCtx aggregate(@Nonnull final Object zeroValue, diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CollectionExpression.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CollectionExpression.java new file mode 100644 index 0000000000..bb308d90bb --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CollectionExpression.java @@ -0,0 +1,45 @@ +/* + * Copyright 2023 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.function; + +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.Collection; +import org.apache.spark.sql.Column; + +import javax.annotation.Nonnull; +import java.util.function.Function; + +@FunctionalInterface +interface CollectionExpression extends Function { + + default CollectionExpression requireBoolean() { + return input -> { + final Collection result = apply(input); + if (result instanceof BooleanCollection) { + return result; + } else { + throw new RuntimeException("Expected boolean result"); + } + }; + } + + default Function toColumnFunction(@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/ColumnFunctions.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunctions.java index 8403897976..43cb0a2510 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunctions.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunctions.java @@ -40,19 +40,19 @@ public static Column not(@Nonnull final Column column) { } - @FhirpathFunction + //@FhirpathFunction @ReturnType(FHIRDefinedType.BOOLEAN) public static Column empty(@Nonnull final Column column) { return ColumnCtx.of(column).empty().getValue(); } - @FhirpathFunction + //@FhirpathFunction @ReturnType(FHIRDefinedType.INTEGER) public static Column count(@Nonnull final Column column) { return ColumnCtx.of(column).count().getValue(); } - @FhirpathFunction + //@FhirpathFunction public static Column first(@Nonnull final Column column) { // how to deal with nulls inside the expressioss? // technically should use filter to remove nulls, but that's expensive 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 new file mode 100644 index 0000000000..5925299f06 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/StandardFunctions.java @@ -0,0 +1,68 @@ +/* + * Copyright 2023 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.function; + +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.IntegerCollection; +import au.csiro.pathling.fhirpath.validation.FhirpathFunction; +import javax.annotation.Nonnull; + +import static au.csiro.pathling.fhirpath.function.CollectionExpression.*; + +public class StandardFunctions { + + + @Nonnull + @FhirpathFunction + public static Collection where(@Nonnull final Collection input, + @Nonnull CollectionExpression expression) { + return input.copyWith( + input.getCtx().filter(expression.requireBoolean().toColumnFunction(input))); + } + // + // + // // Maybe these too can be implemented as colum functions???? + // @FhirpathFunction + // public Collection iif(@Nonnull final Collection input, + // @Nonnull CollectionExpression expression, @Nonnull Collection thenValue, + // @Nonnull Collection otherwiseValue) { + // // if we do not need to modify the context then maybe enough to just pass the bound expressions + // // (but in fact it's lazy eval anyway and at some point we should check + // functions.when(requireBoolean(expression).apply(input).getSingleton(), thenValue.getColumn()) + // .otherwise(otherwiseValue.getColumn()); + // // we need to check that the result of the expression is boolean + // return Collection.nullCollection(); + // } + + @FhirpathFunction + public static Collection first(@Nonnull final Collection input) { + return input.copyWith(input.getCtx().first()); + } + + @FhirpathFunction + public static BooleanCollection empty(@Nonnull final Collection input) { + return BooleanCollection.build(input.getCtx().empty()); + } + + @FhirpathFunction + public static IntegerCollection count(@Nonnull final Collection input) { + return IntegerCollection.build(input.getCtx().count()); + } + +} 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 new file mode 100644 index 0000000000..8154da53d7 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/WrappedFunction.java @@ -0,0 +1,110 @@ +/* + * Copyright 2023 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.function; + +import au.csiro.pathling.fhirpath.EvaluationContext; +import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.FunctionInput; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.validation.FhirpathFunction; +import lombok.Value; +import javax.annotation.Nonnull; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +@Value +public class WrappedFunction implements NamedFunction { + + String name; + Method method; + + @Override + @Nonnull + public String getName() { + return name; + } + + + @Value + private static class ParamResolver { + + EvaluationContext evaluationContext; + Collection input; + + public Object resolveArgument(@Nonnull final Parameter parameter, + FhirPath argument) { + if (Collection.class.isAssignableFrom(parameter.getType())) { + // evaluate collection types + return argument.apply(input, evaluationContext); + } else if (CollectionExpression.class.isAssignableFrom(parameter.getType())) { + // bind with context + return (CollectionExpression)(c -> argument.apply(c, evaluationContext)); + } else { + throw new RuntimeException("Cannot resolve parameter:" + parameter); + } + } + } + + @Override + @Nonnull + public Collection invoke(@Nonnull final FunctionInput functionInput) { + // first arguemnt to the method is always the input collection + + final ParamResolver resolver = new ParamResolver(functionInput.getContext(), + functionInput.getInput()); + + final Stream resolvedArguments = IntStream.range(0, method.getParameterCount() - 1) + .mapToObj(i -> resolver.resolveArgument(method.getParameters()[i + 1], + functionInput.getArguments().get(i))); + // eval arguments + final Object[] invocationArgs = Stream.concat(Stream.of(functionInput.getInput()), + resolvedArguments).toArray(Object[]::new); + try { + return (Collection) method.invoke(null, invocationArgs); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public static WrappedFunction of(@Nonnull final Method method) { + return new WrappedFunction(method.getName(), method); + } + + @Nonnull + public static List of(@Nonnull final Class clazz) { + return Stream.of(clazz.getDeclaredMethods()) + .filter(m -> m.getAnnotation(FhirpathFunction.class) != null) + .map(WrappedFunction::of).collect(Collectors.toUnmodifiableList()); + } + + @Nonnull + public static Map mapOf(@Nonnull final Class clazz) { + return of(clazz).stream().collect(Collectors.toUnmodifiableMap(NamedFunction::getName, + Function.identity())); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java index 59459641ae..6e3badd2f8 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java @@ -20,10 +20,12 @@ import au.csiro.pathling.fhirpath.function.OfTypeFunction; import au.csiro.pathling.fhirpath.function.ResolveFunction; import au.csiro.pathling.fhirpath.function.ReverseResolveFunction; +import au.csiro.pathling.fhirpath.function.StandardFunctions; import au.csiro.pathling.fhirpath.function.SumFunction; import au.csiro.pathling.fhirpath.function.ToStringFunction; import au.csiro.pathling.fhirpath.function.UntilFunction; import au.csiro.pathling.fhirpath.function.WhereFunction; +import au.csiro.pathling.fhirpath.function.WrappedFunction; import au.csiro.pathling.fhirpath.function.terminology.DesignationFunction; import au.csiro.pathling.fhirpath.function.terminology.DisplayFunction; import au.csiro.pathling.fhirpath.function.terminology.MemberOfFunction; @@ -51,7 +53,7 @@ public StaticFunctionRegistry() { .put("ofType", new OfTypeFunction()) .put("reverseResolve", new ReverseResolveFunction()) .put("memberOf", new MemberOfFunction()) - .put("where", new WhereFunction()) + //.put("where", new WhereFunction()) .put("subsumes", new SubsumesFunction()) .put("subsumedBy", new SubsumesFunction(true)) //.put("empty", new EmptyFunction()) @@ -66,13 +68,14 @@ public StaticFunctionRegistry() { .put("allFalse", new BooleansTestFunction(ALL_FALSE)) .put("extension", new ExtensionFunction()) .put("until", new UntilFunction()) - //.put("exists", new ExistsFunction()) + //.put("exists", new ExistsFunction()) .put("display", new DisplayFunction()) .put("property", new PropertyFunction()) .put("designation", new DesignationFunction()) .put("toString", new ToStringFunction()) .put("getId", new GetIdFunction()) .putAll(ColumnFunction0.mapOf(ColumnFunctions.class)) + .putAll(WrappedFunction.mapOf(StandardFunctions.class)) .build()); } From e10e6f0d7109e9124d21574a02ab521494e31a4c Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Fri, 15 Sep 2023 16:17:48 +1000 Subject: [PATCH 80/95] Added first draft of new binary operators. --- .../fhirpath/operator/BinaryOperators.java | 43 +++++++++++++ .../operator/WrappedBinaryOperator.java | 63 +++++++++++++++++++ .../pathling/fhirpath/parser/Visitor.java | 11 +++- .../validation/FhirpathBinaryOperator.java | 28 +++++++++ 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperators.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/WrappedBinaryOperator.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/FhirpathBinaryOperator.java diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperators.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperators.java new file mode 100644 index 0000000000..b08c4a3af1 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/BinaryOperators.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 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 au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.validation.FhirpathBinaryOperator; +import org.apache.spark.sql.functions; +import javax.annotation.Nonnull; + +public class BinaryOperators { + + @FhirpathBinaryOperator + public static BooleanCollection contains(@Nonnull final Collection collection, + @Nonnull final Collection element) { + return BooleanCollection.build(collection.getCtx().vectorize( + c -> functions.array_contains(c, element.getSingleton()), + c -> c.equalTo(element.getSingleton())) + ); + } + + @FhirpathBinaryOperator + public static BooleanCollection in(@Nonnull final Collection element, + @Nonnull final Collection collection) { + return contains(collection, element); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/WrappedBinaryOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/WrappedBinaryOperator.java new file mode 100644 index 0000000000..d23af38f1f --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/operator/WrappedBinaryOperator.java @@ -0,0 +1,63 @@ +/* + * Copyright 2023 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 au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.validation.FhirpathBinaryOperator; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import lombok.Value; + +@Value +public class WrappedBinaryOperator implements BinaryOperator { + + Method method; + + @Nonnull + public Collection invoke(@Nonnull final BinaryOperatorInput operatorInput) { + // first arguemnt to the method is always the input collection + + // eval arguments + final Object[] invocationArgs = Stream.of(operatorInput.getLeft(), operatorInput.getRight()) + .toArray(Object[]::new); + try { + return (Collection) method.invoke(null, invocationArgs); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public static WrappedBinaryOperator of(@Nonnull final Method method) { + return new WrappedBinaryOperator(method); + } + + @Nonnull + public static Map mapOf(@Nonnull final Class clazz) { + return Stream.of(clazz.getDeclaredMethods()) + .filter(m -> m.getAnnotation(FhirpathBinaryOperator.class) != null) + .collect(Collectors.toUnmodifiableMap(Method::getName, WrappedBinaryOperator::new)); + + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java index b738ce6e63..9b56b30219 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java @@ -22,7 +22,10 @@ import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.operator.BinaryOperator; import au.csiro.pathling.fhirpath.operator.BinaryOperatorType; +import au.csiro.pathling.fhirpath.operator.BinaryOperators; +import au.csiro.pathling.fhirpath.operator.WrappedBinaryOperator; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.AdditiveExpressionContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.AndExpressionContext; @@ -43,6 +46,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.antlr.v4.runtime.tree.ParseTree; +import java.util.Map; /** * This class processes all types of expressions, and delegates the special handling of supported @@ -88,6 +92,10 @@ public FhirPath visitInvocationExpression( }; } + + private static final Map BINARY_OPERATORS = WrappedBinaryOperator.mapOf( + BinaryOperators.class); + @Nonnull private FhirPath visitBinaryOperator( @Nullable final ParseTree leftContext, @@ -95,7 +103,8 @@ private FhirPath visitBinaryOperator( requireNonNull(operatorName); return new EvalOperatorPath(new Visitor().visit(leftContext), new Visitor().visit(rightContext), - BinaryOperatorType.fromSymbol(operatorName).getInstance()); + BINARY_OPERATORS.getOrDefault(operatorName, + BinaryOperatorType.fromSymbol(operatorName).getInstance())); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/FhirpathBinaryOperator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/FhirpathBinaryOperator.java new file mode 100644 index 0000000000..2600d41e06 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/validation/FhirpathBinaryOperator.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 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.validation; + +import javax.annotation.Nonnull; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@Nonnull +public @interface FhirpathBinaryOperator { + +} From 31d63106566a5fd007d428f5b5fd90531724609b Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Thu, 21 Sep 2023 15:15:05 +1000 Subject: [PATCH 81/95] Implementing extension support and 'typeOf' operation. --- .../config/EncodingConfiguration.java | 17 +-- .../csiro/pathling/encoders/FhirEncoders.java | 28 +++++ .../collection/BooleanCollection.java | 11 +- .../fhirpath/collection/Collection.java | 103 +++++++++++------- .../fhirpath/collection/MixedCollection.java | 66 ++++------- .../collection/ResourceCollection.java | 30 ++--- .../pathling/fhirpath/column/ColumnCtx.java | 14 +++ .../definition/BaseNodeDefinition.java | 57 ++++++++++ .../definition/BasicElementDefinition.java | 86 --------------- .../fhirpath/definition/ChildDefinition.java | 51 +++++++++ ...nition.java => ChoiceChildDefinition.java} | 21 ++-- .../definition/ElementChildDefinition.java | 77 +++++++++++++ .../definition/ElementDefinition.java | 61 +++-------- .../fhirpath/definition/NodeDefinition.java | 6 +- .../definition/ReferenceDefinition.java | 12 +- .../definition/ResolvedChoiceDefinition.java | 43 -------- .../definition/ResourceDefinition.java | 22 +--- .../fhirpath/function/StandardFunctions.java | 36 ++++++ .../registry/StaticFunctionRegistry.java | 4 +- .../fhirpath/parser/InvocationVisitor.java | 8 +- .../pathling/views/FhirViewExecutor.java | 1 + .../csiro/pathling/UnitTestDependencies.java | 6 +- .../test/builders/ElementPathBuilder.java | 4 +- .../pathling/test/helpers/FhirHelpers.java | 3 +- sql-on-fhir | 2 +- 25 files changed, 423 insertions(+), 346 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BaseNodeDefinition.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BasicElementDefinition.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChildDefinition.java rename fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/{ChoiceElementDefinition.java => ChoiceChildDefinition.java} (78%) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementChildDefinition.java delete mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResolvedChoiceDefinition.java diff --git a/encoders/src/main/java/au/csiro/pathling/config/EncodingConfiguration.java b/encoders/src/main/java/au/csiro/pathling/config/EncodingConfiguration.java index e5983acd78..0dbf89b9fc 100644 --- a/encoders/src/main/java/au/csiro/pathling/config/EncodingConfiguration.java +++ b/encoders/src/main/java/au/csiro/pathling/config/EncodingConfiguration.java @@ -26,6 +26,7 @@ import java.util.Set; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; +import au.csiro.pathling.encoders.FhirEncoders; import lombok.Builder; import lombok.Data; @@ -59,19 +60,5 @@ public class EncodingConfiguration { */ @NotNull @Builder.Default - private Set openTypes = Set.of( - "boolean", - "code", - "date", - "dateTime", - "decimal", - "integer", - "string", - "Coding", - "CodeableConcept", - "Address", - "Identifier", - "Reference" - ); - + private Set openTypes = FhirEncoders.STANDARD_OPEN_TYPES; } diff --git a/encoders/src/main/java/au/csiro/pathling/encoders/FhirEncoders.java b/encoders/src/main/java/au/csiro/pathling/encoders/FhirEncoders.java index 170164ca9c..d9698c1277 100644 --- a/encoders/src/main/java/au/csiro/pathling/encoders/FhirEncoders.java +++ b/encoders/src/main/java/au/csiro/pathling/encoders/FhirEncoders.java @@ -41,6 +41,25 @@ */ public class FhirEncoders { + /** + * The reasonable default set of open types to encode with extension values. + */ + public static final Set STANDARD_OPEN_TYPES = Set.of( + "boolean", + "code", + "date", + "dateTime", + "decimal", + "integer", + "string", + "Coding", + "CodeableConcept", + "Address", + "Identifier", + "Reference" + ); + + /** * Cache of Encoders instances. */ @@ -308,6 +327,15 @@ public Builder withOpenTypes(final Set openTypes) { return this; } + /** + * Sets the reasonable default list of types to be encoded for open types, such as extensions. + * + * @return this builder + */ + public Builder withStandardOenTypes() { + return withOpenTypes(STANDARD_OPEN_TYPES); + } + /** * Switches on/off the support for extensions in encoders. * 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 924601f04a..af4be48411 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 @@ -42,6 +42,7 @@ public class BooleanCollection extends Collection implements Materializable> COMPARABLE_TYPES = ImmutableSet .of(BooleanCollection.class); + private static final BooleanCollection FALSE_COLLECTION = BooleanCollection.fromValue(false); protected BooleanCollection(@Nonnull final Column column, @Nonnull final Optional type, @@ -72,7 +73,11 @@ public static BooleanCollection build(@Nonnull final Column column, */ @Nonnull public static BooleanCollection fromLiteral(@Nonnull final String literal) { - final boolean value = literal.equals("true"); + return BooleanCollection.fromValue(literal.equals("true")); + } + + @Nonnull + public static BooleanCollection fromValue(final boolean value) { return BooleanCollection.build(lit(value), Optional.empty()); } @@ -86,6 +91,10 @@ public static BooleanCollection build(@Nonnull final ColumnCtx value) { return BooleanCollection.build(value.getValue(), Optional.empty()); } + @Nonnull + public static BooleanCollection falseCollection() { + return FALSE_COLLECTION; + } @Nonnull @Override 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 1d64e12297..95413220ea 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 @@ -17,13 +17,18 @@ package au.csiro.pathling.fhirpath.collection; +import au.csiro.pathling.encoders.ExtensionSupport; import au.csiro.pathling.fhirpath.ColumnHelpers; import au.csiro.pathling.fhirpath.Comparable; import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.Numeric; import au.csiro.pathling.fhirpath.column.ColumnCtx; +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.utilities.Preconditions; import com.google.common.collect.ImmutableMap; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -32,8 +37,8 @@ import java.util.function.Function; import javax.annotation.Nonnull; import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.apache.spark.sql.Column; import org.apache.spark.sql.functions; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; @@ -44,7 +49,7 @@ * @author John Grimes */ @Getter -@AllArgsConstructor(access = AccessLevel.PROTECTED) +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) public class Collection implements Comparable, Numeric { // See https://hl7.org/fhir/fhirpath.html#types. @@ -78,25 +83,25 @@ public class Collection implements Comparable, Numeric { * A {@link Column} representing the result of evaluating this expression. */ @Nonnull - private Column column; + private final Column column; /** * The type of the result of evaluating this expression, if known. */ @Nonnull - private Optional type; + private final Optional type; /** * The FHIR type of the result of evaluating this expression, if there is one. */ @Nonnull - private Optional fhirType; + private final Optional fhirType; /** * The FHIR definition that describes this path, if there is one. */ @Nonnull - private Optional definition; + private final Optional definition; /** * Builds a generic {@link Collection} with the specified column, FHIRPath type, FHIR type and @@ -196,43 +201,61 @@ public static Optional> classForType( */ @Nonnull public Optional traverse(@Nonnull final String elementName) { - final Optional elementDefinition = definition - .filter(def -> def instanceof ElementDefinition) - .map(def -> (ElementDefinition) def); + + final Optional maybeChildDef = definition.flatMap( + def -> def.getChildElement(elementName)) + .filter(ChildDefinition.class::isInstance); + + return maybeChildDef.flatMap( + childDef -> ExtensionSupport.EXTENSION_ELEMENT_NAME().equals(elementName) + ? traverseExtensions(childDef) + : Optional.of(traverseChild(childDef))); + } + + @Nonnull + protected Collection traverseChild(@Nonnull final ChildDefinition childDef) { + // It is only possible to traverse to a child with an element definition. + if (childDef instanceof ChoiceChildDefinition) { + return MixedCollection.build(this, (ChoiceChildDefinition) childDef); + } else if (childDef instanceof ElementChildDefinition) { + return traverseElement((ElementChildDefinition) childDef); + } else { + throw new IllegalArgumentException("Unsupported child definition type: " + childDef + .getClass().getSimpleName()); + } + } + + + @Nonnull + protected Collection traverseElement(@Nonnull final ElementDefinition childDef) { // It is only possible to traverse to a child with an element definition. - return elementDefinition - // Get the named child definition. - .flatMap(def -> def.getChildElement(elementName)) - .map(def -> Collection.build(getCtx().traverse(elementName).getValue(), def)); + return Collection.build(getCtx().traverse(childDef.getElementName()).getValue(), childDef); + } + + @Nonnull + protected Optional traverseExtensions( + @Nonnull final ChildDefinition extensionDefinition) { + // check the provided definition is of an extension + Preconditions.checkArgument(extensionDefinition instanceof ElementDefinition, + "Cannot traverse to an extension with a non-ElementDefinition"); + return getExtensionMap().map(em -> + Collection.build( + // We need here to deal with the situation where _fid is an array of element ids + ColumnCtx.of(getFid()).transform(em::apply).unnest().getValue(), + (ElementDefinition) extensionDefinition)); } - // @Nonnull - // private Optional traverseExtension(@Nonnull final SparkSession spark, - // @Nonnull final String name) { - // // This code introspects the type of the extension container column to determine the valid child - // // element names. - // final String extensionElementName = ExtensionSupport.EXTENSION_ELEMENT_NAME(); - // final MapType mapType = (MapType) spark.emptyDataFrame().select(extensionElementName) - // .schema() - // .fields()[0].dataType(); - // final ArrayType arrayType = (ArrayType) mapType.valueType(); - // final StructType structType = (StructType) arrayType.elementType(); - // final List fieldNames = Arrays.stream(structType.fields()).map(StructField::name) - // .collect(Collectors.toList()); - // // Add the extension field name, so that we can support traversal to nested extensions. - // fieldNames.add(extensionElementName); - // - // // If the field is part of the encoded extension container, pass control to the generic code to - // // determine the correct element definition. - // if (fieldNames.contains(name)) { - // // Create a new expression that looks like a path traversal. - // final String resultExpression = this.expression + "." + expression; - // // Build a new FhirPath object, with a column that uses `getField` to extract the - // // appropriate child. - // return Result.build(column.getField(expression), resultExpression, def); - // } - // return Optional.empty(); - // } + @Nonnull + protected Column getFid() { + return column.getField(ExtensionSupport.FID_FIELD_NAME()); + } + + @Nonnull + protected Optional getExtensionMap() { + // TODO: This most likely needs to be implemented a + // as a member column propagated from the parent resource/collection + return Optional.of(functions.col(ExtensionSupport.EXTENSIONS_FIELD_NAME())); + } /** * @return whether the order of the collection returned by this expression has any meaning diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java index 1b561452ae..b28ec7da50 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/MixedCollection.java @@ -1,21 +1,19 @@ package au.csiro.pathling.fhirpath.collection; import static au.csiro.pathling.utilities.Preconditions.checkArgument; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; -import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.FhirPathType; -import au.csiro.pathling.fhirpath.definition.ChoiceElementDefinition; -import au.csiro.pathling.fhirpath.definition.ElementDefinition; +import au.csiro.pathling.fhirpath.definition.ChoiceChildDefinition; import au.csiro.pathling.fhirpath.definition.NodeDefinition; import java.util.Optional; import javax.annotation.Nonnull; import lombok.Getter; import org.apache.spark.sql.Column; +import org.apache.spark.sql.functions; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** - * Represents a choice element, which can be resolved to any one of a number of data types. + * Represents a polymorphic collection, which can be resolved to any one of a number of data types. * * @author John Grimes */ @@ -23,55 +21,47 @@ public class MixedCollection extends Collection { /** - * The collection from which this choice element is derived. + * The definition of this choice element. */ @Nonnull - private final Collection from; + private final ChoiceChildDefinition choiceDefinition; - /** - * The definition of this choice element. - */ @Nonnull - private final ChoiceElementDefinition choiceDefinition; + private final Collection parent; protected MixedCollection(@Nonnull final Column column, @Nonnull final Optional type, @Nonnull final Optional fhirType, @Nonnull final Optional definition, - final boolean singular, @Nonnull final Collection from) { + @Nonnull final Collection parent) { super(column, type, fhirType, definition); - this.from = from; - checkArgument(definition.isPresent() && definition.get() instanceof ChoiceElementDefinition, + checkArgument(definition.isPresent() && definition.get() instanceof ChoiceChildDefinition, "definition must be a ChoiceElementDefinition"); - this.choiceDefinition = (ChoiceElementDefinition) definition.get(); + this.choiceDefinition = (ChoiceChildDefinition) definition.get(); + this.parent = parent; } /** * Returns a new instance with the specified column and definition. * - * @param column The column to use * @param definition The definition to use - * @param singular Whether the collection is singular - * @param from The collection from which this choice element is derived * @return A new instance of {@link MixedCollection} */ @Nonnull - public static MixedCollection build(@Nonnull final Column column, - @Nonnull final Optional definition, final boolean singular, - @Nonnull final Collection from) { - return new MixedCollection(column, Optional.empty(), Optional.empty(), definition, singular, - from); + public static MixedCollection build(@Nonnull final Collection parent, + @Nonnull final ChoiceChildDefinition definition) { + return new MixedCollection(functions.lit(null), Optional.empty(), Optional.empty(), + Optional.of(definition), parent); } + @Nonnull @Override public Optional traverse(@Nonnull final String elementName) { - return Optional.empty(); - } - - @Nonnull - private Optional resolveChoiceDefinition(@Nonnull final String type) { - return choiceDefinition.getChildByType(type); + // FHIRPATH_NOTE: this should technically result in unchecked traversal + throw new UnsupportedOperationException( + "Direct traversal of polymorphic collections is not supported." + + " Please use 'ofType()' to specify the type of element to traverse."); } /** @@ -79,23 +69,13 @@ private Optional resolveChoiceDefinition(@Nonnull final Strin * type. * * @param type The type of element to return - * @param context The {@link EvaluationContext} to use * @return A new collection representing just the elements of this collection with the specified * type */ @Nonnull - public Optional resolveChoice(@Nonnull final String type, - @Nonnull final EvaluationContext context) { - final String elementName = choiceDefinition.getElementName(); - final String columnName = ChoiceElementDefinition.getColumnName(elementName, type); - if (from instanceof ResourceCollection) { - final Optional elementColumn = ((ResourceCollection) from).getElementColumn( - columnName); - final Optional definition = resolveChoiceDefinition(type); - checkUserInput(elementColumn.isPresent() && definition.isPresent(), - "No such child: " + columnName); - return elementColumn.map(column -> Collection.build(column, definition.get())); - } - return from.traverse(columnName); + public Optional resolveChoice(@Nonnull final String type) { + return choiceDefinition.getChildByType(type).map( + parent::traverseElement + ); } } 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 994d6b2f7d..d807ed17bb 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 @@ -17,11 +17,10 @@ package au.csiro.pathling.fhirpath.collection; -import static au.csiro.pathling.utilities.Preconditions.checkPresent; - import au.csiro.pathling.encoders.EncoderBuilder; import au.csiro.pathling.encoders.ExtensionSupport; import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.definition.NodeDefinition; import au.csiro.pathling.fhirpath.definition.ResourceDefinition; import au.csiro.pathling.io.source.DataSource; @@ -39,7 +38,6 @@ import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; -import org.apache.spark.sql.functions; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -152,12 +150,10 @@ public Optional getElementColumn(@Nonnull final String elementName) { } @Nonnull - public Column getExtensionContainerColumn() { - final Optional maybeExtensionColumn = Optional - .ofNullable(elementsToColumns.get(ExtensionSupport.EXTENSIONS_FIELD_NAME())); - return checkPresent(maybeExtensionColumn, - "Extension container column '_extension' not present in the resource." - + " Check if extension support was enabled when data were imported!"); + @Override + protected Column getFid() { + return getElementColumn(ExtensionSupport.FID_FIELD_NAME()).orElseThrow( + () -> new IllegalStateException("Resource does not have an 'id' column")); } /** @@ -167,17 +163,13 @@ public ResourceType getResourceType() { return resourceDefinition.getResourceType(); } + @Nonnull @Override - public Optional traverse(@Nonnull final String elementName) { - // Get the child column from the map of elements to columns. - return getElementColumn(elementName).flatMap(value -> - // Get the child element definition from the resource definition. - resourceDefinition.getChildElement(elementName).map(definition -> { - // final boolean singular = isSingular() && definition.getMaxCardinality().isPresent() - // && definition.getMaxCardinality().get() == 1; - return Collection.build(value, definition); - })); + protected Collection traverseElement(@Nonnull final ElementDefinition childDef) { + // TODO: what does mean if an element is present in the definition but not in + // the schema? + return getElementColumn(childDef.getElementName()).map( + value -> Collection.build(value, childDef)).get(); } - } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java index 459c787448..9942e93d98 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java @@ -44,6 +44,11 @@ public ColumnCtx vectorize(@Nonnull final Function arrayExpressi c -> arrayExpression.apply(functions.when(c.isNotNull(), functions.array(c)))); } + @Nonnull + public ColumnCtx unnest() { + return of(ValueFunctions.unnest(value)); + } + @Nonnull public ColumnCtx traverse(@Nonnull final String fieldName) { return of(ValueFunctions.unnest(value.getField(fieldName))); @@ -67,6 +72,15 @@ public ColumnCtx filter(Function lambda) { ); } + + @Nonnull + public ColumnCtx transform(Function lambda) { + return vectorize( + c -> functions.transform(c, lambda::apply), + c -> functions.when(c.isNotNull(), lambda.apply(c)) + ); + } + @Nonnull public ColumnCtx aggregate(@Nonnull final Object zeroValue, BiFunction aggregator) { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BaseNodeDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BaseNodeDefinition.java new file mode 100644 index 0000000000..7e5f8f36c9 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BaseNodeDefinition.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 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.definition; + +import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import java.util.Optional; +import javax.annotation.Nonnull; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; + +/** + * Base implemention of a NodeDefition based on a BaseRuntimeElementDefinition. + * + * @author John Grimes + */ +abstract public class BaseNodeDefinition> implements + NodeDefinition { + + @Nonnull + protected final ED elementDefinition; + + protected BaseNodeDefinition(@Nonnull final ED elementDefinition) { + this.elementDefinition = elementDefinition; + } + + @Override + @Nonnull + public Optional getChildElement(@Nonnull final String name) { + return Optional.of(elementDefinition) + .filter(BaseRuntimeElementCompositeDefinition.class::isInstance) + .map(BaseRuntimeElementCompositeDefinition.class::cast) + .flatMap(compElementDef -> Optional.ofNullable(compElementDef.getChildByName(name)) + .or(() -> Optional.ofNullable(compElementDef.getChildByName(name + "[x]")))) + .map(ChildDefinition::build); + } + + @Nonnull + public Optional getFhirType() { + return ElementDefinition.getFhirTypeFromElementDefinition(elementDefinition); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BasicElementDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BasicElementDefinition.java deleted file mode 100644 index ec11111ead..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BasicElementDefinition.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2023 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.definition; - -import ca.uhn.fhir.context.BaseRuntimeChildDefinition; -import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; -import ca.uhn.fhir.context.BaseRuntimeElementDefinition; -import java.util.Optional; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; - -/** - * Encapsulates the FHIR definitions for an element. - * - * @author John Grimes - */ -public class BasicElementDefinition implements - ElementDefinition { - - @Nonnull - protected final ChildDefinitionType childDefinition; - - @Nonnull - protected final Optional elementDefinition; - - protected BasicElementDefinition(@Nonnull final ChildDefinitionType childDefinition) { - this.childDefinition = childDefinition; - elementDefinition = getElementDefinition(); - } - - @Nonnull - protected Optional getElementDefinition() { - @Nullable BaseRuntimeElementDefinition child; - try { - child = childDefinition.getChildByName(childDefinition.getElementName()); - } catch (final Throwable e) { - child = null; - } - return Optional.ofNullable(child); - } - - @Override - @Nonnull - public String getElementName() { - return childDefinition.getElementName(); - } - - @Override - @Nonnull - public Optional getChildElement(@Nonnull final String name) { - return elementDefinition - .filter(elementDef -> elementDef instanceof BaseRuntimeElementCompositeDefinition) - .map(compElementDef -> (BaseRuntimeElementCompositeDefinition) compElementDef) - .flatMap(compElementDef -> Optional.ofNullable(compElementDef.getChildByName(name)) - .or(() -> Optional.ofNullable(compElementDef.getChildByName(name + "[x]")))) - .map(ElementDefinition::build); - } - - @Override - public Optional getMaxCardinality() { - return Optional.of(childDefinition.getMax()); - } - - @Override - @Nonnull - public Optional getFhirType() { - return elementDefinition.flatMap(ElementDefinition::getFhirTypeFromElementDefinition); - } - -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChildDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChildDefinition.java new file mode 100644 index 0000000000..5ff723418b --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChildDefinition.java @@ -0,0 +1,51 @@ +package au.csiro.pathling.fhirpath.definition; + +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; +import ca.uhn.fhir.context.RuntimeChildExtension; +import ca.uhn.fhir.context.RuntimeChildResourceDefinition; +import java.util.Optional; +import javax.annotation.Nonnull; + + +/** + * Encapsulates the FHIR definitions for a child. + * + * @author Piotr Szul + */ +public interface ChildDefinition extends NodeDefinition { + + /** + * @param childDefinition A HAPI {@link BaseRuntimeChildDefinition} that describes this element + * @return A shiny new ElementDefinition + */ + @Nonnull + static ChildDefinition build(@Nonnull final BaseRuntimeChildDefinition childDefinition) { + // TODO: check why this is safe to remove + // if (childDefinition instanceof RuntimeChildAny && "valueReference".equals(childDefinition + // .getElementName())) { + // return new ReferenceExtensionDefinition((RuntimeChildAny) childDefinition); + // } else + if (childDefinition instanceof RuntimeChildResourceDefinition) { + return new ReferenceDefinition((RuntimeChildResourceDefinition) childDefinition); + } else if (childDefinition instanceof RuntimeChildChoiceDefinition + && !(childDefinition instanceof RuntimeChildExtension)) { + return new ChoiceChildDefinition((RuntimeChildChoiceDefinition) childDefinition); + } else { + return new ElementChildDefinition(childDefinition); + } + } + + + /** + * @return the name of this child + */ + @Nonnull + String getName(); + + /** + * @return The maximum cardinality for this child + */ + @Nonnull + Optional getMaxCardinality(); +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChoiceElementDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChoiceChildDefinition.java similarity index 78% rename from fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChoiceElementDefinition.java rename to fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChoiceChildDefinition.java index 6bb45cf3aa..5bebc319eb 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChoiceElementDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChoiceChildDefinition.java @@ -7,7 +7,6 @@ import java.util.Optional; import javax.annotation.Nonnull; import org.apache.commons.lang.WordUtils; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; /** * Represents the definition of an element that can be represented by multiple different data @@ -16,7 +15,7 @@ * @author John Grimes * @see Polymorphism in FHIR */ -public class ChoiceElementDefinition implements ElementDefinition { +public class ChoiceChildDefinition implements ChildDefinition { @Nonnull private final RuntimeChildChoiceDefinition childDefinition; @@ -24,7 +23,7 @@ public class ChoiceElementDefinition implements ElementDefinition { @Nonnull private final Map> elementNameToDefinition; - protected ChoiceElementDefinition(@Nonnull final RuntimeChildChoiceDefinition childDefinition) { + protected ChoiceChildDefinition(@Nonnull final RuntimeChildChoiceDefinition childDefinition) { this.childDefinition = childDefinition; elementNameToDefinition = new HashMap<>(); childDefinition.getValidChildNames() @@ -46,26 +45,22 @@ public static String getColumnName(@Nonnull final String elementName, @Nonnull @Override - public String getElementName() { + public String getName() { return childDefinition.getElementName(); } @Nonnull @Override public Optional getChildElement(@Nonnull final String name) { - return Optional.empty(); + return getChildByElementName(name); } + @Nonnull @Override public Optional getMaxCardinality() { return Optional.of(childDefinition.getMax()); } - @Nonnull - @Override - public Optional getFhirType() { - return Optional.empty(); - } /** * Returns the child element definition for the given type, if it exists. @@ -75,7 +70,7 @@ public Optional getFhirType() { */ @Nonnull public Optional getChildByType(@Nonnull final String type) { - final String key = ChoiceElementDefinition.getColumnName(getElementName(), type); + final String key = ChoiceChildDefinition.getColumnName(getName(), type); return getChildByElementName(key); } @@ -86,9 +81,9 @@ public Optional getChildByType(@Nonnull final String type) { * @return the child element definition, if it exists */ @Nonnull - public Optional getChildByElementName(final String name) { + private Optional getChildByElementName(final String name) { return Optional.ofNullable(elementNameToDefinition.get(name)) - .map(def -> new ResolvedChoiceDefinition(def, this)); + .map(def -> new ElementChildDefinition(def, this.childDefinition, name)); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementChildDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementChildDefinition.java new file mode 100644 index 0000000000..6df7d095b8 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementChildDefinition.java @@ -0,0 +1,77 @@ +/* + * Copyright 2023 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.definition; + +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import java.util.Objects; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Encapsulates the FHIR definitions for a child resolved to specific element. + * + * @author John Grimes + */ +public class ElementChildDefinition extends + BaseNodeDefinition> implements + ElementDefinition { + + @Nonnull + protected final BaseRuntimeChildDefinition childDefinition; + + @Nonnull + protected final String elementName; + + protected ElementChildDefinition( + @Nonnull final BaseRuntimeElementDefinition elementDefinition, + @Nonnull final BaseRuntimeChildDefinition childDefinition, + @Nonnull final String elementName) { + super(elementDefinition); + this.childDefinition = childDefinition; + this.elementName = elementName; + } + + protected ElementChildDefinition(@Nonnull final BaseRuntimeChildDefinition childDefinition, + @Nonnull final String elementName) { + this(Objects.requireNonNull(childDefinition.getChildByName(elementName)), childDefinition, + elementName); + } + + protected ElementChildDefinition( + @Nonnull final BaseRuntimeChildDefinition childDefinition) { + this(childDefinition, childDefinition.getElementName()); + } + + @Nonnull + @Override + public String getElementName() { + return elementName; + } + + @Nonnull + @Override + public String getName() { + return childDefinition.getElementName(); + } + + @Override + public Optional getMaxCardinality() { + return Optional.of(childDefinition.getMax()); + } +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java index 4a604f2c8f..9f7f072bf3 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java @@ -1,54 +1,25 @@ package au.csiro.pathling.fhirpath.definition; -import ca.uhn.fhir.context.BaseRuntimeChildDefinition; -import ca.uhn.fhir.context.BaseRuntimeElementDefinition; -import ca.uhn.fhir.context.RuntimeChildAny; -import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; -import ca.uhn.fhir.context.RuntimeChildResourceDefinition; +import ca.uhn.fhir.context.*; + import java.util.Optional; import javax.annotation.Nonnull; -import org.hl7.fhir.instance.model.api.IBase; -import org.hl7.fhir.r4.model.BackboneElement; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; -public interface ElementDefinition extends NodeDefinition { +import ca.uhn.fhir.model.api.annotation.DatatypeDef; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; - /** - * @param childDefinition A HAPI {@link BaseRuntimeChildDefinition} that describes this element - * @return A shiny new ElementDefinition - */ - @Nonnull - static ElementDefinition build(@Nonnull final BaseRuntimeChildDefinition childDefinition) { - // TODO: check why this is safe to remove - // if (childDefinition instanceof RuntimeChildAny && "valueReference".equals(childDefinition - // .getElementName())) { - // return new ReferenceExtensionDefinition((RuntimeChildAny) childDefinition); - // } else - if (childDefinition instanceof RuntimeChildResourceDefinition) { - return new ReferenceDefinition((RuntimeChildResourceDefinition) childDefinition); - } else if (childDefinition instanceof RuntimeChildChoiceDefinition) { - return new ChoiceElementDefinition((RuntimeChildChoiceDefinition) childDefinition); - } else { - return new BasicElementDefinition<>(childDefinition); - } - } +/** + * Encapsulates the FHIR definitions for a child resolved to specific element. + */ +public interface ElementDefinition extends ChildDefinition { @Nonnull static Optional getFhirTypeFromElementDefinition( - @Nonnull final BaseRuntimeElementDefinition elementDefinition) { - return Optional.ofNullable(elementDefinition.newInstance()) - .flatMap(ElementDefinition::getFhirTypeFromObject); - } - - @Nonnull - static Optional getFhirTypeFromObject(@Nonnull final IBase hapiObject) { - // BackboneElements do not seem to correctly report their FHIR type. - if (hapiObject.getClass().getSuperclass() == BackboneElement.class) { - return Optional.of(FHIRDefinedType.BACKBONEELEMENT); - } else { - final FHIRDefinedType fhirType = FHIRDefinedType.fromCode(hapiObject.fhirType()); - return Optional.ofNullable(fhirType); - } + @Nonnull final BaseRuntimeElementDefinition elementDefinition) { + return Optional.ofNullable( + elementDefinition.getImplementingClass().getAnnotation(DatatypeDef.class)) + .map(DatatypeDef::name) + .map(FHIRDefinedType::fromCode); } /** @@ -57,16 +28,10 @@ static Optional getFhirTypeFromObject(@Nonnull final IBase hapi @Nonnull String getElementName(); - /** - * @return The maximum cardinality for this element - */ - Optional getMaxCardinality(); - /** * @return The {@link FHIRDefinedType} that corresponds to the type of this element. Not all * elements have a type, e.g. polymorphic elements. */ @Nonnull Optional getFhirType(); - } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/NodeDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/NodeDefinition.java index 50b56618b4..e6cb098019 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/NodeDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/NodeDefinition.java @@ -4,9 +4,9 @@ import javax.annotation.Nonnull; /** - * + * Base abstractions for FHIR defintions. */ -public interface NodeDefinition { +public interface NodeDefinition { /** * Returns the child element of this definition with the specified name. @@ -15,6 +15,6 @@ public interface NodeDefinition { * @return a new {@link NodeDefinition} describing the child */ @Nonnull - Optional getChildElement(@Nonnull String name); + Optional getChildElement(@Nonnull String name); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ReferenceDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ReferenceDefinition.java index ead29290ec..5119b59559 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ReferenceDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ReferenceDefinition.java @@ -34,10 +34,15 @@ * * @author John Grimes */ -public class ReferenceDefinition extends BasicElementDefinition { +public class ReferenceDefinition extends ElementChildDefinition { + + + private final List> resourceTypes; protected ReferenceDefinition(@Nonnull final RuntimeChildResourceDefinition childDefinition) { super(childDefinition); + resourceTypes = ((RuntimeChildResourceDefinition) childDefinition).getResourceTypes(); + requireNonNull(resourceTypes); } /** @@ -47,9 +52,8 @@ protected ReferenceDefinition(@Nonnull final RuntimeChildResourceDefinition chil */ @Nonnull public Set getReferenceTypes() { - final List> resourceTypes = childDefinition.getResourceTypes(); - requireNonNull(resourceTypes); - + // final List> resourceTypes = ((RuntimeChildResourceDefinition) childDefinition).getResourceTypes(); + // requireNonNull(resourceTypes); return resourceTypes.stream() .map(clazz -> { final String resourceCode; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResolvedChoiceDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResolvedChoiceDefinition.java deleted file mode 100644 index 24472137aa..0000000000 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResolvedChoiceDefinition.java +++ /dev/null @@ -1,43 +0,0 @@ -package au.csiro.pathling.fhirpath.definition; - -import ca.uhn.fhir.context.BaseRuntimeElementDefinition; -import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; -import java.util.Optional; -import javax.annotation.Nonnull; -import lombok.Getter; -import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; - -public class ResolvedChoiceDefinition implements ElementDefinition { - - @Nonnull - private final BaseRuntimeElementDefinition elementDefinition; - - @Nonnull - @Getter - private final Optional maxCardinality; - - public ResolvedChoiceDefinition(@Nonnull final BaseRuntimeElementDefinition definition, - @Nonnull final - ChoiceElementDefinition parent) { - this.elementDefinition = definition; - this.maxCardinality = parent.getMaxCardinality(); - } - - @Nonnull - @Override - public String getElementName() { - return elementDefinition.getName(); - } - - @Nonnull - @Override - public Optional getChildElement(@Nonnull final String name) { - return Optional.empty(); - } - - @Nonnull - @Override - public Optional getFhirType() { - return ElementDefinition.getFhirTypeFromElementDefinition(elementDefinition); - } -} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResourceDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResourceDefinition.java index 0ea563e310..88f12cb68b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResourceDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ResourceDefinition.java @@ -18,7 +18,6 @@ package au.csiro.pathling.fhirpath.definition; import ca.uhn.fhir.context.RuntimeResourceDefinition; -import java.util.Optional; import javax.annotation.Nonnull; import lombok.Getter; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -31,7 +30,7 @@ * @author John Grimes */ @Getter -public class ResourceDefinition implements NodeDefinition { +public class ResourceDefinition extends BaseNodeDefinition { /** * The HAPI FHIR resource type. @@ -39,31 +38,14 @@ public class ResourceDefinition implements NodeDefinition { @Nonnull private final ResourceType resourceType; - @Nonnull - private final RuntimeResourceDefinition definition; - /** * @param resourceType The {@link ResourceType} that describes this resource * @param definition The HAPI {@link RuntimeResourceDefinition} for this resource */ public ResourceDefinition(@Nonnull final ResourceType resourceType, @Nonnull final RuntimeResourceDefinition definition) { + super(definition); this.resourceType = resourceType; - this.definition = definition; - } - - /** - * Returns the child element of this resource with the specified name. - * - * @param name The name of the child element - * @return A new ElementDefinition describing the child - */ - @Nonnull - @Override - public Optional getChildElement(@Nonnull final String name) { - return Optional.ofNullable(definition.getChildByName(name)) - .or(() -> Optional.ofNullable(definition.getChildByName(name + "[x]"))) - .map(ElementDefinition::build); } /** 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 5925299f06..a89b4e311a 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 @@ -17,16 +17,24 @@ package au.csiro.pathling.fhirpath.function; +import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.collection.BooleanCollection; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.collection.IntegerCollection; +import au.csiro.pathling.fhirpath.collection.MixedCollection; +import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.validation.FhirpathFunction; +import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; import javax.annotation.Nonnull; +import static au.csiro.pathling.fhirpath.Comparable.ComparisonOperation.EQUALS; import static au.csiro.pathling.fhirpath.function.CollectionExpression.*; +import static au.csiro.pathling.utilities.Preconditions.checkUserInput; public class StandardFunctions { + public static final String EXTENSION_ELEMENT_NAME = "extension"; + public static final String URL_ELEMENT_NAME = "url"; @Nonnull @FhirpathFunction @@ -65,4 +73,32 @@ public static IntegerCollection count(@Nonnull final Collection input) { return IntegerCollection.build(input.getCtx().count()); } + /** + * A function that returns the extensions of the current element that match a given URL. + * + * @author Piotr Szul + * @see extension + */ + @FhirpathFunction + public static Collection extension(@Nonnull final Collection input, + @Nonnull final StringCollection url) { + return input.traverse(EXTENSION_ELEMENT_NAME).map(extensionCollection -> + where(extensionCollection, c -> c.traverse(URL_ELEMENT_NAME).map( + urlCollection -> urlCollection.getComparison(EQUALS).apply(url)) + .map(BooleanCollection::build).orElse(BooleanCollection.falseCollection())) + ).orElse(Collection.nullCollection()); + } + + @FhirpathFunction + public static Collection ofType(@Nonnull final MixedCollection input, + @Nonnull final Collection typeSpecifier) { + // TODO: implement as annotation or collection type + checkUserInput(typeSpecifier.getType().isPresent() && FhirPathType.TYPE_SPECIFIER.equals( + typeSpecifier.getType().get()), + "Argument to ofType function must be a type specifier"); + // TODO: This should work on any collection type - not just mixed + // if the type of the collection does not match the required type then it should return an empty collection. + return input.resolveChoice(typeSpecifier.getFhirType().get().toCode()) + .orElse(Collection.nullCollection()); + } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java index 6e3badd2f8..16fb0635a8 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java @@ -50,7 +50,7 @@ public StaticFunctionRegistry() { super(new Builder() //.put("count", new CountFunction()) .put("resolve", new ResolveFunction()) - .put("ofType", new OfTypeFunction()) + //.put("ofType", new OfTypeFunction()) .put("reverseResolve", new ReverseResolveFunction()) .put("memberOf", new MemberOfFunction()) //.put("where", new WhereFunction()) @@ -66,7 +66,7 @@ public StaticFunctionRegistry() { .put("anyFalse", new BooleansTestFunction(ANY_FALSE)) .put("allTrue", new BooleansTestFunction(ALL_TRUE)) .put("allFalse", new BooleansTestFunction(ALL_FALSE)) - .put("extension", new ExtensionFunction()) + //.put("extension", new ExtensionFunction()) .put("until", new UntilFunction()) //.put("exists", new ExistsFunction()) .put("display", new DisplayFunction()) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index aab05a5094..dd0cfda033 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -26,6 +26,7 @@ import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.FunctionInput; import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.MixedCollection; import au.csiro.pathling.fhirpath.function.NamedFunction; import au.csiro.pathling.fhirpath.function.registry.FunctionRegistry.NoSuchFunctionException; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; @@ -68,15 +69,16 @@ public FhirPath visitMemberInvocation( // TODO: refactor to an expression return (input, context) -> { - try { + if (!(input instanceof MixedCollection)) { // Attempt path traversal. final Optional result = input.traverse(fhirPath); checkUserInput(result.isPresent(), "No such child: " + fhirPath); return result.get(); - } catch (final InvalidUserInputError e) { + } else { try { - // TODO: what is this about? + // TODO: Should it really be a function or rather an expression? + // In the former case though the better definition of type specifier path would be needed. // If it is not a valid path traversal, see if it is a valid type specifier. final FHIRDefinedType fhirType = FHIRDefinedType.fromCode(fhirPath); return Collection.build(functions.lit(null), Optional.of(FhirPathType.TYPE_SPECIFIER), diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index e4e5f931f0..e6894e7a7d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -72,6 +72,7 @@ public Dataset buildQuery(@Nonnull final FhirView view) { final List select = parseSelect(view.getSelect(), evaluationContext, Collections.emptyList()); + final List where = view.getWhere() == null ? Collections.emptyList() : view.getWhere().stream() diff --git a/fhirpath/src/test/java/au/csiro/pathling/UnitTestDependencies.java b/fhirpath/src/test/java/au/csiro/pathling/UnitTestDependencies.java index de9a7d97e0..6dcd19c1d9 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/UnitTestDependencies.java +++ b/fhirpath/src/test/java/au/csiro/pathling/UnitTestDependencies.java @@ -29,7 +29,6 @@ import au.csiro.pathling.views.SelectClauseTypeAdapterFactory; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import javax.annotation.Nonnull; @@ -109,7 +108,10 @@ public static IParser jsonParser(@Nonnull final FhirContext fhirContext) { @ConditionalOnMissingBean @Nonnull static FhirEncoders fhirEncoders() { - return FhirEncoders.forR4().getOrCreate(); + return FhirEncoders.forR4() + .withExtensionsEnabled(true) + .withStandardOenTypes() + .getOrCreate(); } @Bean diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java index d06f0195b9..2c074e022c 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java @@ -23,7 +23,7 @@ import au.csiro.pathling.fhirpath.Nesting; import au.csiro.pathling.fhirpath.collection.ResourceCollection; -import au.csiro.pathling.fhirpath.definition.BasicElementDefinition; +import au.csiro.pathling.fhirpath.definition.ElementChildDefinition; import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.test.helpers.SparkHelpers.IdAndValueColumns; import javax.annotation.Nonnull; @@ -80,7 +80,7 @@ public ElementPathBuilder(@Nonnull final SparkSession spark) { singular = false; nesting = new Nesting(); fhirType = FHIRDefinedType.NULL; - definition = mock(BasicElementDefinition.class); + definition = mock(ElementChildDefinition.class); } @Nonnull diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java index a33163ce71..2414e67d10 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/FhirHelpers.java @@ -19,6 +19,7 @@ import static java.util.Objects.requireNonNull; +import au.csiro.pathling.fhirpath.definition.ChildDefinition; import au.csiro.pathling.fhirpath.definition.ResourceDefinition; import au.csiro.pathling.fhirpath.definition.ElementDefinition; import ca.uhn.fhir.context.FhirContext; @@ -35,7 +36,7 @@ public class FhirHelpers { @Nonnull - public static Optional getChildOfResource( + public static Optional getChildOfResource( @Nonnull final FhirContext fhirContext, @Nonnull final String resourceCode, @Nonnull final String elementName) { final RuntimeResourceDefinition hapiDefinition = fhirContext diff --git a/sql-on-fhir b/sql-on-fhir index 7d353da07d..b2b814459f 160000 --- a/sql-on-fhir +++ b/sql-on-fhir @@ -1 +1 @@ -Subproject commit 7d353da07dbe42a0fd73f922180faa75756d4eeb +Subproject commit b2b814459fd6c8aa88870cbfe9dc1813f331483f From 76f6bcf98115874e25c161a9a88f3a59ecb1e8a6 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Tue, 26 Sep 2023 12:36:27 +1000 Subject: [PATCH 82/95] Refactoring of FHIRViews execution. Implementation of 'union' and 'forEach' clauses. Separation of 'v1' FHIRView test from our custom enhanced tests. --- .../pathling/fhirpath/parser/ParserTest.java | 4 +- .../fhirpath/collection/Collection.java | 4 +- .../pathling/fhirpath/column/ColumnCtx.java | 174 +++++++-------- .../definition/ElementDefinition.java | 10 +- .../csiro/pathling/views/DatasetContext.java | 59 ++++++ .../pathling/views/ExecutionContext.java | 74 +++++++ .../pathling/views/FhirViewExecutor.java | 181 ++++++++++------ .../pathling/views/ForEachSelection.java | 2 + .../csiro/pathling/views/UnionSelection.java | 2 + .../assertions/BaseFhirPathAssertion.java | 24 ++- ...est.java => AbstractFhirViewTestBase.java} | 52 ++++- .../pathling/views/FhirViewExtraTest.java | 25 +++ .../views/FhirViewTestDataSource.java | 63 ------ .../csiro/pathling/views/FhirViewV1Test.java | 25 +++ .../test/resources/viewTests/unnesting.json | 200 ++++++++++++++++++ sql-on-fhir | 2 +- .../csiro/pathling/utilities/Functions.java | 53 +++++ .../pathling/utilities/FunctionsTest.java | 40 ++++ 18 files changed, 767 insertions(+), 227 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/DatasetContext.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/views/ExecutionContext.java rename fhirpath/src/test/java/au/csiro/pathling/views/{FhirViewTest.java => AbstractFhirViewTestBase.java} (85%) create mode 100644 fhirpath/src/test/java/au/csiro/pathling/views/FhirViewExtraTest.java delete mode 100644 fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTestDataSource.java create mode 100644 fhirpath/src/test/java/au/csiro/pathling/views/FhirViewV1Test.java create mode 100644 fhirpath/src/test/resources/viewTests/unnesting.json create mode 100644 utilities/src/main/java/au/csiro/pathling/utilities/Functions.java create mode 100644 utilities/src/test/java/au/csiro/pathling/utilities/FunctionsTest.java diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java index 0cc3050a2c..857b533b67 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java @@ -656,7 +656,7 @@ void testExtensionsOnResources() { void testExtensionFunction() { // This should be the same as: "extension.where($this.url='http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName').valueString" assertThatResultOf( - "extension('http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName').valueString") + "extension('http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName').value.ofType(string)") .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testExtensionFunction.tsv"); @@ -694,7 +694,7 @@ void testComplexExtensionsOnComplexPath() { assertThatResultOf( "address.where($this.city = 'Boston')" + ".extension('http://hl7.org/fhir/StructureDefinition/geolocation')" - + ".extension('latitude').valueDecimal") + + ".extension('latitude').value.ofType(decimal)") .isElementPath(DecimalCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testComplexExtensionsOnComplexPath.tsv"); 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 95413220ea..14fa355fd4 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 @@ -241,7 +241,7 @@ protected Optional traverseExtensions( return getExtensionMap().map(em -> Collection.build( // We need here to deal with the situation where _fid is an array of element ids - ColumnCtx.of(getFid()).transform(em::apply).unnest().getValue(), + ColumnCtx.of(getFid()).transform(em::apply).flatten().getValue(), (ElementDefinition) extensionDefinition)); } @@ -331,5 +331,5 @@ public Column getSingleton() { public ColumnCtx getCtx() { return ColumnCtx.of(getColumn()); } - + } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java index 9942e93d98..f81eb689ba 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java @@ -18,100 +18,104 @@ package au.csiro.pathling.fhirpath.column; import au.csiro.pathling.encoders.ValueFunctions; + import java.util.function.BiFunction; import java.util.function.Function; import javax.annotation.Nonnull; + import lombok.Value; import org.apache.spark.sql.Column; import org.apache.spark.sql.functions; -import org.apache.spark.sql.types.ArrayType; @Value(staticConstructor = "of") public class ColumnCtx { - Column value; - - @Nonnull - public ColumnCtx vectorize(@Nonnull final Function arrayExpression, - @Nonnull final Function singularExpression) { - return of(ValueFunctions.ifArray(value, arrayExpression::apply, singularExpression::apply)); - } - - @Nonnull - public ColumnCtx vectorize(@Nonnull final Function arrayExpression) { - // the default implementation just wraps the element info array if needed - return vectorize(arrayExpression, - c -> arrayExpression.apply(functions.when(c.isNotNull(), functions.array(c)))); - } - - @Nonnull - public ColumnCtx unnest() { - return of(ValueFunctions.unnest(value)); - } - - @Nonnull - public ColumnCtx traverse(@Nonnull final String fieldName) { - return of(ValueFunctions.unnest(value.getField(fieldName))); - } - - @Nonnull - public ColumnCtx singular() { - return vectorize( - c -> functions.when(functions.size(c).leq(1), c.getItem(0)) - .otherwise(functions.raise_error( - functions.lit("Expected a single value, but found multiple values"))), - Function.identity() - ); - } - - @Nonnull - public ColumnCtx filter(Function lambda) { - return vectorize( - c -> functions.filter(c, lambda::apply), - c -> functions.when(c.isNotNull(), functions.when(lambda.apply(c), c)) - ); - } - - - @Nonnull - public ColumnCtx transform(Function lambda) { - return vectorize( - c -> functions.transform(c, lambda::apply), - c -> functions.when(c.isNotNull(), lambda.apply(c)) - ); - } - - @Nonnull - public ColumnCtx aggregate(@Nonnull final Object zeroValue, - BiFunction aggregator) { - - return vectorize( - c -> functions.when(c.isNull(), zeroValue) - .otherwise(functions.aggregate(c, functions.lit(zeroValue), aggregator::apply)), - c -> functions.when(c.isNull(), zeroValue).otherwise(c) - ); - // this is OK because aggregator(zero, x) == x - } - - - @Nonnull - public ColumnCtx first() { - return vectorize(c -> c.getItem(0), Function.identity()); - } - - @Nonnull - public ColumnCtx count() { - return vectorize( - c -> functions.when(c.isNull(), 0).otherwise(functions.size(c)), - c -> functions.when(c.isNull(), 0).otherwise(1) - ); - } - - @Nonnull - public ColumnCtx empty() { - return vectorize( - c -> functions.when(c.isNotNull(), functions.size(c).equalTo(0)).otherwise(true), - Column::isNull); - } + Column value; + + @Nonnull + public ColumnCtx vectorize(@Nonnull final Function arrayExpression, + @Nonnull final Function singularExpression) { + return of(ValueFunctions.ifArray(value, arrayExpression::apply, singularExpression::apply)); + } + + @Nonnull + public ColumnCtx vectorize(@Nonnull final Function arrayExpression) { + // the default implementation just wraps the element info array if needed + return vectorize(arrayExpression, + c -> arrayExpression.apply(functions.when(c.isNotNull(), functions.array(c)))); + } + + @Nonnull + public ColumnCtx flatten() { + return of(ValueFunctions.unnest(value)); + } + + @Nonnull + public ColumnCtx traverse(@Nonnull final String fieldName) { + return of(ValueFunctions.unnest(value.getField(fieldName))); + } + + @Nonnull + public ColumnCtx singular() { + return vectorize( + c -> functions.when(functions.size(c).leq(1), c.getItem(0)) + .otherwise(functions.raise_error( + functions.lit("Expected a single value, but found multiple values"))), + Function.identity() + ); + } + + @Nonnull + public ColumnCtx filter(final Function lambda) { + return vectorize( + c -> functions.filter(c, lambda::apply), + c -> functions.when(c.isNotNull(), functions.when(lambda.apply(c), c)) + ); + } + + + @Nonnull + public ColumnCtx transform(final Function lambda) { + return vectorize( + c -> functions.transform(c, lambda::apply), + c -> functions.when(c.isNotNull(), lambda.apply(c)) + ); + } + + @Nonnull + public ColumnCtx aggregate(@Nonnull final Object zeroValue, + final BiFunction aggregator) { + + return vectorize( + c -> functions.when(c.isNull(), zeroValue) + .otherwise(functions.aggregate(c, functions.lit(zeroValue), aggregator::apply)), + c -> functions.when(c.isNull(), zeroValue).otherwise(c) + ); + // this is OK because aggregator(zero, x) == x + } + + + @Nonnull + public ColumnCtx first() { + return vectorize(c -> c.getItem(0), Function.identity()); + } + + @Nonnull + public ColumnCtx count() { + return vectorize( + c -> functions.when(c.isNull(), 0).otherwise(functions.size(c)), + c -> functions.when(c.isNull(), 0).otherwise(1) + ); + } + @Nonnull + public ColumnCtx empty() { + return vectorize( + c -> functions.when(c.isNotNull(), functions.size(c).equalTo(0)).otherwise(true), + Column::isNull); + } + + public ColumnCtx explode() { + return vectorize(functions::explode, Function.identity()); + } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java index 9f7f072bf3..60994edef3 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementDefinition.java @@ -5,6 +5,7 @@ import java.util.Optional; import javax.annotation.Nonnull; +import ca.uhn.fhir.model.api.annotation.Block; import ca.uhn.fhir.model.api.annotation.DatatypeDef; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; @@ -16,10 +17,13 @@ public interface ElementDefinition extends ChildDefinition { @Nonnull static Optional getFhirTypeFromElementDefinition( @Nonnull final BaseRuntimeElementDefinition elementDefinition) { - return Optional.ofNullable( - elementDefinition.getImplementingClass().getAnnotation(DatatypeDef.class)) + final Class elementClass = elementDefinition.getImplementingClass(); + return Optional.ofNullable(elementClass.getAnnotation(DatatypeDef.class)) .map(DatatypeDef::name) - .map(FHIRDefinedType::fromCode); + .map(FHIRDefinedType::fromCode) + // special case for backbone elements + .or(() -> Optional.ofNullable(elementClass.getAnnotation(Block.class)) + .map(__ -> FHIRDefinedType.BACKBONEELEMENT)); } /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/DatasetContext.java b/fhirpath/src/main/java/au/csiro/pathling/views/DatasetContext.java new file mode 100644 index 0000000000..23506a5e5c --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/DatasetContext.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 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.views; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; + +import javax.annotation.Nonnull; + +import static au.csiro.pathling.utilities.Strings.randomAlias; + +/** + * Wrapper that allows to apply side effect operations to the dataset. This is needed to support the + * forEach clause, and more specifically to materialize exploded columns. + * TODO: consider refactoring this somehow futher loclalize the side-effect + * possibly using something similar to the functional IO monad (i.e. accumulate + * the necessary transformations of the dataset rather then applying it directly) + */ +@Getter +@AllArgsConstructor +public class DatasetContext { + + @Nonnull + Dataset dataset; + + /** + * Takes a given column and return an alias to the materialized version of it, that is an alias to + * evaluated projection of the column. This is needed for some types of expressions, e.g.: + * `explode` (so most likely all generators) in order to be able to use the column in further + * expressions, including being able to traverse to its fields. + * + * @param column the column to materialize + * @return the materialized column + */ + @Nonnull + public Column materialize(@Nonnull final Column column) { + final String materializedColumnName = randomAlias(); + dataset = dataset.withColumn(materializedColumnName, column); + return dataset.col(materializedColumnName); + } +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ExecutionContext.java b/fhirpath/src/main/java/au/csiro/pathling/views/ExecutionContext.java new file mode 100644 index 0000000000..2051492504 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ExecutionContext.java @@ -0,0 +1,74 @@ +/* + * Copyright 2023 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.views; + +import org.apache.spark.sql.Column; +import javax.annotation.Nonnull; +import java.util.Optional; + +/** + * The execution context for a FHIR view, which encapsulates the minimalistic set of operations + * required to evaluate a view. + */ +public interface ExecutionContext { + + /** + * Creates a new execution context that is a sub-context of this one, with the given path. + * + * @param path the path to the sub-context + * @param unnest whether to unnest the sub-context + * @return the new sub-context + */ + @Nonnull + ExecutionContext subContext(@Nonnull String path, boolean unnest); + + /** + * Evaluates the given FHIRPath path and returns the result as a column. + * + * @param path the path to evaluate + * @return the result as a column + */ + @Nonnull + Column evalExpression(@Nonnull final String path); + + /** + * Evaluates the given FHIRPath path and returns the result as a column with the given alias. + * + * @param path the path to evaluate + * @param alias the alias to use for the column + * @return the result as a column + */ + @Nonnull + default Column evalExpression(@Nonnull final String path, @Nonnull final String alias) { + return evalExpression(path).as(alias); + } + + /** + * Evaluates the given FHIRPath path and returns the result as a column with the given alias. + * + * @param path the path to evaluate + * @param maybeAlias the alias to use for the column + * @return the result as a column + */ + @Nonnull + default Column evalExpression(@Nonnull final String path, @Nonnull final + Optional maybeAlias) { + return maybeAlias.map(alias -> evalExpression(path, alias)) + .orElse(evalExpression(path)); + } +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index e6894e7a7d..cc24769b2b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -1,8 +1,10 @@ package au.csiro.pathling.views; +import static au.csiro.pathling.utilities.Functions.backCurried; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; +import au.csiro.pathling.encoders.ValueFunctions; import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.annotations.NotImplemented; import au.csiro.pathling.fhirpath.collection.Collection; @@ -13,15 +15,18 @@ import au.csiro.pathling.io.source.DataSource; import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; import javax.annotation.Nonnull; +import lombok.Value; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; +import org.apache.spark.sql.functions; import org.hl7.fhir.r4.model.Enumerations.ResourceType; /** @@ -44,6 +49,50 @@ public class FhirViewExecutor { private final Optional terminologyServiceFactory; + @Value + private static class ExecutionContextImpl implements ExecutionContext { + + EvaluationContext evaluationContext; + DatasetContext datasetContext; + + @Nonnull + @Override + public Column evalExpression(@Nonnull final String path) { + return internalEvalExpression(path).getColumn(); + } + + @Nonnull + @Override + public ExecutionContextImpl subContext(@Nonnull final String expression, final boolean unnest) { + final Collection newInputContext = internalEvalExpression(expression); + return new ExecutionContextImpl( + evaluationContext.withInputContext(unnest + ? unnest(newInputContext) + : newInputContext), + datasetContext); + } + + + @Nonnull + private Collection internalEvalExpression(@Nonnull final String expression) { + final String updatedExpression = evaluationContext.getConstantReplacer() + .map(replacer -> replacer.execute(expression)) + .orElse(expression); + return new Parser().evaluate(updatedExpression, evaluationContext); + } + + @Nonnull + private Collection unnest(@Nonnull final Collection collection) { + return collection.copyWith( + datasetContext.materialize(collection.getCtx().explode().getValue())); + } + + @Nonnull + Dataset getDataset() { + return datasetContext.getDataset(); + } + } + public FhirViewExecutor(@Nonnull final FhirContext fhirContext, @Nonnull final SparkSession sparkSession, @Nonnull final DataSource dataset, @Nonnull final Optional terminologyServiceFactory) { @@ -55,98 +104,104 @@ public FhirViewExecutor(@Nonnull final FhirContext fhirContext, @Nonnull public Dataset buildQuery(@Nonnull final FhirView view) { - // Get the constants from the query, and build a replacer. - final Optional constantReplacer = Optional.ofNullable(view.getConstants()) - .map(c -> c.stream() - .collect(toMap(ConstantDeclaration::getName, ConstantDeclaration::getValue))) - .map(ConstantReplacer::new); - // Build a new expression parser, and parse all the column expressions within the query. - final ResourceType resourceType = ResourceType.fromCode(view.getResource()); - final Dataset dataset = dataSource.read(resourceType); - final ResourceCollection inputContext = ResourceCollection.build(fhirContext, dataset, - resourceType, false); - final EvaluationContext evaluationContext = new EvaluationContext(inputContext, inputContext, - fhirContext, sparkSession, dataset, StaticFunctionRegistry.getInstance(), - terminologyServiceFactory, constantReplacer); + final ExecutionContextImpl executionContext = newExecutionContext(view); + final List select = parseSelect(view.getSelect(), executionContext); - final List select = parseSelect(view.getSelect(), evaluationContext, - Collections.emptyList()); - final List where = view.getWhere() == null ? Collections.emptyList() : view.getWhere().stream() .map(WhereClause::getExpression) .collect(toList()); final Optional filterCondition = where.stream() - .map(e -> evalExpression(e, evaluationContext)) - .map(Collection::getColumn) + .map(executionContext::evalExpression) .reduce(Column::and); + // TODO: Make the context immutable + // NOTE: The executionContext is mutable with regards to the dataset + // Due to the necessity or column "materializations" needed for unnesting. + final Dataset dataset = executionContext.getDataset(); return filterCondition .map(dataset::filter) .orElse(dataset) .select(select.toArray(new Column[0])); } - @Nonnull - private List parseSelect(@Nonnull final List selectGroup, - @Nonnull final EvaluationContext context, @Nonnull final List currentSelection) { - final List newSelection = new ArrayList<>(currentSelection); - for (final SelectClause select : selectGroup) { - if (select instanceof DirectSelection) { - newSelection.addAll(directSelection(context, (DirectSelection) select, currentSelection)); + private ExecutionContextImpl newExecutionContext(@Nonnull final FhirView view) { + final Optional constantReplacer = Optional.ofNullable(view.getConstants()) + .map(c -> c.stream() + .collect(toMap(ConstantDeclaration::getName, ConstantDeclaration::getValue))) + .map(ConstantReplacer::new); + + // Build a new expression parser, and parse all the column expressions within the query. + final ResourceType resourceType = ResourceType.fromCode(view.getResource()); + final Dataset dataset = dataSource.read(resourceType); + final ResourceCollection inputContext = ResourceCollection.build(fhirContext, dataset, + resourceType, false); + final EvaluationContext evaluationContext = new EvaluationContext(inputContext, inputContext, + fhirContext, sparkSession, dataset, StaticFunctionRegistry.getInstance(), + terminologyServiceFactory, constantReplacer); + return new ExecutionContextImpl(evaluationContext, + new DatasetContext(dataset)); + } - } else if (select instanceof FromSelection) { - newSelection.addAll( - nestedSelection(context, (FromSelection) select, currentSelection, false)); + @Nonnull + private List parseSelect(@Nonnull final List selectGroup, + @Nonnull final ExecutionContext context) { - } else if (select instanceof ForEachSelection) { - newSelection.addAll( - nestedSelection(context, (ForEachSelection) select, currentSelection, true)); + return selectGroup.stream() + .flatMap(select -> evalSelect(select, context).stream()) + .collect(Collectors.toUnmodifiableList()); + } - } else { - throw new IllegalStateException("Unknown select clause type: " + select.getClass()); - } + @Nonnull + private List evalSelect(@Nonnull final SelectClause select, @Nonnull final ExecutionContext context) { + // TODO: move to the classes ??? + if (select instanceof DirectSelection) { + return directSelection(context, (DirectSelection) select); + } else if (select instanceof FromSelection) { + return nestedSelection(context, (FromSelection) select, false); + } else if (select instanceof ForEachSelection) { + return nestedSelection(context, (ForEachSelection) select, true); + } else if (select instanceof UnionSelection) { + return unionSelection(context, (UnionSelection) select); + } else { + throw new IllegalStateException("Unknown select clause type: " + select.getClass()); } + } - return newSelection; + @Nonnull + private List unionSelection(@Nonnull final ExecutionContext context, + @Nonnull final UnionSelection select) { + final List unionColumns = nestedSelection(context, select, false); + // need to combine all these columns here + return List.of(functions.flatten(functions.array( + unionColumns.stream() + .map(c -> ValueFunctions.ifArray(c, Function.identity()::apply, + functions::array)) + .toArray(Column[]::new) + ))); } @Nonnull - private List directSelection(@Nonnull final EvaluationContext context, - @Nonnull final DirectSelection select, @Nonnull final List currentSelection) { - final Collection path = evalExpression(select.getPath(), context); - final List newColumns = new ArrayList<>(currentSelection); - final Column column = path.getColumn(); - final Optional alias = Optional.ofNullable(select.getAlias()); - newColumns.add(alias.map(column::alias).orElse(column)); - return newColumns; + private List directSelection(@Nonnull final ExecutionContext context, + @Nonnull final DirectSelection select) { + return List.of( + context.evalExpression(select.getPath(), Optional.ofNullable(select.getAlias()))); } @Nonnull @NotImplemented - private List nestedSelection(final @Nonnull EvaluationContext context, - @Nonnull final NestedSelectClause select, @Nonnull final List currentSelection, + private List nestedSelection(final @Nonnull ExecutionContext context, + @Nonnull final NestedSelectClause select, final boolean unnest) { - final Collection from = evalExpression(select.getPath(), context); - final Collection nextInputContext = unnest - // TODO: FIX FIRST - ? from - // .unnest() - : from; - final EvaluationContext nextContext = context.withInputContext(nextInputContext); - return parseSelect(select.getSelect(), nextContext, currentSelection); - } - @Nonnull - private Collection evalExpression(@Nonnull final String expression, - @Nonnull final EvaluationContext context) { - final String updatedExpression = context.getConstantReplacer() - .map(replacer -> replacer.execute(expression)) - .orElse(expression); - return new Parser().evaluate(updatedExpression, context); - } + final ExecutionContext nextContext = Optional.ofNullable(select.getPath()) + .map(backCurried(context::subContext).apply(unnest)) + .orElse(context); + + return parseSelect(select.getSelect(), nextContext); + } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java index 446e0d31b3..9ced9da80b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelection.java @@ -3,6 +3,7 @@ import java.util.List; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import com.google.gson.annotations.SerializedName; import lombok.Data; import lombok.EqualsAndHashCode; @@ -24,6 +25,7 @@ public class ForEachSelection extends NestedSelectClause { * href="https://build.fhir.org/ig/FHIR/sql-on-fhir-v2/StructureDefinition-ViewDefinition-definitions.html#diff_ViewDefinition.select.forEach">ViewDefinition.select.forEach */ @NotNull + @SerializedName("forEach") String path; /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/UnionSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/UnionSelection.java index f1e4c72322..82342e6c78 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/UnionSelection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/UnionSelection.java @@ -3,6 +3,7 @@ import java.util.List; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import com.google.gson.annotations.SerializedName; import lombok.Data; import lombok.EqualsAndHashCode; @@ -40,6 +41,7 @@ public class UnionSelection extends NestedSelectClause { */ @NotNull @Size(min = 1) + @SerializedName("union") List select; } diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java index e3e52d713c..1170b76995 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/BaseFhirPathAssertion.java @@ -22,7 +22,12 @@ import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.collection.ResourceCollection; import org.apache.spark.sql.Column; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.apache.spark.sql.catalyst.expressions.Literal; +import org.apache.spark.sql.functions; +import org.apache.spark.sql.types.ArrayType; +import org.apache.spark.sql.types.StructType; import javax.annotation.Nonnull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -54,12 +59,21 @@ public abstract class BaseFhirPathAssertion> @Nonnull public DatasetAssert selectResult() { final Column[] selection = new Column[]{ - evaluationContext.getResource().traverse("id").get().getColumn(), - result.getColumn() + evaluationContext.getResource().traverse("id").get().getColumn().alias("id"), + result.getColumn().alias("value") }; - // TODO: Update this to make sure that it is ordered - // and exploded is needed - return DatasetAssert.of(evaluationContext.getDataset().select(selection)); + // and exploded the result if needed to compare with CSV + final Dataset resultDataset = evaluationContext.getDataset() + .select(selection); + final StructType schema = resultDataset.schema(); + final Dataset explodedDataset = schema.fields()[schema.fieldIndex( + "value")].dataType() instanceof ArrayType + ? resultDataset.select(resultDataset.col("id"), + functions.explode(resultDataset.col("value")) + .alias("value")) + : resultDataset; + + return DatasetAssert.of(explodedDataset); } @Nonnull diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/AbstractFhirViewTestBase.java similarity index 85% rename from fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java rename to fhirpath/src/test/java/au/csiro/pathling/views/AbstractFhirViewTestBase.java index 88e51c6bbd..4f5f2631aa 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/AbstractFhirViewTestBase.java @@ -22,12 +22,17 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.stream.Stream; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import lombok.Value; +import lombok.extern.slf4j.Slf4j; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoders; @@ -49,7 +54,7 @@ @SpringBootUnitTest @TestInstance(Lifecycle.PER_CLASS) -class FhirViewTest { +abstract class AbstractFhirViewTestBase { static Path tempDir; @@ -68,8 +73,16 @@ class FhirViewTest { @MockBean TerminologyServiceFactory terminologyServiceFactory; + + private final String testLocationGlob; + + protected AbstractFhirViewTestBase(final String testLocationGlob) { + this.testLocationGlob = testLocationGlob; + } + @BeforeAll static void beforeAll() throws IOException { + System.out.println("Creating temp directory"); tempDir = Files.createTempDirectory("pathling-fhir-view-test"); } @@ -77,7 +90,7 @@ static void beforeAll() throws IOException { Stream requests() throws IOException { final ObjectMapper mapper = new ObjectMapper(); final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - final Resource[] resources = resolver.getResources("classpath:tests/sql-on-fhir/v1/*.json"); + final Resource[] resources = resolver.getResources(testLocationGlob); return Stream.of(resources) // Get each test file. .map(resource -> { @@ -109,7 +122,7 @@ DataSource getDataSource(@Nonnull final JsonNode testDefinition) { // Create a parent directory based upon the test name. final JsonNode resources = testDefinition.get("resources"); final Path directory = getTempDir(testDefinition); - final FhirViewTestDataSource result = new FhirViewTestDataSource(); + final TestDataSource result = new TestDataSource(); for (final Iterator it = resources.elements(); it.hasNext(); ) { final JsonNode resource = it.next(); @@ -226,4 +239,37 @@ public String toString() { } } + /** + * A class for making FHIR data available for the view tests. + * + * @author John Grimes + */ + @Slf4j + public static class TestDataSource implements DataSource { + + private static final Map> resourceTypeToDataset = new HashMap<>(); + + public void put(@Nonnull final ResourceType resourceType, @Nonnull final Dataset dataset) { + resourceTypeToDataset.put(resourceType, dataset); + } + + @Nonnull + @Override + public Dataset read(@Nullable final ResourceType resourceType) { + return resourceTypeToDataset.get(resourceType); + } + + @Nonnull + @Override + public Dataset read(@Nullable final String resourceCode) { + return resourceTypeToDataset.get(ResourceType.fromCode(resourceCode)); + } + + @Nonnull + @Override + public Set getResourceTypes() { + return resourceTypeToDataset.keySet(); + } + + } } diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewExtraTest.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewExtraTest.java new file mode 100644 index 0000000000..c970862a49 --- /dev/null +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewExtraTest.java @@ -0,0 +1,25 @@ +/* + * Copyright 2023 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.views; + +public class FhirViewExtraTest extends AbstractFhirViewTestBase { + + public FhirViewExtraTest() { + super("classpath:viewTests/*.json"); + } +} diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTestDataSource.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTestDataSource.java deleted file mode 100644 index 93e936e408..0000000000 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewTestDataSource.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2023 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.views; - -import au.csiro.pathling.io.source.DataSource; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import lombok.extern.slf4j.Slf4j; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.hl7.fhir.r4.model.Enumerations.ResourceType; - -/** - * A class for making FHIR data available for the view tests. - * - * @author John Grimes - */ -@Slf4j -public class FhirViewTestDataSource implements DataSource { - - private static final Map> resourceTypeToDataset = new HashMap<>(); - - public void put(@Nonnull final ResourceType resourceType, @Nonnull final Dataset dataset) { - resourceTypeToDataset.put(resourceType, dataset); - } - - @Nonnull - @Override - public Dataset read(@Nullable final ResourceType resourceType) { - return resourceTypeToDataset.get(resourceType); - } - - @Nonnull - @Override - public Dataset read(@Nullable final String resourceCode) { - return resourceTypeToDataset.get(ResourceType.fromCode(resourceCode)); - } - - @Nonnull - @Override - public Set getResourceTypes() { - return resourceTypeToDataset.keySet(); - } - -} diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewV1Test.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewV1Test.java new file mode 100644 index 0000000000..38f2f716e1 --- /dev/null +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewV1Test.java @@ -0,0 +1,25 @@ +/* + * Copyright 2023 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.views; + +public class FhirViewV1Test extends AbstractFhirViewTestBase { + + public FhirViewV1Test() { + super("classpath:tests/sql-on-fhir/v1/*.json"); + } +} diff --git a/fhirpath/src/test/resources/viewTests/unnesting.json b/fhirpath/src/test/resources/viewTests/unnesting.json new file mode 100644 index 0000000000..10aff51b22 --- /dev/null +++ b/fhirpath/src/test/resources/viewTests/unnesting.json @@ -0,0 +1,200 @@ +{ + "title": "Unnesting tests", + "authors": [ + "niquola" + ], + "generated": true, + "resources": [ + { + "resourceType": "Patient", + "id": "pt1", + "name": [ + { + "use": "official", + "family": "f1.1", + "given": [ + "g1.1" + ] + }, + { + "family": "f1.2", + "given": [ + "g1.2", + "g1.3" + ] + } + ], + "gender": "male", + "birthDate": "1950-01-01", + "address": [ + { + "city": "c1" + } + ] + }, + { + "resourceType": "Patient", + "id": "pt2", + "name": [ + { + "family": "f2.1", + "given": [ + "g2.1" + ] + }, + { + "use": "official", + "family": "f2.2", + "given": [ + "g2.2", + "g2.3" + ] + } + ], + "gender": "female", + "birthDate": "1950-01-01" + } + ], + "tests": [ + { + "title": "basic unnesting", + "view": { + "resource": "Patient", + "select": [ + { + "alias": "id", + "path": "id" + }, + { + "forEach": "name", + "select": [ + { + "alias": "family", + "path": "family" + }, + { + "alias": "given", + "path": "given" + } + ] + } + ] + }, + "expect": [ + { + "id": "pt1", + "family": "f1.1", + "given": ["g1.1"] + }, + { + "id": "pt1", + "family": "f1.2", + "given": ["g1.2", "g1.3"] + }, + { + "id": "pt2", + "family": "f2.1", + "given": ["g2.1"] + }, + { + "id": "pt2", + "family": "f2.2", + "given": ["g2.2", "g2.3"] + } + ] + }, + { + "title": "two levels of unnesting with $this", + "view": { + "resource": "Patient", + "select": [ + { + "alias": "id", + "path": "id" + }, + { + "forEach": "name", + "select": [ + { + "alias": "family", + "path": "family" + }, + { + "forEach": "given", + "select": [ + { + "alias": "given", + "path": "$this" + } + ] + } + ] + } + ] + }, + "expect": [ + { + "id": "pt1", + "family": "f1.1", + "given": "g1.1" + }, + { + "id": "pt1", + "family": "f1.2", + "given": "g1.2" + }, + { + "id": "pt1", + "family": "f1.2", + "given": "g1.3" + }, + { + "id": "pt2", + "family": "f2.1", + "given": "g2.1" + }, + { + "id": "pt2", + "family": "f2.2", + "given": "g2.2" + }, + { + "id": "pt2", + "family": "f2.2", + "given": "g2.3" + } + ] + }, + { + "title": "unnesting of singular element", + "view": { + "resource": "Patient", + "select": [ + { + "alias": "id", + "path": "id" + }, + { + "forEach": "gender", + "select": [ + { + "alias": "gender", + "path": "$this" + } + ] + } + ] + }, + "expect": [ + { + "id": "pt1", + "gender": "male" + }, + { + "id": "pt2", + "gender": "female" + } + ] + } + ] +} diff --git a/sql-on-fhir b/sql-on-fhir index b2b814459f..da3352132c 160000 --- a/sql-on-fhir +++ b/sql-on-fhir @@ -1 +1 @@ -Subproject commit b2b814459fd6c8aa88870cbfe9dc1813f331483f +Subproject commit da3352132cb248032f341e9e578a026ad8343c96 diff --git a/utilities/src/main/java/au/csiro/pathling/utilities/Functions.java b/utilities/src/main/java/au/csiro/pathling/utilities/Functions.java new file mode 100644 index 0000000000..c19429b453 --- /dev/null +++ b/utilities/src/main/java/au/csiro/pathling/utilities/Functions.java @@ -0,0 +1,53 @@ +/* + * Copyright 2023 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.utilities; + +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Utility class containing some functional programming helper functions. + */ +public interface Functions { + + /** + * Converts a function that takes two arguments to its curried version. + * + * @param f the function to be curried + * @param the first argument type + * @param the second argument type + * @param the return type + * @return the curried function + */ + static Function> curried(final BiFunction f) { + return a1 -> a2 -> f.apply(a1, a2); + } + + /** + * Converts a function that takes two arguments to its curried version with reversed arguments. + * + * @param f the function to be curried + * @param the first argument type + * @param the second argument type + * @param the return type + * @return the curried function + */ + static Function> backCurried(final BiFunction f) { + return a2 -> a1 -> f.apply(a1, a2); + } +} diff --git a/utilities/src/test/java/au/csiro/pathling/utilities/FunctionsTest.java b/utilities/src/test/java/au/csiro/pathling/utilities/FunctionsTest.java new file mode 100644 index 0000000000..23279f233b --- /dev/null +++ b/utilities/src/test/java/au/csiro/pathling/utilities/FunctionsTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2023 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.utilities; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class FunctionsTest { + + public static String biFunction(final boolean a, final int b) { + return a + "_" + b; + } + + @Test + void curried() { + assertEquals("true_7", Functions.curried(FunctionsTest::biFunction).apply(true).apply(7)); + } + + @Test + void backCurried() { + assertEquals("false_13", + Functions.backCurried(FunctionsTest::biFunction).apply(13).apply(false)); + } +} From 4e0dd319732f8e7c9c10819dd752ca9a882b27f2 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Tue, 26 Sep 2023 14:41:45 +1000 Subject: [PATCH 83/95] Added (temporarily) new 'v1' tests to extended tests. Implemented 'getResourceKey()' function. --- .../collection/ResourceCollection.java | 9 ++ .../fhirpath/collection/StringCollection.java | 10 ++ .../fhirpath/function/StandardFunctions.java | 17 ++- .../test/resources/viewTests/fn_exists.json | 100 +++++++++++++ .../test/resources/viewTests/fn_first.json | 48 +++++++ .../src/test/resources/viewTests/fn_join.json | 50 +++++++ .../test/resources/viewTests/fn_rkeys.json | 81 +++++++++++ .../src/test/resources/viewTests/where.json | 132 ++++++++++++++++++ 8 files changed, 441 insertions(+), 6 deletions(-) create mode 100644 fhirpath/src/test/resources/viewTests/fn_exists.json create mode 100644 fhirpath/src/test/resources/viewTests/fn_first.json create mode 100644 fhirpath/src/test/resources/viewTests/fn_join.json create mode 100644 fhirpath/src/test/resources/viewTests/fn_rkeys.json create mode 100644 fhirpath/src/test/resources/viewTests/where.json 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 d807ed17bb..227fc92a2b 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 @@ -20,6 +20,7 @@ import au.csiro.pathling.encoders.EncoderBuilder; import au.csiro.pathling.encoders.ExtensionSupport; import au.csiro.pathling.fhirpath.FhirPathType; +import au.csiro.pathling.fhirpath.column.ColumnCtx; import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.definition.NodeDefinition; import au.csiro.pathling.fhirpath.definition.ResourceDefinition; @@ -172,4 +173,12 @@ protected Collection traverseElement(@Nonnull final ElementDefinition childDef) return getElementColumn(childDef.getElementName()).map( value -> Collection.build(value, childDef)).get(); } + + @Nonnull + public ColumnCtx getKeyColumn() { + return getElementColumn("id_versioned") + .map(ColumnCtx::of) + .orElseThrow( + () -> new IllegalStateException("Resource does not have an 'id_versioned' column")); + } } 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 81895ffea8..57c5e4e427 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 @@ -66,6 +66,16 @@ public static StringCollection build(@Nonnull final Column column, Optional.of(FHIRDefinedType.STRING), definition); } + /** + * Returns a new instance with the specified column. + */ + @Nonnull + public static StringCollection build(@Nonnull final Column column) { + return new StringCollection(column, Optional.of(FhirPathType.STRING), + Optional.of(FHIRDefinedType.STRING), Optional.empty()); + } + + /** * Returns a new instance, parsed from a FHIRPath literal. * 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 a89b4e311a..8acc59ce20 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 @@ -17,20 +17,20 @@ package au.csiro.pathling.fhirpath.function; +import static au.csiro.pathling.fhirpath.Comparable.ComparisonOperation.EQUALS; +import static au.csiro.pathling.utilities.Preconditions.checkUserInput; + import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.collection.BooleanCollection; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.collection.IntegerCollection; import au.csiro.pathling.fhirpath.collection.MixedCollection; +import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.validation.FhirpathFunction; -import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; import javax.annotation.Nonnull; -import static au.csiro.pathling.fhirpath.Comparable.ComparisonOperation.EQUALS; -import static au.csiro.pathling.fhirpath.function.CollectionExpression.*; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; - +@SuppressWarnings("unused") public class StandardFunctions { public static final String EXTENSION_ELEMENT_NAME = "extension"; @@ -39,7 +39,7 @@ public class StandardFunctions { @Nonnull @FhirpathFunction public static Collection where(@Nonnull final Collection input, - @Nonnull CollectionExpression expression) { + @Nonnull final CollectionExpression expression) { return input.copyWith( input.getCtx().filter(expression.requireBoolean().toColumnFunction(input))); } @@ -101,4 +101,9 @@ public static Collection ofType(@Nonnull final MixedCollection input, return input.resolveChoice(typeSpecifier.getFhirType().get().toCode()) .orElse(Collection.nullCollection()); } + + @FhirpathFunction + public static StringCollection getResourceKey(@Nonnull final ResourceCollection input) { + return StringCollection.build(input.getKeyColumn().getValue()); + } } diff --git a/fhirpath/src/test/resources/viewTests/fn_exists.json b/fhirpath/src/test/resources/viewTests/fn_exists.json new file mode 100644 index 0000000000..d9422fc842 --- /dev/null +++ b/fhirpath/src/test/resources/viewTests/fn_exists.json @@ -0,0 +1,100 @@ +{ + "title": "exists function", + "resources": [ + { + "resourceType": "Patient", + "id": "p1", + "name": [ + { + "use": "official", + "family": "f1" + } + ] + }, + { + "resourceType": "Patient", + "id": "p2" + }, + { + "resourceType": "Patient", + "id": "p3", + "name": [ + { + "use": "nickname", + "given": ["g3"] + } + ] + } + ], + "tests": [ + { + "title": "exists in field path", + "view": { + "resource": "Patient", + "select": [ + { "path": "id" }, + { "path": "name.exists()", "alias": "has_name" } + ] + }, + "expect": [ + { "id": "p1", "has_name": true }, + { "id": "p2", "has_name": false }, + { "id": "p3", "has_name": true } + ] + }, + { + "title": "nested exists in field path", + "view": { + "resource": "Patient", + "select": [ + { "path": "id" }, + { "path": "name.given.exists()", "alias": "has_name" } + ] + }, + "expect": [ + { "id": "p1", "has_name": false }, + { "id": "p2", "has_name": false }, + { "id": "p3", "has_name": true } + ] + }, + { + "title": "exists in where path", + "view": { + "resource": "Patient", + "select": [{ "path": "id" }], + "where": [ + { + "path": "name.exists()" + } + ] + }, + "expect": [{ "id": "p1" }, { "id": "p3" }] + }, + { + "title": "nested exists in where path", + "view": { + "resource": "Patient", + "select": [{ "path": "id" }], + "where": [ + { + "path": "name.given.exists()" + } + ] + }, + "expect": [{ "id": "p3" }] + }, + { + "title": "exists in forEach path", + "view": { + "resource": "Patient", + "select": [ + { + "forEach": "where(name.exists())", + "select": [{ "path": "id" }] + } + ] + }, + "expect": [{ "id": "p1" }, { "id": "p3" }] + } + ] +} diff --git a/fhirpath/src/test/resources/viewTests/fn_first.json b/fhirpath/src/test/resources/viewTests/fn_first.json new file mode 100644 index 0000000000..bbefd9a00b --- /dev/null +++ b/fhirpath/src/test/resources/viewTests/fn_first.json @@ -0,0 +1,48 @@ +{ + "title": "first function", + "resources": [ + { + "resourceType": "Patient", + "name": [ + { + "use": "official", + "family": "f1", + "given": ["g1.1", "g1.2"] + }, + { + "use": "usual", + "given": ["g2.1"] + }, + { + "use": "maiden", + "family": "f3", + "given": ["g3.1", "g3.2"], + "period": { "end": "2002" } + } + ] + } + ], + "tests": [ + { + "title": "table level first()", + "view": { + "resource": "Patient", + "select": [{ "path": "name.first().use" }] + }, + "expect": [{ "use": "official" }] + }, + { + "title": "table and field level first()", + "view": { + "resource": "Patient", + "select": [ + { + "path": "name.first().given.first()", + "alias": "given" + } + ] + }, + "expect": [{ "given": "g1.1" }] + } + ] +} diff --git a/fhirpath/src/test/resources/viewTests/fn_join.json b/fhirpath/src/test/resources/viewTests/fn_join.json new file mode 100644 index 0000000000..a50f6168a9 --- /dev/null +++ b/fhirpath/src/test/resources/viewTests/fn_join.json @@ -0,0 +1,50 @@ +{ + "title": "join function", + "resources": [ + { + "resourceType": "Patient", + "id": "p1", + "name": [ + { + "use": "official", + "given": ["p1.g1", "p1.g2"] + } + ] + } + ], + "tests": [ + { + "title": "join with comma", + "view": { + "resource": "Patient", + "select": [ + { "path": "id" }, + { "path": "name.given.join(',')", "alias": "given" } + ] + }, + "expect": [{ "id": "p1", "given": "p1.g1,p1.g2" }] + }, + { + "title": "join with empty value", + "view": { + "resource": "Patient", + "select": [ + { "path": "id" }, + { "path": "name.given.join('')", "alias": "given" } + ] + }, + "expect": [{ "id": "p1", "given": "p1.g1p1.g2" }] + }, + { + "title": "join with no value - default to no separator", + "view": { + "resource": "Patient", + "select": [ + { "path": "id" }, + { "path": "name.given.join()", "alias": "given" } + ] + }, + "expect": [{ "id": "p1", "given": "p1.g1p1.g2" }] + } + ] +} diff --git a/fhirpath/src/test/resources/viewTests/fn_rkeys.json b/fhirpath/src/test/resources/viewTests/fn_rkeys.json new file mode 100644 index 0000000000..905b79a124 --- /dev/null +++ b/fhirpath/src/test/resources/viewTests/fn_rkeys.json @@ -0,0 +1,81 @@ +{ + "title": "resourceKey and referenceKey functions", + "resources": [ + { + "resourceType": "Observation", + "id": "o1", + "basedOn": [ + { + "reference": "ServiceRequest/123" + }, + { + "reference": "https://example.org/fhir/ServiceRequest/456" + }, + { + "reference": "CarePlan/123" + } + ] + } + ], + "tests": [ + { + "title": "get resource key", + "view": { + "resource": "Observation", + "select": [ + { + "path": "getResourceKey()", + "alias": "id" + } + ] + }, + "expect": [ + { + "id": "Observation/o1" + } + ] + }, + { + "title": "get reference key", + "view": { + "resource": "Observation", + "forEach": "basedOn", + "select": [ + { + "alias": "rid", + "path": "getReferenceKey()" + } + ] + }, + "expect": [ + { + "rid": "ServiceRequest/123" + }, + { + "rid": "https://example.org/fhir/ServiceRequest/456" + }, + { + "rid": "CarePlan/123" + } + ] + }, + { + "title": "get reference key with resource param", + "view": { + "resource": "Observation", + "forEach": "basedOn.where(getReferenceKey('CarePlan').exists())", + "select": [ + { + "alias": "rid", + "path": "getReferenceKey()" + } + ] + }, + "expect": [ + { + "rid": "CarePlan/123" + } + ] + } + ] +} diff --git a/fhirpath/src/test/resources/viewTests/where.json b/fhirpath/src/test/resources/viewTests/where.json new file mode 100644 index 0000000000..5853054ef3 --- /dev/null +++ b/fhirpath/src/test/resources/viewTests/where.json @@ -0,0 +1,132 @@ +{ + "title": "where function and element", + "resources": [ + { + "resourceType": "Patient", + "id": "p1", + "name": [ + { + "use": "official", + "family": "f1" + } + ] + }, + { + "resourceType": "Patient", + "id": "p2", + "name": [ + { + "use": "nickname", + "family": "f2" + } + ] + }, + { + "resourceType": "Patient", + "id": "p3", + "name": [ + { + "use": "nickname", + "given": ["g3"], + "family": "f3" + } + ] + }, + { + "resourceType": "Observation", + "id": "o1", + "valueInteger": 12 + }, + { + "resourceType": "Observation", + "id": "o2", + "valueInteger": 10 + } + ], + "tests": [ + { + "title": "simple where path with result", + "view": { + "resource": "Patient", + "select": [{ "path": "id" }], + "where": [{ "path": "name.use = 'official'" }] + }, + "expect": [{ "id": "p1" }] + }, + { + "title": "simple where path with no results", + "view": { + "resource": "Patient", + "select": [{ "path": "id" }], + "where": [{ "path": "name.use = 'maiden'" }] + }, + "expect": [] + }, + { + "title": "simple where path with greater than inequality", + "view": { + "resource": "Observation", + "select": [{ "path": "id" }], + "where": [{ "path": "valueInteger > 11" }] + }, + "expect": [{ "id": "o1" }] + }, + { + "title": "simple where path with less than inequality", + "view": { + "resource": "Observation", + "select": [{ "path": "id" }], + "where": [{ "path": "valueInteger < 11" }] + }, + "expect": [{ "id": "o2" }] + }, + { + "title": "multiple where paths", + "view": { + "resource": "Patient", + "select": [{ "path": "id" }], + "where": [ + { "path": "name.use = 'official'" }, + { "path": "name.family = 'f1'" } + ] + }, + "expect": [{ "id": "p1" }] + }, + { + "title": "where path with an 'and' connector", + "view": { + "resource": "Patient", + "select": [{ "path": "id" }], + "where": [{ "path": "name.use = 'official' and name.family = 'f1'" }] + }, + "expect": [{ "id": "p1" }] + }, + { + "title": "where path with an 'or' connector", + "view": { + "resource": "Patient", + "select": [{ "path": "id" }], + "where": [{ "path": "name.use = 'official' or name.family = 'f2'" }] + }, + "expect": [{ "id": "p1" }, { "id": "p2" }] + }, + { + "title": "where path with a where function (and)", + "view": { + "resource": "Patient", + "select": [{ "path": "id" }], + "where": [{ "path": "name.where(use = 'official' and family = 'f1')" }] + }, + "expect": [{ "id": "p1" }] + }, + { + "title": "where path with a where function (or)", + "view": { + "resource": "Patient", + "select": [{ "path": "id" }], + "where": [{ "path": "name.where(use = 'official' or family = 'f2')" }] + }, + "expect": [{ "id": "p1" }, { "id": "p2" }] + } + ] +} From c1771b8d21507a2d68ead24f879f665df84d6863 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Tue, 26 Sep 2023 15:33:11 +1000 Subject: [PATCH 84/95] Implementing optional arguments to FHIR functions. Implementing `join()` function. --- .../fhirpath/collection/StringCollection.java | 26 +++ .../pathling/fhirpath/column/ColumnCtx.java | 180 +++++++++--------- .../function/CollectionExpression.java | 2 +- .../fhirpath/function/StandardFunctions.java | 12 +- .../fhirpath/function/WrappedFunction.java | 43 +++-- .../views/AbstractFhirViewTestBase.java | 1 - 6 files changed, 161 insertions(+), 103 deletions(-) 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 57c5e4e427..2afb059cb2 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 @@ -23,11 +23,15 @@ import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.Materializable; +import au.csiro.pathling.fhirpath.column.ColumnCtx; import au.csiro.pathling.fhirpath.definition.NodeDefinition; +import java.util.Objects; import java.util.Optional; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; import org.apache.spark.sql.Row; +import org.apache.spark.sql.catalyst.expressions.Literal; +import org.apache.spark.unsafe.types.UTF8String; import org.hl7.fhir.r4.model.Base64BinaryType; import org.hl7.fhir.r4.model.CodeType; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; @@ -75,6 +79,16 @@ public static StringCollection build(@Nonnull final Column column) { Optional.of(FHIRDefinedType.STRING), Optional.empty()); } + /** + * Returns a new instance with the specified column context. + * + * @param column The column context to use + * @return A new instance of {@link StringCollection} + */ + @Nonnull + public static StringCollection build(@Nonnull final ColumnCtx column) { + return build(column.getValue()); + } /** * Returns a new instance, parsed from a FHIRPath literal. @@ -97,6 +111,18 @@ public static String parseStringLiteral(final @Nonnull String fhirPath) { return value; } + @Nonnull + public String toLiteralValue() { + return Optional.of(getColumn().expr()) + .filter(Literal.class::isInstance) + .map(Literal.class::cast) + .map(Literal::value) + .filter(UTF8String.class::isInstance) + .map(Objects::toString) + .orElseThrow(() -> new IllegalStateException( + "Cannot convert column to literal value: " + getColumn())); + } + /** * Gets a value from a row for a String or String literal. * diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java index f81eb689ba..03f63e21ba 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java @@ -30,92 +30,96 @@ @Value(staticConstructor = "of") public class ColumnCtx { - Column value; - - @Nonnull - public ColumnCtx vectorize(@Nonnull final Function arrayExpression, - @Nonnull final Function singularExpression) { - return of(ValueFunctions.ifArray(value, arrayExpression::apply, singularExpression::apply)); - } - - @Nonnull - public ColumnCtx vectorize(@Nonnull final Function arrayExpression) { - // the default implementation just wraps the element info array if needed - return vectorize(arrayExpression, - c -> arrayExpression.apply(functions.when(c.isNotNull(), functions.array(c)))); - } - - @Nonnull - public ColumnCtx flatten() { - return of(ValueFunctions.unnest(value)); - } - - @Nonnull - public ColumnCtx traverse(@Nonnull final String fieldName) { - return of(ValueFunctions.unnest(value.getField(fieldName))); - } - - @Nonnull - public ColumnCtx singular() { - return vectorize( - c -> functions.when(functions.size(c).leq(1), c.getItem(0)) - .otherwise(functions.raise_error( - functions.lit("Expected a single value, but found multiple values"))), - Function.identity() - ); - } - - @Nonnull - public ColumnCtx filter(final Function lambda) { - return vectorize( - c -> functions.filter(c, lambda::apply), - c -> functions.when(c.isNotNull(), functions.when(lambda.apply(c), c)) - ); - } - - - @Nonnull - public ColumnCtx transform(final Function lambda) { - return vectorize( - c -> functions.transform(c, lambda::apply), - c -> functions.when(c.isNotNull(), lambda.apply(c)) - ); - } - - @Nonnull - public ColumnCtx aggregate(@Nonnull final Object zeroValue, - final BiFunction aggregator) { - - return vectorize( - c -> functions.when(c.isNull(), zeroValue) - .otherwise(functions.aggregate(c, functions.lit(zeroValue), aggregator::apply)), - c -> functions.when(c.isNull(), zeroValue).otherwise(c) - ); - // this is OK because aggregator(zero, x) == x - } - - - @Nonnull - public ColumnCtx first() { - return vectorize(c -> c.getItem(0), Function.identity()); - } - - @Nonnull - public ColumnCtx count() { - return vectorize( - c -> functions.when(c.isNull(), 0).otherwise(functions.size(c)), - c -> functions.when(c.isNull(), 0).otherwise(1) - ); - } - - @Nonnull - public ColumnCtx empty() { - return vectorize( - c -> functions.when(c.isNotNull(), functions.size(c).equalTo(0)).otherwise(true), - Column::isNull); - } - - public ColumnCtx explode() { - return vectorize(functions::explode, Function.identity()); - } + Column value; + + @Nonnull + public ColumnCtx vectorize(@Nonnull final Function arrayExpression, + @Nonnull final Function singularExpression) { + return of(ValueFunctions.ifArray(value, arrayExpression::apply, singularExpression::apply)); + } + + @Nonnull + public ColumnCtx vectorize(@Nonnull final Function arrayExpression) { + // the default implementation just wraps the element info array if needed + return vectorize(arrayExpression, + c -> arrayExpression.apply(functions.when(c.isNotNull(), functions.array(c)))); + } + + @Nonnull + public ColumnCtx flatten() { + return of(ValueFunctions.unnest(value)); + } + + @Nonnull + public ColumnCtx traverse(@Nonnull final String fieldName) { + return of(ValueFunctions.unnest(value.getField(fieldName))); + } + + @Nonnull + public ColumnCtx singular() { + return vectorize( + c -> functions.when(functions.size(c).leq(1), c.getItem(0)) + .otherwise(functions.raise_error( + functions.lit("Expected a single value, but found multiple values"))), + Function.identity() + ); + } + + @Nonnull + public ColumnCtx filter(final Function lambda) { + return vectorize( + c -> functions.filter(c, lambda::apply), + c -> functions.when(c.isNotNull(), functions.when(lambda.apply(c), c)) + ); + } + + + @Nonnull + public ColumnCtx transform(final Function lambda) { + return vectorize( + c -> functions.transform(c, lambda::apply), + c -> functions.when(c.isNotNull(), lambda.apply(c)) + ); + } + + @Nonnull + public ColumnCtx aggregate(@Nonnull final Object zeroValue, + final BiFunction aggregator) { + + return vectorize( + c -> functions.when(c.isNull(), zeroValue) + .otherwise(functions.aggregate(c, functions.lit(zeroValue), aggregator::apply)), + c -> functions.when(c.isNull(), zeroValue).otherwise(c) + ); + // this is OK because aggregator(zero, x) == x + } + + + @Nonnull + public ColumnCtx first() { + return vectorize(c -> c.getItem(0), Function.identity()); + } + + @Nonnull + public ColumnCtx count() { + return vectorize( + c -> functions.when(c.isNull(), 0).otherwise(functions.size(c)), + c -> functions.when(c.isNull(), 0).otherwise(1) + ); + } + + @Nonnull + public ColumnCtx empty() { + return vectorize( + c -> functions.when(c.isNotNull(), functions.size(c).equalTo(0)).otherwise(true), + Column::isNull); + } + + public ColumnCtx explode() { + return vectorize(functions::explode, Function.identity()); + } + + public ColumnCtx join(@Nonnull final String separator) { + return vectorize(c -> functions.array_join(c, separator), Function.identity()); + } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CollectionExpression.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CollectionExpression.java index bb308d90bb..89919f741c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CollectionExpression.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/CollectionExpression.java @@ -25,7 +25,7 @@ import java.util.function.Function; @FunctionalInterface -interface CollectionExpression extends Function { +public interface CollectionExpression extends Function { default CollectionExpression requireBoolean() { return input -> { 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 8acc59ce20..64e5b89d14 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 @@ -19,6 +19,7 @@ import static au.csiro.pathling.fhirpath.Comparable.ComparisonOperation.EQUALS; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; +import static java.util.Objects.nonNull; import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.collection.BooleanCollection; @@ -29,12 +30,14 @@ import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.validation.FhirpathFunction; import javax.annotation.Nonnull; +import javax.annotation.Nullable; @SuppressWarnings("unused") public class StandardFunctions { public static final String EXTENSION_ELEMENT_NAME = "extension"; public static final String URL_ELEMENT_NAME = "url"; + public static final String JOIN_DEFAULT_SEPARATOR = ""; @Nonnull @FhirpathFunction @@ -101,9 +104,16 @@ public static Collection ofType(@Nonnull final MixedCollection input, return input.resolveChoice(typeSpecifier.getFhirType().get().toCode()) .orElse(Collection.nullCollection()); } - @FhirpathFunction public static StringCollection getResourceKey(@Nonnull final ResourceCollection input) { return StringCollection.build(input.getKeyColumn().getValue()); } + + @FhirpathFunction + public static StringCollection join(@Nonnull final StringCollection input, @Nullable final StringCollection separator) { + return StringCollection.build(input.getCtx().join( + nonNull(separator) ? separator.toLiteralValue() : JOIN_DEFAULT_SEPARATOR + )); + } + } 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 8154da53d7..1b3b99ae62 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 @@ -17,13 +17,13 @@ package au.csiro.pathling.fhirpath.function; +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.collection.Collection; import au.csiro.pathling.fhirpath.validation.FhirpathFunction; -import lombok.Value; -import javax.annotation.Nonnull; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; @@ -33,6 +33,9 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.Value; @Value public class WrappedFunction implements NamedFunction { @@ -53,16 +56,29 @@ private static class ParamResolver { EvaluationContext evaluationContext; Collection input; + @Nullable public Object resolveArgument(@Nonnull final Parameter parameter, - FhirPath argument) { - if (Collection.class.isAssignableFrom(parameter.getType())) { + final FhirPath argument) { + + if (isNull(argument)) { + // check the pararmeter is happy with a null value + if (parameter.getAnnotation(Nullable.class) != null) { + return null; + } else { + throw new RuntimeException( + "Parameter " + parameter + " is not nullable and no argument was provided"); + } + // return Optional.ofNullable(parameter.getAnnotation(Nullable.class)) + // .map(__ -> null).orElseThrow(() -> new RuntimeException( + // "Parameter " + parameter + " is not nullable and no argument was provided")); + } else if (Collection.class.isAssignableFrom(parameter.getType())) { // evaluate collection types return argument.apply(input, evaluationContext); } else if (CollectionExpression.class.isAssignableFrom(parameter.getType())) { // bind with context - return (CollectionExpression)(c -> argument.apply(c, evaluationContext)); + return (CollectionExpression) (c -> argument.apply(c, evaluationContext)); } else { - throw new RuntimeException("Cannot resolve parameter:" + parameter); + throw new RuntimeException("Cannot resolve parameter:" + parameter); } } } @@ -75,17 +91,20 @@ public Collection invoke(@Nonnull final FunctionInput functionInput) { final ParamResolver resolver = new ParamResolver(functionInput.getContext(), functionInput.getInput()); + final List> actualArguments = functionInput.getArguments(); + + // TODO: make it nicer final Stream resolvedArguments = IntStream.range(0, method.getParameterCount() - 1) .mapToObj(i -> resolver.resolveArgument(method.getParameters()[i + 1], - functionInput.getArguments().get(i))); + (i < actualArguments.size()) + ? actualArguments.get(i) + : null)); // eval arguments final Object[] invocationArgs = Stream.concat(Stream.of(functionInput.getInput()), resolvedArguments).toArray(Object[]::new); try { return (Collection) method.invoke(null, invocationArgs); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { + } catch (final IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } @@ -95,14 +114,14 @@ public static WrappedFunction of(@Nonnull final Method method) { } @Nonnull - public static List of(@Nonnull final Class clazz) { + public static List> of(@Nonnull final Class clazz) { return Stream.of(clazz.getDeclaredMethods()) .filter(m -> m.getAnnotation(FhirpathFunction.class) != null) .map(WrappedFunction::of).collect(Collectors.toUnmodifiableList()); } @Nonnull - public static Map mapOf(@Nonnull final Class clazz) { + public static Map> mapOf(@Nonnull final Class clazz) { return of(clazz).stream().collect(Collectors.toUnmodifiableMap(NamedFunction::getName, Function.identity())); } diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/AbstractFhirViewTestBase.java b/fhirpath/src/test/java/au/csiro/pathling/views/AbstractFhirViewTestBase.java index 4f5f2631aa..4d48889f47 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/AbstractFhirViewTestBase.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/AbstractFhirViewTestBase.java @@ -270,6 +270,5 @@ public Dataset read(@Nullable final String resourceCode) { public Set getResourceTypes() { return resourceTypeToDataset.keySet(); } - } } From eb1a4f8df7899490664100497602d6b2989d73b6 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Tue, 26 Sep 2023 15:57:45 +1000 Subject: [PATCH 85/95] Implemented 'not()', 'empty()' and 'exists()' for non-resource paths. --- .../pathling/fhirpath/column/ColumnCtx.java | 7 +++++- .../fhirpath/function/StandardFunctions.java | 23 ++++++++++++++++--- .../registry/StaticFunctionRegistry.java | 16 ++----------- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java index 03f63e21ba..236c5331e8 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java @@ -118,8 +118,13 @@ public ColumnCtx empty() { public ColumnCtx explode() { return vectorize(functions::explode, Function.identity()); } - + public ColumnCtx join(@Nonnull final String separator) { return vectorize(c -> functions.array_join(c, separator), Function.identity()); } + + public ColumnCtx not() { + return transform(functions::not); + } + } 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 64e5b89d14..7023886d01 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 @@ -104,16 +104,33 @@ public static Collection ofType(@Nonnull final MixedCollection input, return input.resolveChoice(typeSpecifier.getFhirType().get().toCode()) .orElse(Collection.nullCollection()); } + @FhirpathFunction public static StringCollection getResourceKey(@Nonnull final ResourceCollection input) { return StringCollection.build(input.getKeyColumn().getValue()); } - + @FhirpathFunction - public static StringCollection join(@Nonnull final StringCollection input, @Nullable final StringCollection separator) { + public static StringCollection join(@Nonnull final StringCollection input, + @Nullable final StringCollection separator) { return StringCollection.build(input.getCtx().join( - nonNull(separator) ? separator.toLiteralValue() : JOIN_DEFAULT_SEPARATOR + nonNull(separator) + ? separator.toLiteralValue() + : JOIN_DEFAULT_SEPARATOR )); } + @FhirpathFunction + public static BooleanCollection exists(@Nonnull final Collection input, + @Nullable final CollectionExpression criteria) { + return not(empty(nonNull(criteria) + ? where(input, criteria) + : input)); + + } + + @FhirpathFunction + public static BooleanCollection not(@Nonnull final BooleanCollection input) { + return BooleanCollection.build(input.getCtx().not()); + } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java index 16fb0635a8..1b8379ebca 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/registry/StaticFunctionRegistry.java @@ -6,25 +6,14 @@ import static au.csiro.pathling.fhirpath.function.BooleansTestFunction.BooleansTestType.ANY_TRUE; import au.csiro.pathling.fhirpath.function.BooleansTestFunction; -import au.csiro.pathling.fhirpath.function.ColumnFunction0; -import au.csiro.pathling.fhirpath.function.ColumnFunctions; -import au.csiro.pathling.fhirpath.function.CountFunction; -import au.csiro.pathling.fhirpath.function.EmptyFunction; -import au.csiro.pathling.fhirpath.function.ExistsFunction; -import au.csiro.pathling.fhirpath.function.ExtensionFunction; -import au.csiro.pathling.fhirpath.function.NamedFunction; -import au.csiro.pathling.fhirpath.function.FirstFunction; import au.csiro.pathling.fhirpath.function.GetIdFunction; import au.csiro.pathling.fhirpath.function.IifFunction; -import au.csiro.pathling.fhirpath.function.NotFunction; -import au.csiro.pathling.fhirpath.function.OfTypeFunction; +import au.csiro.pathling.fhirpath.function.NamedFunction; import au.csiro.pathling.fhirpath.function.ResolveFunction; import au.csiro.pathling.fhirpath.function.ReverseResolveFunction; import au.csiro.pathling.fhirpath.function.StandardFunctions; -import au.csiro.pathling.fhirpath.function.SumFunction; import au.csiro.pathling.fhirpath.function.ToStringFunction; import au.csiro.pathling.fhirpath.function.UntilFunction; -import au.csiro.pathling.fhirpath.function.WhereFunction; import au.csiro.pathling.fhirpath.function.WrappedFunction; import au.csiro.pathling.fhirpath.function.terminology.DesignationFunction; import au.csiro.pathling.fhirpath.function.terminology.DisplayFunction; @@ -32,7 +21,6 @@ import au.csiro.pathling.fhirpath.function.terminology.PropertyFunction; import au.csiro.pathling.fhirpath.function.terminology.SubsumesFunction; import au.csiro.pathling.fhirpath.function.terminology.TranslateFunction; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; /** @@ -74,7 +62,7 @@ public StaticFunctionRegistry() { .put("designation", new DesignationFunction()) .put("toString", new ToStringFunction()) .put("getId", new GetIdFunction()) - .putAll(ColumnFunction0.mapOf(ColumnFunctions.class)) + //.putAll(ColumnFunction0.mapOf(ColumnFunctions.class)) .putAll(WrappedFunction.mapOf(StandardFunctions.class)) .build()); } From c7ee10b48aedf9ad8f835698104e4363207dc5f9 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Wed, 27 Sep 2023 10:35:40 +1000 Subject: [PATCH 86/95] Adding support for empty results in FhirView test cases and correcting deserialization of WhereClause. --- .../au/csiro/pathling/views/WhereClause.java | 3 +-- .../views/AbstractFhirViewTestBase.java | 24 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/WhereClause.java b/fhirpath/src/main/java/au/csiro/pathling/views/WhereClause.java index 0f2d1acf4c..5f6b0ccae8 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/WhereClause.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/WhereClause.java @@ -19,13 +19,12 @@ public class WhereClause { * The FHIRPath expression for the filter. */ @NotNull - @SerializedName("expr") + @SerializedName("path") String expression; /** * An optional human-readable description of the filter. */ - @SerializedName("desc") @Nullable String description; diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/AbstractFhirViewTestBase.java b/fhirpath/src/test/java/au/csiro/pathling/views/AbstractFhirViewTestBase.java index 4d48889f47..dd76a9090f 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/AbstractFhirViewTestBase.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/AbstractFhirViewTestBase.java @@ -4,6 +4,7 @@ import static au.csiro.pathling.UnitTestDependencies.jsonParser; import static au.csiro.pathling.test.assertions.Assertions.assertThat; import static au.csiro.pathling.validation.ValidationUtils.ensureValid; +import static java.util.Objects.nonNull; import au.csiro.pathling.encoders.FhirEncoders; import au.csiro.pathling.io.source.DataSource; @@ -22,6 +23,7 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -79,7 +81,7 @@ abstract class AbstractFhirViewTestBase { protected AbstractFhirViewTestBase(final String testLocationGlob) { this.testLocationGlob = testLocationGlob; } - + @BeforeAll static void beforeAll() throws IOException { System.out.println("Creating temp directory"); @@ -172,6 +174,9 @@ List toTestParameters(@Nonnull final JsonNode testDefinition, view.get("title").asText().replaceAll("\\W+", "_")); final Path expectedPath = directory.resolve(expectedFileName); List expectedColumns = null; + + // Always create the file first to account for empty 'expect' element. + Files.createFile(expectedPath); for (final Iterator rowIt = view.get("expect").elements(); rowIt.hasNext(); ) { final JsonNode row = rowIt.next(); @@ -184,13 +189,16 @@ List toTestParameters(@Nonnull final JsonNode testDefinition, // Append the row to the file. Files.write(expectedPath, (row + "\n").getBytes(StandardCharsets.UTF_8), - StandardOpenOption.CREATE, StandardOpenOption.APPEND); + StandardOpenOption.APPEND); } final String testName = testDefinition.get("title").asText() + " - " + view.get("title").asText(); result.add( - new TestParameters(testName, sourceData, fhirView, expectedPath, expectedColumns)); + new TestParameters(testName, sourceData, fhirView, expectedPath, + nonNull(expectedColumns) + ? expectedColumns + : Collections.emptyList())); testNumber++; } @@ -246,25 +254,25 @@ public String toString() { */ @Slf4j public static class TestDataSource implements DataSource { - + private static final Map> resourceTypeToDataset = new HashMap<>(); - + public void put(@Nonnull final ResourceType resourceType, @Nonnull final Dataset dataset) { resourceTypeToDataset.put(resourceType, dataset); } - + @Nonnull @Override public Dataset read(@Nullable final ResourceType resourceType) { return resourceTypeToDataset.get(resourceType); } - + @Nonnull @Override public Dataset read(@Nullable final String resourceCode) { return resourceTypeToDataset.get(ResourceType.fromCode(resourceCode)); } - + @Nonnull @Override public Set getResourceTypes() { From e104a0833d1527994352210d6b05164f564478b5 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Wed, 27 Sep 2023 11:42:30 +1000 Subject: [PATCH 87/95] Added direct traversal to nested choice element by name (e.g. `valueInteger`) --- .../definition/BaseNodeDefinition.java | 34 +++++++++++++++++-- .../fhirpath/definition/ChildDefinition.java | 9 +++-- .../definition/ElementChildDefinition.java | 1 + .../src/test/resources/viewTests/where.json | 6 ++-- .../csiro/pathling/utilities/Functions.java | 19 ++++++++++- 5 files changed, 61 insertions(+), 8 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BaseNodeDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BaseNodeDefinition.java index 7e5f8f36c9..bea4f50548 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BaseNodeDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/BaseNodeDefinition.java @@ -17,12 +17,20 @@ package au.csiro.pathling.fhirpath.definition; +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.Nonnull; +import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; +import org.apache.commons.lang3.tuple.Pair; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; +import static au.csiro.pathling.utilities.Functions.maybeCast; + /** * Base implemention of a NodeDefition based on a BaseRuntimeElementDefinition. * @@ -34,16 +42,38 @@ abstract public class BaseNodeDefinition nestedChildElementsByName; + protected BaseNodeDefinition(@Nonnull final ED elementDefinition) { this.elementDefinition = elementDefinition; + + // we need to map all choices to be available as children here + //noinspection unchecked + final Stream allChildren = Optional.of(elementDefinition) + .flatMap(maybeCast(BaseRuntimeElementCompositeDefinition.class)).stream() + .flatMap(compElementDef -> compElementDef.getChildren().stream()); + + nestedChildElementsByName = allChildren + .filter(ChildDefinition::isChildChoiceDefinition) + .map(RuntimeChildChoiceDefinition.class::cast) + .flatMap( + choiceDef -> choiceDef.getValidChildNames().stream().map(n -> Pair.of(n, choiceDef))) + .collect(Collectors.toUnmodifiableMap(Pair::getLeft, Pair::getRight)); } @Override @Nonnull public Optional getChildElement(@Nonnull final String name) { + + // TODO: make it nicer + final RuntimeChildChoiceDefinition choiceChild = nestedChildElementsByName.get(name); + if (choiceChild != null) { + return Optional.of(new ElementChildDefinition(choiceChild, name)); + } + return Optional.of(elementDefinition) - .filter(BaseRuntimeElementCompositeDefinition.class::isInstance) - .map(BaseRuntimeElementCompositeDefinition.class::cast) + .flatMap(maybeCast(BaseRuntimeElementCompositeDefinition.class)) .flatMap(compElementDef -> Optional.ofNullable(compElementDef.getChildByName(name)) .or(() -> Optional.ofNullable(compElementDef.getChildByName(name + "[x]")))) .map(ChildDefinition::build); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChildDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChildDefinition.java index 5ff723418b..65932e57fe 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChildDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ChildDefinition.java @@ -28,8 +28,7 @@ static ChildDefinition build(@Nonnull final BaseRuntimeChildDefinition childDefi // } else if (childDefinition instanceof RuntimeChildResourceDefinition) { return new ReferenceDefinition((RuntimeChildResourceDefinition) childDefinition); - } else if (childDefinition instanceof RuntimeChildChoiceDefinition - && !(childDefinition instanceof RuntimeChildExtension)) { + } else if (isChildChoiceDefinition(childDefinition)) { return new ChoiceChildDefinition((RuntimeChildChoiceDefinition) childDefinition); } else { return new ElementChildDefinition(childDefinition); @@ -37,6 +36,12 @@ static ChildDefinition build(@Nonnull final BaseRuntimeChildDefinition childDefi } + static boolean isChildChoiceDefinition( + @Nonnull final BaseRuntimeChildDefinition childDefinition) { + return childDefinition instanceof RuntimeChildChoiceDefinition + && !(childDefinition instanceof RuntimeChildExtension); + } + /** * @return the name of this child */ diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementChildDefinition.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementChildDefinition.java index 6df7d095b8..bd062d797f 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementChildDefinition.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/definition/ElementChildDefinition.java @@ -70,6 +70,7 @@ public String getName() { return childDefinition.getElementName(); } + @Nonnull @Override public Optional getMaxCardinality() { return Optional.of(childDefinition.getMax()); diff --git a/fhirpath/src/test/resources/viewTests/where.json b/fhirpath/src/test/resources/viewTests/where.json index 5853054ef3..830f3c6a27 100644 --- a/fhirpath/src/test/resources/viewTests/where.json +++ b/fhirpath/src/test/resources/viewTests/where.json @@ -67,7 +67,7 @@ "view": { "resource": "Observation", "select": [{ "path": "id" }], - "where": [{ "path": "valueInteger > 11" }] + "where": [{ "path": "value.ofType(integer) > 11" }] }, "expect": [{ "id": "o1" }] }, @@ -115,7 +115,7 @@ "view": { "resource": "Patient", "select": [{ "path": "id" }], - "where": [{ "path": "name.where(use = 'official' and family = 'f1')" }] + "where": [{ "path": "name.exists(use = 'official' and family = 'f1')" }] }, "expect": [{ "id": "p1" }] }, @@ -124,7 +124,7 @@ "view": { "resource": "Patient", "select": [{ "path": "id" }], - "where": [{ "path": "name.where(use = 'official' or family = 'f2')" }] + "where": [{ "path": "name.exists(use = 'official' or family = 'f2')" }] }, "expect": [{ "id": "p1" }, { "id": "p2" }] } diff --git a/utilities/src/main/java/au/csiro/pathling/utilities/Functions.java b/utilities/src/main/java/au/csiro/pathling/utilities/Functions.java index c19429b453..18b5c46fce 100644 --- a/utilities/src/main/java/au/csiro/pathling/utilities/Functions.java +++ b/utilities/src/main/java/au/csiro/pathling/utilities/Functions.java @@ -17,6 +17,7 @@ package au.csiro.pathling.utilities; +import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; @@ -24,7 +25,7 @@ * Utility class containing some functional programming helper functions. */ public interface Functions { - + /** * Converts a function that takes two arguments to its curried version. * @@ -50,4 +51,20 @@ static Function> curried(final BiFunction Function> backCurried(final BiFunction f) { return a2 -> a1 -> f.apply(a1, a2); } + + + /** + * Returns function that conditionally casts an object to a given class, returning an empty + * optional if the cast fails. + * + * @param clazz the class to cast to + * @param the class to cast to + * @return the function + */ + static Function> maybeCast(final Class clazz) { + return o -> clazz.isInstance(o) + ? Optional.of(clazz.cast(o)) + : Optional.empty(); + } + } From fe8a1bc15ab74a3181f429e9143f06e6fab80ad1 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Thu, 28 Sep 2023 13:44:33 +1000 Subject: [PATCH 88/95] Fixing the fn_rkeys test. --- .../test/resources/viewTests/fn_rkeys.json | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/fhirpath/src/test/resources/viewTests/fn_rkeys.json b/fhirpath/src/test/resources/viewTests/fn_rkeys.json index 905b79a124..ab586e80b4 100644 --- a/fhirpath/src/test/resources/viewTests/fn_rkeys.json +++ b/fhirpath/src/test/resources/viewTests/fn_rkeys.json @@ -39,11 +39,15 @@ "title": "get reference key", "view": { "resource": "Observation", - "forEach": "basedOn", "select": [ { - "alias": "rid", - "path": "getReferenceKey()" + "forEach": "basedOn", + "select": [ + { + "alias": "rid", + "path": "getReferenceKey()" + } + ] } ] }, @@ -63,11 +67,15 @@ "title": "get reference key with resource param", "view": { "resource": "Observation", - "forEach": "basedOn.where(getReferenceKey('CarePlan').exists())", "select": [ { - "alias": "rid", - "path": "getReferenceKey()" + "forEach": "basedOn.where(getReferenceKey(CarePlan).exists())", + "select": [ + { + "alias": "rid", + "path": "getReferenceKey()" + } + ] } ] }, From 82aea7a7d4727c5c84255415e2a12684f1da6c77 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Thu, 28 Sep 2023 14:03:19 +1000 Subject: [PATCH 89/95] Updating the sql-on-fire pointer --- .../src/test/resources/viewTests/union.json | 67 +++++++++++++++++++ sql-on-fhir | 2 +- 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 fhirpath/src/test/resources/viewTests/union.json diff --git a/fhirpath/src/test/resources/viewTests/union.json b/fhirpath/src/test/resources/viewTests/union.json new file mode 100644 index 0000000000..debb30828d --- /dev/null +++ b/fhirpath/src/test/resources/viewTests/union.json @@ -0,0 +1,67 @@ +{ + "title": "union", + "description": "Union used to unify different branches of resource into flat table", + "resources": [ + { + "resourceType": "Patient", + "id": "pt1", + "contact": [ + { + "telecom": [ + { + "system": "phone", + "value": "1" + }, + { + "system": "phone", + "value": "2" + } + ] + } + ], + "telecom": [ + { + "system": "phone", + "value": "3" + }, + { + "system": "phone", + "value": "4" + } + ] + } + ], + "tests": [ + { + "title": "", + "description": "union all telecoms", + "view": { + "resource": "Patient", + "select": [ + { + "alias": "id", + "path": "id" + }, + { + "union": [ + { + "alias": "phone", + "path": "telecom.value" + }, + { + "alias": "phone", + "path": "contact.telecom.value" + } + ] + } + ] + }, + "expect": [ + { + "id": "pt1", + "phone": [ "3", "4" , "1", "2" ] + } + ] + } + ] +} diff --git a/sql-on-fhir b/sql-on-fhir index da3352132c..35f8e6e832 160000 --- a/sql-on-fhir +++ b/sql-on-fhir @@ -1 +1 @@ -Subproject commit da3352132cb248032f341e9e578a026ad8343c96 +Subproject commit 35f8e6e8320442f2a8805f555dfbcecb7ecdb3c1 From ae4cbd869ec965a561095deb3644b38eb25a44c2 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Thu, 28 Sep 2023 15:47:48 +1000 Subject: [PATCH 90/95] Implementing 'expectError' and 'expectCount' for FhirViewTests. Implementing 'collection' and singular value selection semantics for DirectSelection. --- .../csiro/pathling/views/DirectSelection.java | 7 + .../pathling/views/ExecutionContext.java | 32 ++++- .../pathling/views/FhirViewExecutor.java | 12 +- .../views/AbstractFhirViewTestBase.java | 132 +++++++++++++----- .../csiro/pathling/views/FhirViewV1Test.java | 2 +- sql-on-fhir | 2 +- 6 files changed, 141 insertions(+), 46 deletions(-) diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java b/fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java index 4e23c991f2..5a87024885 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/DirectSelection.java @@ -48,4 +48,11 @@ public class DirectSelection extends SelectClause { @Nullable String description; + /** + * Indicates whether the column may have multiple values. Defaults to false if unset. + * + * @see ViewDefinition.select.collection + */ + boolean collection; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ExecutionContext.java b/fhirpath/src/main/java/au/csiro/pathling/views/ExecutionContext.java index 2051492504..6e8d762001 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/ExecutionContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ExecutionContext.java @@ -41,34 +41,52 @@ public interface ExecutionContext { * Evaluates the given FHIRPath path and returns the result as a column. * * @param path the path to evaluate + * @param singularise whether to singularise the result * @return the result as a column */ @Nonnull - Column evalExpression(@Nonnull final String path); + Column evalExpression(@Nonnull final String path, final boolean singularise); + + + /** + * Evaluates the given FHIRPath path and returns the result as a column. + * + * @param path the path to evaluate + * @return the result as a column + */ + @Nonnull + default Column evalExpression(@Nonnull final String path) { + return evalExpression(path, true); + } + /** * Evaluates the given FHIRPath path and returns the result as a column with the given alias. * * @param path the path to evaluate + * @param singularise whether to singularise the result * @param alias the alias to use for the column * @return the result as a column */ @Nonnull - default Column evalExpression(@Nonnull final String path, @Nonnull final String alias) { - return evalExpression(path).as(alias); + default Column evalExpression(@Nonnull final String path, final boolean singularise, + @Nonnull final String alias) { + return evalExpression(path, singularise).as(alias); } /** * Evaluates the given FHIRPath path and returns the result as a column with the given alias. * * @param path the path to evaluate + * @param singularise whether to singularise the result * @param maybeAlias the alias to use for the column * @return the result as a column */ @Nonnull - default Column evalExpression(@Nonnull final String path, @Nonnull final - Optional maybeAlias) { - return maybeAlias.map(alias -> evalExpression(path, alias)) - .orElse(evalExpression(path)); + default Column evalExpression(@Nonnull final String path, final boolean singularise, + @Nonnull final + Optional maybeAlias) { + return maybeAlias.map(alias -> evalExpression(path, singularise, alias)) + .orElse(evalExpression(path, singularise)); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index cc24769b2b..28ce4289dd 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -57,8 +57,10 @@ private static class ExecutionContextImpl implements ExecutionContext { @Nonnull @Override - public Column evalExpression(@Nonnull final String path) { - return internalEvalExpression(path).getColumn(); + public Column evalExpression(@Nonnull final String path, final boolean singularise) { + return singularise + ? internalEvalExpression(path).getSingleton() + : internalEvalExpression(path).getColumn(); } @Nonnull @@ -156,7 +158,8 @@ private List parseSelect(@Nonnull final List selectGroup, } @Nonnull - private List evalSelect(@Nonnull final SelectClause select, @Nonnull final ExecutionContext context) { + private List evalSelect(@Nonnull final SelectClause select, + @Nonnull final ExecutionContext context) { // TODO: move to the classes ??? if (select instanceof DirectSelection) { return directSelection(context, (DirectSelection) select); @@ -188,7 +191,8 @@ private List unionSelection(@Nonnull final ExecutionContext context, private List directSelection(@Nonnull final ExecutionContext context, @Nonnull final DirectSelection select) { return List.of( - context.evalExpression(select.getPath(), Optional.ofNullable(select.getAlias()))); + context.evalExpression(select.getPath(), !select.isCollection(), + Optional.ofNullable(select.getAlias()))); } @Nonnull diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/AbstractFhirViewTestBase.java b/fhirpath/src/test/java/au/csiro/pathling/views/AbstractFhirViewTestBase.java index dd76a9090f..1485e97718 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/AbstractFhirViewTestBase.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/AbstractFhirViewTestBase.java @@ -5,6 +5,8 @@ import static au.csiro.pathling.test.assertions.Assertions.assertThat; import static au.csiro.pathling.validation.ValidationUtils.ensureValid; import static java.util.Objects.nonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import au.csiro.pathling.encoders.FhirEncoders; import au.csiro.pathling.io.source.DataSource; @@ -30,11 +32,13 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.Value; import lombok.extern.slf4j.Slf4j; +import org.apache.spark.SparkException; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoders; @@ -78,6 +82,57 @@ abstract class AbstractFhirViewTestBase { private final String testLocationGlob; + + @FunctionalInterface + interface Expectation { + + void expect(@Nonnull final Supplier> result); + } + + abstract static class ResultExpectation implements Expectation { + + @Override + public void expect(@Nonnull final Supplier> result) { + expectResult(result.get()); + } + + abstract void expectResult(@Nonnull final Dataset rowDataset); + } + + static class ExpectError implements Expectation { + + @Override + public void expect(@Nonnull final Supplier> result) { + assertThrows(SparkException.class, () -> result.get().collectAsList()); + } + } + + @Value + class Expect extends ResultExpectation { + + Path expectedJson; + List expectedColumns; + + @Override + void expectResult(@Nonnull final Dataset rowDataset) { + final Dataset expectedResult = spark.read().json(expectedJson.toString()) + .selectExpr(expectedColumns.toArray(new String[0])); + assertThat(rowDataset).hasRows(expectedResult); + } + } + + @Value + static class ExpectCount extends ResultExpectation { + + long count; + + @Override + void expectResult(@Nonnull final Dataset rowDataset) { + assertEquals(count, rowDataset.count()); + } + } + + protected AbstractFhirViewTestBase(final String testLocationGlob) { this.testLocationGlob = testLocationGlob; } @@ -173,42 +228,53 @@ List toTestParameters(@Nonnull final JsonNode testDefinition, String.format("%02d_%s.json", testNumber, view.get("title").asText().replaceAll("\\W+", "_")); final Path expectedPath = directory.resolve(expectedFileName); - List expectedColumns = null; - - // Always create the file first to account for empty 'expect' element. - Files.createFile(expectedPath); - for (final Iterator rowIt = view.get("expect").elements(); rowIt.hasNext(); ) { - final JsonNode row = rowIt.next(); - - // Get the columns from the first row. - if (expectedColumns == null) { - final List columns = new ArrayList<>(); - row.fields().forEachRemaining(field -> columns.add(field.getKey())); - expectedColumns = columns; - } - - // Append the row to the file. - Files.write(expectedPath, (row + "\n").getBytes(StandardCharsets.UTF_8), - StandardOpenOption.APPEND); - } final String testName = testDefinition.get("title").asText() + " - " + view.get("title").asText(); result.add( - new TestParameters(testName, sourceData, fhirView, expectedPath, - nonNull(expectedColumns) - ? expectedColumns - : Collections.emptyList())); + new TestParameters(testName, sourceData, fhirView, getExpectation(view, expectedPath))); testNumber++; } - return result; - } catch (final IOException e) { throw new RuntimeException(e); } } + + @Nonnull + Expectation getExpectation(@Nonnull final JsonNode testDefinition, + @Nonnull final Path expectedPath) + throws IOException { + @Nullable + JsonNode expectation = null; + if (nonNull(testDefinition.get("expectError"))) { + return new ExpectError(); + } else if (nonNull(expectation = testDefinition.get("expectCount"))) { + return new ExpectCount(expectation.asLong()); + } else if (nonNull(expectation = testDefinition.get("expect"))) { + List expectedColumns = null; + Files.createFile(expectedPath); + for (final Iterator rowIt = expectation.elements(); rowIt.hasNext(); ) { + final JsonNode row = rowIt.next(); + // Get the columns from the first row. + if (expectedColumns == null) { + final List columns = new ArrayList<>(); + row.fields().forEachRemaining(field -> columns.add(field.getKey())); + expectedColumns = columns; + } + // Append the row to the file. + Files.write(expectedPath, (row + "\n").getBytes(StandardCharsets.UTF_8), + StandardOpenOption.APPEND); + } + return new Expect(expectedPath, expectedColumns != null + ? expectedColumns + : Collections.emptyList()); + } else { + throw new RuntimeException("No expectation found"); + } + } + @Nonnull private static Path getTempDir(final @Nonnull JsonNode testDefinition) throws IOException { final String directoryName = testDefinition.get("title").asText().replaceAll("\\W+", "_"); @@ -223,13 +289,14 @@ private static Path getTempDir(final @Nonnull JsonNode testDefinition) throws IO @ParameterizedTest @MethodSource("requests") void test(@Nonnull final TestParameters parameters) { - final FhirView view = parameters.getView(); - final FhirViewExecutor executor = new FhirViewExecutor(fhirContext, spark, - parameters.getSourceData(), Optional.ofNullable(terminologyServiceFactory)); - final Dataset result = executor.buildQuery(view); - final Dataset expectedResult = spark.read().json(parameters.getExpectedJson().toString()) - .selectExpr(parameters.getExpectedColumns().toArray(new String[0])); - assertThat(result).hasRows(expectedResult); + + parameters.getExpectation().expect(() -> { + + final FhirView view = parameters.getView(); + final FhirViewExecutor executor = new FhirViewExecutor(fhirContext, spark, + parameters.getSourceData(), Optional.ofNullable(terminologyServiceFactory)); + return executor.buildQuery(view); + }); } @Value @@ -238,8 +305,7 @@ static class TestParameters { String title; DataSource sourceData; FhirView view; - Path expectedJson; - List expectedColumns; + Expectation expectation; @Override public String toString() { diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewV1Test.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewV1Test.java index 38f2f716e1..d63ca83019 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewV1Test.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewV1Test.java @@ -20,6 +20,6 @@ public class FhirViewV1Test extends AbstractFhirViewTestBase { public FhirViewV1Test() { - super("classpath:tests/sql-on-fhir/v1/*.json"); + super("classpath:tests/sql-on-fhir/content/*.json"); } } diff --git a/sql-on-fhir b/sql-on-fhir index 35f8e6e832..25c2f314fb 160000 --- a/sql-on-fhir +++ b/sql-on-fhir @@ -1 +1 @@ -Subproject commit 35f8e6e8320442f2a8805f555dfbcecb7ecdb3c1 +Subproject commit 25c2f314fba79cefa4c4bf1c9de4aeb6e5e2204c From 1f158729ca41487ba09dc4e8fa69461d8424a02d Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Fri, 29 Sep 2023 12:17:52 +1000 Subject: [PATCH 91/95] Re-implementing type specifiers as special cases of FhirPath expressions. Implementing 'r-keys' functions: getReferenceKey() and getResourceKey(). Cleaning up our extended FhirView tests. --- .../pathling/fhirpath/parser/ParserTest.java | 4 +- .../pathling/fhirpath/TypeSpecifier.java | 102 ++++++ .../fhirpath/collection/Collection.java | 11 +- .../fhirpath/function/StandardFunctions.java | 43 ++- .../fhirpath/function/WrappedFunction.java | 9 +- .../fhirpath/parser/InvocationVisitor.java | 13 +- .../fhirpath/parser/TypeSpecifierVisitor.java | 332 ++++++++++++++++++ .../pathling/fhirpath/parser/Visitor.java | 6 +- .../fhirpath/path/TypeSpecifierPath.java | 41 +++ ...1Test.java => FhirViewComplianceTest.java} | 4 +- .../{fn_rkeys.json => ex_fn_rkeys.json} | 0 .../test/resources/viewTests/fn_exists.json | 100 ------ .../test/resources/viewTests/fn_first.json | 48 --- .../src/test/resources/viewTests/fn_join.json | 50 --- .../src/test/resources/viewTests/union.json | 6 +- .../test/resources/viewTests/unnesting.json | 21 +- .../src/test/resources/viewTests/where.json | 132 ------- sql-on-fhir | 2 +- 18 files changed, 560 insertions(+), 364 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/TypeSpecifier.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TypeSpecifierVisitor.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/TypeSpecifierPath.java rename fhirpath/src/test/java/au/csiro/pathling/views/{FhirViewV1Test.java => FhirViewComplianceTest.java} (87%) rename fhirpath/src/test/resources/viewTests/{fn_rkeys.json => ex_fn_rkeys.json} (100%) delete mode 100644 fhirpath/src/test/resources/viewTests/fn_exists.json delete mode 100644 fhirpath/src/test/resources/viewTests/fn_first.json delete mode 100644 fhirpath/src/test/resources/viewTests/fn_join.json delete mode 100644 fhirpath/src/test/resources/viewTests/where.json diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java index 857b533b67..77abfd8fe1 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java @@ -656,7 +656,7 @@ void testExtensionsOnResources() { void testExtensionFunction() { // This should be the same as: "extension.where($this.url='http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName').valueString" assertThatResultOf( - "extension('http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName').value.ofType(string)") + "extension('http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName').value.ofType(FHIR.string)") .isElementPath(StringCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testExtensionFunction.tsv"); @@ -694,7 +694,7 @@ void testComplexExtensionsOnComplexPath() { assertThatResultOf( "address.where($this.city = 'Boston')" + ".extension('http://hl7.org/fhir/StructureDefinition/geolocation')" - + ".extension('latitude').value.ofType(decimal)") + + ".extension('latitude').valueDecimal") .isElementPath(DecimalCollection.class) .selectResult() .hasRows(spark, "responses/ParserTest/testComplexExtensionsOnComplexPath.tsv"); diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TypeSpecifier.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TypeSpecifier.java new file mode 100644 index 0000000000..3422979985 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/TypeSpecifier.java @@ -0,0 +1,102 @@ +/* + * Copyright 2023 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; + +import lombok.AllArgsConstructor; +import lombok.Value; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; +import javax.annotation.Nonnull; + +/** + * Represents a FHIRPath type specifier, which is a namespace and a type name. + */ +@Value +@AllArgsConstructor +public class TypeSpecifier { + + public static final String FHIR_NAMESPACE = "FHIR"; + public static final String DEFAULT_NAMESPACE = FHIR_NAMESPACE; + + @Nonnull + String namespace; + + @Nonnull + String typeName; + + public TypeSpecifier(@Nonnull final String typeName) { + this(DEFAULT_NAMESPACE, typeName); + } + + /** + * @return true if this type specifier is a type in FHIR namespace. + */ + boolean isFhirType() { + return namespace.equals(FHIR_NAMESPACE); + } + + + /** + * Returns a copy of this type specifier with the new namespace. + * + * @param namespace the new namespace + * @return the type specifier with different namespace + */ + @Nonnull + public TypeSpecifier withNamespace(@Nonnull final String namespace) { + return new TypeSpecifier(namespace, typeName); + } + + /** + * Converts this type specifier to a FHIR type. + * + * @return the FHIR type + * @throws IllegalStateException if this type specifier is not a FHIR type + */ + @Nonnull + public FHIRDefinedType toFhirType() { + if (!isFhirType()) { + throw new IllegalStateException("Not a FHIR type: " + this); + } + return FHIRDefinedType.fromCode(typeName); + } + + /** + * Creates a type specifier from a FHRI type. + * + * @return the type specifier + */ + public static TypeSpecifier fromFhirType(@Nonnull final FHIRDefinedType type) { + return new TypeSpecifier(FHIR_NAMESPACE, type.toCode()); + } + + /** + * Parses a type specifier from a string. + * + * @return the parsed type specifier + */ + public static TypeSpecifier fromString(@Nonnull final String typeSpecifier) { + final String[] parts = typeSpecifier.split("\\."); + if (parts.length == 1) { + return new TypeSpecifier(DEFAULT_NAMESPACE, parts[0]); + } else if (parts.length == 2) { + return new TypeSpecifier(parts[0], parts[1]); + } else { + throw new IllegalArgumentException("Invalid type specifier: " + typeSpecifier); + } + } +} 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 14fa355fd4..44756eab45 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 @@ -30,12 +30,14 @@ import au.csiro.pathling.fhirpath.definition.NodeDefinition; import au.csiro.pathling.utilities.Preconditions; import com.google.common.collect.ImmutableMap; + import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Map; import java.util.Optional; import java.util.function.Function; import javax.annotation.Nonnull; + import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -315,12 +317,13 @@ public Collection copyWith(@Nonnull final Column newValue) { @Nonnull public Collection copyWith(@Nonnull final ColumnCtx newValue) { - // TODO: This is very very suspicious - // Really need to understand what the relationships between all the different types - // some of them seem redundant return copyWith(newValue.getValue()); } + @Nonnull + public Collection filter(@Nonnull final Function lambda) { + return copyWith(getCtx().filter(lambda)); + } public Column getSingleton() { return ColumnHelpers.singular(getColumn()); @@ -331,5 +334,5 @@ public Column getSingleton() { public ColumnCtx getCtx() { return ColumnCtx.of(getColumn()); } - + } 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 7023886d01..edf676f238 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 @@ -18,10 +18,10 @@ package au.csiro.pathling.fhirpath.function; import static au.csiro.pathling.fhirpath.Comparable.ComparisonOperation.EQUALS; -import static au.csiro.pathling.utilities.Preconditions.checkUserInput; +import static au.csiro.pathling.utilities.Preconditions.checkArgument; import static java.util.Objects.nonNull; -import au.csiro.pathling.fhirpath.FhirPathType; +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.IntegerCollection; @@ -29,8 +29,12 @@ import au.csiro.pathling.fhirpath.collection.ResourceCollection; import au.csiro.pathling.fhirpath.collection.StringCollection; 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.apache.spark.sql.Column; +import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; @SuppressWarnings("unused") public class StandardFunctions { @@ -39,12 +43,13 @@ public class StandardFunctions { public static final String URL_ELEMENT_NAME = "url"; public static final String JOIN_DEFAULT_SEPARATOR = ""; + public static final String REFERENCE_ELEMENT_NAME = "reference"; + @Nonnull @FhirpathFunction public static Collection where(@Nonnull final Collection input, @Nonnull final CollectionExpression expression) { - return input.copyWith( - input.getCtx().filter(expression.requireBoolean().toColumnFunction(input))); + return input.filter(expression.requireBoolean().toColumnFunction(input)); } // // @@ -94,14 +99,10 @@ public static Collection extension(@Nonnull final Collection input, @FhirpathFunction public static Collection ofType(@Nonnull final MixedCollection input, - @Nonnull final Collection typeSpecifier) { - // TODO: implement as annotation or collection type - checkUserInput(typeSpecifier.getType().isPresent() && FhirPathType.TYPE_SPECIFIER.equals( - typeSpecifier.getType().get()), - "Argument to ofType function must be a type specifier"); + @Nonnull final TypeSpecifier typeSpecifier) { // TODO: This should work on any collection type - not just mixed // if the type of the collection does not match the required type then it should return an empty collection. - return input.resolveChoice(typeSpecifier.getFhirType().get().toCode()) + return input.resolveChoice(typeSpecifier.toFhirType().toCode()) .orElse(Collection.nullCollection()); } @@ -110,6 +111,23 @@ public static StringCollection getResourceKey(@Nonnull final ResourceCollection return StringCollection.build(input.getKeyColumn().getValue()); } + @FhirpathFunction + // TODO: This needs to be somehow constrained to the collections of References + public static Collection getReferenceKey(@Nonnull final Collection 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.getField(REFERENCE_ELEMENT_NAME).rlike(regex))) + .map(input::filter).orElse(input) + .traverse(REFERENCE_ELEMENT_NAME).orElse(Collection.nullCollection()); + } + @FhirpathFunction public static StringCollection join(@Nonnull final StringCollection input, @Nullable final StringCollection separator) { @@ -133,4 +151,9 @@ public static BooleanCollection exists(@Nonnull final Collection input, public static BooleanCollection not(@Nonnull final BooleanCollection input) { return BooleanCollection.build(input.getCtx().not()); } + + public static boolean isTypeSpecifierFunction(@Nonnull final String functionName) { + return "ofType".equals(functionName) || "getReferenceKey".equals(functionName); + } + } 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 1b3b99ae62..125cf4b9f2 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 @@ -22,7 +22,9 @@ import au.csiro.pathling.fhirpath.EvaluationContext; import au.csiro.pathling.fhirpath.FhirPath; import au.csiro.pathling.fhirpath.FunctionInput; +import au.csiro.pathling.fhirpath.TypeSpecifier; import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.path.TypeSpecifierPath; import au.csiro.pathling.fhirpath.validation.FhirpathFunction; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -58,8 +60,8 @@ private static class ParamResolver { @Nullable public Object resolveArgument(@Nonnull final Parameter parameter, - final FhirPath argument) { - + final FhirPath argument) { + if (isNull(argument)) { // check the pararmeter is happy with a null value if (parameter.getAnnotation(Nullable.class) != null) { @@ -77,6 +79,9 @@ public Object resolveArgument(@Nonnull final Parameter parameter, } else if (CollectionExpression.class.isAssignableFrom(parameter.getType())) { // bind with context return (CollectionExpression) (c -> argument.apply(c, evaluationContext)); + } else if (TypeSpecifier.class.isAssignableFrom(parameter.getType())) { + // bind type specifier + return ((TypeSpecifierPath) argument).getTypeSpecifier(); } else { throw new RuntimeException("Cannot resolve parameter:" + parameter); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java index dd0cfda033..30734f3be5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/InvocationVisitor.java @@ -17,6 +17,7 @@ package au.csiro.pathling.fhirpath.parser; +import static au.csiro.pathling.fhirpath.function.StandardFunctions.isTypeSpecifierFunction; import static au.csiro.pathling.utilities.Preconditions.checkUserInput; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; @@ -36,6 +37,7 @@ import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ParamListContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ThisInvocationContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.TotalInvocationContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathVisitor; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -105,9 +107,16 @@ public FhirPath visitFunctionInvocation( @Nullable final FunctionInvocationContext ctx) { final String functionIdentifier = requireNonNull(ctx).function().identifier().getText(); - @Nullable final ParamListContext paramList = ctx.function().paramList(); - final Visitor paramListVisitor = new Visitor(); + + // NOTE: Here we assume that a function is either a type specifier function + // (and all the arguments are type specifiers) or regular function + // (none of the arguments are type specifiers). + final FhirPathVisitor> paramListVisitor = + isTypeSpecifierFunction(functionIdentifier) + ? new TypeSpecifierVisitor() + : new Visitor(); + final List> arguments = Optional.ofNullable(paramList) .map(ParamListContext::expression) .map(p -> p.stream() diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TypeSpecifierVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TypeSpecifierVisitor.java new file mode 100644 index 0000000000..6ebf5cd6a9 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/TypeSpecifierVisitor.java @@ -0,0 +1,332 @@ +/* + * Copyright 2023 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.parser; + +import au.csiro.pathling.errors.InvalidUserInputError; +import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.TypeSpecifier; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.AdditiveExpressionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.AndExpressionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.BooleanLiteralContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.CodingLiteralContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.CombineExpressionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.DateLiteralContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.DateTimeLiteralContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.DateTimePrecisionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.EqualityExpressionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ExternalConstantContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ExternalConstantTermContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.FunctionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.FunctionInvocationContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.IdentifierContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ImpliesExpressionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.IndexInvocationContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.IndexerExpressionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.InequalityExpressionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.InvocationExpressionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.InvocationTermContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.LiteralTermContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.MemberInvocationContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.MembershipExpressionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.MultiplicativeExpressionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.NullLiteralContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.NumberLiteralContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.OrExpressionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ParamListContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ParenthesizedTermContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.PluralDateTimePrecisionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.PolarityExpressionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.QualifiedIdentifierContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.QuantityContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.QuantityLiteralContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.StringLiteralContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.TermExpressionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.ThisInvocationContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.TimeLiteralContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.TotalInvocationContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.TypeExpressionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.TypeSpecifierContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.UnionExpressionContext; +import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.UnitContext; +import au.csiro.pathling.fhirpath.path.TypeSpecifierPath; +import javax.annotation.Nonnull; + +/** + * A special visitor for the type specifiers arguments in the FHIRPath function invocations. + * + * @author Piotr Szul + */ +class TypeSpecifierVisitor extends FhirPathBaseVisitor> { + + private final boolean isNamespace; + + TypeSpecifierVisitor(final boolean isNamespace) { + this.isNamespace = isNamespace; + } + + TypeSpecifierVisitor() { + this(false); + } + + @Override + public FhirPath visitIndexerExpression( + final IndexerExpressionContext ctx) { + throw newUnexpectedExpressionException("IndexerExpression"); + } + + @Override + public FhirPath visitPolarityExpression( + final PolarityExpressionContext ctx) { + throw newUnexpectedExpressionException("PolarityExpression"); + } + + @Override + public FhirPath visitAdditiveExpression( + final AdditiveExpressionContext ctx) { + throw newUnexpectedExpressionException("AdditiveExpression"); + } + + @Override + public FhirPath visitCombineExpression( + final CombineExpressionContext ctx) { + throw newUnexpectedExpressionException("CombineExpression"); + } + + @Override + public FhirPath visitMultiplicativeExpression( + final MultiplicativeExpressionContext ctx) { + throw newUnexpectedExpressionException("MultiplicativeExpression"); + } + + @Override + public FhirPath visitUnionExpression(final UnionExpressionContext ctx) { + throw newUnexpectedExpressionException("UnionExpression"); + } + + @Override + public FhirPath visitOrExpression(final OrExpressionContext ctx) { + throw newUnexpectedExpressionException("OrExpression"); + } + + @Override + public FhirPath visitAndExpression(final AndExpressionContext ctx) { + throw newUnexpectedExpressionException("AndExpression"); + } + + @Override + public FhirPath visitMembershipExpression( + final MembershipExpressionContext ctx) { + throw newUnexpectedExpressionException("MembershipExpression"); + } + + @Override + public FhirPath visitInequalityExpression( + final InequalityExpressionContext ctx) { + throw newUnexpectedExpressionException("InequalityExpression"); + } + + @Override + public FhirPath visitInvocationExpression( + final InvocationExpressionContext ctx) { + if (!isNamespace) { + final TypeSpecifier unqualifiedTypeSpecifier = ((TypeSpecifierPath) ctx.expression() + .accept(new TypeSpecifierVisitor(true))).getTypeSpecifier(); + return new TypeSpecifierPath( + unqualifiedTypeSpecifier.withNamespace(ctx.invocation().getText())); + } else { + throw newUnexpectedExpressionException("InvocationExpression"); + } + } + + @Override + public FhirPath visitEqualityExpression( + final EqualityExpressionContext ctx) { + throw newUnexpectedExpressionException("EqualityExpression"); + } + + @Override + public FhirPath visitImpliesExpression( + final ImpliesExpressionContext ctx) { + throw newUnexpectedExpressionException("ImpliesExpression"); + } + + @Override + public FhirPath visitTermExpression(final TermExpressionContext ctx) { + return visitChildren(ctx); + } + + @Override + public FhirPath visitTypeExpression(final TypeExpressionContext ctx) { + throw newUnexpectedExpressionException("TypeExpression"); + } + + @Override + public FhirPath visitInvocationTerm(final InvocationTermContext ctx) { + return visitChildren(ctx); + } + + @Override + public FhirPath visitLiteralTerm(final LiteralTermContext ctx) { + throw newUnexpectedExpressionException("LiteralTerm"); + } + + @Override + public FhirPath visitExternalConstantTerm( + final ExternalConstantTermContext ctx) { + throw newUnexpectedExpressionException("ExternalConstantTerm"); + } + + @Override + public FhirPath visitParenthesizedTerm( + final ParenthesizedTermContext ctx) { + throw newUnexpectedExpressionException("ParenthesizedTerm"); + } + + @Override + public FhirPath visitNullLiteral(final NullLiteralContext ctx) { + throw newUnexpectedExpressionException("NullLiteral"); + } + + @Override + public FhirPath visitBooleanLiteral(final BooleanLiteralContext ctx) { + throw newUnexpectedExpressionException("BooleanLiteral"); + } + + @Override + public FhirPath visitStringLiteral(final StringLiteralContext ctx) { + throw newUnexpectedExpressionException("StringLiteral"); + } + + @Override + public FhirPath visitNumberLiteral(final NumberLiteralContext ctx) { + throw newUnexpectedExpressionException("NumberLiteral"); + } + + @Override + public FhirPath visitDateLiteral(final DateLiteralContext ctx) { + throw newUnexpectedExpressionException("DateLiteral"); + } + + @Override + public FhirPath visitDateTimeLiteral(final DateTimeLiteralContext ctx) { + throw newUnexpectedExpressionException("DateTimeLiteral"); + } + + @Override + public FhirPath visitTimeLiteral(final TimeLiteralContext ctx) { + throw newUnexpectedExpressionException("TimeLiteral"); + } + + @Override + public FhirPath visitQuantityLiteral(final QuantityLiteralContext ctx) { + throw newUnexpectedExpressionException("QuantityLiteral"); + } + + @Override + public FhirPath visitCodingLiteral(final CodingLiteralContext ctx) { + throw newUnexpectedExpressionException("CodingLiteral"); + } + + @Override + public FhirPath visitExternalConstant(final ExternalConstantContext ctx) { + throw newUnexpectedExpressionException("ExternalConstant"); + } + + @Override + public FhirPath visitMemberInvocation(final MemberInvocationContext ctx) { + return visitChildren(ctx); + } + + @Override + public FhirPath visitFunctionInvocation( + final FunctionInvocationContext ctx) { + throw newUnexpectedExpressionException("FunctionInvocation"); + } + + @Override + public FhirPath visitThisInvocation(final ThisInvocationContext ctx) { + throw newUnexpectedExpressionException("ThisInvocation"); + } + + @Override + public FhirPath visitIndexInvocation(final IndexInvocationContext ctx) { + throw newUnexpectedExpressionException("IndexInvocation"); + } + + @Override + public FhirPath visitTotalInvocation(final TotalInvocationContext ctx) { + throw newUnexpectedExpressionException("TotalInvocation"); + } + + @Override + public FhirPath visitFunction(final FunctionContext ctx) { + throw newUnexpectedExpressionException("Function"); + } + + @Override + public FhirPath visitParamList(final ParamListContext ctx) { + throw newUnexpectedExpressionException("ParamList"); + } + + @Override + public FhirPath visitQuantity(final QuantityContext ctx) { + throw newUnexpectedExpressionException("Quantity"); + } + + @Override + public FhirPath visitUnit(final UnitContext ctx) { + throw newUnexpectedExpressionException("Unit"); + } + + @Override + public FhirPath visitDateTimePrecision( + final DateTimePrecisionContext ctx) { + throw newUnexpectedExpressionException("DateTimePrecision"); + } + + @Override + public FhirPath visitPluralDateTimePrecision( + final PluralDateTimePrecisionContext ctx) { + throw newUnexpectedExpressionException("PluralDateTimePrecision"); + } + + @Override + public FhirPath visitTypeSpecifier(final TypeSpecifierContext ctx) { + throw newUnexpectedExpressionException("TypeSpecifier"); + } + + @Override + public FhirPath visitQualifiedIdentifier( + final QualifiedIdentifierContext ctx) { + throw newUnexpectedExpressionException("QualifiedIdentifier"); + } + + @Override + public FhirPath visitIdentifier(final IdentifierContext ctx) { + return new TypeSpecifierPath(new TypeSpecifier(ctx.getText())); + } + + @Nonnull + private RuntimeException newUnexpectedExpressionException(@Nonnull final String expressionType) { + return new InvalidUserInputError( + "Unexpected expression type: " + expressionType + " in type specifier"); + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java index 9b56b30219..ebd77c7a2b 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java @@ -86,10 +86,8 @@ public FhirPath visitInvocationExpression( requireNonNull(ctx).expression()); final FhirPath invocationVerb = ctx.invocation() .accept(new InvocationVisitor()); - return (input, context) -> { - // TODO: perhpas we should also create the new cotext here (with different %context) - return invocationVerb.apply(invocationSubject.apply(input, context), context); - }; + return (input, context) -> invocationVerb.apply(invocationSubject.apply(input, context), + context); } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/TypeSpecifierPath.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/TypeSpecifierPath.java new file mode 100644 index 0000000000..fd083eed00 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/TypeSpecifierPath.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 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.path; + +import au.csiro.pathling.fhirpath.EvaluationContext; +import au.csiro.pathling.fhirpath.FhirPath; +import au.csiro.pathling.fhirpath.TypeSpecifier; +import au.csiro.pathling.fhirpath.collection.Collection; +import lombok.Value; + +import javax.annotation.Nonnull; + +/** + * FHIRPath expression with a type specifier value. + */ +@Value +public class TypeSpecifierPath implements FhirPath { + + TypeSpecifier typeSpecifier; + + @Override + public Collection apply(@Nonnull final Collection input, + @Nonnull final EvaluationContext context) { + throw new UnsupportedOperationException("TypeSpecifierPath cannot be evaluated directly"); + } +} diff --git a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewV1Test.java b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewComplianceTest.java similarity index 87% rename from fhirpath/src/test/java/au/csiro/pathling/views/FhirViewV1Test.java rename to fhirpath/src/test/java/au/csiro/pathling/views/FhirViewComplianceTest.java index d63ca83019..f12483dfd7 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewV1Test.java +++ b/fhirpath/src/test/java/au/csiro/pathling/views/FhirViewComplianceTest.java @@ -17,9 +17,9 @@ package au.csiro.pathling.views; -public class FhirViewV1Test extends AbstractFhirViewTestBase { +public class FhirViewComplianceTest extends AbstractFhirViewTestBase { - public FhirViewV1Test() { + public FhirViewComplianceTest() { super("classpath:tests/sql-on-fhir/content/*.json"); } } diff --git a/fhirpath/src/test/resources/viewTests/fn_rkeys.json b/fhirpath/src/test/resources/viewTests/ex_fn_rkeys.json similarity index 100% rename from fhirpath/src/test/resources/viewTests/fn_rkeys.json rename to fhirpath/src/test/resources/viewTests/ex_fn_rkeys.json diff --git a/fhirpath/src/test/resources/viewTests/fn_exists.json b/fhirpath/src/test/resources/viewTests/fn_exists.json deleted file mode 100644 index d9422fc842..0000000000 --- a/fhirpath/src/test/resources/viewTests/fn_exists.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "title": "exists function", - "resources": [ - { - "resourceType": "Patient", - "id": "p1", - "name": [ - { - "use": "official", - "family": "f1" - } - ] - }, - { - "resourceType": "Patient", - "id": "p2" - }, - { - "resourceType": "Patient", - "id": "p3", - "name": [ - { - "use": "nickname", - "given": ["g3"] - } - ] - } - ], - "tests": [ - { - "title": "exists in field path", - "view": { - "resource": "Patient", - "select": [ - { "path": "id" }, - { "path": "name.exists()", "alias": "has_name" } - ] - }, - "expect": [ - { "id": "p1", "has_name": true }, - { "id": "p2", "has_name": false }, - { "id": "p3", "has_name": true } - ] - }, - { - "title": "nested exists in field path", - "view": { - "resource": "Patient", - "select": [ - { "path": "id" }, - { "path": "name.given.exists()", "alias": "has_name" } - ] - }, - "expect": [ - { "id": "p1", "has_name": false }, - { "id": "p2", "has_name": false }, - { "id": "p3", "has_name": true } - ] - }, - { - "title": "exists in where path", - "view": { - "resource": "Patient", - "select": [{ "path": "id" }], - "where": [ - { - "path": "name.exists()" - } - ] - }, - "expect": [{ "id": "p1" }, { "id": "p3" }] - }, - { - "title": "nested exists in where path", - "view": { - "resource": "Patient", - "select": [{ "path": "id" }], - "where": [ - { - "path": "name.given.exists()" - } - ] - }, - "expect": [{ "id": "p3" }] - }, - { - "title": "exists in forEach path", - "view": { - "resource": "Patient", - "select": [ - { - "forEach": "where(name.exists())", - "select": [{ "path": "id" }] - } - ] - }, - "expect": [{ "id": "p1" }, { "id": "p3" }] - } - ] -} diff --git a/fhirpath/src/test/resources/viewTests/fn_first.json b/fhirpath/src/test/resources/viewTests/fn_first.json deleted file mode 100644 index bbefd9a00b..0000000000 --- a/fhirpath/src/test/resources/viewTests/fn_first.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "title": "first function", - "resources": [ - { - "resourceType": "Patient", - "name": [ - { - "use": "official", - "family": "f1", - "given": ["g1.1", "g1.2"] - }, - { - "use": "usual", - "given": ["g2.1"] - }, - { - "use": "maiden", - "family": "f3", - "given": ["g3.1", "g3.2"], - "period": { "end": "2002" } - } - ] - } - ], - "tests": [ - { - "title": "table level first()", - "view": { - "resource": "Patient", - "select": [{ "path": "name.first().use" }] - }, - "expect": [{ "use": "official" }] - }, - { - "title": "table and field level first()", - "view": { - "resource": "Patient", - "select": [ - { - "path": "name.first().given.first()", - "alias": "given" - } - ] - }, - "expect": [{ "given": "g1.1" }] - } - ] -} diff --git a/fhirpath/src/test/resources/viewTests/fn_join.json b/fhirpath/src/test/resources/viewTests/fn_join.json deleted file mode 100644 index a50f6168a9..0000000000 --- a/fhirpath/src/test/resources/viewTests/fn_join.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "title": "join function", - "resources": [ - { - "resourceType": "Patient", - "id": "p1", - "name": [ - { - "use": "official", - "given": ["p1.g1", "p1.g2"] - } - ] - } - ], - "tests": [ - { - "title": "join with comma", - "view": { - "resource": "Patient", - "select": [ - { "path": "id" }, - { "path": "name.given.join(',')", "alias": "given" } - ] - }, - "expect": [{ "id": "p1", "given": "p1.g1,p1.g2" }] - }, - { - "title": "join with empty value", - "view": { - "resource": "Patient", - "select": [ - { "path": "id" }, - { "path": "name.given.join('')", "alias": "given" } - ] - }, - "expect": [{ "id": "p1", "given": "p1.g1p1.g2" }] - }, - { - "title": "join with no value - default to no separator", - "view": { - "resource": "Patient", - "select": [ - { "path": "id" }, - { "path": "name.given.join()", "alias": "given" } - ] - }, - "expect": [{ "id": "p1", "given": "p1.g1p1.g2" }] - } - ] -} diff --git a/fhirpath/src/test/resources/viewTests/union.json b/fhirpath/src/test/resources/viewTests/union.json index debb30828d..65feff3f0e 100644 --- a/fhirpath/src/test/resources/viewTests/union.json +++ b/fhirpath/src/test/resources/viewTests/union.json @@ -46,11 +46,13 @@ "union": [ { "alias": "phone", - "path": "telecom.value" + "path": "telecom.value", + "collection": true }, { "alias": "phone", - "path": "contact.telecom.value" + "path": "contact.telecom.value", + "collection": true } ] } diff --git a/fhirpath/src/test/resources/viewTests/unnesting.json b/fhirpath/src/test/resources/viewTests/unnesting.json index 10aff51b22..ae26a05ae3 100644 --- a/fhirpath/src/test/resources/viewTests/unnesting.json +++ b/fhirpath/src/test/resources/viewTests/unnesting.json @@ -74,7 +74,8 @@ }, { "alias": "given", - "path": "given" + "path": "given", + "collection": true } ] } @@ -84,22 +85,32 @@ { "id": "pt1", "family": "f1.1", - "given": ["g1.1"] + "given": [ + "g1.1" + ] }, { "id": "pt1", "family": "f1.2", - "given": ["g1.2", "g1.3"] + "given": [ + "g1.2", + "g1.3" + ] }, { "id": "pt2", "family": "f2.1", - "given": ["g2.1"] + "given": [ + "g2.1" + ] }, { "id": "pt2", "family": "f2.2", - "given": ["g2.2", "g2.3"] + "given": [ + "g2.2", + "g2.3" + ] } ] }, diff --git a/fhirpath/src/test/resources/viewTests/where.json b/fhirpath/src/test/resources/viewTests/where.json deleted file mode 100644 index 830f3c6a27..0000000000 --- a/fhirpath/src/test/resources/viewTests/where.json +++ /dev/null @@ -1,132 +0,0 @@ -{ - "title": "where function and element", - "resources": [ - { - "resourceType": "Patient", - "id": "p1", - "name": [ - { - "use": "official", - "family": "f1" - } - ] - }, - { - "resourceType": "Patient", - "id": "p2", - "name": [ - { - "use": "nickname", - "family": "f2" - } - ] - }, - { - "resourceType": "Patient", - "id": "p3", - "name": [ - { - "use": "nickname", - "given": ["g3"], - "family": "f3" - } - ] - }, - { - "resourceType": "Observation", - "id": "o1", - "valueInteger": 12 - }, - { - "resourceType": "Observation", - "id": "o2", - "valueInteger": 10 - } - ], - "tests": [ - { - "title": "simple where path with result", - "view": { - "resource": "Patient", - "select": [{ "path": "id" }], - "where": [{ "path": "name.use = 'official'" }] - }, - "expect": [{ "id": "p1" }] - }, - { - "title": "simple where path with no results", - "view": { - "resource": "Patient", - "select": [{ "path": "id" }], - "where": [{ "path": "name.use = 'maiden'" }] - }, - "expect": [] - }, - { - "title": "simple where path with greater than inequality", - "view": { - "resource": "Observation", - "select": [{ "path": "id" }], - "where": [{ "path": "value.ofType(integer) > 11" }] - }, - "expect": [{ "id": "o1" }] - }, - { - "title": "simple where path with less than inequality", - "view": { - "resource": "Observation", - "select": [{ "path": "id" }], - "where": [{ "path": "valueInteger < 11" }] - }, - "expect": [{ "id": "o2" }] - }, - { - "title": "multiple where paths", - "view": { - "resource": "Patient", - "select": [{ "path": "id" }], - "where": [ - { "path": "name.use = 'official'" }, - { "path": "name.family = 'f1'" } - ] - }, - "expect": [{ "id": "p1" }] - }, - { - "title": "where path with an 'and' connector", - "view": { - "resource": "Patient", - "select": [{ "path": "id" }], - "where": [{ "path": "name.use = 'official' and name.family = 'f1'" }] - }, - "expect": [{ "id": "p1" }] - }, - { - "title": "where path with an 'or' connector", - "view": { - "resource": "Patient", - "select": [{ "path": "id" }], - "where": [{ "path": "name.use = 'official' or name.family = 'f2'" }] - }, - "expect": [{ "id": "p1" }, { "id": "p2" }] - }, - { - "title": "where path with a where function (and)", - "view": { - "resource": "Patient", - "select": [{ "path": "id" }], - "where": [{ "path": "name.exists(use = 'official' and family = 'f1')" }] - }, - "expect": [{ "id": "p1" }] - }, - { - "title": "where path with a where function (or)", - "view": { - "resource": "Patient", - "select": [{ "path": "id" }], - "where": [{ "path": "name.exists(use = 'official' or family = 'f2')" }] - }, - "expect": [{ "id": "p1" }, { "id": "p2" }] - } - ] -} diff --git a/sql-on-fhir b/sql-on-fhir index 25c2f314fb..6264de32c7 160000 --- a/sql-on-fhir +++ b/sql-on-fhir @@ -1 +1 @@ -Subproject commit 25c2f314fba79cefa4c4bf1c9de4aeb6e5e2204c +Subproject commit 6264de32c79dd121c64153e1a5a517ff3d487ea7 From 764b7adc5203e67d2be4be30cc3512913fd9cfa9 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Tue, 3 Oct 2023 11:44:01 +1000 Subject: [PATCH 92/95] Updating the sql-on-fhir pointer after merging changes from the main repostory. --- sql-on-fhir | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql-on-fhir b/sql-on-fhir index 6264de32c7..b2b3e77201 160000 --- a/sql-on-fhir +++ b/sql-on-fhir @@ -1 +1 @@ -Subproject commit 6264de32c79dd121c64153e1a5a517ff3d487ea7 +Subproject commit b2b3e77201d7e5a79a5c6651829e3246da149170 From 1e1e306ec39350370f718f01c0ce13ae4ad3f50e Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Tue, 3 Oct 2023 11:50:29 +1000 Subject: [PATCH 93/95] Replacing 'where()' with 'exists()' in FHIRView where clauses. --- sql-on-fhir | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql-on-fhir b/sql-on-fhir index b2b3e77201..cd1c15de93 160000 --- a/sql-on-fhir +++ b/sql-on-fhir @@ -1 +1 @@ -Subproject commit b2b3e77201d7e5a79a5c6651829e3246da149170 +Subproject commit cd1c15de939d52ddaf4bca80dd47b5077379ed49 From 272b301d0410d1cb54db29c36bfca54862cbb41c Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Tue, 3 Oct 2023 12:33:54 +1000 Subject: [PATCH 94/95] Adding support for FHIR operations on singular ResoureceCollections, to allow FHIRView paths like: forEach = 'where(gender='male').name, --- .../security/ga4gh/PassportScopeEnforcer.java | 4 +- .../fhirpath/parser/AbstractParserTest.java | 6 +- .../fhirpath/collection/Collection.java | 7 +- .../collection/IntegerCollection.java | 4 +- .../collection/ResourceCollection.java | 41 +++++-- .../fhirpath/collection/StringCollection.java | 2 +- .../pathling/fhirpath/column/ColumnCtx.java | 36 +++--- .../fhirpath/column/SingleRowCtx.java | 54 +++++++++ .../fhirpath/column/StdColumnCtx.java | 54 +++++++++ .../fhirpath/function/ColumnFunctions.java | 17 ++- .../csiro/pathling/views/DatasetContext.java | 5 +- .../pathling/views/FhirViewExecutor.java | 4 +- .../test/resources/viewTests/resource.json | 108 ++++++++++++++++++ 13 files changed, 285 insertions(+), 57 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/SingleRowCtx.java create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/StdColumnCtx.java create mode 100644 fhirpath/src/test/resources/viewTests/resource.json diff --git a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java index b13093444f..913070423b 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java +++ b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportScopeEnforcer.java @@ -84,8 +84,8 @@ public Dataset enforce(@Nonnull final ResourceType subjectResource, // Build a new expression parser, and parse all the column expressions within the query. final ResourceCollection inputContext = ResourceCollection - .build(getFhirContext(), getDataSource().read(subjectResource), subjectResource, - false); + .build(getFhirContext(), getDataSource().read(subjectResource), subjectResource + ); return filterDataset(inputContext, filters, dataset, dataset.col("id"), Column::or); } } diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java index 4a52120ac7..cca418982e 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/AbstractParserTest.java @@ -37,13 +37,11 @@ import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; -import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Enumerations.ResourceType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; -import java.util.Collections; @SpringBootUnitTest @ExtendWith(TimingExtension.class) @@ -87,7 +85,7 @@ void setUp() { @SuppressWarnings("SameParameterValue") void setSubjectResource(@Nonnull final ResourceType resourceType) { final ResourceCollection subjectResource = ResourceCollection - .build(fhirContext, dataSource.read(resourceType), resourceType, false); + .build(fhirContext, dataSource.read(resourceType), resourceType); evaluationContext = new EvaluationContextBuilder(spark, fhirContext) .dataset(dataSource.read(resourceType)) @@ -108,7 +106,7 @@ void mockResource(final ResourceType... resourceTypes) { protected FhirPathAssertion assertThatResultOf(@Nonnull final ResourceType resourceType, @Nonnull final String expression) { final ResourceCollection subjectResource = ResourceCollection - .build(fhirContext, dataSource.read(resourceType), resourceType, false); + .build(fhirContext, dataSource.read(resourceType), resourceType); final EvaluationContext evaluationContext = new EvaluationContextBuilder(spark, fhirContext) .dataset(dataSource.read(resourceType)) 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 44756eab45..76311f5988 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 @@ -23,6 +23,7 @@ import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.Numeric; import au.csiro.pathling.fhirpath.column.ColumnCtx; +import au.csiro.pathling.fhirpath.column.StdColumnCtx; import au.csiro.pathling.fhirpath.definition.ChildDefinition; import au.csiro.pathling.fhirpath.definition.ChoiceChildDefinition; import au.csiro.pathling.fhirpath.definition.ElementChildDefinition; @@ -243,7 +244,7 @@ protected Optional traverseExtensions( return getExtensionMap().map(em -> Collection.build( // We need here to deal with the situation where _fid is an array of element ids - ColumnCtx.of(getFid()).transform(em::apply).flatten().getValue(), + StdColumnCtx.of(getFid()).transform(em::apply).flatten().getValue(), (ElementDefinition) extensionDefinition)); } @@ -317,7 +318,7 @@ public Collection copyWith(@Nonnull final Column newValue) { @Nonnull public Collection copyWith(@Nonnull final ColumnCtx newValue) { - return copyWith(newValue.getValue()); + return copyWith((newValue).getValue()); } @Nonnull @@ -332,7 +333,7 @@ public Column getSingleton() { @Nonnull // TODO: Find a better name public ColumnCtx getCtx() { - return ColumnCtx.of(getColumn()); + return StdColumnCtx.of(getColumn()); } } 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 d9e835748c..527797667f 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 @@ -81,8 +81,8 @@ public static IntegerCollection build(@Nonnull final Column column) { @Nonnull - public static IntegerCollection build(ColumnCtx count) { - return build(count.getValue()); + public static IntegerCollection build(final ColumnCtx columnCtx) { + return build(columnCtx.getValue()); } /** 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 227fc92a2b..18ab8dd7b2 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 @@ -21,6 +21,8 @@ import au.csiro.pathling.encoders.ExtensionSupport; import au.csiro.pathling.fhirpath.FhirPathType; import au.csiro.pathling.fhirpath.column.ColumnCtx; +import au.csiro.pathling.fhirpath.column.SingleRowCtx; +import au.csiro.pathling.fhirpath.column.StdColumnCtx; import au.csiro.pathling.fhirpath.definition.ElementDefinition; import au.csiro.pathling.fhirpath.definition.NodeDefinition; import au.csiro.pathling.fhirpath.definition.ResourceDefinition; @@ -39,6 +41,7 @@ import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; +import org.apache.spark.sql.functions; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType; import org.hl7.fhir.r4.model.Enumerations.ResourceType; @@ -74,7 +77,7 @@ protected ResourceCollection(@Nonnull final Column column, @Nonnull final Optional type, @Nonnull final Optional fhirType, @Nonnull final Optional definition, - final boolean singular, @Nonnull final Map elementsToColumns, + @Nonnull final Map elementsToColumns, @Nonnull final ResourceDefinition resourceDefinition, @Nonnull final Dataset dataset) { super(column, type, fhirType, definition); @@ -98,13 +101,11 @@ private static Optional getFhirType(@Nonnull final ResourceType * @param fhirContext the {@link FhirContext} to use for sourcing the resource definition * @param dataset the {@link Dataset} that contains the resource data * @param resourceType the type of the resource - * @param singular whether the resource is singular * @return A shiny new ResourcePath */ @Nonnull public static ResourceCollection build(@Nonnull final FhirContext fhirContext, - @Nonnull final Dataset dataset, @Nonnull final ResourceType resourceType, - final boolean singular) { + @Nonnull final Dataset dataset, @Nonnull final ResourceType resourceType) { // Get the resource definition from HAPI. final String resourceCode = resourceType.toCode(); @@ -116,9 +117,10 @@ public static ResourceCollection build(@Nonnull final FhirContext fhirContext, final Map elementsToColumns = Stream.of(dataset.columns()) .collect(Collectors.toUnmodifiableMap(Function.identity(), dataset::col)); - // We use the ID column as the value column for a ResourcePath. - return new ResourceCollection(dataset.col("id"), Optional.empty(), - getFhirType(resourceType), Optional.of(definition), singular, elementsToColumns, definition, + // We use a literal column as the resource value - the actual value is not important. + // But the non-null value indicates that the resource should be included in any result. + return new ResourceCollection(functions.lit(true), Optional.empty(), + getFhirType(resourceType), Optional.of(definition), elementsToColumns, definition, dataset); } @@ -171,14 +173,31 @@ protected Collection traverseElement(@Nonnull final ElementDefinition childDef) // TODO: what does mean if an element is present in the definition but not in // the schema? return getElementColumn(childDef.getElementName()).map( - value -> Collection.build(value, childDef)).get(); + value -> Collection.build( + // TODO: simplify this + functions.when(getCtx().getValue().isNotNull(), value), + childDef)).get(); } - + + + @Nonnull + @Override + public Collection copyWith(@Nonnull final Column newValue) { + return new ResourceCollection(newValue, getType(), getFhirType(), getDefinition(), + elementsToColumns, resourceDefinition, dataset); + } + @Nonnull - public ColumnCtx getKeyColumn() { + public StdColumnCtx getKeyColumn() { return getElementColumn("id_versioned") - .map(ColumnCtx::of) + .map(StdColumnCtx::of) .orElseThrow( () -> new IllegalStateException("Resource does not have an 'id_versioned' column")); } + + @Nonnull + @Override + public ColumnCtx getCtx() { + return SingleRowCtx.of(getColumn()); + } } 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 2afb059cb2..82ca426706 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 @@ -122,7 +122,7 @@ public String toLiteralValue() { .orElseThrow(() -> new IllegalStateException( "Cannot convert column to literal value: " + getColumn())); } - + /** * Gets a value from a row for a String or String literal. * diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java index 236c5331e8..c0704caf63 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/ColumnCtx.java @@ -17,26 +17,28 @@ package au.csiro.pathling.fhirpath.column; -import au.csiro.pathling.encoders.ValueFunctions; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.functions; +import javax.annotation.Nonnull; import java.util.function.BiFunction; import java.util.function.Function; -import javax.annotation.Nonnull; -import lombok.Value; -import org.apache.spark.sql.Column; -import org.apache.spark.sql.functions; -@Value(staticConstructor = "of") -public class ColumnCtx { +public abstract class ColumnCtx { + + public abstract Column getValue(); + + @Nonnull + public abstract ColumnCtx vectorize(@Nonnull final Function arrayExpression, + @Nonnull final Function singularExpression); - Column value; @Nonnull - public ColumnCtx vectorize(@Nonnull final Function arrayExpression, - @Nonnull final Function singularExpression) { - return of(ValueFunctions.ifArray(value, arrayExpression::apply, singularExpression::apply)); - } + public abstract ColumnCtx flatten(); + + @Nonnull + public abstract ColumnCtx traverse(@Nonnull final String fieldName); @Nonnull public ColumnCtx vectorize(@Nonnull final Function arrayExpression) { @@ -45,16 +47,6 @@ public ColumnCtx vectorize(@Nonnull final Function arrayExpressi c -> arrayExpression.apply(functions.when(c.isNotNull(), functions.array(c)))); } - @Nonnull - public ColumnCtx flatten() { - return of(ValueFunctions.unnest(value)); - } - - @Nonnull - public ColumnCtx traverse(@Nonnull final String fieldName) { - return of(ValueFunctions.unnest(value.getField(fieldName))); - } - @Nonnull public ColumnCtx singular() { return vectorize( diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/SingleRowCtx.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/SingleRowCtx.java new file mode 100644 index 0000000000..418c196006 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/SingleRowCtx.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 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.column; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.functions; +import javax.annotation.Nonnull; +import java.util.function.Function; + +@EqualsAndHashCode(callSuper = true) +@Value(staticConstructor = "of") +public class SingleRowCtx extends ColumnCtx { + + Column value; + + @Override + @Nonnull + public SingleRowCtx vectorize(@Nonnull final Function arrayExpression, + @Nonnull final Function singularExpression) { + return SingleRowCtx.of(singularExpression.apply(value)); + } + + @Override + @Nonnull + public ColumnCtx flatten() { + // TODO: ???? What should it be + throw new UnsupportedOperationException("Cannot flatten a single row"); + } + + @Override + @Nonnull + public ColumnCtx traverse(@Nonnull final String fieldName) { + return StdColumnCtx.of( + functions.when(value.isNotNull(), functions.col(fieldName)) + ); + } +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/StdColumnCtx.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/StdColumnCtx.java new file mode 100644 index 0000000000..ee4cf6a1f7 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/column/StdColumnCtx.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 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.column; + +import au.csiro.pathling.encoders.ValueFunctions; + +import java.util.function.Function; +import javax.annotation.Nonnull; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.apache.spark.sql.Column; + +@EqualsAndHashCode(callSuper = true) +@Value(staticConstructor = "of") +public class StdColumnCtx extends ColumnCtx { + + Column value; + + @Override + @Nonnull + public StdColumnCtx vectorize(@Nonnull final Function arrayExpression, + @Nonnull final Function singularExpression) { + return StdColumnCtx.of( + ValueFunctions.ifArray(value, arrayExpression::apply, singularExpression::apply)); + } + + @Override + @Nonnull + public StdColumnCtx flatten() { + return of(ValueFunctions.unnest(value)); + } + + @Override + @Nonnull + public StdColumnCtx traverse(@Nonnull final String fieldName) { + return of(ValueFunctions.unnest(value.getField(fieldName))); + } +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunctions.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunctions.java index 43cb0a2510..ef4b2ee067 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunctions.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/function/ColumnFunctions.java @@ -17,11 +17,10 @@ package au.csiro.pathling.fhirpath.function; -import au.csiro.pathling.fhirpath.column.ColumnCtx; +import au.csiro.pathling.fhirpath.column.StdColumnCtx; import au.csiro.pathling.fhirpath.validation.FhirpathFunction; import au.csiro.pathling.fhirpath.validation.Numeric; import au.csiro.pathling.fhirpath.validation.ReturnType; -import au.csiro.pathling.sql.misc.TemporalDifferenceFunction; import java.util.function.Function; import javax.annotation.Nonnull; import org.apache.spark.sql.Column; @@ -33,7 +32,7 @@ public class ColumnFunctions { @FhirpathFunction @ReturnType(FHIRDefinedType.BOOLEAN) public static Column not(@Nonnull final Column column) { - return ColumnCtx.of(column).vectorize( + return StdColumnCtx.of(column).vectorize( c -> functions.when(c.isNotNull(), functions.transform(c, functions::not)), c -> functions.when(c.isNotNull(), functions.not(c)) ).getValue(); @@ -43,27 +42,27 @@ public static Column not(@Nonnull final Column column) { //@FhirpathFunction @ReturnType(FHIRDefinedType.BOOLEAN) public static Column empty(@Nonnull final Column column) { - return ColumnCtx.of(column).empty().getValue(); + return StdColumnCtx.of(column).empty().getValue(); } //@FhirpathFunction @ReturnType(FHIRDefinedType.INTEGER) public static Column count(@Nonnull final Column column) { - return ColumnCtx.of(column).count().getValue(); + return StdColumnCtx.of(column).count().getValue(); } //@FhirpathFunction public static Column first(@Nonnull final Column column) { // how to deal with nulls inside the expressioss? // technically should use filter to remove nulls, but that's expensive - return ColumnCtx.of(column).first().getValue(); + return StdColumnCtx.of(column).first().getValue(); } @FhirpathFunction public static Column last(@Nonnull final Column column) { // we need to use `element_at()` here are `getItem()` does not support column arguments // NOTE: `element_at()` is 1-indexed as opposed to `getItem()` which is 0-indexed - return ColumnCtx.of(column).vectorize( + return StdColumnCtx.of(column).vectorize( c -> functions.when(c.isNull().or(functions.size(c).equalTo(0)), null) .otherwise(functions.element_at(c, functions.size(c))), Function.identity() @@ -72,12 +71,12 @@ public static Column last(@Nonnull final Column column) { @FhirpathFunction public static Column singular(@Nonnull final Column column) { - return ColumnCtx.of(column).singular().getValue(); + return StdColumnCtx.of(column).singular().getValue(); } @FhirpathFunction public static Column sum(@Nonnull @Numeric final Column column) { - return ColumnCtx.of(column).aggregate(0, Column::plus).getValue(); + return StdColumnCtx.of(column).aggregate(0, Column::plus).getValue(); } // // @ReturnType(FHIRDefinedType.INTEGER) diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/DatasetContext.java b/fhirpath/src/main/java/au/csiro/pathling/views/DatasetContext.java index 23506a5e5c..55e38fe849 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/DatasetContext.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/DatasetContext.java @@ -51,9 +51,12 @@ public class DatasetContext { * @return the materialized column */ @Nonnull - public Column materialize(@Nonnull final Column column) { + public Column materialize(@Nonnull final Column column, final boolean removeNulls) { final String materializedColumnName = randomAlias(); dataset = dataset.withColumn(materializedColumnName, column); + if (removeNulls) { + dataset = dataset.filter(dataset.col(materializedColumnName).isNotNull()); + } return dataset.col(materializedColumnName); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java index 28ce4289dd..612429f3f7 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirViewExecutor.java @@ -86,7 +86,7 @@ private Collection internalEvalExpression(@Nonnull final String expression) { @Nonnull private Collection unnest(@Nonnull final Collection collection) { return collection.copyWith( - datasetContext.materialize(collection.getCtx().explode().getValue())); + datasetContext.materialize(collection.getCtx().explode().getValue(), true)); } @Nonnull @@ -140,7 +140,7 @@ private ExecutionContextImpl newExecutionContext(@Nonnull final FhirView view) { final ResourceType resourceType = ResourceType.fromCode(view.getResource()); final Dataset dataset = dataSource.read(resourceType); final ResourceCollection inputContext = ResourceCollection.build(fhirContext, dataset, - resourceType, false); + resourceType); final EvaluationContext evaluationContext = new EvaluationContext(inputContext, inputContext, fhirContext, sparkSession, dataset, StaticFunctionRegistry.getInstance(), terminologyServiceFactory, constantReplacer); diff --git a/fhirpath/src/test/resources/viewTests/resource.json b/fhirpath/src/test/resources/viewTests/resource.json new file mode 100644 index 0000000000..37be2fe561 --- /dev/null +++ b/fhirpath/src/test/resources/viewTests/resource.json @@ -0,0 +1,108 @@ +{ + "title": "where function and element", + "resources": [ + { + "resourceType": "Patient", + "id": "p1", + "gender": "male", + "name": [ + { + "use": "official", + "family": "f1" + } + ] + }, + { + "resourceType": "Patient", + "id": "p2", + "gender": "female", + "name": [ + { + "use": "nickname", + "family": "f2" + } + ] + } + ], + "tests": [ + { + "title": "count() on resource", + "view": { + "resource": "Patient", + "select": [ + { + "path": "id", + "alias": "id" + }, + { + "path": "count()", + "alias": "count" + } + ] + }, + "expect": [ + { + "id": "p1", + "count": 1 + }, + { + "id": "p2", + "count": 1 + } + ] + }, + { + "title": "count() on resource with where", + "view": { + "resource": "Patient", + "select": [ + { + "path": "id", + "alias": "id" + }, + { + "path": "where(gender='male').count()", + "alias": "count" + } + ] + }, + "expect": [ + { + "id": "p1", + "count": 1 + }, + { + "id": "p2", + "count": 0 + } + ] + }, + { + "title": "traversal on resource with where", + "view": { + "resource": "Patient", + "select": [ + { + "path": "id", + "alias": "id" + }, + { + "path": "where(gender='male').name.family", + "alias": "familyName" + } + ] + }, + "expect": [ + { + "id": "p1", + "familyName": "f1" + }, + { + "id": "p2", + "familyName": null + } + ] + } + + ] +} From d306c6907fecac16d0a9ed818959825d546ca477 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Tue, 3 Oct 2023 14:19:49 +1000 Subject: [PATCH 95/95] Updating test cases for 'forEach' removing the null rows from the final result. --- .../test/resources/viewTests/resource.json | 72 +++++++++++++++++-- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/fhirpath/src/test/resources/viewTests/resource.json b/fhirpath/src/test/resources/viewTests/resource.json index 37be2fe561..108f7c5d59 100644 --- a/fhirpath/src/test/resources/viewTests/resource.json +++ b/fhirpath/src/test/resources/viewTests/resource.json @@ -8,7 +8,11 @@ "name": [ { "use": "official", - "family": "f1" + "family": "f1", + "given": [ + "g1.1", + "g1.2" + ] } ] }, @@ -19,7 +23,11 @@ "name": [ { "use": "nickname", - "family": "f2" + "family": "f2", + "given": [ + "g2.1", + "g2.2" + ] } ] } @@ -61,7 +69,7 @@ "alias": "id" }, { - "path": "where(gender='male').count()", + "path": "where(gender='male').empty()", "alias": "count" } ] @@ -69,11 +77,11 @@ "expect": [ { "id": "p1", - "count": 1 + "count": false }, { "id": "p2", - "count": 0 + "count": true } ] }, @@ -102,7 +110,59 @@ "familyName": null } ] + }, + { + "title": "for each traversal on resource with where", + "view": { + "resource": "Patient", + "select": [ + { + "path": "id", + "alias": "id" + }, + { + "forEach": "where(gender='male').name.given", + "select": [ + { + "path": "$this", + "alias": "givenName" + } + ] + } + ] + }, + "expect": [ + { + "id": "p1", + "givenName": "g1.1" + }, + { + "id": "p1", + "givenName": "g1.2" + } + ] + }, + { + "title": "for each traversal on resource with where", + "view": { + "resource": "Patient", + "select": [ + { + "forEach": "where(gender='male')", + "select": [ + { + "path": "id", + "alias": "id" + } + ] + } + ] + }, + "expect": [ + { + "id": "p1" + } + ] } - ] }