Search in sources :

Example 1 with ResourcePath

use of au.csiro.pathling.fhirpath.ResourcePath in project pathling by aehrc.

the class ReverseResolveFunction method invoke.

@Nonnull
@Override
public FhirPath invoke(@Nonnull final NamedFunctionInput input) {
    checkUserInput(input.getInput() instanceof ResourcePath, "Input to " + NAME + " function must be a resource: " + input.getInput().getExpression());
    final ResourcePath inputPath = (ResourcePath) input.getInput();
    final String expression = NamedFunction.expressionFromInput(input, NAME);
    checkUserInput(input.getArguments().size() == 1, "reverseResolve function accepts a single argument: " + expression);
    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<ResourceType> 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<Row> 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.
    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 ResourcePath currentResource = nonLiteralArgument.getCurrentResource().get();
    final Optional<Column> 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);
    // 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)));
    // 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);
    input.getContext().getNodeIdColumns().putIfAbsent(expression, datasetWithEid.getColumn());
    final ResourcePath result = currentResource.copy(expression, datasetWithEid.getDataset(), inputPath.getIdColumn(), Optional.of(syntheticEid), currentResource.getValueColumn(), false, thisColumn);
    result.setCurrentResource(currentResource);
    return result;
}
Also used : FhirPath(au.csiro.pathling.fhirpath.FhirPath) ResourceType(org.hl7.fhir.r4.model.Enumerations.ResourceType) ResourcePath(au.csiro.pathling.fhirpath.ResourcePath) DatasetWithColumn(au.csiro.pathling.QueryHelpers.DatasetWithColumn) Column(org.apache.spark.sql.Column) DatasetWithColumn(au.csiro.pathling.QueryHelpers.DatasetWithColumn) ReferencePath(au.csiro.pathling.fhirpath.element.ReferencePath) Row(org.apache.spark.sql.Row) NonLiteralPath(au.csiro.pathling.fhirpath.NonLiteralPath) WindowSpec(org.apache.spark.sql.expressions.WindowSpec) Nonnull(javax.annotation.Nonnull)

Example 2 with ResourcePath

use of au.csiro.pathling.fhirpath.ResourcePath in project pathling by aehrc.

the class SearchExecutor method initializeDataset.

@Nonnull
private Dataset<Row> initializeDataset() {
    final ResourcePath resourcePath = ResourcePath.build(getFhirContext(), getDatabase(), subjectResource, subjectResource.toCode(), true, true);
    final Dataset<Row> subjectDataset = resourcePath.getDataset();
    final Column subjectIdColumn = resourcePath.getIdColumn();
    final Dataset<Row> dataset;
    if (filters.isEmpty() || filters.get().getValuesAsQueryTokens().isEmpty()) {
        // If there are no filters, return all resources.
        dataset = subjectDataset;
    } else {
        final Collection<FhirPath> fhirPaths = new ArrayList<>();
        @Nullable Column filterIdColumn = null;
        @Nullable Column filterColumn = null;
        ResourcePath currentContext = ResourcePath.build(getFhirContext(), getDatabase(), subjectResource, subjectResource.toCode(), true);
        // 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);
                // 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(), currentContext.getEidColumn(), fhirPath.getValueColumn(), currentContext.isSingular(), currentContext.getThisColumn());
            }
            // Combine all the columns at this level with AND logic.
            filterColumn = filterColumn == null ? orColumn : filterColumn.and(orColumn);
        }
        checkNotNull(filterIdColumn);
        checkNotNull(filterColumn);
        check(!fhirPaths.isEmpty());
        // Get the full resources which are present in the filtered dataset.
        final String filterIdAlias = randomAlias();
        final Dataset<Row> filteredIds = currentContext.getDataset().select(filterIdColumn.alias(filterIdAlias)).filter(filterColumn);
        dataset = subjectDataset.join(filteredIds, subjectIdColumn.equalTo(col(filterIdAlias)), "left_semi");
    }
    if (getConfiguration().getSpark().getCacheDatasets()) {
        // 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();
    }
    return dataset;
}
Also used : FhirPath(au.csiro.pathling.fhirpath.FhirPath) BooleanPath(au.csiro.pathling.fhirpath.element.BooleanPath) ArrayList(java.util.ArrayList) Parser(au.csiro.pathling.fhirpath.parser.Parser) ResourcePath(au.csiro.pathling.fhirpath.ResourcePath) Column(org.apache.spark.sql.Column) Row(org.apache.spark.sql.Row) StringParam(ca.uhn.fhir.rest.param.StringParam) ParserContext(au.csiro.pathling.fhirpath.parser.ParserContext) BooleanLiteralPath(au.csiro.pathling.fhirpath.literal.BooleanLiteralPath) StringOrListParam(ca.uhn.fhir.rest.param.StringOrListParam) Nullable(javax.annotation.Nullable) Nonnull(javax.annotation.Nonnull)

