48
48
import ca .uhn .fhir .jpa .interceptor .JpaPreResourceAccessDetails ;
49
49
import ca .uhn .fhir .jpa .model .config .PartitionSettings ;
50
50
import ca .uhn .fhir .jpa .model .dao .JpaPid ;
51
+ import ca .uhn .fhir .jpa .model .entity .BaseResourceIndexedSearchParam ;
51
52
import ca .uhn .fhir .jpa .model .entity .IBaseResourceEntity ;
52
53
import ca .uhn .fhir .jpa .model .entity .ResourceTag ;
53
54
import ca .uhn .fhir .jpa .model .search .SearchBuilderLoadIncludesParameters ;
97
98
import org .apache .commons .lang3 .StringUtils ;
98
99
import org .apache .commons .lang3 .Validate ;
99
100
import org .apache .commons .lang3 .math .NumberUtils ;
101
+ import org .apache .commons .lang3 .tuple .Pair ;
100
102
import org .hl7 .fhir .instance .model .api .IAnyResource ;
101
103
import org .hl7 .fhir .instance .model .api .IBaseResource ;
102
104
import org .slf4j .Logger ;
107
109
import org .springframework .transaction .support .TransactionSynchronizationManager ;
108
110
109
111
import javax .annotation .Nonnull ;
112
+ import javax .annotation .Nullable ;
110
113
import javax .persistence .EntityManager ;
111
114
import javax .persistence .PersistenceContext ;
112
115
import javax .persistence .PersistenceContextType ;
@@ -1305,73 +1308,53 @@ public Set<JpaPid> loadIncludes(SearchBuilderLoadIncludesParameters<JpaPid> theP
1305
1308
paths = param .getPathsSplitForResourceType (resType );
1306
1309
// end replace
1307
1310
1308
- String targetResourceType = defaultString (nextInclude .getParamTargetType (), null );
1311
+ Set <String > targetResourceTypes = computeTargetResourceTypes (nextInclude , param );
1312
+
1309
1313
for (String nextPath : paths ) {
1310
- boolean haveTargetTypesDefinedByParam = param .hasTargets ();
1311
1314
String findPidFieldSqlColumn = findPidFieldName .equals (MY_SOURCE_RESOURCE_PID ) ? "src_resource_id" : "target_resource_id" ;
1312
1315
String fieldsToLoad = "r." + findPidFieldSqlColumn + " AS " + RESOURCE_ID_ALIAS ;
1313
1316
if (findVersionFieldName != null ) {
1314
1317
fieldsToLoad += ", r.target_resource_version AS " + RESOURCE_VERSION_ALIAS ;
1315
1318
}
1316
-
1317
- // Query for includes lookup has consider 2 cases
1319
+
1320
+ // Query for includes lookup has 2 cases
1318
1321
// Case 1: Where target_resource_id is available in hfj_res_link table for local references
1319
1322
// Case 2: Where target_resource_id is null in hfj_res_link table and referred by a canonical url in target_resource_url
1320
1323
1321
1324
// Case 1:
1325
+ Map <String , Object > localReferenceQueryParams = new HashMap <>();
1326
+
1322
1327
String searchPidFieldSqlColumn = searchPidFieldName .equals (MY_TARGET_RESOURCE_PID ) ? "target_resource_id" : "src_resource_id" ;
1323
- StringBuilder resourceIdBasedQuery = new StringBuilder ("SELECT " + fieldsToLoad +
1328
+ StringBuilder localReferenceQuery = new StringBuilder ("SELECT " + fieldsToLoad +
1324
1329
" FROM hfj_res_link r " +
1325
1330
" WHERE r.src_path = :src_path AND " +
1326
1331
" r.target_resource_id IS NOT NULL AND " +
1327
1332
" r." + searchPidFieldSqlColumn + " IN (:target_pids) " );
1328
- if (targetResourceType != null ) {
1329
- resourceIdBasedQuery .append (" AND r.target_resource_type = :target_resource_type " );
1330
- } else if (haveTargetTypesDefinedByParam ) {
1331
- resourceIdBasedQuery .append (" AND r.target_resource_type in (:target_resource_types) " );
1332
- }
1333
-
1334
- // Case 2:
1335
- String fieldsToLoadFromSpidxUriTable = "rUri.res_id" ;
1336
- // to match the fields loaded in union
1337
- if (fieldsToLoad .split ("," ).length > 1 ) {
1338
- for (int i = 0 ; i < fieldsToLoad .split ("," ).length - 1 ; i ++) {
1339
- fieldsToLoadFromSpidxUriTable += ", NULL" ;
1333
+ localReferenceQueryParams .put ("src_path" , nextPath );
1334
+ // we loop over target_pids later.
1335
+ if (targetResourceTypes != null ) {
1336
+ if (targetResourceTypes .size () == 1 ) {
1337
+ localReferenceQuery .append (" AND r.target_resource_type = :target_resource_type " );
1338
+ localReferenceQueryParams .put ("target_resource_type" , targetResourceTypes .iterator ().next ());
1339
+ } else {
1340
+ localReferenceQuery .append (" AND r.target_resource_type in (:target_resource_types) " );
1341
+ localReferenceQueryParams .put ("target_resource_types" , targetResourceTypes );
1340
1342
}
1341
1343
}
1342
- //@formatter:off
1343
- StringBuilder resourceUrlBasedQuery = new StringBuilder ("SELECT " + fieldsToLoadFromSpidxUriTable +
1344
- " FROM hfj_res_link r " +
1345
- " JOIN hfj_spidx_uri rUri ON ( " +
1346
- " r.target_resource_url = rUri.sp_uri AND " +
1347
- " rUri.sp_name = 'url' " );
1348
-
1349
- if (targetResourceType != null ) {
1350
- resourceUrlBasedQuery .append (" AND rUri.res_type = :target_resource_type " );
1351
1344
1352
- } else if (haveTargetTypesDefinedByParam ) {
1353
- resourceUrlBasedQuery .append (" AND rUri.res_type IN (:target_resource_types) " );
1354
- }
1345
+ // Case 2:
1346
+ Pair <String , Map <String , Object >> canonicalQuery = buildCanonicalUrlQuery (findVersionFieldName , searchPidFieldSqlColumn , targetResourceTypes );
1355
1347
1356
- resourceUrlBasedQuery .append (" ) " );
1357
- resourceUrlBasedQuery .append (
1358
- " WHERE r.src_path = :src_path AND " +
1359
- " r.target_resource_id IS NULL AND " +
1360
- " r." + searchPidFieldSqlColumn + " IN (:target_pids) " );
1361
1348
//@formatter:on
1362
1349
1363
- String sql = resourceIdBasedQuery + " UNION " + resourceUrlBasedQuery ;
1350
+ String sql = localReferenceQuery + " UNION " + canonicalQuery . getLeft () ;
1364
1351
1365
1352
List <Collection <JpaPid >> partitions = partition (nextRoundMatches , getMaximumPageSize ());
1366
1353
for (Collection <JpaPid > nextPartition : partitions ) {
1367
1354
Query q = entityManager .createNativeQuery (sql , Tuple .class );
1368
- q .setParameter ("src_path" , nextPath );
1369
1355
q .setParameter ("target_pids" , JpaPid .toLongList (nextPartition ));
1370
- if (targetResourceType != null ) {
1371
- q .setParameter ("target_resource_type" , targetResourceType );
1372
- } else if (haveTargetTypesDefinedByParam ) {
1373
- q .setParameter ("target_resource_types" , param .getTargets ());
1374
- }
1356
+ localReferenceQueryParams .forEach (q ::setParameter );
1357
+ canonicalQuery .getRight ().forEach (q ::setParameter );
1375
1358
1376
1359
if (maxCount != null ) {
1377
1360
q .setMaxResults (maxCount );
@@ -1395,7 +1378,7 @@ public Set<JpaPid> loadIncludes(SearchBuilderLoadIncludesParameters<JpaPid> theP
1395
1378
1396
1379
nextRoundMatches .clear ();
1397
1380
for (JpaPid next : pidsToInclude ) {
1398
- if (original .contains (next ) == false && allAdded .contains (next ) == false ) {
1381
+ if ( ! original .contains (next ) && ! allAdded .contains (next ) ) {
1399
1382
nextRoundMatches .add (next );
1400
1383
}
1401
1384
}
@@ -1406,7 +1389,7 @@ public Set<JpaPid> loadIncludes(SearchBuilderLoadIncludesParameters<JpaPid> theP
1406
1389
break ;
1407
1390
}
1408
1391
1409
- } while (includes .size () > 0 && nextRoundMatches .size () > 0 && addedSomeThisRound );
1392
+ } while (! includes .isEmpty () && ! nextRoundMatches .isEmpty () && addedSomeThisRound );
1410
1393
1411
1394
allAdded .removeAll (original );
1412
1395
@@ -1415,7 +1398,7 @@ public Set<JpaPid> loadIncludes(SearchBuilderLoadIncludesParameters<JpaPid> theP
1415
1398
// Interceptor call: STORAGE_PREACCESS_RESOURCES
1416
1399
// This can be used to remove results from the search result details before
1417
1400
// the user has a chance to know that they were in the results
1418
- if (allAdded .size () > 0 ) {
1401
+ if (! allAdded .isEmpty () ) {
1419
1402
1420
1403
if (CompositeInterceptorBroadcaster .hasHooks (Pointcut .STORAGE_PREACCESS_RESOURCES , myInterceptorBroadcaster , request )) {
1421
1404
List <JpaPid > includedPidList = new ArrayList <>(allAdded );
@@ -1440,6 +1423,62 @@ public Set<JpaPid> loadIncludes(SearchBuilderLoadIncludesParameters<JpaPid> theP
1440
1423
return allAdded ;
1441
1424
}
1442
1425
1426
+ @ Nullable
1427
+ private static Set <String > computeTargetResourceTypes (Include nextInclude , RuntimeSearchParam param ) {
1428
+ String targetResourceType = defaultString (nextInclude .getParamTargetType (), null );
1429
+ boolean haveTargetTypesDefinedByParam = param .hasTargets ();
1430
+ Set <String > targetResourceTypes ;
1431
+ if (targetResourceType != null ) {
1432
+ targetResourceTypes = Set .of (targetResourceType );
1433
+ } else if (haveTargetTypesDefinedByParam ) {
1434
+ targetResourceTypes = param .getTargets ();
1435
+ } else {
1436
+ // all types!
1437
+ targetResourceTypes = null ;
1438
+ }
1439
+ return targetResourceTypes ;
1440
+ }
1441
+
1442
+ @ Nonnull
1443
+ private Pair <String , Map <String , Object >> buildCanonicalUrlQuery (String theVersionFieldName , String thePidFieldSqlColumn , Set <String > theTargetResourceTypes ) {
1444
+ String fieldsToLoadFromSpidxUriTable = "rUri.res_id" ;
1445
+ if (theVersionFieldName != null ) {
1446
+ // canonical-uri references aren't versioned, but we need to match the column count for the UNION
1447
+ fieldsToLoadFromSpidxUriTable += ", NULL" ;
1448
+ }
1449
+ // The logical join will be by hfj_spidx_uri on sp_name='uri' and sp_uri=target_resource_url.
1450
+ // But sp_name isn't indexed, so we use hash_identity instead.
1451
+ if (theTargetResourceTypes == null ) {
1452
+ // hash_identity includes the resource type. So a null wildcard must be replaced with a list of all types.
1453
+ theTargetResourceTypes = myDaoRegistry .getRegisteredDaoTypes ();
1454
+ }
1455
+ assert !theTargetResourceTypes .isEmpty ();
1456
+
1457
+ Set <Long > identityHashesForTypes = theTargetResourceTypes .stream ()
1458
+ .map (type -> BaseResourceIndexedSearchParam .calculateHashIdentity (myPartitionSettings , myRequestPartitionId , type , "url" ))
1459
+ .collect (Collectors .toSet ());
1460
+
1461
+ Map <String , Object > canonicalUriQueryParams = new HashMap <>();
1462
+ StringBuilder canonicalUrlQuery = new StringBuilder (
1463
+ "SELECT " + fieldsToLoadFromSpidxUriTable +
1464
+ " FROM hfj_res_link r " +
1465
+ " JOIN hfj_spidx_uri rUri ON ( " );
1466
+ // join on hash_identity and sp_uri - indexed in IDX_SP_URI_HASH_IDENTITY_V2
1467
+ if (theTargetResourceTypes .size () == 1 ) {
1468
+ canonicalUrlQuery .append (" rUri.hash_identity = :uri_identity_hash " );
1469
+ canonicalUriQueryParams .put ("uri_identity_hash" , identityHashesForTypes .iterator ().next ());
1470
+ } else {
1471
+ canonicalUrlQuery .append (" rUri.hash_identity in (:uri_identity_hashes) " );
1472
+ canonicalUriQueryParams .put ("uri_identity_hashes" , identityHashesForTypes );
1473
+ }
1474
+
1475
+ canonicalUrlQuery .append (" AND r.target_resource_url = rUri.sp_uri )" +
1476
+ " WHERE r.src_path = :src_path AND " +
1477
+ " r.target_resource_id IS NULL AND " +
1478
+ " r." + thePidFieldSqlColumn + " IN (:target_pids) " );
1479
+ return Pair .of (canonicalUrlQuery .toString (), canonicalUriQueryParams );
1480
+ }
1481
+
1443
1482
private List <Collection <JpaPid >> partition (Collection <JpaPid > theNextRoundMatches , int theMaxLoad ) {
1444
1483
if (theNextRoundMatches .size () <= theMaxLoad ) {
1445
1484
return Collections .singletonList (theNextRoundMatches );
0 commit comments