Skip to content

Commit 1ec0784

Browse files
authored
feat: enable support for get_key_columns and cleanup tests with unknown failures to specific failures. (#721)
* docs: lint fix for samples * feat: enable foreign key support * feat: add support for spanner fk * feat: disable foreign key as without on delete cascade the db cleanup process fails * fix: add comment as to why foreign key creation is disabled in django * fix: move test_check_constraints_sql_keywords to django3.2 skipped tests * chor: cleanup tests * tests: add failing tests in skipped test
1 parent bfb2e20 commit 1ec0784

File tree

4 files changed

+53
-29
lines changed

4 files changed

+53
-29
lines changed

README.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,4 @@ LIMITATIONS
260260
Spanner has certain limitations of it's own and a full set of limitations are documented over `here <https://cloud.google.com/spanner/quotas#schema_limits>`_
261261
It is recommended that you go through that list.
262262

263-
Django spanner has a set of limitations as well, please go through the `list <https://googleapis.dev/python/django-google-spanner/latest/limitations.html>`_.
263+
Django spanner has a set of limitations as well, please go through the `list <https://github.com/googleapis/python-spanner-django/blob/main/docs/limitations.rst>`_.

django_spanner/features.py

+8-20
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,12 @@ class DatabaseFeatures(BaseDatabaseFeatures):
6161

6262
# Django tests that aren't supported by Spanner.
6363
skip_tests = (
64-
# No foreign key constraints in Spanner.
64+
# Spanner does not support very long FK name: 400 Foreign Key name not valid
6565
"backends.tests.FkConstraintsTests.test_check_constraints",
66+
# No foreign key ON DELETE CASCADE in Spanner.
67+
"fixtures_regress.tests.TestFixtures.test_loaddata_raises_error_when_fixture_has_invalid_foreign_key",
6668
# Spanner does not support empty list of DML statement.
6769
"backends.tests.BackendTestCase.test_cursor_executemany_with_empty_params_list",
68-
"fixtures_regress.tests.TestFixtures.test_loaddata_raises_error_when_fixture_has_invalid_foreign_key",
6970
# No Django transaction management in Spanner.
7071
"basic.tests.SelectOnSaveTests.test_select_on_save_lying_update",
7172
# django_spanner monkey patches AutoField to have a default value.
@@ -119,6 +120,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
119120
"validation.test_validators.TestModelsWithValidators.test_custom_validator_raises_error_for_incorrect_value",
120121
"validation.test_validators.TestModelsWithValidators.test_field_validators_can_be_any_iterable",
121122
# Tests that assume a serial pk.
123+
"servers.tests.LiveServerDatabase.test_fixtures_loaded",
122124
"admin_filters.tests.ListFiltersTests.test_booleanfieldlistfilter_nullbooleanfield",
123125
"admin_filters.tests.ListFiltersTests.test_booleanfieldlistfilter_tuple",
124126
"admin_filters.tests.ListFiltersTests.test_booleanfieldlistfilter",
@@ -283,12 +285,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
283285
"transaction_hooks.tests.TestConnectionOnCommit.test_inner_savepoint_does_not_affect_outer",
284286
# No sequence for AutoField in Spanner.
285287
"introspection.tests.IntrospectionTests.test_sequence_list",
286-
# DatabaseIntrospection.get_key_columns() is only required if this
287-
# backend needs it (which it currently doesn't).
288-
"introspection.tests.IntrospectionTests.test_get_key_columns",
289-
# DatabaseIntrospection.get_relations() isn't implemented:
290-
# https://github.com/googleapis/python-spanner-django/issues/311
291-
"introspection.tests.IntrospectionTests.test_get_relations",
292288
# pyformat parameters not supported on INSERT:
293289
# https://github.com/googleapis/python-spanner-django/issues/343
294290
"backends.tests.BackendTestCase.test_cursor_execute_with_pyformat",
@@ -375,19 +371,15 @@ class DatabaseFeatures(BaseDatabaseFeatures):
375371
"model_forms.tests.UniqueTest.test_override_unique_together_message",
376372
# os.chmod() doesn't work on Kokoro?
377373
"file_uploads.tests.DirectoryCreationTests.test_readonly_root",
378-
# Tests that sometimes fail on Kokoro for unknown reasons.
374+
# Failing on kokoro but passes locally. Issue: Multiple queries executed expected 1.
379375
"contenttypes_tests.test_models.ContentTypesTests.test_cache_not_shared_between_managers",
380-
"migration_test_data_persistence.tests.MigrationDataNormalPersistenceTestCase.test_persistence",
381-
"servers.test_liveserverthread.LiveServerThreadTest.test_closes_connections",
382-
"servers.tests.LiveServerDatabase.test_fixtures_loaded",
383-
"view_tests.tests.test_csrf.CsrfViewTests.test_no_cookies",
384-
"view_tests.tests.test_csrf.CsrfViewTests.test_no_referer",
385-
"view_tests.tests.test_i18n.SetLanguageTests.test_lang_from_translated_i18n_pattern",
386376
)
387377
if USING_DJANGO_3:
388378
skip_tests += (
389379
# Spanner does not support UUID field natively
390380
"model_fields.test_uuid.TestQuerying.test_iexact",
381+
# Spanner does not support very long FK name: 400 Foreign Key name not valid
382+
"backends.tests.FkConstraintsTests.test_check_constraints_sql_keywords",
391383
# Spanner does not support setting a default value on columns.
392384
"schema.tests.SchemaTests.test_alter_text_field_to_not_null_with_default_value",
393385
# Direct SQL query test that do not follow spanner syntax.
@@ -452,8 +444,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
452444
# Spanner does not support SELECTing an arbitrary expression that also
453445
# appears in the GROUP BY clause.
454446
"annotations.tests.NonAggregateAnnotationTestCase.test_grouping_by_q_expression_annotation",
455-
# No foreign key constraints in Spanner.
456-
"backends.tests.FkConstraintsTests.test_check_constraints_sql_keywords",
457447
# No Django transaction management in Spanner.
458448
"transactions.tests.DisableDurabiltityCheckTests.test_nested_both_durable",
459449
"transactions.tests.DisableDurabiltityCheckTests.test_nested_inner_durable",
@@ -489,9 +479,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
489479
"schema.tests.SchemaTests.test_ci_cs_db_collation",
490480
# Spanner limitation: Cannot rename tables and columns.
491481
"migrations.test_operations.OperationTests.test_rename_field_case",
492-
# Tests that sometimes fail on Kokoro for unknown reasons.
493-
"migrations.test_operations.OperationTests.test_add_constraint_combinable",
494-
# Tests that fail but are not related to spanner.
482+
# Warning is not raised, not related to spanner.
495483
"test_utils.test_testcase.TestDataTests.test_undeepcopyable_warning",
496484
)
497485
else:

django_spanner/introspection.py

+30-5
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,6 @@ def get_relations(self, cursor, table_name):
150150
"""Return a dictionary of {field_name: (field_name_other_table, other_table)}
151151
representing all the relationships in the table.
152152
153-
TODO: DO NOT USE THIS METHOD UNTIL
154-
https://github.com/googleapis/python-spanner-django/issues/313
155-
is resolved so that foreign keys can be supported, as documented in:
156-
https://github.com/googleapis/python-spanner-django/issues/311
157-
158153
:type cursor: :class:`~google.cloud.spanner_dbapi.cursor.Cursor`
159154
:param cursor: A reference to a Spanner Database cursor.
160155
@@ -348,3 +343,33 @@ def get_constraints(self, cursor, table_name):
348343
constraints[index_name]["unique"] = is_unique
349344

350345
return constraints
346+
347+
def get_key_columns(self, cursor, table_name):
348+
"""
349+
Return a list of (column_name, referenced_table, referenced_column)
350+
for all key columns in the given table.
351+
"""
352+
key_columns = []
353+
cursor.execute(
354+
"""SELECT
355+
tc.COLUMN_NAME as column_name,
356+
ccu.TABLE_NAME as referenced_table,
357+
ccu.COLUMN_NAME as referenced_column
358+
from
359+
INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS tc
360+
JOIN
361+
INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS as rc
362+
ON
363+
tc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
364+
JOIN
365+
INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE as ccu
366+
ON
367+
rc.CONSTRAINT_NAME = ccu.CONSTRAINT_NAME
368+
WHERE
369+
tc.TABLE_NAME="{table}"
370+
""".format(
371+
table=self.connection.ops.quote_name(table_name)
372+
)
373+
)
374+
key_columns.extend(cursor.fetchall())
375+
return key_columns

django_spanner/schema.py

+14-3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
4141
sql_alter_column_type = "ALTER COLUMN %(column)s %(type)s"
4242

4343
sql_delete_column = "ALTER TABLE %(table)s DROP COLUMN %(column)s"
44+
# Spanner does not suppport ON DELETE CASCADE for foreign keys.
45+
# This can cause failures in django, hence sql_create_inline_fk is disabled.
46+
# sql_create_inline_fk = "CONSTRAINT FK_%(to_table)s_%(to_column)s_%(from_table)s_%(from_column)s FOREIGN KEY (%(from_column_norm)s) REFERENCES %(to_table_norm)s (%(to_column_norm)s)" # noqa
47+
sql_create_inline_fk = None
4448

4549
def create_model(self, model):
4650
"""
@@ -77,14 +81,21 @@ def create_model(self, model):
7781
params.extend(extra_params)
7882
# FK
7983
if field.remote_field and field.db_constraint:
84+
from_table = field.model._meta.db_table
85+
from_column = field.column
8086
to_table = field.remote_field.model._meta.db_table
8187
to_column = field.remote_field.model._meta.get_field(
8288
field.remote_field.field_name
8389
).column
8490
if self.sql_create_inline_fk:
85-
definition += " " + self.sql_create_inline_fk % {
86-
"to_table": self.quote_name(to_table),
87-
"to_column": self.quote_name(to_column),
91+
definition += ", " + self.sql_create_inline_fk % {
92+
"from_table": from_table,
93+
"from_column": from_column,
94+
"to_table": to_table,
95+
"to_column": to_column,
96+
"from_column_norm": self.quote_name(from_column),
97+
"to_table_norm": self.quote_name(to_table),
98+
"to_column_norm": self.quote_name(to_column),
8899
}
89100
elif self.connection.features.supports_foreign_keys:
90101
self.deferred_sql.append(

0 commit comments

Comments
 (0)