Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7c97266
Handle multiple target types for ReferenceSearchParam
feordin Dec 19, 2025
67e0e5d
Potential fix for code scanning alert no. 3065: Useless assignment to…
feordin Jan 9, 2026
5f15637
Add SQL perf test and documentation for ReferenceResourceTypeId NULL …
Feb 9, 2026
a234c70
Update perf test to use 3 NULL distributions (10%, 30%, 60%)
Feb 9, 2026
925ef11
Fix: use temp table instead of table variable for dynamic SQL scope
Feb 9, 2026
de43a19
Fix XQuery syntax: replace unsupported union operator with RelOp Phys…
Feb 9, 2026
39f8829
Make Part 1 idempotent: skip DB/table/data creation if already populated
Feb 9, 2026
d934a21
Add test results analysis: all variants fail correctness, recommend n…
Feb 9, 2026
56f34df
Fix test data: correlate ReferenceResourceTypeId with SearchParamId p…
Feb 9, 2026
435d4ee
Add Q8-Q11 all-target-types variants; fix test data explanation
Feb 10, 2026
e920705
Add Run 2 results: Q10/Q11 pass correctness, Q11 UNION ALL best for i…
Feb 10, 2026
aeee6d8
Simplify SQL perf test: Q1 vs Q2 only, 20M rows per table
Feb 10, 2026
8b5e535
Add Run 3 results (20M rows): 12/12 PASS, 6x seek improvement, 0 regr…
Feb 10, 2026
2416d25
Add OR IS NULL for multi-type reference filters to prevent dropping u…
Feb 10, 2026
18b6dd4
Remove test results
feordin Feb 10, 2026
e18cea4
Improve SqlQueryGeneratorTests to properly verify both type IDs in ge…
Feb 10, 2026
2f79feb
remove test explanation, (now added to wiki)
feordin Feb 10, 2026
a1f033b
Update expression generator factory for R5
feordin Feb 17, 2026
8ab80dc
Remove problematic mock
feordin Feb 17, 2026
21725e1
fixes for $everything
feordin Feb 17, 2026
b3655e0
handle resource type bug in R5
feordin Feb 18, 2026
96f3b7c
remove OR case
feordin Feb 20, 2026
675fbd8
Remove unnecessary edit to UntypedReferenceRewriter
feordin Feb 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions docs/rest/Data/ReferenceMultipleTargetTypesBundle.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"resource": {
"resourceType": "Patient",
"id": "ref-test-patient1",
"name": [{ "family": "RefTestPatient", "given": ["Alice"] }],
"gender": "female",
"birthDate": "1990-01-15"
},
"request": { "method": "PUT", "url": "Patient/ref-test-patient1" }
},
{
"resource": {
"resourceType": "Patient",
"id": "ref-test-patient2",
"name": [{ "family": "RefTestPatient", "given": ["Bob"] }],
"gender": "male",
"birthDate": "1985-06-20"
},
"request": { "method": "PUT", "url": "Patient/ref-test-patient2" }
},
{
"resource": {
"resourceType": "Group",
"id": "ref-test-group1",
"type": "person",
"actual": true,
"name": "Test Group Alpha",
"member": [
{ "entity": { "reference": "Patient/ref-test-patient1" } }
]
},
"request": { "method": "PUT", "url": "Group/ref-test-group1" }
},
{
"resource": {
"resourceType": "Practitioner",
"id": "ref-test-practitioner1",
"name": [{ "family": "RefTestDoc", "given": ["Carol"] }]
},
"request": { "method": "PUT", "url": "Practitioner/ref-test-practitioner1" }
},
{
"resource": {
"resourceType": "Device",
"id": "ref-test-device1",
"status": "active",
"deviceName": [{ "name": "Test Thermometer", "type": "user-friendly-name" }]
},
"request": { "method": "PUT", "url": "Device/ref-test-device1" }
},
{
"resource": {
"resourceType": "Location",
"id": "ref-test-location1",
"name": "Test Clinic Room 1",
"status": "active"
},
"request": { "method": "PUT", "url": "Location/ref-test-location1" }
},
{
"resource": {
"resourceType": "Encounter",
"id": "ref-test-encounter-patient",
"status": "finished",
"class": { "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", "code": "AMB" },
"subject": { "reference": "Patient/ref-test-patient1" }
},
"request": { "method": "PUT", "url": "Encounter/ref-test-encounter-patient" }
},
{
"resource": {
"resourceType": "Encounter",
"id": "ref-test-encounter-group",
"status": "finished",
"class": { "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", "code": "AMB" },
"subject": { "reference": "Group/ref-test-group1" }
},
"request": { "method": "PUT", "url": "Encounter/ref-test-encounter-group" }
},
{
"resource": {
"resourceType": "Observation",
"id": "ref-test-obs-patient",
"status": "final",
"code": { "coding": [{ "system": "http://loinc.org", "code": "8867-4", "display": "Heart rate" }] },
"subject": { "reference": "Patient/ref-test-patient1" },
"valueQuantity": { "value": 72, "unit": "beats/minute" }
},
"request": { "method": "PUT", "url": "Observation/ref-test-obs-patient" }
},
{
"resource": {
"resourceType": "Observation",
"id": "ref-test-obs-device",
"status": "final",
"code": { "coding": [{ "system": "http://loinc.org", "code": "8310-5", "display": "Body temperature" }] },
"subject": { "reference": "Device/ref-test-device1" },
"valueQuantity": { "value": 36.6, "unit": "C" }
},
"request": { "method": "PUT", "url": "Observation/ref-test-obs-device" }
},
{
"resource": {
"resourceType": "Observation",
"id": "ref-test-obs-location",
"status": "final",
"code": { "coding": [{ "system": "http://loinc.org", "code": "8867-4", "display": "Heart rate" }] },
"subject": { "reference": "Location/ref-test-location1" },
"valueQuantity": { "value": 0, "unit": "beats/minute" }
},
"request": { "method": "PUT", "url": "Observation/ref-test-obs-location" }
},
{
"resource": {
"resourceType": "Condition",
"id": "ref-test-condition1",
"clinicalStatus": { "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", "code": "active" }] },
"verificationStatus": { "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/condition-ver-status", "code": "confirmed" }] },
"code": { "coding": [{ "system": "http://snomed.info/sct", "code": "386661006", "display": "Fever" }] },
"subject": { "reference": "Patient/ref-test-patient1" },
"asserter": { "reference": "Practitioner/ref-test-practitioner1" }
},
"request": { "method": "PUT", "url": "Condition/ref-test-condition1" }
},
{
"resource": {
"resourceType": "Condition",
"id": "ref-test-condition2",
"clinicalStatus": { "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", "code": "active" }] },
"verificationStatus": { "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/condition-ver-status", "code": "confirmed" }] },
"code": { "coding": [{ "system": "http://snomed.info/sct", "code": "25064002", "display": "Headache" }] },
"subject": { "reference": "Patient/ref-test-patient2" },
"asserter": { "reference": "Patient/ref-test-patient1" }
},
"request": { "method": "PUT", "url": "Condition/ref-test-condition2" }
}
]
}
143 changes: 143 additions & 0 deletions docs/rest/ReferenceMultipleTargetTypes.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Tests for PR 5285: Handle multiple target types for ReferenceSearchParam
#
# The UntypedReferenceRewriter now handles reference search parameters with
# multiple target types. Previously, only single-target-type references were
# rewritten. Now, multi-target references get an OR expression over all
# valid target types (plus IS NULL for untyped string references).
#
# Key search parameters exercised:
# - Encounter.subject targets: Patient, Group
# - Observation.subject targets: Patient, Group, Device, Location
# - Condition.asserter targets: Patient, Practitioner, PractitionerRole, RelatedPerson

