Search in sources :

Example 1 with Parser

use of au.csiro.pathling.fhirpath.parser.Parser 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 2 with Parser

use of au.csiro.pathling.fhirpath.parser.Parser 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)

Example 3 with Parser

use of au.csiro.pathling.fhirpath.parser.Parser in project pathling by aehrc.

the class AggregateExecutor method buildQuery.

/**
 * @param query an {@link AggregateRequest}
 * @return a {@link ResultWithExpressions}, which includes the uncollected {@link Dataset}
 */
@SuppressWarnings("WeakerAccess")
@Nonnull
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.
    final ResourcePath inputContext = ResourcePath.build(getFhirContext(), getDatabase(), query.getSubjectResource(), query.getSubjectResource().toCode(), true);
    final ParserContext groupingAndFilterContext = buildParserContext(inputContext, Collections.singletonList(inputContext.getIdColumn()));
    final Parser parser = new Parser(groupingAndFilterContext);
    final List<FhirPath> filters = parseFilters(parser, query.getFilters());
    final List<FhirPathAndContext> groupingParseResult = parseMaterializableExpressions(groupingAndFilterContext, query.getGroupings(), "Grouping");
    final List<FhirPath> groupings = groupingParseResult.stream().map(FhirPathAndContext::getFhirPath).collect(Collectors.toList());
    // Join all filter and grouping expressions together.
    final Column idColumn = inputContext.getIdColumn();
    Dataset<Row> groupingsAndFilters = joinExpressionsAndFilters(inputContext, groupings, filters, idColumn);
    // Apply filters.
    groupingsAndFilters = applyFilters(groupingsAndFilters, filters);
    // Remove synthetic fields from struct values (such as _fid) before grouping.
    final DatasetWithColumnMap datasetWithNormalizedGroupings = createColumns(groupingsAndFilters, groupings.stream().map(FhirPath::getValueColumn).map(PathlingFunctions::pruneSyntheticFields).toArray(Column[]::new));
    groupingsAndFilters = datasetWithNormalizedGroupings.getDataset();
    final List<Column> 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.getEidColumn(), inputContext.getValueColumn(), 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<FhirPath> aggregations = parseAggregations(aggregationParser, query.getAggregations());
    // Join the aggregations together, using equality of the grouping column values as the join
    // condition.
    final List<Column> aggregationColumns = aggregations.stream().map(FhirPath::getValueColumn).collect(Collectors.toList());
    final Dataset<Row> joinedAggregations = joinExpressionsByColumns(aggregations, groupingColumns);
    // The final column selection will be the grouping columns, followed by the aggregation
    // columns.
    final List<Column> finalSelection = new ArrayList<>(groupingColumns);
    finalSelection.addAll(aggregationColumns);
    final Dataset<Row> finalDataset = joinedAggregations.select(finalSelection.toArray(new Column[0])).distinct();
    return new ResultWithExpressions(finalDataset, aggregations, groupings, filters);
}
Also used : FhirPath(au.csiro.pathling.fhirpath.FhirPath) PathlingFunctions(au.csiro.pathling.sql.PathlingFunctions) Parser(au.csiro.pathling.fhirpath.parser.Parser) ResourcePath(au.csiro.pathling.fhirpath.ResourcePath) DatasetWithColumnMap(au.csiro.pathling.QueryHelpers.DatasetWithColumnMap) Column(org.apache.spark.sql.Column) Row(org.apache.spark.sql.Row) ParserContext(au.csiro.pathling.fhirpath.parser.ParserContext) Nonnull(javax.annotation.Nonnull)

Aggregations

FhirPath (au.csiro.pathling.fhirpath.FhirPath)3 ResourcePath (au.csiro.pathling.fhirpath.ResourcePath)3 Parser (au.csiro.pathling.fhirpath.parser.Parser)3 ParserContext (au.csiro.pathling.fhirpath.parser.ParserContext)3 Nonnull (javax.annotation.Nonnull)3 Column (org.apache.spark.sql.Column)3 Row (org.apache.spark.sql.Row)3 BooleanPath (au.csiro.pathling.fhirpath.element.BooleanPath)2 BooleanLiteralPath (au.csiro.pathling.fhirpath.literal.BooleanLiteralPath)2 Nullable (javax.annotation.Nullable)2 DatasetWithColumn (au.csiro.pathling.QueryHelpers.DatasetWithColumn)1 DatasetWithColumnMap (au.csiro.pathling.QueryHelpers.DatasetWithColumnMap)1 PathlingFunctions (au.csiro.pathling.sql.PathlingFunctions)1 StringOrListParam (ca.uhn.fhir.rest.param.StringOrListParam)1 StringParam (ca.uhn.fhir.rest.param.StringParam)1 ArrayList (java.util.ArrayList)1