Search in sources :

Example 1 with BooleanPath

use of au.csiro.pathling.fhirpath.element.BooleanPath in project pathling by aehrc.

the class WhereFunction method invoke.

@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.getThisValueColumn());
    final Column thisEid = checkPresent(argumentPath.getThisOrderingColumn());
    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());
}
Also used : org.apache.spark.sql.functions.when(org.apache.spark.sql.functions.when) NonLiteralPath(au.csiro.pathling.fhirpath.NonLiteralPath) NamedFunction.expressionFromInput(au.csiro.pathling.fhirpath.function.NamedFunction.expressionFromInput) Column(org.apache.spark.sql.Column) FhirPath(au.csiro.pathling.fhirpath.FhirPath) BooleanPath(au.csiro.pathling.fhirpath.element.BooleanPath) org.apache.spark.sql.functions.lit(org.apache.spark.sql.functions.lit) Nonnull(javax.annotation.Nonnull) Preconditions.checkPresent(au.csiro.pathling.utilities.Preconditions.checkPresent) Preconditions.checkUserInput(au.csiro.pathling.utilities.Preconditions.checkUserInput) BooleanPath(au.csiro.pathling.fhirpath.element.BooleanPath) Column(org.apache.spark.sql.Column) NonLiteralPath(au.csiro.pathling.fhirpath.NonLiteralPath) Nonnull(javax.annotation.Nonnull)

Example 2 with BooleanPath

use of au.csiro.pathling.fhirpath.element.BooleanPath 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 BooleanPath

use of au.csiro.pathling.fhirpath.element.BooleanPath in project pathling by aehrc.

the class NotFunction method invoke.

@Nonnull
@Override
public FhirPath invoke(@Nonnull final NamedFunctionInput input) {
    checkNoArguments(NAME, input);
    final NonLiteralPath inputPath = input.getInput();
    checkUserInput(inputPath instanceof BooleanPath, "Input to not function must be Boolean: " + inputPath.getExpression());
    final String expression = expressionFromInput(input, NAME);
    // The not function is just a thin wrapper over the Spark not function.
    final Column valueColumn = not(inputPath.getValueColumn());
    return ElementPath.build(expression, inputPath.getDataset(), inputPath.getIdColumn(), inputPath.getEidColumn(), valueColumn, inputPath.isSingular(), Optional.empty(), inputPath.getThisColumn(), FHIRDefinedType.BOOLEAN);
}
Also used : BooleanPath(au.csiro.pathling.fhirpath.element.BooleanPath) Column(org.apache.spark.sql.Column) NonLiteralPath(au.csiro.pathling.fhirpath.NonLiteralPath) Nonnull(javax.annotation.Nonnull)

Example 4 with BooleanPath

use of au.csiro.pathling.fhirpath.element.BooleanPath in project pathling by aehrc.

the class ComparisonOperatorDateTest method comparesDates.

@Test
void comparesDates() {
    final Optional<ElementDefinition> optionalLeftDefinition = FhirHelpers.getChildOfResource(fhirContext, "MedicationRequest", "authoredOn");
    assertTrue(optionalLeftDefinition.isPresent());
    final ElementDefinition leftDefinition = optionalLeftDefinition.get();
    final Dataset<Row> leftDataset = new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType).withRow("patient-1", "2013-06-10T15:33:22Z").withRow("patient-2", "2014-09-25T22:04:19+10:00").withRow("patient-3", "2018-05-18T11:03:55-05:00").build();
    final ElementPath leftPath = new ElementPathBuilder(spark).dataset(leftDataset).idAndValueColumns().expression("authoredOn").singular(true).definition(leftDefinition).buildDefined();
    final Optional<ElementDefinition> optionalRightDefinition = FhirHelpers.getChildOfResource(fhirContext, "Condition", "onsetDateTime");
    assertTrue(optionalRightDefinition.isPresent());
    final ElementDefinition rightDefinition = optionalRightDefinition.get();
    final Dataset<Row> rightDataset = new DatasetBuilder(spark).withIdColumn(ID_ALIAS).withColumn(DataTypes.StringType).withRow("patient-1", "2013-06-10T12:33:22Z").withRow("patient-2", "2014-09-25T12:04:19Z").withRow("patient-3", "2018-05-19T11:03:55.123Z").build();
    final ElementPath rightPath = new ElementPathBuilder(spark).dataset(rightDataset).idAndValueColumns().expression("reverseResolve(Condition.subject).onsetDateTime").singular(true).definition(rightDefinition).buildDefined();
    final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).groupingColumns(Collections.singletonList(leftPath.getIdColumn())).build();
    final OperatorInput comparisonInput = new OperatorInput(parserContext, leftPath, rightPath);
    final Operator lessThan = Operator.getInstance("<=");
    final FhirPath result = lessThan.invoke(comparisonInput);
    assertTrue(result instanceof BooleanPath);
    assertThat((ElementPath) result).hasExpression("authoredOn <= reverseResolve(Condition.subject).onsetDateTime").isSingular().hasFhirType(FHIRDefinedType.BOOLEAN);
    final Dataset<Row> expectedDataset = new DatasetBuilder(spark).withIdColumn().withColumn(DataTypes.BooleanType).withRow("patient-1", false).withRow("patient-2", true).withRow("patient-3", true).build();
    assertThat(result).selectOrderedResult().hasRows(expectedDataset);
}
Also used : FhirPath(au.csiro.pathling.fhirpath.FhirPath) BooleanPath(au.csiro.pathling.fhirpath.element.BooleanPath) ParserContextBuilder(au.csiro.pathling.test.builders.ParserContextBuilder) ElementPath(au.csiro.pathling.fhirpath.element.ElementPath) ElementDefinition(au.csiro.pathling.fhirpath.element.ElementDefinition) Row(org.apache.spark.sql.Row) DatasetBuilder(au.csiro.pathling.test.builders.DatasetBuilder) ParserContext(au.csiro.pathling.fhirpath.parser.ParserContext) ElementPathBuilder(au.csiro.pathling.test.builders.ElementPathBuilder) Test(org.junit.jupiter.api.Test) SpringBootTest(org.springframework.boot.test.context.SpringBootTest)

