Skip to content

Commit 8984eb9

Browse files
committed
django/DatabaseIntrospection: implement get_primary_key_column, get_relations
Implemented: * DatabaseIntrospection.get_primary_key_column * DatabaseIntrospection.get_relations However, get_relations tests CANNOT yet be enabled because Cloud Spanner doesn't yet have ON DELETE cascading for FOREIGN KEYS which limits #313, which renders the method useless as it will always return {} instead of being populated with values that are derived from FOREIGN KEYs. Fixes #312 Updates #311
1 parent 622dc1d commit 8984eb9

6 files changed

+77
-43
lines changed

spanner/dbapi/autocommit_off_connection.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -170,17 +170,24 @@ def list_tables(self):
170170
# with a transaction otherwise we get back:
171171
# 400 Unsupported concurrency mode in query using INFORMATION_SCHEMA.
172172
# hence this specialized method.
173-
self.run_prior_DDL_statements()
174-
175-
with self.__dbhandle.snapshot() as snapshot:
176-
res = snapshot.execute_sql("""
173+
return self.run_sql_in_snapshot(
174+
"""
177175
SELECT
178176
t.table_name
179177
FROM
180178
information_schema.tables AS t
181179
WHERE
182180
t.table_catalog = '' and t.table_schema = ''
183-
""")
181+
""",
182+
)
183+
184+
def run_sql_in_snapshot(self, sql):
185+
# Some SQL e.g. for INFORMATION_SCHEMA cannot be run in read-write transactions
186+
# hence this method exists to circumvent that limit.
187+
self.run_prior_DDL_statements()
188+
189+
with self.__dbhandle.snapshot() as snapshot:
190+
res = snapshot.execute_sql(sql)
184191
return list(res)
185192

186193
def get_table_column_schema(self, table_name):

spanner/dbapi/autocommit_off_cursor.py

+3-10
Original file line numberDiff line numberDiff line change
@@ -357,18 +357,11 @@ def __run_prior_DDL_statements(self):
357357
return self.__connection.run_prior_DDL_statements()
358358

359359
def list_tables(self):
360-
# We CANNOT list tables with
361-
# SELECT
362-
# t.table_name
363-
# FROM
364-
# information_schema.tables AS t
365-
# WHERE
366-
# t.table_catalog = '' and t.table_schema = ''
367-
# with a transaction otherwise we get back:
368-
# 400 Unsupported concurrency mode in query using INFORMATION_SCHEMA.
369-
# hence this specialized method.
370360
return self.__connection.list_tables()
371361

362+
def run_sql_in_snapshot(self, sql):
363+
return self.__connection.run_sql_in_snapshot(sql)
364+
372365
def get_table_column_schema(self, table_name):
373366
return self.__connection.get_table_column_schema(table_name)
374367

spanner/dbapi/autocommit_on_connection.py

+13-15
Original file line numberDiff line numberDiff line change
@@ -89,27 +89,25 @@ def run_prior_DDL_statements(self):
8989
return self.__handle_update_ddl(ddl_statements)
9090

9191
def list_tables(self):
92-
# We CANNOT list tables with
93-
# SELECT
94-
# t.table_name
95-
# FROM
96-
# information_schema.tables AS t
97-
# WHERE
98-
# t.table_catalog = '' and t.table_schema = ''
99-
# with a transaction otherwise we get back:
100-
# 400 Unsupported concurrency mode in query using INFORMATION_SCHEMA.
101-
# hence this specialized method.
102-
self.run_prior_DDL_statements()
103-
104-
with self.__dbhandle.snapshot() as snapshot:
105-
res = snapshot.execute_sql("""
92+
return self.run_sql_in_snapshot(
93+
"""
10694
SELECT
10795
t.table_name
10896
FROM
10997
information_schema.tables AS t
11098
WHERE
11199
t.table_catalog = '' and t.table_schema = ''
112-
""")
100+
""",
101+
)
102+
103+
def run_sql_in_snapshot(self, sql):
104+
# Some SQL e.g. for INFORMATION_SCHEMA cannot be run in read-write transactions
105+
# hence this method exists to circumvent that limit.
106+
self.run_prior_DDL_statements()
107+
108+
with self.__dbhandle.snapshot() as snapshot:
109+
print('RUN_SQL_IN_SNAPSHOT: %s' % sql)
110+
res = snapshot.execute_sql(sql)
113111
return list(res)
114112

