Skip to content

Commit 6506d22

Browse files
authored
Merge pull request #626 from chisholm/sqlite-unit-tests
Querying, unit testing with SQLite
2 parents 03fd9de + 40cab27 commit 6506d22

File tree

2 files changed

+140
-80
lines changed

2 files changed

+140
-80
lines changed

stix2/datastore/relational_db/query.py

Lines changed: 73 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def _check_support(stix_id):
3131
raise DataSourceError(f"Reading {stix_type} objects is not supported.")
3232

3333

34-
def _tables_for(stix_class, metadata):
34+
def _tables_for(stix_class, metadata, db_backend):
3535
"""
3636
Get the core and type-specific tables for the given class
3737
@@ -42,17 +42,24 @@ def _tables_for(stix_class, metadata):
4242
"""
4343
# Info about the type-specific table
4444
type_table_name = table_name_for(stix_class)
45-
type_schema_name = schema_for(stix_class)
46-
type_table = metadata.tables[f"{type_schema_name}.{type_table_name}"]
45+
type_schema_name = db_backend.schema_for(stix_class)
46+
canon_type_table_name = canonicalize_table_name(type_table_name, type_schema_name)
47+
48+
type_table = metadata.tables[canon_type_table_name]
4749

4850
# Some fixed info about core tables
49-
if type_schema_name == "sco":
50-
core_table_name = "common.core_sco"
51+
if stix2.utils.is_sco(stix_class._type, stix2.DEFAULT_VERSION):
52+
canon_core_table_name = canonicalize_table_name(
53+
"core_sco", db_backend.schema_for_core()
54+
)
55+
5156
else:
5257
# for SROs and SMOs too?
53-
core_table_name = "common.core_sdo"
58+
canon_core_table_name = canonicalize_table_name(
59+
"core_sdo", db_backend.schema_for_core()
60+
)
5461

55-
core_table = metadata.tables[core_table_name]
62+
core_table = metadata.tables[canon_core_table_name]
5663

5764
return core_table, type_table
5865

@@ -138,18 +145,30 @@ def _read_hashes(fk_id, hashes_table, conn):
138145
return hashes
139146

140147

141-
def _read_external_references(stix_id, metadata, conn):
148+
def _read_external_references(stix_id, metadata, conn, db_backend):
142149
"""
143150
Read external references from some fixed tables in the common schema.
144151
145152
:param stix_id: A STIX ID used to filter table rows
146153
:param metadata: SQLAlchemy Metadata object containing all the table
147154
information
148155
:param conn: An SQLAlchemy DB connection
156+
:param db_backend: A backend object with information about how data is
157+
stored in the database
149158
:return: The external references, as a list of dicts
150159
"""
151-
ext_refs_table = metadata.tables["common.external_references"]
152-
ext_refs_hashes_table = metadata.tables["common.external_references_hashes"]
160+
ext_refs_table = metadata.tables[
161+
canonicalize_table_name(
162+
"external_references",
163+
db_backend.schema_for_core()
164+
)
165+
]
166+
ext_refs_hashes_table = metadata.tables[
167+
canonicalize_table_name(
168+
"external_references_hashes",
169+
db_backend.schema_for_core()
170+
)
171+
]
153172
ext_refs = []
154173

155174
ext_refs_columns = (col for col in ext_refs_table.c if col.key != "id")
@@ -169,43 +188,44 @@ def _read_external_references(stix_id, metadata, conn):
169188
return ext_refs
170189

171190

172-
def _read_object_marking_refs(stix_id, stix_type_class, metadata, conn):
191+
def _read_object_marking_refs(stix_id, common_table_kind, metadata, conn, db_backend):
173192
"""
174193
Read object marking refs from one of a couple special tables in the common
175194
schema.
176195
177196
:param stix_id: A STIX ID, used to filter table rows
178-
:param stix_type_class: STIXTypeClass enum value, used to determine whether
197+
:param common_table_kind: "sco" or "sdo", used to determine whether
179198
to read the table for SDOs or SCOs
180199
:param metadata: SQLAlchemy Metadata object containing all the table
181200
information
182201
:param conn: An SQLAlchemy DB connection
202+
:param db_backend: A backend object with information about how data is
203+
stored in the database
183204
:return: The references as a list of strings
184205
"""
185206

186-
marking_table_name = "object_marking_refs_"
187-
if stix_type_class is stix2.utils.STIXTypeClass.SCO:
188-
marking_table_name += "sco"
189-
else:
190-
marking_table_name += "sdo"
207+
marking_table_name = canonicalize_table_name(
208+
"object_marking_refs_" + common_table_kind,
209+
db_backend.schema_for_core()
210+
)
191211

192212
# The SCO/SDO object_marking_refs tables are mostly identical; they just
193213
# have different foreign key constraints (to different core tables).
194-
marking_table = metadata.tables["common." + marking_table_name]
214+
marking_table = metadata.tables[marking_table_name]
195215

196216
stmt = sa.select(marking_table.c.ref_id).where(marking_table.c.id == stix_id)
197217
refs = conn.scalars(stmt).all()
198218

199219
return refs
200220

201221

202-
def _read_granular_markings(stix_id, stix_type_class, metadata, conn, db_backend):
222+
def _read_granular_markings(stix_id, common_table_kind, metadata, conn, db_backend):
203223
"""
204224
Read granular markings from one of a couple special tables in the common
205225
schema.
206226
207227
:param stix_id: A STIX ID, used to filter table rows
208-
:param stix_type_class: STIXTypeClass enum value, used to determine whether
228+
:param common_table_kind: "sco" or "sdo", used to determine whether
209229
to read the table for SDOs or SCOs
210230
:param metadata: SQLAlchemy Metadata object containing all the table
211231
information
@@ -215,13 +235,11 @@ def _read_granular_markings(stix_id, stix_type_class, metadata, conn, db_backend
215235
:return: Granular markings as a list of dicts
216236
"""
217237

