Skip to content

Commit ef07a1f

Browse files
authored
bug: Support Retrieval of Cross-Schema Foreign Keys (#681)
Allow `get_multi_foreign_keys` to retrieve foreign keys between tables in different schemas. The current SQL used to retrieve them has a mistake in the JOINs used to retrieve them, confusing the `table_schema` column and the `constraint_schema` column. This goes unnoticed as long as everything is in the same schema. Include schemas names in ALTER TABLE schema.table DROP CONSTRAINT DDL and quote the table name. fixes: #638
1 parent 1d818a8 commit ef07a1f

File tree

2 files changed

+77
-4
lines changed

2 files changed

+77
-4
lines changed

google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -607,8 +607,17 @@ def visit_drop_table(self, drop_table, **kw):
607607
constrs = ""
608608
for cons in drop_table.element.constraints:
609609
if isinstance(cons, ForeignKeyConstraint) and cons.name:
610+
effective_schema = self.preparer.schema_for_object(drop_table.element)
611+
if effective_schema:
612+
table = (
613+
f"{self.preparer.quote_schema(effective_schema)}"
614+
"."
615+
f"{self.preparer.quote(drop_table.element.name)}"
616+
)
617+
else:
618+
table = self.preparer.quote(drop_table.element.name)
610619
constrs += "ALTER TABLE {table} DROP CONSTRAINT {constr};".format(
611-
table=drop_table.element.name,
620+
table=table,
612621
constr=self.preparer.quote(cons.name),
613622
)
614623

@@ -1472,10 +1481,12 @@ def get_multi_foreign_keys(
14721481
)
14731482
FROM information_schema.table_constraints AS tc
14741483
JOIN information_schema.constraint_column_usage AS ccu
1475-
USING (table_catalog, table_schema, constraint_name)
1484+
ON ccu.table_catalog = tc.table_catalog
1485+
and ccu.constraint_schema = tc.table_schema
1486+
and ccu.constraint_name = tc.constraint_name
14761487
JOIN information_schema.constraint_table_usage AS ctu
14771488
ON ctu.table_catalog = tc.table_catalog
1478-
and ctu.table_schema = tc.table_schema
1489+
and ctu.constraint_schema = tc.table_schema
14791490
and ctu.constraint_name = tc.constraint_name
14801491
JOIN information_schema.key_column_usage AS kcu
14811492
ON kcu.table_catalog = tc.table_catalog

test/system/test_basics.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
Table,
1919
Column,
2020
Integer,
21+
ForeignKey,
2122
PrimaryKeyConstraint,
2223
String,
2324
Index,
2425
MetaData,
2526
Boolean,
2627
BIGINT,
28+
inspect,
2729
select,
2830
update,
2931
delete,
@@ -59,6 +61,16 @@ def define_tables(cls, metadata):
5961
Column("ID", Integer, primary_key=True),
6062
Column("name", String(20)),
6163
)
64+
# Add a foreign key example.
65+
Table(
66+
"number_colors",
67+
metadata,
68+
Column("ID", Integer, primary_key=True),
69+
Column(
70+
"number_id", Integer, ForeignKey("numbers.number", name="number_fk")
71+
),
72+
Column("color", String(20)),
73+
)
6274

6375
with cls.bind.begin() as conn:
6476
conn.execute(text("CREATE SCHEMA IF NOT EXISTS schema"))
@@ -69,6 +81,19 @@ def define_tables(cls, metadata):
6981
Column("name", String(20)),
7082
schema="schema",
7183
)
84+
# Add a foreign key example which crosses schema.
85+
Table(
86+
"number_colors",
87+
metadata,
88+
Column("ID", Integer, primary_key=True),
89+
Column(
90+
"number_id",
91+
Integer,
92+
ForeignKey("numbers.number", name="cross_schema_number_fk"),
93+
),
94+
Column("color", String(20)),
95+
schema="schema",
96+
)
7297

7398
def test_hello_world(self, connection):
7499
greeting = connection.execute(text("select 'Hello World'"))
@@ -88,7 +113,7 @@ def test_reflect(self, connection):
88113
engine = connection.engine
89114
meta: MetaData = MetaData()
90115
meta.reflect(bind=engine)
91-
eq_(2, len(meta.tables))
116+
eq_(3, len(meta.tables))
92117
table = meta.tables["numbers"]
93118
eq_(5, len(table.columns))
94119
eq_("number", table.columns[0].name)
@@ -238,3 +263,40 @@ class User(Base):
238263

239264
eq_(len(inserted_rows), len(selected_rows))
240265
eq_(set(inserted_rows), set(selected_rows))
266+
267+
def test_cross_schema_fk_lookups(self, connection):
268+
"""Ensures we introspect FKs within & across schema."""
269+
270+
engine = connection.engine
271+
272+
insp = inspect(engine)
273+
eq_(
274+
{
275+
(None, "number_colors"): [
276+
{
277+
"name": "number_fk",
278+
"referred_table": "numbers",
279+
"referred_schema": None,
280+
"referred_columns": ["number"],
281+
"constrained_columns": ["number_id"],
282+
}
283+
]
284+
},
285+
insp.get_multi_foreign_keys(filter_names=["number_colors"]),
286+
)
287+
eq_(
288+
{
289+
("schema", "number_colors"): [
290+
{
291+
"name": "cross_schema_number_fk",
292+
"referred_table": "numbers",
293+
"referred_schema": None,
294+
"referred_columns": ["number"],
295+
"constrained_columns": ["number_id"],
296+
}
297+
]
298+
},
299+
insp.get_multi_foreign_keys(
300+
filter_names=["number_colors"], schema="schema"
301+
),
302+
)

0 commit comments

Comments
 (0)