use of au.csiro.pathling.fhirpath.FhirPath 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;
}
use of au.csiro.pathling.fhirpath.FhirPath 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());
}
use of au.csiro.pathling.fhirpath.FhirPath in project pathling by aehrc.
the class MemberOfFunction method validateInput.
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 FhirPath inputPath = input.getInput();
checkUserInput(inputPath instanceof ElementPath && (((ElementPath) inputPath).getFhirType().equals(FHIRDefinedType.CODING) || ((ElementPath) 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);
checkUserInput(argument instanceof StringLiteralPath, "memberOf function accepts one argument of type String literal");
}
use of au.csiro.pathling.fhirpath.FhirPath in project pathling by aehrc.
the class TranslateFunction method validateInput.
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 FhirPath inputPath = input.getInput();
checkUserInput(TerminologyUtils.isCodingOrCodeableConcept(inputPath), String.format("Input to %s function is of unsupported type: %s", NAME, inputPath.getExpression()));
final List<FhirPath> 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));
}
use of au.csiro.pathling.fhirpath.FhirPath 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;
}
Aggregations