Search in sources :

Example 1 with NonLiteralPath

use of au.csiro.pathling.fhirpath.NonLiteralPath 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;
}
Also used : FhirPath(au.csiro.pathling.fhirpath.FhirPath) ResourceType(org.hl7.fhir.r4.model.Enumerations.ResourceType) ResourcePath(au.csiro.pathling.fhirpath.ResourcePath) DatasetWithColumn(au.csiro.pathling.QueryHelpers.DatasetWithColumn) Column(org.apache.spark.sql.Column) DatasetWithColumn(au.csiro.pathling.QueryHelpers.DatasetWithColumn) ReferencePath(au.csiro.pathling.fhirpath.element.ReferencePath) Row(org.apache.spark.sql.Row) NonLiteralPath(au.csiro.pathling.fhirpath.NonLiteralPath) WindowSpec(org.apache.spark.sql.expressions.WindowSpec) Nonnull(javax.annotation.Nonnull)

Example 2 with NonLiteralPath

use of au.csiro.pathling.fhirpath.NonLiteralPath 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 3 with NonLiteralPath

use of au.csiro.pathling.fhirpath.NonLiteralPath in project pathling by aehrc.

the class SubsumesFunction method invoke.

@Nonnull
@Override
public FhirPath invoke(@Nonnull final NamedFunctionInput input) {
    validateInput(input);
    final NonLiteralPath inputFhirPath = input.getInput();
    final Dataset<Row> idAndCodingSet = createJoinedDataset(input.getInput(), input.getArguments().get(0));
    // Process the subsumption operation per partition, adding a result column to the dataset.
    final Column codingPairCol = struct(idAndCodingSet.col(COL_INPUT_CODINGS), idAndCodingSet.col(COL_ARG_CODINGS));
    @SuppressWarnings({ "OptionalGetWithoutIsPresent", "TypeMayBeWeakened" }) final SubsumptionMapperWithPreview mapper = new SubsumptionMapperWithPreview(MDC.get("requestId"), input.getContext().getTerminologyServiceFactory().get(), inverted);
    final Dataset<Row> resultDataset = SqlExtensions.mapWithPartitionPreview(idAndCodingSet, codingPairCol, SimpleCodingsDecoders::decodeListPair, mapper, StructField.apply("result", DataTypes.BooleanType, true, Metadata.empty()));
    final Column resultColumn = col("result");
    // Construct a new result expression.
    final String expression = expressionFromInput(input, functionName);
    return ElementPath.build(expression, resultDataset, inputFhirPath.getIdColumn(), inputFhirPath.getEidColumn(), resultColumn, inputFhirPath.isSingular(), inputFhirPath.getCurrentResource(), inputFhirPath.getThisColumn(), FHIRDefinedType.BOOLEAN);
}
Also used : Column(org.apache.spark.sql.Column) SimpleCodingsDecoders(au.csiro.pathling.fhirpath.encoding.SimpleCodingsDecoders) Row(org.apache.spark.sql.Row) NonLiteralPath(au.csiro.pathling.fhirpath.NonLiteralPath) Nonnull(javax.annotation.Nonnull)

Example 4 with NonLiteralPath

use of au.csiro.pathling.fhirpath.NonLiteralPath in project pathling by aehrc.

the class CombineOperator method invoke.

@Nonnull
@Override
public FhirPath invoke(@Nonnull final OperatorInput input) {
    final String expression = Operator.buildExpression(input, NAME);
    final FhirPath left = input.getLeft();
    final FhirPath right = input.getRight();
    final Dataset<Row> leftTrimmed = left.getUnionableDataset(right);
    final Dataset<Row> rightTrimmed = right.getUnionableDataset(left);
    final int valueColumnIndex = Arrays.asList(leftTrimmed.columns()).indexOf(left.getValueColumn().toString());
    final Dataset<Row> dataset = leftTrimmed.union(rightTrimmed);
    final String columnName = dataset.columns()[valueColumnIndex];
    final DatasetWithColumn datasetWithColumn = createColumn(dataset, dataset.col("`" + columnName + "`"));
    final Optional<Column> eidColumn = Optional.of(array(monotonically_increasing_id()));
    final Optional<Column> thisColumn = left instanceof NonLiteralPath ? ((NonLiteralPath) left).getThisColumn() : Optional.empty();
    return left.combineWith(right, datasetWithColumn.getDataset(), expression, left.getIdColumn(), eidColumn, datasetWithColumn.getColumn(), false, thisColumn);
}
Also used : FhirPath(au.csiro.pathling.fhirpath.FhirPath) DatasetWithColumn(au.csiro.pathling.QueryHelpers.DatasetWithColumn) QueryHelpers.createColumn(au.csiro.pathling.QueryHelpers.createColumn) DatasetWithColumn(au.csiro.pathling.QueryHelpers.DatasetWithColumn) Column(org.apache.spark.sql.Column) Row(org.apache.spark.sql.Row) NonLiteralPath(au.csiro.pathling.fhirpath.NonLiteralPath) Nonnull(javax.annotation.Nonnull)

Example 5 with NonLiteralPath

use of au.csiro.pathling.fhirpath.NonLiteralPath in project pathling by aehrc.

the class PathTraversalOperator method invoke.

