Skip to content

Commit bcfb616

Browse files
committed
advanced search: add aliases to allow search on multiple plugin results
1 parent 239be18 commit bcfb616

File tree

1 file changed

+28
-13
lines changed

1 file changed

+28
-13
lines changed

src/storage/query_conversion.py

+28-13
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from storage.schema import AnalysisEntry, FileObjectEntry, FirmwareEntry
1111

1212
if TYPE_CHECKING:
13+
from sqlalchemy.orm.util import AliasedClass
1314
from sqlalchemy.sql import Select
1415

1516
FIRMWARE_ORDER = FirmwareEntry.vendor.asc(), FirmwareEntry.device_name.asc()
@@ -73,12 +74,18 @@ def build_query_from_dict( # noqa: C901, PLR0912
7374

7475
analysis_search_dict = {key: value for key, value in query_dict.items() if key.startswith('processed_analysis')}
7576
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+
)
8289

8390
firmware_search_dict = get_search_keys_from_dict(query_dict, FirmwareEntry, blacklist=['uid'])
8491
if firmware_search_dict:
@@ -105,6 +112,14 @@ def build_query_from_dict( # noqa: C901, PLR0912
105112
return query.distinct()
106113

107114

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+
108123
def get_search_keys_from_dict(query_dict: dict, table, blacklist: Optional[list[str]] = None) -> dict[str, Any]:
109124
return {key: value for key, value in query_dict.items() if key not in (blacklist or []) and hasattr(table, key)}
110125

@@ -138,13 +153,13 @@ def _get_column(key: str, table: type[FirmwareEntry] | type[FileObjectEntry] | t
138153
return column
139154

140155

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):
143158
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
146161
# 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)
148163

149164

150165
def _get_array_filter(field, key, value):
@@ -166,8 +181,8 @@ def _to_list(value):
166181
return value if isinstance(value, list) else [value]
167182

168183

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
171186
if isinstance(value, dict) and '$exists' in value:
172187
# "$exists" (aka key exists in json document) is a special case because
173188
# we need to query the element one level above the actual key

0 commit comments

Comments
 (0)