Example 3 with ResourcePath

use of au.csiro.pathling.fhirpath.ResourcePath in project pathling by aehrc.

the class ElementPath method getInstance.

@Nonnull
private static ElementPath getInstance(@Nonnull final String expression, @Nonnull final Dataset<Row> dataset, @Nonnull final Column idColumn, @Nonnull final Optional<Column> eidColumn, @Nonnull final Column valueColumn, final boolean singular, @Nonnull final Optional<ResourcePath> currentResource, @Nonnull final Optional<Column> thisColumn, @Nonnull final FHIRDefinedType fhirType) {
    // Look up the class that represents an element with the specified FHIR type.
    final Class<? extends ElementPath> elementPathClass = ElementDefinition.elementClassForType(fhirType).orElse(ElementPath.class);
    final DatasetWithColumnMap datasetWithColumns = eidColumn.map(eidCol -> createColumns(dataset, eidCol, valueColumn)).orElseGet(() -> createColumns(dataset, valueColumn));
    try {
        // Call its constructor and return.
        final Constructor<? extends ElementPath> constructor = elementPathClass.getDeclaredConstructor(String.class, Dataset.class, Column.class, Optional.class, Column.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);
    } catch (final NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
        throw new RuntimeException("Problem building an ElementPath class", e);
    }
}
Also used : Getter(lombok.Getter) Dataset(org.apache.spark.sql.Dataset) NonLiteralPath(au.csiro.pathling.fhirpath.NonLiteralPath) FHIRDefinedType(org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType) Column(org.apache.spark.sql.Column) QueryHelpers.createColumns(au.csiro.pathling.QueryHelpers.createColumns) Row(org.apache.spark.sql.Row) Constructor(java.lang.reflect.Constructor) ResourcePath(au.csiro.pathling.fhirpath.ResourcePath) InvocationTargetException(java.lang.reflect.InvocationTargetException) AccessLevel(lombok.AccessLevel) DatasetWithColumnMap(au.csiro.pathling.QueryHelpers.DatasetWithColumnMap) FhirPath(au.csiro.pathling.fhirpath.FhirPath) Optional(java.util.Optional) InvalidUserInputError(au.csiro.pathling.errors.InvalidUserInputError) Nonnull(javax.annotation.Nonnull) DatasetWithColumnMap(au.csiro.pathling.QueryHelpers.DatasetWithColumnMap) InvocationTargetException(java.lang.reflect.InvocationTargetException) Nonnull(javax.annotation.Nonnull)

Example 4 with ResourcePath

use of au.csiro.pathling.fhirpath.ResourcePath in project pathling by aehrc.

the class CountFunctionTest method countsByResourceIdentity.

@Test
void countsByResourceIdentity() {
    final Dataset<Row> 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(database.read(ResourceType.PATIENT)).thenReturn(patientDataset);
    final ResourcePath inputPath = ResourcePath.build(fhirContext, database, ResourceType.PATIENT, "Patient", false);
    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 FhirPath result = count.invoke(countInput);
    final Dataset<Row> 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(IntegerPath.class).hasFhirType(FHIRDefinedType.UNSIGNEDINT).selectOrderedResult().hasRows(expectedDataset);
}
Also used : ResourcePath(au.csiro.pathling.fhirpath.ResourcePath) FhirPath(au.csiro.pathling.fhirpath.FhirPath) ResourceDatasetBuilder(au.csiro.pathling.test.builders.ResourceDatasetBuilder) IntegerPath(au.csiro.pathling.fhirpath.element.IntegerPath) ParserContextBuilder(au.csiro.pathling.test.builders.ParserContextBuilder) Row(org.apache.spark.sql.Row) DatasetBuilder(au.csiro.pathling.test.builders.DatasetBuilder) ResourceDatasetBuilder(au.csiro.pathling.test.builders.ResourceDatasetBuilder) ParserContext(au.csiro.pathling.fhirpath.parser.ParserContext) Test(org.junit.jupiter.api.Test) SpringBootTest(org.springframework.boot.test.context.SpringBootTest)

