10
10
from storage .schema import AnalysisEntry , FileObjectEntry , FirmwareEntry
11
11
12
12
if TYPE_CHECKING :
13
+ from sqlalchemy .orm .util import AliasedClass
13
14
from sqlalchemy .sql import Select
14
15
15
16
FIRMWARE_ORDER = FirmwareEntry .vendor .asc (), FirmwareEntry .device_name .asc ()
@@ -73,12 +74,18 @@ def build_query_from_dict( # noqa: C901, PLR0912
73
74
74
75
analysis_search_dict = {key : value for key , value in query_dict .items () if key .startswith ('processed_analysis' )}
75
76
if analysis_search_dict :
76
- query = query .join (
77
- AnalysisEntry , AnalysisEntry .uid == (FileObjectEntry .uid if not fw_only else FirmwareEntry .uid )
78
- )
79
- for key , value in analysis_search_dict .items ():
80
- _ , plugin , subkey = key .split ('.' , maxsplit = 2 )
81
- filters .append ((_add_analysis_filter_to_query (key , value , subkey )) & (AnalysisEntry .plugin == plugin ))
77
+ # group analysis query items per plugin and create an alias and a join for each (otherwise it is not possible
78
+ # to search for the results of multiple plugins at the same time)
79
+ for plugin , plugin_search_list in _group_query_by_plugin (analysis_search_dict ).items ():
80
+ analysis_alias = aliased (AnalysisEntry )
81
+ query = query .join (
82
+ analysis_alias , analysis_alias .uid == (FileObjectEntry .uid if not fw_only else FirmwareEntry .uid )
83
+ )
84
+ for value , key , subkey in plugin_search_list :
85
+ filters .append (
86
+ (_add_analysis_filter_to_query (analysis_alias , key , value , subkey ))
87
+ & (analysis_alias .plugin == plugin )
88
+ )
82
89
83
90
firmware_search_dict = get_search_keys_from_dict (query_dict , FirmwareEntry , blacklist = ['uid' ])
84
91
if firmware_search_dict :
@@ -105,6 +112,14 @@ def build_query_from_dict( # noqa: C901, PLR0912
105
112
return query .distinct ()
106
113
107
114
115
+ def _group_query_by_plugin (analysis_search_dict : dict [str , Any ]) -> dict [str , tuple [Any , str , str ]]:
116
+ query_per_plugin = {}
117
+ for key , value in analysis_search_dict .items ():
118
+ _ , plugin , subkey = key .split ('.' , maxsplit = 2 )
119
+ query_per_plugin .setdefault (plugin , []).append ((value , key , subkey ))
120
+ return query_per_plugin
121
+
122
+
108
123
def get_search_keys_from_dict (query_dict : dict , table , blacklist : Optional [list [str ]] = None ) -> dict [str , Any ]:
109
124
return {key : value for key , value in query_dict .items () if key not in (blacklist or []) and hasattr (table , key )}
110
125
@@ -138,13 +153,13 @@ def _get_column(key: str, table: type[FirmwareEntry] | type[FileObjectEntry] | t
138
153
return column
139
154
140
155
141
- def _add_analysis_filter_to_query (key : str , value : Any , subkey : str ):
142
- if hasattr (AnalysisEntry , subkey ):
156
+ def _add_analysis_filter_to_query (analysis : AliasedClass , key : str , value : Any , subkey : str ):
157
+ if hasattr (analysis , subkey ):
143
158
if subkey == 'summary' : # special case: array field
144
- return _get_array_filter (AnalysisEntry .summary , key , value )
145
- return getattr (AnalysisEntry , subkey ) == value
159
+ return _get_array_filter (analysis .summary , key , value )
160
+ return getattr (analysis , subkey ) == value
146
161
# no metadata field, actual analysis result key in `AnalysisEntry.result` (JSON)
147
- return _add_json_filter (key , value , subkey )
162
+ return _add_json_filter (analysis , key , value , subkey )
148
163
149
164
150
165
def _get_array_filter (field , key , value ):
@@ -166,8 +181,8 @@ def _to_list(value):
166
181
return value if isinstance (value , list ) else [value ]
167
182
168
183
169
- def _add_json_filter (key , value , subkey ):
170
- column = AnalysisEntry .result
184
+ def _add_json_filter (analysis : AliasedClass , key : str , value : Any , subkey : str ):
185
+ column = analysis .result
171
186
if isinstance (value , dict ) and '$exists' in value :
172
187
# "$exists" (aka key exists in json document) is a special case because
173
188
# we need to query the element one level above the actual key
0 commit comments