@@ -99,6 +99,22 @@ def _read_simple_properties(stix_id, core_table, type_table, conn):
9999 return obj_dict
100100
101101
102+ def _read_simple_array (fk_id , elt_column_name , array_table , conn ):
103+ """
104+ Read array elements from a given table.
105+
106+ :param fk_id: A foreign key value used to find the correct array elements
107+ :param elt_column_name: The name of the table column which contains the
108+ array elements
109+ :param array_table: A SQLAlchemy Table object containing the array data
110+ :param conn: An SQLAlchemy DB connection
111+ :return: The array, as a list
112+ """
113+ stmt = sa .select (array_table .c [elt_column_name ]).where (array_table .c .id == fk_id )
114+ refs = conn .scalars (stmt ).all ()
115+ return refs
116+
117+
102118def _read_hashes (fk_id , hashes_table , conn ):
103119 """
104120 Read hashes from a table.
@@ -178,7 +194,7 @@ def _read_object_marking_refs(stix_id, stix_type_class, metadata, conn):
178194 return refs
179195
180196
181- def _read_granular_markings (stix_id , stix_type_class , metadata , conn ):
197+ def _read_granular_markings (stix_id , stix_type_class , metadata , conn , db_backend ):
182198 """
183199 Read granular markings from one of a couple special tables in the common
184200 schema.
@@ -189,6 +205,8 @@ def _read_granular_markings(stix_id, stix_type_class, metadata, conn):
189205 :param metadata: SQLAlchemy Metadata object containing all the table
190206 information
191207 :param conn: An SQLAlchemy DB connection
208+ :param db_backend: A backend object with information about how data is
209+ stored in the database
192210 :return: Granular markings as a list of dicts
193211 """
194212
@@ -200,30 +218,43 @@ def _read_granular_markings(stix_id, stix_type_class, metadata, conn):
200218
201219 marking_table = metadata .tables ["common." + marking_table_name ]
202220
203- stmt = sa .select (
204- marking_table .c .lang ,
205- marking_table .c .marking_ref ,
206- marking_table .c .selectors ,
207- ).where (marking_table .c .id == stix_id )
208-
209- marking_dicts = conn .execute (stmt ).mappings ().all ()
210- return marking_dicts
221+ if db_backend .array_allowed ():
222+ # arrays allowed: everything combined in the same table
223+ stmt = sa .select (
224+ marking_table .c .lang ,
225+ marking_table .c .marking_ref ,
226+ marking_table .c .selectors ,
227+ ).where (marking_table .c .id == stix_id )
211228
229+ marking_dicts = conn .execute (stmt ).mappings ().all ()
212230
213- def _read_simple_array (fk_id , elt_column_name , array_table , conn ):
214- """
215- Read array elements from a given table.
231+ else :
232+ # arrays not allowed: selectors are in their own table
233+ stmt = sa .select (
234+ marking_table .c .lang ,
235+ marking_table .c .marking_ref ,
236+ marking_table .c .selectors ,
237+ ).where (marking_table .c .id == stix_id )
238+
239+ marking_dicts = list (conn .execute (stmt ).mappings ())
240+
241+ for idx , marking_dict in enumerate (marking_dicts ):
242+ # make a mutable shallow-copy of the row mapping
243+ marking_dicts [idx ] = marking_dict = dict (marking_dict )
244+ selector_id = marking_dict .pop ("selectors" )
245+
246+ selector_table_name = f"{ marking_table .fullname } _selector"
247+ selector_table = metadata .tables [selector_table_name ]
248+
249+ selectors = _read_simple_array (
250+ selector_id ,
251+ "selector" ,
252+ selector_table ,
253+ conn
254+ )
255+ marking_dict ["selectors" ] = selectors
216256
217- :param fk_id: A foreign key value used to find the correct array elements
218- :param elt_column_name: The name of the table column which contains the
219- array elements
220- :param array_table: A SQLAlchemy Table object containing the array data
221- :param conn: An SQLAlchemy DB connection
222- :return: The array, as a list
223- """
224- stmt = sa .select (array_table .c [elt_column_name ]).where (array_table .c .id == fk_id )
225- refs = conn .scalars (stmt ).all ()
226- return refs
257+ return marking_dicts
227258
228259
229260def _read_kill_chain_phases (stix_id , type_table , metadata , conn ):
@@ -437,10 +468,26 @@ def _read_complex_property_value(obj_id, prop_name, prop_instance, obj_table, me
437468 ref_table = metadata .tables [ref_table_name ]
438469 prop_value = _read_simple_array (obj_id , "ref_id" , ref_table , conn )
439470
440- elif isinstance (prop_instance .contained , stix2 .properties .EnumProperty ):
441- enum_table_name = f"{ obj_table .fullname } _{ prop_name } "
442- enum_table = metadata .tables [enum_table_name ]
443- prop_value = _read_simple_array (obj_id , prop_name , enum_table , conn )
471+ elif isinstance (prop_instance .contained , (
472+ # Most of these list-of-simple-type cases would occur when array
473+ # columns are disabled.
474+ stix2 .properties .BinaryProperty ,
475+ stix2 .properties .BooleanProperty ,
476+ stix2 .properties .EnumProperty ,
477+ stix2 .properties .HexProperty ,
478+ stix2 .properties .IntegerProperty ,
479+ stix2 .properties .FloatProperty ,
480+ stix2 .properties .StringProperty ,
481+ stix2 .properties .TimestampProperty ,
482+ )):
483+ array_table_name = f"{ obj_table .fullname } _{ prop_name } "
484+ array_table = metadata .tables [array_table_name ]
485+ prop_value = _read_simple_array (
486+ obj_id ,
487+ prop_name ,
488+ array_table ,
489+ conn
490+ )
444491
445492 elif isinstance (prop_instance .contained , stix2 .properties .EmbeddedObjectProperty ):
446493 join_table_name = f"{ obj_table .fullname } _{ prop_name } "
@@ -494,7 +541,16 @@ def _read_complex_property_value(obj_id, prop_name, prop_instance, obj_table, me
494541 return prop_value
495542
496543
497- def _read_complex_top_level_property_value (stix_id , stix_type_class , prop_name , prop_instance , type_table , metadata , conn ):
544+ def _read_complex_top_level_property_value (
545+ stix_id ,
546+ stix_type_class ,
547+ prop_name ,
548+ prop_instance ,
549+ type_table ,
550+ metadata ,
551+ conn ,
552+ db_backend
553+ ):
498554 """
499555 Read property values which require auxiliary tables to store. These
500556 require a lot of special cases. This function has additional support for
@@ -511,6 +567,8 @@ def _read_complex_top_level_property_value(stix_id, stix_type_class, prop_name,
511567 :param metadata: SQLAlchemy Metadata object containing all the table
512568 information
513569 :param conn: An SQLAlchemy DB connection
570+ :param db_backend: A backend object with information about how data is
571+ stored in the database
514572 :return: The property value
515573 """
516574
@@ -519,26 +577,53 @@ def _read_complex_top_level_property_value(stix_id, stix_type_class, prop_name,
519577 prop_value = _read_external_references (stix_id , metadata , conn )
520578
521579 elif prop_name == "object_marking_refs" :
522- prop_value = _read_object_marking_refs (stix_id , stix_type_class , metadata , conn )
580+ prop_value = _read_object_marking_refs (
581+ stix_id ,
582+ stix_type_class ,
583+ metadata ,
584+ conn
585+ )
523586
524587 elif prop_name == "granular_markings" :
525- prop_value = _read_granular_markings (stix_id , stix_type_class , metadata , conn )
588+ prop_value = _read_granular_markings (
589+ stix_id ,
590+ stix_type_class ,
591+ metadata ,
592+ conn ,
593+ db_backend
594+ )
595+
596+ # Will apply when array columns are unsupported/disallowed by the backend
597+ elif prop_name == "labels" :
598+ label_table = metadata .tables [
599+ f"common.core_{ stix_type_class .name .lower ()} _labels"
600+ ]
601+ prop_value = _read_simple_array (stix_id , "label" , label_table , conn )
526602
527603 else :
528604 # Other properties use specific table patterns depending on property type
529- prop_value = _read_complex_property_value (stix_id , prop_name , prop_instance , type_table , metadata , conn )
605+ prop_value = _read_complex_property_value (
606+ stix_id ,
607+ prop_name ,
608+ prop_instance ,
609+ type_table ,
610+ metadata ,
611+ conn
612+ )
530613
531614 return prop_value
532615
533616
534- def read_object (stix_id , metadata , conn ):
617+ def read_object (stix_id , metadata , conn , db_backend ):
535618 """
536619 Read a STIX object from the database, identified by a STIX ID.
537620
538621 :param stix_id: A STIX ID
539622 :param metadata: SQLAlchemy Metadata object containing all the table
540623 information
541624 :param conn: An SQLAlchemy DB connection
625+ :param db_backend: A backend object with information about how data is
626+ stored in the database
542627 :return: A STIX object
543628 """
544629 _check_support (stix_id )
@@ -554,7 +639,7 @@ def read_object(stix_id, metadata, conn):
554639 if type_table .schema == "common" :
555640 # Applies to extension-definition SMO, whose data is stored in the
556641 # common schema; it does not get its own. This type class is used to
557- # determine which markings tables to use; its markings are
642+ # determine which common tables to use; its markings are
558643 # in the *_sdo tables.
559644 stix_type_class = stix2 .utils .STIXTypeClass .SDO
560645 else :
@@ -578,6 +663,7 @@ def read_object(stix_id, metadata, conn):
578663 type_table ,
579664 metadata ,
580665 conn ,
666+ db_backend
581667 )
582668
583669 if prop_value is not None :
0 commit comments