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;
}
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));
}
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);
}
Aggregations