218-
marking_table_name = "granular_marking_"
219-
if stix_type_class is stix2.utils.STIXTypeClass.SCO:
220-
marking_table_name += "sco"
221-
else:
222-
marking_table_name += "sdo"
223-
224-
marking_table = metadata.tables["common." + marking_table_name]
238+
marking_table_name = canonicalize_table_name(
239+
"granular_marking_" + common_table_kind,
240+
db_backend.schema_for_core()
241+
)
242+
marking_table = metadata.tables[marking_table_name]
225243

226244
if db_backend.array_allowed():
227245
# arrays allowed: everything combined in the same table
@@ -334,7 +352,7 @@ def _read_dictionary_property(
334352
stmt = sa.select(
335353
dict_table.c.name, list_table.c.value
336354
).select_from(dict_table).join(
337-
list_table, list_table.c.id == dict_table.c.values
355+
list_table, list_table.c.id == dict_table.c["values"]
338356
).where(
339357
dict_table.c.id == stix_id
340358
)
@@ -610,7 +628,7 @@ def _read_complex_property_value(
610628

611629
def _read_complex_top_level_property_value(
612630
stix_id,
613-
stix_type_class,
631+
common_table_kind,
614632
prop_name,
615633
prop_instance,
616634
type_table,
@@ -624,8 +642,8 @@ def _read_complex_top_level_property_value(
624642
reading top-level common properties, which use special fixed tables.
625643
626644
:param stix_id: STIX ID of an object to read
627-
:param stix_type_class: The kind of object (SCO, SDO, etc). Which DB
628-
tables to read can depend on this.
645+
:param common_table_kind: Used to find auxiliary common tables, e.g. those
646+
for object markings, granular markings, etc. Either "sco" or "sdo".
629647
:param prop_name: The name of the property to read
630648
:param prop_instance: A Property (subclass) instance with property
631649
config information
@@ -641,20 +659,26 @@ def _read_complex_top_level_property_value(
641659

642660
# Common properties: these use a fixed set of tables for all STIX objects
643661
if prop_name == "external_references":
644-
prop_value = _read_external_references(stix_id, metadata, conn)
662+
prop_value = _read_external_references(
663+
stix_id,
664+
metadata,
665+
conn,
666+
db_backend
667+
)
645668

646669
elif prop_name == "object_marking_refs":
647670
prop_value = _read_object_marking_refs(
648671
stix_id,
649-
stix_type_class,
672+
common_table_kind,
650673
metadata,
651674
conn,
675+
db_backend,
652676
)
653677

654678
elif prop_name == "granular_markings":
655679
prop_value = _read_granular_markings(
656680
stix_id,
657-
stix_type_class,
681+
common_table_kind,
658682
metadata,
659683
conn,
660684
db_backend,
@@ -663,7 +687,10 @@ def _read_complex_top_level_property_value(
663687
# Will apply when array columns are unsupported/disallowed by the backend
664688
elif prop_name == "labels":
665689
label_table = metadata.tables[
666-
f"common.core_{stix_type_class.name.lower()}_labels"
690+
canonicalize_table_name(
691+
f"core_{common_table_kind}_labels",
692+
db_backend.schema_for_core()
693+
)
667694
]
668695
prop_value = _read_simple_array(stix_id, "label", label_table, conn)
669696

@@ -702,16 +729,10 @@ def read_object(stix_id, metadata, conn, db_backend):
702729
stix_type = stix2.utils.get_type_from_id(stix_id)
703730
raise DataSourceError("Can't find registered class for type: " + stix_type)
704731

705-
core_table, type_table = _tables_for(stix_class, metadata)
706-
707-
if type_table.schema == "common":
708-
# Applies to extension-definition SMO, whose data is stored in the
709-
# common schema; it does not get its own. This type class is used to
710-
# determine which common tables to use; its markings are
711-
# in the *_sdo tables.
712-
stix_type_class = stix2.utils.STIXTypeClass.SDO
713-
else:
714-
stix_type_class = stix2.utils.to_enum(type_table.schema, stix2.utils.STIXTypeClass)
732+
core_table, type_table = _tables_for(stix_class, metadata, db_backend)
733+
# Used to find auxiliary common tables, e.g. those for object markings,
734+
# granular markings, etc.
735+
common_table_kind = core_table.name[-3:]
715736

716737
simple_props = _read_simple_properties(stix_id, core_table, type_table, conn)
717738
if simple_props is None:
@@ -725,7 +746,7 @@ def read_object(stix_id, metadata, conn, db_backend):
725746
if prop_name not in obj_dict:
726747
prop_value = _read_complex_top_level_property_value(
727748
stix_id,
728-
stix_type_class,
749+
common_table_kind,
729750
prop_name,
730751
prop_instance,
731752
type_table,
@@ -737,5 +758,10 @@ def read_object(stix_id, metadata, conn, db_backend):
737758
if prop_value is not None:
738759
obj_dict[prop_name] = prop_value
739760

740-
stix_obj = stix_class(**obj_dict, allow_custom=True)
761+
stix_obj = stix2.parse(
762+
obj_dict,
763+
allow_custom=True,
764+
version=stix2.DEFAULT_VERSION
765+
)
766+
741767
return stix_obj

0 commit comments

Comments
 (0)