Example 5 with BooleanPath

use of au.csiro.pathling.fhirpath.element.BooleanPath in project pathling by aehrc.

the class QueryExecutor method getFilteredIds.

@Nonnull
private DatasetWithColumn getFilteredIds(@Nonnull final Iterable<String> filters, @Nonnull final ResourcePath inputContext, @Nonnull final BinaryOperator<Column> operator) {
    ResourcePath currentContext = inputContext;
    @Nullable Column filterColumn = null;
    for (final String filter : filters) {
        // Parse the filter expression.
        final ParserContext parserContext = buildParserContext(currentContext, Collections.singletonList(currentContext.getIdColumn()));
        final Parser parser = new Parser(parserContext);
        final FhirPath fhirPath = 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());
        // Add the filter column to the overall filter expression using the supplied operator.
        final Column filterValue = fhirPath.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.getIdColumn(), currentContext.getEidColumn(), currentContext.getValueColumn(), currentContext.isSingular(), currentContext.getThisColumn());
    }
    checkNotNull(filterColumn);
    // Return a dataset of filtered IDs with an aliased ID column, ready for joining.
    final String filterIdAlias = randomAlias();
    final Dataset<Row> dataset = currentContext.getDataset().select(currentContext.getIdColumn().alias(filterIdAlias));
    return new DatasetWithColumn(dataset.filter(filterColumn), col(filterIdAlias));
}
Also used : ResourcePath(au.csiro.pathling.fhirpath.ResourcePath) FhirPath(au.csiro.pathling.fhirpath.FhirPath) DatasetWithColumn(au.csiro.pathling.QueryHelpers.DatasetWithColumn) Column(org.apache.spark.sql.Column) DatasetWithColumn(au.csiro.pathling.QueryHelpers.DatasetWithColumn) BooleanPath(au.csiro.pathling.fhirpath.element.BooleanPath) Row(org.apache.spark.sql.Row) ParserContext(au.csiro.pathling.fhirpath.parser.ParserContext) BooleanLiteralPath(au.csiro.pathling.fhirpath.literal.BooleanLiteralPath) Nullable(javax.annotation.Nullable) Parser(au.csiro.pathling.fhirpath.parser.Parser) Nonnull(javax.annotation.Nonnull)

Aggregations

BooleanPath (au.csiro.pathling.fhirpath.element.BooleanPath)9 FhirPath (au.csiro.pathling.fhirpath.FhirPath)7 Nonnull (javax.annotation.Nonnull)7 Column (org.apache.spark.sql.Column)7 Row (org.apache.spark.sql.Row)6 NonLiteralPath (au.csiro.pathling.fhirpath.NonLiteralPath)4 ParserContext (au.csiro.pathling.fhirpath.parser.ParserContext)4 BooleanLiteralPath (au.csiro.pathling.fhirpath.literal.BooleanLiteralPath)3 ResourcePath (au.csiro.pathling.fhirpath.ResourcePath)2 ElementDefinition (au.csiro.pathling.fhirpath.element.ElementDefinition)2 ElementPath (au.csiro.pathling.fhirpath.element.ElementPath)2 Parser (au.csiro.pathling.fhirpath.parser.Parser)2 DatasetBuilder (au.csiro.pathling.test.builders.DatasetBuilder)2 ElementPathBuilder (au.csiro.pathling.test.builders.ElementPathBuilder)2 ParserContextBuilder (au.csiro.pathling.test.builders.ParserContextBuilder)2 Nullable (javax.annotation.Nullable)2 Test (org.junit.jupiter.api.Test)2 SpringBootTest (org.springframework.boot.test.context.SpringBootTest)2 DatasetWithColumn (au.csiro.pathling.QueryHelpers.DatasetWithColumn)1 NonLiteralPath.findEidColumn (au.csiro.pathling.fhirpath.NonLiteralPath.findEidColumn)1