Search in sources :

Example 1 with ParserContext

use of au.csiro.pathling.fhirpath.parser.ParserContext 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");
}
Also used : FhirPath(au.csiro.pathling.fhirpath.FhirPath) ElementPath(au.csiro.pathling.fhirpath.element.ElementPath) StringLiteralPath(au.csiro.pathling.fhirpath.literal.StringLiteralPath) ParserContext(au.csiro.pathling.fhirpath.parser.ParserContext)

Example 2 with ParserContext

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

the class SubsumesFunction method validateInput.

private void validateInput(@Nonnull final NamedFunctionInput input) {
    final ParserContext context = input.getContext();
    checkUserInput(context.getTerminologyServiceFactory().isPresent(), "Attempt to call terminology function " + functionName + " when terminology service has not been configured");
    checkUserInput(input.getArguments().size() == 1, functionName + " function accepts one argument of type Coding or CodeableConcept");
    validateExpressionType(input.getInput(), "input");
    validateExpressionType(input.getArguments().get(0), "argument");
}
Also used : ParserContext(au.csiro.pathling.fhirpath.parser.ParserContext)

Example 3 with ParserContext

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

the class TranslateFunction method invoke.

@Nonnull
@Override
public FhirPath invoke(@Nonnull final NamedFunctionInput input) {
    validateInput(input);
    final ElementPath inputPath = (ElementPath) input.getInput();
    final ParserContext inputContext = input.getContext();
    final Column idColumn = inputPath.getIdColumn();
    final Column conceptColumn = inputPath.getValueColumn();
    final boolean isCodeableConcept = isCodeableConcept(inputPath);
    final Column codingArrayCol = isCodeableConcept ? conceptColumn.getField("coding") : when(conceptColumn.isNotNull(), array(conceptColumn)).otherwise(lit(null));
    // The definition of the result is always the Coding element.
    @SuppressWarnings("OptionalGetWithoutIsPresent") final ElementDefinition resultDefinition = isCodeableConcept ? inputPath.getChildElement("coding").get() : inputPath.getDefinition().get();
    // Prepare the data which will be used within the map operation. All of these things must be
    // Serializable.
    @SuppressWarnings("OptionalGetWithoutIsPresent") final TerminologyServiceFactory terminologyServiceFactory = inputContext.getTerminologyServiceFactory().get();
    final Arguments arguments = Arguments.of(input);
    final String conceptMapUrl = arguments.getValue(0, String.class);
    final boolean reverse = arguments.getValueOr(1, DEFAULT_REVERSE);
    final String equivalence = arguments.getValueOr(2, DEFAULT_EQUIVALENCE);
    final Dataset<Row> dataset = inputPath.getDataset();
    final MapperWithPreview<List<SimpleCoding>, Row[], ConceptTranslator> mapper = new TranslateMapperWithPreview(MDC.get("requestId"), terminologyServiceFactory, conceptMapUrl, reverse, Strings.parseCsvList(equivalence, wrapInUserInputError(ConceptMapEquivalence::fromCode)));
    final Dataset<Row> translatedDataset = SqlExtensions.mapWithPartitionPreview(dataset, codingArrayCol, SimpleCodingsDecoders::decodeList, mapper, StructField.apply("result", DataTypes.createArrayType(CodingEncoding.DATA_TYPE), true, Metadata.empty()));
    // The result is an array of translations per each input element, which we now
    // need to explode in the same way as for path traversal, creating unique element ids.
    final MutablePair<Column, Column> valueAndEidColumns = new MutablePair<>();
    final Dataset<Row> resultDataset = inputPath.explodeArray(translatedDataset, translatedDataset.col("result"), valueAndEidColumns);
    // Construct a new result expression.
    final String expression = expressionFromInput(input, NAME);
    return ElementPath.build(expression, resultDataset, idColumn, Optional.of(valueAndEidColumns.getRight()), valueAndEidColumns.getLeft(), false, inputPath.getCurrentResource(), inputPath.getThisColumn(), resultDefinition);
}
Also used : TerminologyServiceFactory(au.csiro.pathling.fhir.TerminologyServiceFactory) MutablePair(org.apache.commons.lang3.tuple.MutablePair) ElementPath(au.csiro.pathling.fhirpath.element.ElementPath) Column(org.apache.spark.sql.Column) ConceptTranslator(au.csiro.pathling.terminology.ConceptTranslator) SimpleCodingsDecoders(au.csiro.pathling.fhirpath.encoding.SimpleCodingsDecoders) List(java.util.List) ConceptMapEquivalence(org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence) ElementDefinition(au.csiro.pathling.fhirpath.element.ElementDefinition) Row(org.apache.spark.sql.Row) ParserContext(au.csiro.pathling.fhirpath.parser.ParserContext) Nonnull(javax.annotation.Nonnull)

Example 4 with ParserContext

use of au.csiro.pathling.fhirpath.parser.ParserContext 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));
}
Also used : FhirPath(au.csiro.pathling.fhirpath.FhirPath) StringLiteralPath(au.csiro.pathling.fhirpath.literal.StringLiteralPath) ParserContext(au.csiro.pathling.fhirpath.parser.ParserContext) BooleanLiteralPath(au.csiro.pathling.fhirpath.literal.BooleanLiteralPath)

Example 5 with ParserContext

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

Aggregations

ParserContext (au.csiro.pathling.fhirpath.parser.ParserContext)75 ParserContextBuilder (au.csiro.pathling.test.builders.ParserContextBuilder)63 Test (org.junit.jupiter.api.Test)53 SpringBootTest (org.springframework.boot.test.context.SpringBootTest)53 ElementPath (au.csiro.pathling.fhirpath.element.ElementPath)52 ElementPathBuilder (au.csiro.pathling.test.builders.ElementPathBuilder)49 FhirPath (au.csiro.pathling.fhirpath.FhirPath)39 Row (org.apache.spark.sql.Row)37 InvalidUserInputError (au.csiro.pathling.errors.InvalidUserInputError)31 DatasetBuilder (au.csiro.pathling.test.builders.DatasetBuilder)26 ResourcePath (au.csiro.pathling.fhirpath.ResourcePath)25 StringLiteralPath (au.csiro.pathling.fhirpath.literal.StringLiteralPath)21 NamedFunctionInput (au.csiro.pathling.fhirpath.function.NamedFunctionInput)17 ResourcePathBuilder (au.csiro.pathling.test.builders.ResourcePathBuilder)14 ResourceDatasetBuilder (au.csiro.pathling.test.builders.ResourceDatasetBuilder)13 ElementDefinition (au.csiro.pathling.fhirpath.element.ElementDefinition)12 Column (org.apache.spark.sql.Column)11 Nonnull (javax.annotation.Nonnull)9 NonLiteralPath (au.csiro.pathling.fhirpath.NonLiteralPath)6 BooleanLiteralPath (au.csiro.pathling.fhirpath.literal.BooleanLiteralPath)5