Example 5 with ResourcePath

use of au.csiro.pathling.fhirpath.ResourcePath in project pathling by aehrc.

the class CountFunctionTest method countsByGrouping.

@Test
void countsByGrouping() {
    final Dataset<Row> 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(database.read(ResourceType.PATIENT)).thenReturn(inputDataset);
    final ResourcePath inputPath = new ResourcePathBuilder(spark).database(database).resourceType(ResourceType.PATIENT).expression("Patient").build();
    final Column groupingColumn = inputPath.getElementColumn("gender");
    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 FhirPath result = count.invoke(countInput);
    final Dataset<Row> expectedDataset = new DatasetBuilder(spark).withColumn(DataTypes.StringType).withColumn(DataTypes.LongType).withRow("female", 2L).withRow("male", 1L).build();
    assertThat(result).hasExpression("count()").isSingular().isElementPath(IntegerPath.class).hasFhirType(FHIRDefinedType.UNSIGNEDINT).selectGroupingResult(Collections.singletonList(groupingColumn)).hasRows(expectedDataset);
}
Also used : FhirPath(au.csiro.pathling.fhirpath.FhirPath) IntegerPath(au.csiro.pathling.fhirpath.element.IntegerPath) ParserContextBuilder(au.csiro.pathling.test.builders.ParserContextBuilder) ResourcePathBuilder(au.csiro.pathling.test.builders.ResourcePathBuilder) ResourcePath(au.csiro.pathling.fhirpath.ResourcePath) ResourceDatasetBuilder(au.csiro.pathling.test.builders.ResourceDatasetBuilder) Column(org.apache.spark.sql.Column) Row(org.apache.spark.sql.Row) DatasetBuilder(au.csiro.pathling.test.builders.DatasetBuilder) ResourceDatasetBuilder(au.csiro.pathling.test.builders.ResourceDatasetBuilder) ParserContext(au.csiro.pathling.fhirpath.parser.ParserContext) Test(org.junit.jupiter.api.Test) SpringBootTest(org.springframework.boot.test.context.SpringBootTest)

Aggregations

ResourcePath (au.csiro.pathling.fhirpath.ResourcePath)46 Test (org.junit.jupiter.api.Test)32 SpringBootTest (org.springframework.boot.test.context.SpringBootTest)30 ParserContextBuilder (au.csiro.pathling.test.builders.ParserContextBuilder)28 Row (org.apache.spark.sql.Row)26 ParserContext (au.csiro.pathling.fhirpath.parser.ParserContext)25 FhirPath (au.csiro.pathling.fhirpath.FhirPath)23 ResourcePathBuilder (au.csiro.pathling.test.builders.ResourcePathBuilder)23 ElementPath (au.csiro.pathling.fhirpath.element.ElementPath)16 ElementPathBuilder (au.csiro.pathling.test.builders.ElementPathBuilder)16 ResourceDatasetBuilder (au.csiro.pathling.test.builders.ResourceDatasetBuilder)16 DatasetBuilder (au.csiro.pathling.test.builders.DatasetBuilder)15 InvalidUserInputError (au.csiro.pathling.errors.InvalidUserInputError)14 Column (org.apache.spark.sql.Column)14 UntypedResourcePath (au.csiro.pathling.fhirpath.UntypedResourcePath)11 Nonnull (javax.annotation.Nonnull)11 NonLiteralPath (au.csiro.pathling.fhirpath.NonLiteralPath)8 UntypedResourcePathBuilder (au.csiro.pathling.test.builders.UntypedResourcePathBuilder)8 ElementDefinition (au.csiro.pathling.fhirpath.element.ElementDefinition)5 StringLiteralPath (au.csiro.pathling.fhirpath.literal.StringLiteralPath)4