@hostname = localhost:44348

### Get the bearer token, if authentication is enabled
# @name bearer
POST https://{{hostname}}/connect/token
content-type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=globalAdminServicePrincipal
&client_secret=globalAdminServicePrincipal
&scope=fhir-api

### Setup test data (transaction bundle)
POST https://{{hostname}}
Content-Type: application/json
Authorization: Bearer {{bearer.response.body.access_token}}

< ./Data/ReferenceMultipleTargetTypesBundle.json

###############################################################################
# TEST 1: Untyped reference search with multiple target types
# Encounter.subject targets Patient and Group.
# BEFORE this PR: type filter was NOT added for multi-target params.
# AFTER this PR: an OR filter over (Patient, Group, NULL) is added.
###############################################################################

### 1a. Untyped - should find encounter with Patient subject
GET {{hostname}}/Encounter?subject=ref-test-patient1
Authorization: Bearer {{bearer.response.body.access_token}}

### 1b. Untyped - should find encounter with Group subject
GET {{hostname}}/Encounter?subject=ref-test-group1
Authorization: Bearer {{bearer.response.body.access_token}}

###############################################################################
# TEST 2: Typed reference search (regression - should work same as before)
###############################################################################

### 2a. Typed - Patient subject
GET {{hostname}}/Encounter?subject=Patient/ref-test-patient1
Authorization: Bearer {{bearer.response.body.access_token}}