115113
def get_table_column_schema(self, table_name):

spanner/dbapi/autocommit_on_cursor.py

+3-10
Original file line numberDiff line numberDiff line change
@@ -269,18 +269,11 @@ def __run_prior_DDL_statements(self):
269269
return self.__db_handle.run_prior_DDL_statements()
270270

271271
def list_tables(self):
272-
# We CANNOT list tables with
273-
# SELECT
274-
# t.table_name
275-
# FROM
276-
# information_schema.tables AS t
277-
# WHERE
278-
# t.table_catalog = '' and t.table_schema = ''
279-
# with a transaction otherwise we get back:
280-
# 400 Unsupported concurrency mode in query using INFORMATION_SCHEMA.
281-
# hence this specialized method.
282272
return self.__db_handle.list_tables()
283273

274+
def run_sql_in_snapshot(self, sql):
275+
return self.__db_handle.run_sql_in_snapshot(sql)
276+
284277
def get_table_column_schema(self, table_name):
285278
return self.__db_handle.get_table_column_schema(table_name)
286279

spanner/django/features.py

-3
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
265265
# DatabaseIntrospection.get_table_description() isn't fully implemented:
266266
# https://github.com/orijtech/django-spanner/issues/248
267267
'introspection.tests.IntrospectionTests.test_get_table_description_col_lengths',
268-
# DatabaseIntrospection.get_primary_key_column() isn't implemented:
269-
# https://github.com/orijtech/django-spanner/issues/312
270-
'introspection.tests.IntrospectionTests.test_get_primary_key_column',
271268
# DatabaseIntrospection.get_relations() isn't implemented:
272269
# https://github.com/orijtech/django-spanner/issues/311
273270
'introspection.tests.IntrospectionTests.test_get_relations',

spanner/django/introspection.py

+46
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,49 @@ def get_table_description(self, cursor, table_name):
5252
)
5353

5454
return descriptions
55+
56+
def get_relations(self, cursor, table_name):
57+
# TODO: PLEASE DO NOT USE THIS METHOD UNTIL
58+
# https://github.com/orijtech/django-spanner/issues/313
59+
# is resolved so that foreign keys can be supported, as documented in:
60+
# https://github.com/orijtech/django-spanner/issues/311
61+
"""
62+
Return a dictionary of {field_name: (field_name_other_table, other_table)}
63+
representing all relationships in the table.
64+
"""
65+
results = cursor.run_sql_in_snapshot(
66+
'''
67+
SELECT
68+
tc.COLUMN_NAME as col, ccu.COLUMN_NAME as ref_col, ccu.TABLE_NAME as ref_table
69+
FROM
70+
INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS tc
71+
JOIN
72+
INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS as rc
73+
ON
74+
tc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
75+
JOIN
76+
INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE as ccu
77+
ON
78+
rc.UNIQUE_CONSTRAINT_NAME = ccu.CONSTRAINT_NAME
79+
WHERE
80+
tc.TABLE_NAME="%s"''' % self.connection.ops.quote_name(table_name),
81+
)
82+
return {column: (referred_column, referred_table) for (column, referred_column, referred_table) in results}
83+
84+
def get_primary_key_column(self, cursor, table_name):
85+
results = cursor.run_sql_in_snapshot(
86+
'''
87+
SELECT
88+
ccu.COLUMN_NAME
89+
FROM
90+
INFORMATION_SCHEMA.TABLE_CONSTRAINTS as tc
91+
RIGHT JOIN
92+
INFORMATION_SCHEMA.KEY_COLUMN_USAGE
93+
AS
94+
ccu ON tc.CONSTRAINT_NAME = ccu.CONSTRAINT_NAME
95+
WHERE
96+
tc.TABLE_NAME="%s" AND tc.CONSTRAINT_TYPE='PRIMARY KEY' AND tc.TABLE_SCHEMA=''
97+
''' % self.connection.ops.quote_name(table_name),
98+
)
99+
100+
return results[0][0] if results else None

0 commit comments

Comments
 (0)