/**
 * Invokes this operator with the specified inputs.
 *
 * @param input A {@link PathTraversalInput} object
 * @return A {@link FhirPath} object representing the resulting expression
 */
@Nonnull
public ElementPath invoke(@Nonnull final PathTraversalInput input) {
    checkUserInput(input.getLeft() instanceof NonLiteralPath, "Path traversal operator cannot be invoked on a literal value: " + input.getLeft().getExpression());
    final NonLiteralPath left = (NonLiteralPath) input.getLeft();
    final String right = input.getRight();
    // If the input expression is the same as the input context, the child will be the start of the
    // expression. This is to account for where we omit the expression that represents the input
    // expression, e.g. "gender" instead of "Patient.gender".
    final String inputContextExpression = input.getContext().getInputContext().getExpression();
    final String expression = left.getExpression().equals(inputContextExpression) ? right : left.getExpression() + "." + right;
    final Optional<ElementDefinition> optionalChild = left.getChildElement(right);
    checkUserInput(optionalChild.isPresent(), "No such child: " + expression);
    final ElementDefinition childDefinition = optionalChild.get();
    final Dataset<Row> leftDataset = left.getDataset();
    final Column field;
    if (ExtensionSupport.EXTENSION_ELEMENT_NAME().equals(right)) {
        // Lookup the extensions by _fid in the extension container.
        field = left.getExtensionContainerColumn().apply(getValueField(left, ExtensionSupport.FID_FIELD_NAME()));
    } else {
        field = getValueField(left, right);
    }
    // If the element has a max cardinality of more than one, it will need to be "exploded" out into
    // multiple rows.
    final boolean maxCardinalityOfOne = childDefinition.getMaxCardinality() == 1;
    final boolean resultSingular = left.isSingular() && maxCardinalityOfOne;
    final Column valueColumn;
    final Optional<Column> eidColumnCandidate;
    final Dataset<Row> resultDataset;
    if (maxCardinalityOfOne) {
        valueColumn = field;
        eidColumnCandidate = left.getEidColumn();
        resultDataset = leftDataset;
    } else {
        final MutablePair<Column, Column> valueAndEidColumns = new MutablePair<>();
        final Dataset<Row> explodedDataset = left.explodeArray(leftDataset, field, valueAndEidColumns);
        final DatasetWithColumnMap datasetWithColumnMap = createColumns(explodedDataset, valueAndEidColumns.getLeft(), valueAndEidColumns.getRight());
        resultDataset = datasetWithColumnMap.getDataset();
        valueColumn = datasetWithColumnMap.getColumn(valueAndEidColumns.getLeft());
        eidColumnCandidate = Optional.of(datasetWithColumnMap.getColumn(valueAndEidColumns.getRight()));
    }
    final Optional<Column> eidColumn = resultSingular ? Optional.empty() : eidColumnCandidate;
    // If there is an element ID column, we need to add it to the parser context so that it can
    // be used within joins in certain situations, e.g. extract.
    eidColumn.ifPresent(c -> input.getContext().getNodeIdColumns().putIfAbsent(expression, c));
    return ElementPath.build(expression, resultDataset, left.getIdColumn(), eidColumn, valueColumn, resultSingular, left.getCurrentResource(), left.getThisColumn(), childDefinition);
}
Also used : MutablePair(org.apache.commons.lang3.tuple.MutablePair) DatasetWithColumnMap(au.csiro.pathling.QueryHelpers.DatasetWithColumnMap) Column(org.apache.spark.sql.Column) ElementDefinition(au.csiro.pathling.fhirpath.element.ElementDefinition) Row(org.apache.spark.sql.Row) NonLiteralPath(au.csiro.pathling.fhirpath.NonLiteralPath) Nonnull(javax.annotation.Nonnull)

Aggregations

NonLiteralPath (au.csiro.pathling.fhirpath.NonLiteralPath)28 Nonnull (javax.annotation.Nonnull)16 Row (org.apache.spark.sql.Row)15 FhirPath (au.csiro.pathling.fhirpath.FhirPath)14 Column (org.apache.spark.sql.Column)14 ElementPathBuilder (au.csiro.pathling.test.builders.ElementPathBuilder)12 Test (org.junit.jupiter.api.Test)12 SpringBootTest (org.springframework.boot.test.context.SpringBootTest)12 ElementPath (au.csiro.pathling.fhirpath.element.ElementPath)9 ResourcePath (au.csiro.pathling.fhirpath.ResourcePath)7 InvalidUserInputError (au.csiro.pathling.errors.InvalidUserInputError)6 UntypedResourcePath (au.csiro.pathling.fhirpath.UntypedResourcePath)6 DatasetBuilder (au.csiro.pathling.test.builders.DatasetBuilder)6 ParserContextBuilder (au.csiro.pathling.test.builders.ParserContextBuilder)6 ResourcePathBuilder (au.csiro.pathling.test.builders.ResourcePathBuilder)6 UntypedResourcePathBuilder (au.csiro.pathling.test.builders.UntypedResourcePathBuilder)6 StringLiteralPath (au.csiro.pathling.fhirpath.literal.StringLiteralPath)5 ParserContext (au.csiro.pathling.fhirpath.parser.ParserContext)5 BooleanPath (au.csiro.pathling.fhirpath.element.BooleanPath)4 ResourceDatasetBuilder (au.csiro.pathling.test.builders.ResourceDatasetBuilder)3