### 2b. Typed - Group subject
GET {{hostname}}/Encounter?subject=Group/ref-test-group1
Authorization: Bearer {{bearer.response.body.access_token}}

### 2c. Typed - wrong type, should return empty
GET {{hostname}}/Encounter?subject=Device/ref-test-device1
Authorization: Bearer {{bearer.response.body.access_token}}

###############################################################################
# TEST 3: Observation.subject (4 target types: Patient, Group, Device, Location)
# Exercises wider OR expressions.
###############################################################################

### 3a. Untyped - Patient subject
GET {{hostname}}/Observation?subject=ref-test-patient1
Authorization: Bearer {{bearer.response.body.access_token}}

### 3b. Untyped - Device subject
GET {{hostname}}/Observation?subject=ref-test-device1
Authorization: Bearer {{bearer.response.body.access_token}}

### 3c. Untyped - Location subject
GET {{hostname}}/Observation?subject=ref-test-location1
Authorization: Bearer {{bearer.response.body.access_token}}

### 3d. Typed - only Device observations
GET {{hostname}}/Observation?subject=Device/ref-test-device1
Authorization: Bearer {{bearer.response.body.access_token}}

###############################################################################
# TEST 4: Comma-separated (OR) references mixing typed and untyped
# Exercises top-level OR handling + per-operand rewriting.
###############################################################################

### 4a. Two untyped - should find both encounters
GET {{hostname}}/Encounter?subject=ref-test-patient1,ref-test-group1
Authorization: Bearer {{bearer.response.body.access_token}}

### 4b. Mixed typed and untyped - should find both encounters
GET {{hostname}}/Encounter?subject=Patient/ref-test-patient1,ref-test-group1
Authorization: Bearer {{bearer.response.body.access_token}}

### 4c. Multiple untyped across 4 target types
GET {{hostname}}/Observation?subject=ref-test-patient1,ref-test-device1,ref-test-location1
Authorization: Bearer {{bearer.response.body.access_token}}

###############################################################################
# TEST 5: Chained search through multi-target reference
###############################################################################

### 5a. Encounter where subject is a Patient named "RefTestPatient"
GET {{hostname}}/Encounter?subject:Patient.family=RefTestPatient
Authorization: Bearer {{bearer.response.body.access_token}}

### 5b. Encounter where subject is a Group named "Test Group Alpha"
GET {{hostname}}/Encounter?subject:Group.name=Test Group Alpha
Authorization: Bearer {{bearer.response.body.access_token}}

###############################################################################
# TEST 6: _include on multi-target reference
###############################################################################

### 6a. Include subject on Encounter (should pull in Patient and Group)
GET {{hostname}}/Encounter?_id=ref-test-encounter-patient,ref-test-encounter-group&_include=Encounter:subject
Authorization: Bearer {{bearer.response.body.access_token}}

### 6b. Include subject on Observation (should pull in Patient, Device, Location)
GET {{hostname}}/Observation?_id=ref-test-obs-patient,ref-test-obs-device,ref-test-obs-location&_include=Observation:subject
Authorization: Bearer {{bearer.response.body.access_token}}

###############################################################################
# TEST 7: Condition.asserter (4 target types)
###############################################################################

### 7a. Untyped - matches Practitioner asserter
GET {{hostname}}/Condition?asserter=ref-test-practitioner1
Authorization: Bearer {{bearer.response.body.access_token}}

### 7b. Untyped - matches Patient as asserter
GET {{hostname}}/Condition?asserter=ref-test-patient1
Authorization: Bearer {{bearer.response.body.access_token}}

### 7c. Typed asserter
GET {{hostname}}/Condition?asserter=Practitioner/ref-test-practitioner1
Authorization: Bearer {{bearer.response.body.access_token}}

### 7d. Comma-separated asserter
GET {{hostname}}/Condition?asserter=ref-test-practitioner1,ref-test-patient1
Authorization: Bearer {{bearer.response.body.access_token}}
Loading
Loading