Skip to content

Commit 5c50f69

Browse files
committed
fix: Support string search on HumanName and Address types
String search parameters whose FHIRPath expression resolves to a complex type (HumanName, Address) are now expanded into sub-field expressions. Each sub-field is matched independently and results are OR'd together, fixing DATATYPE_MISMATCH errors from Spark.
1 parent c393488 commit 5c50f69

File tree

9 files changed

+903
-13
lines changed

9 files changed

+903
-13
lines changed

fhirpath/src/main/java/au/csiro/pathling/search/SearchColumnBuilder.java

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright © 2018-2025 Commonwealth Scientific and Industrial Research
2+
* Copyright © 2018-2026 Commonwealth Scientific and Industrial Research
33
* Organisation (CSIRO) ABN 41 687 119 230.
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,7 +21,6 @@
2121

2222
import au.csiro.pathling.fhirpath.FhirPath;
2323
import au.csiro.pathling.fhirpath.collection.Collection;
24-
import au.csiro.pathling.fhirpath.column.ColumnRepresentation;
2524
import au.csiro.pathling.fhirpath.evaluation.CrossResourceStrategy;
2625
import au.csiro.pathling.fhirpath.evaluation.SingleResourceEvaluator;
2726
import au.csiro.pathling.fhirpath.evaluation.SingleResourceEvaluatorBuilder;
@@ -34,6 +33,9 @@
3433
import java.io.IOException;
3534
import java.io.InputStream;
3635
import java.io.UncheckedIOException;
36+
import java.util.List;
37+
import java.util.Map;
38+
import java.util.stream.Stream;
3739
import lombok.Value;
3840
import org.apache.spark.sql.Column;
3941
import org.hl7.fhir.r4.model.Enumerations.FHIRDefinedType;
@@ -78,6 +80,21 @@ public class SearchColumnBuilder {
7880
/** Resource path for the bundled R4 search parameters. */
7981
private static final String R4_REGISTRY_RESOURCE = "/fhir/R4/search-parameters.json";
8082

83+
/**
84+
* Mappings from complex FHIR types to their string sub-fields.
85+
*
86+
* <p>When a string search parameter expression resolves to one of these complex types, the search
87+
* is expanded to match against each sub-field independently. A match on any sub-field satisfies
88+
* the search criterion.
89+
*
90+
* @see <a href="https://hl7.org/fhir/search.html#string">String Search</a>
91+
*/
92+
private static final Map<FHIRDefinedType, List<String>> COMPLEX_TYPE_STRING_SUBFIELDS =
93+
Map.of(
94+
FHIRDefinedType.HUMANNAME, List.of("family", "given", "text", "prefix", "suffix"),
95+
FHIRDefinedType.ADDRESS,
96+
List.of("text", "line", "city", "district", "state", "postalCode", "country"));
97+
8198
/** The FHIR context for resource definitions. */
8299
@Nonnull FhirContext fhirContext;
83100

@@ -261,6 +278,11 @@ private Column buildCriterionFilter(
261278
/**
262279
* Builds a filter expression for a single FHIRPath expression within a search criterion.
263280
*
281+
* <p>When the expression resolves to a complex type with known string sub-fields (HumanName,
282+
* Address), the expression is expanded into multiple sub-field expressions that are each
283+
* evaluated independently. The filter results are OR'd together so that a match on any sub-field
284+
* satisfies the criterion.
285+
*
264286
* @param paramType the search parameter type
265287
* @param criterion the search criterion (for modifier and values)
266288
* @param expression the FHIRPath expression to evaluate
@@ -274,14 +296,11 @@ private Column buildExpressionFilter(
274296
@Nonnull final String expression,
275297
@Nonnull final SingleResourceEvaluator evaluator) {
276298

277-
// Parse the FHIRPath expression
299+
// Parse and evaluate the FHIRPath expression to determine its type.
278300
final FhirPath fhirPath = parser.parse(expression);
279-
280-
// Evaluate the FHIRPath to extract the value column
281301
final Collection result = evaluator.evaluate(fhirPath);
282-
final ColumnRepresentation valueColumn = result.getColumn();
283302

284-
// Get FHIR type from collection - fail if not available
303+
// Get FHIR type from collection - fail if not available.
285304
final FHIRDefinedType fhirType =
286305
result
287306
.getFhirType()
@@ -290,11 +309,32 @@ private Column buildExpressionFilter(
290309
new InvalidSearchParameterException(
291310
"Cannot determine FHIR type for expression: " + expression));
292311

293-
// Get the appropriate filter for the parameter type, modifier, and FHIR type
294-
final SearchFilter filter = getFilterForType(paramType, criterion.getModifier(), fhirType);
312+
// If the expression resolves to a complex type with known string sub-fields, expand into
313+
// sub-field expressions and OR the results together.
314+
final List<String> subFields = COMPLEX_TYPE_STRING_SUBFIELDS.get(fhirType);
315+
if (subFields != null) {
316+
return subFields.stream()
317+
.map(subField -> expression + "." + subField)
318+
.flatMap(
319+
subFieldExpr -> {
320+
final FhirPath subFhirPath = parser.parse(subFieldExpr);
321+
final Collection subResult = evaluator.evaluate(subFhirPath);
322+
// Skip sub-fields that do not exist for this resource type.
323+
if (subResult.getFhirType().isEmpty()) {
324+
return Stream.empty();
325+
}
326+
final SearchFilter filter =
327+
getFilterForType(
328+
paramType, criterion.getModifier(), subResult.getFhirType().get());
329+
return Stream.of(filter.buildFilter(subResult.getColumn(), criterion.getValues()));
330+
})
331+
.reduce(Column::or)
332+
.orElse(lit(false));
333+
}
295334

296-
// Build and return the filter expression
297-
return filter.buildFilter(valueColumn, criterion.getValues());
335+
// Standard path: build filter directly from the evaluated column.
336+
final SearchFilter filter = getFilterForType(paramType, criterion.getModifier(), fhirType);
337+
return filter.buildFilter(result.getColumn(), criterion.getValues());
298338
}
299339

300340
/**

0 commit comments

Comments
 (0)