1+ import collections
12import inspect
23
34import sqlalchemy as sa
@@ -280,7 +281,15 @@ def _read_kill_chain_phases(stix_id, type_table, metadata, conn):
280281 return kill_chain_phases
281282
282283
283- def _read_dictionary_property (stix_id , type_table , prop_name , prop_instance , metadata , conn ):
284+ def _read_dictionary_property (
285+ stix_id ,
286+ type_table ,
287+ prop_name ,
288+ prop_instance ,
289+ metadata ,
290+ conn ,
291+ db_backend
292+ ):
284293 """
285294 Read a dictionary from a table.
286295
@@ -292,23 +301,57 @@ def _read_dictionary_property(stix_id, type_table, prop_name, prop_instance, met
292301 :param metadata: SQLAlchemy Metadata object containing all the table
293302 information
294303 :param conn: An SQLAlchemy DB connection
304+ :param db_backend: A backend object with information about how data is
305+ stored in the database
295306 :return: The dictionary, or None if no dictionary entries were found
296307 """
297308 dict_table_name = f"{ type_table .fullname } _{ prop_name } "
298309 dict_table = metadata .tables [dict_table_name ]
299310
300311 if len (prop_instance .valid_types ) == 1 :
301- stmt = sa .select (
302- dict_table .c .name , dict_table .c .value ,
303- ).where (
304- dict_table .c .id == stix_id ,
305- )
312+ valid_type = prop_instance .valid_types [0 ]
313+
314+ if isinstance (valid_type , stix2 .properties .ListProperty ):
315+ if db_backend .array_allowed ():
316+ stmt = sa .select (
317+ dict_table .c .name , dict_table .c ["values" ],
318+ ).where (
319+ dict_table .c .id == stix_id ,
320+ )
306321
307- results = conn .execute (stmt )
308- dict_value = dict (results .all ())
322+ results = conn .execute (stmt )
323+ dict_value = dict (results .all ())
324+
325+ else :
326+ # Dict contains a list, but array columns are not supported.
327+ # So query from an auxiliary table.
328+ list_table_name = f"{ dict_table_name } _values"
329+ list_table = metadata .tables [list_table_name ]
330+ stmt = sa .select (
331+ dict_table .c .name , list_table .c .value
332+ ).select_from (dict_table ).join (
333+ list_table , list_table .c .id == dict_table .c .values
334+ ).where (
335+ dict_table .c .id == stix_id
336+ )
337+
338+ results = conn .execute (stmt )
339+ dict_value = collections .defaultdict (list )
340+ for key , value in results :
341+ dict_value [key ].append (value )
342+
343+ else :
344+ stmt = sa .select (
345+ dict_table .c .name , dict_table .c .value ,
346+ ).where (
347+ dict_table .c .id == stix_id ,
348+ )
349+
350+ results = conn .execute (stmt )
351+ dict_value = dict (results .all ())
309352
310353 else :
311- # In this case, we get one column per valid type
354+ # In this case, we get one column per valid type (assume no lists here)
312355 type_cols = (col for col in dict_table .c if col .key not in ("id" , "name" ))
313356 stmt = sa .select (dict_table .c .name , * type_cols ).where (dict_table .c .id == stix_id )
314357 results = conn .execute (stmt )
@@ -437,7 +480,15 @@ def _read_embedded_object_list(fk_id, join_table, embedded_type, metadata, conn)
437480 return obj_list
438481
439482
440- def _read_complex_property_value (obj_id , prop_name , prop_instance , obj_table , metadata , conn ):
483+ def _read_complex_property_value (
484+ obj_id ,
485+ prop_name ,
486+ prop_instance ,
487+ obj_table ,
488+ metadata ,
489+ conn ,
490+ db_backend
491+ ):
441492 """
442493 Read property values which require auxiliary tables to store. These are
443494 idiosyncratic and just require a lot of special cases. This function has
@@ -456,6 +507,8 @@ def _read_complex_property_value(obj_id, prop_name, prop_instance, obj_table, me
456507 :param metadata: SQLAlchemy Metadata object containing all the table
457508 information
458509 :param conn: An SQLAlchemy DB connection
510+ :param db_backend: A backend object with information about how data is
511+ stored in the database
459512 :return: The property value
460513 """
461514
@@ -523,7 +576,15 @@ def _read_complex_property_value(obj_id, prop_name, prop_instance, obj_table, me
523576 elif isinstance (prop_instance , stix2 .properties .DictionaryProperty ):
524577 # ExtensionsProperty/HashesProperty subclasses DictionaryProperty, so
525578 # this must come after those
526- prop_value = _read_dictionary_property (obj_id , obj_table , prop_name , prop_instance , metadata , conn )
579+ prop_value = _read_dictionary_property (
580+ obj_id ,
581+ obj_table ,
582+ prop_name ,
583+ prop_instance ,
584+ metadata ,
585+ conn ,
586+ db_backend
587+ )
527588
528589 elif isinstance (prop_instance , stix2 .properties .EmbeddedObjectProperty ):
529590 prop_value = _read_embedded_object (
@@ -611,6 +672,7 @@ def _read_complex_top_level_property_value(
611672 type_table ,
612673 metadata ,
613674 conn ,
675+ db_backend
614676 )
615677
616678 return prop_value
0 commit comments