27
27
)
28
28
from airone .lib .elasticsearch import (
29
29
AdvancedSearchResultRecord ,
30
- AdvancedSearchResultRecordAttr ,
31
30
AdvancedSearchResults ,
32
31
AttrHint ,
33
32
)
@@ -245,6 +244,11 @@ class AdvancedSearchAPI(generics.GenericAPIView):
245
244
"""
246
245
NOTE for now it's just copied from /api/v1/entry/search, but it should be
247
246
rewritten with DRF components.
247
+
248
+ Join Attrs implementation notes:
249
+ - Pagination is applied at root level first, then join & filter operations
250
+ - This may result in fewer items than requested limit
251
+ - Each join triggers a new ES query (N+1 pattern)
248
252
"""
249
253
250
254
@extend_schema (
@@ -280,8 +284,18 @@ def _get_joined_resp(
280
284
prev_results : list [AdvancedSearchResultRecord ], join_attr : AdvancedSearchJoinAttrInfo
281
285
) -> tuple [bool , AdvancedSearchResults ]:
282
286
"""
283
- This is a helper method for join_attrs that will get specified attr values
284
- that prev_result's ones refer to.
287
+ Process join operation for a single attribute.
288
+
289
+ Flow:
290
+ 1. Get related entities from prev_results
291
+ 2. Extract referral IDs and names
292
+ 3. Execute new ES query for joined entities
293
+ 4. Apply filters if specified
294
+
295
+ Note:
296
+ - Each call triggers new ES query
297
+ - Results may be reduced by join filters
298
+ - Pagination from root level may lead to incomplete results
285
299
"""
286
300
entities = Entity .objects .filter (
287
301
id__in = [result .entity ["id" ] for result in prev_results ]
@@ -369,21 +383,20 @@ def _get_joined_resp(
369
383
370
384
# === End of Function: _get_joined_resp() ===
371
385
372
- def _get_ref_id_from_es_result (attrinfo ):
373
- if attrinfo ["type" ] == AttrType . OBJECT :
374
- if attrinfo .get ("value" ) is not None :
386
+ def _get_ref_id_from_es_result (attrinfo ) -> list [ int | None ] :
387
+ match attrinfo ["type" ]:
388
+ case AttrType . OBJECT if attrinfo .get ("value" ) is not None :
375
389
return [attrinfo ["value" ].get ("id" )]
376
390
377
- if attrinfo ["type" ] == AttrType .NAMED_OBJECT :
378
- if attrinfo .get ("value" ) is not None :
391
+ case AttrType .NAMED_OBJECT if attrinfo .get ("value" ) is not None :
379
392
[ref_info ] = attrinfo ["value" ].values ()
380
393
return [ref_info .get ("id" )]
381
394
382
- if attrinfo [ "type" ] == AttrType .ARRAY_OBJECT :
383
- return [x .get ("id" ) for x in attrinfo ["value" ]]
395
+ case AttrType .ARRAY_OBJECT :
396
+ return [x .get ("id" ) for x in attrinfo ["value" ]]
384
397
385
- if attrinfo [ "type" ] == AttrType .ARRAY_NAMED_OBJECT :
386
- return sum ([[y ["id" ] for y in x .values ()] for x in attrinfo ["value" ]], [])
398
+ case AttrType .ARRAY_NAMED_OBJECT :
399
+ return sum ([[y ["id" ] for y in x .values ()] for x in attrinfo ["value" ]], [])
387
400
388
401
return []
389
402
@@ -461,11 +474,13 @@ def _get_ref_id_from_es_result(attrinfo):
461
474
462
475
if not settings .AIRONE_SPANNER_ENABLED :
463
476
for join_attr in join_attrs :
477
+ # Note: Each iteration here represents a potential N+1 query
478
+ # The trade-off is between query performance and result accuracy
464
479
(will_filter_by_joined_attr , joined_resp ) = _get_joined_resp (
465
480
resp .ret_values , join_attr
466
481
)
467
- # Prepare blank joining info for entries without matches
468
- blank_joining_info : dict [ str , AdvancedSearchResultRecordAttr ] = {
482
+ # This is needed to set result as blank value
483
+ blank_joining_info = {
469
484
"%s.%s" % (join_attr .name , k .name ): {
470
485
"is_readable" : True ,
471
486
"type" : AttrType .STRING ,
@@ -484,11 +499,11 @@ def _get_ref_id_from_es_result(attrinfo):
484
499
for x in joined_resp .ret_values
485
500
}
486
501
487
- # Insert results to previous search results
488
- new_ret_values = []
489
- joined_ret_values = []
502
+ # this inserts result to previous search result
503
+ new_ret_values : list [ AdvancedSearchResultRecord ] = []
504
+ joined_ret_values : list [ AdvancedSearchResultRecord ] = []
490
505
for resp_result in resp .ret_values :
491
- # Get referral info from joined search result
506
+ # joining search result to original one
492
507
ref_info = resp_result .attrs .get (join_attr .name )
493
508
494
509
# This get referral Item-ID from joined search result
@@ -504,12 +519,12 @@ def _get_ref_id_from_es_result(attrinfo):
504
519
505
520
else :
506
521
# join EMPTY value
507
- resp_result .attrs |= blank_joining_info
522
+ resp_result .attrs |= blank_joining_info # type: ignore
508
523
joined_ret_values .append (deepcopy (resp_result ))
509
524
510
525
if len (ref_list ) == 0 :
511
526
# join EMPTY value
512
- resp_result .attrs |= blank_joining_info
527
+ resp_result .attrs |= blank_joining_info # type: ignore
513
528
joined_ret_values .append (deepcopy (resp_result ))
514
529
515
530
if will_filter_by_joined_attr :
0 commit comments