Repository: googleapis/python-spanner-django

Issue:
django: dropping a field with an index fails
```
======================================================================
ERROR: test_remove_fk (migrations.test_operations.OperationTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/tests/migrations/test_operations.py", line 1297, in test_remove_fk
    self.assertColumnNotExists("test_rfk_rider", "pony_id")
  File "/home/tim/code/django/tests/migrations/test_base.py", line 45, in assertColumnNotExists
    self.assertNotIn(column, [c.name for c in self.get_table_description(table, using=using)])
  File "/home/tim/code/django/tests/migrations/test_base.py", line 31, in get_table_description
    return connections[using].introspection.get_table_description(cursor, table)
  File "/home/tim/code/spanner-django/django_spanner/introspection.py", line 40, in get_table_description
    cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
  File "/home/tim/code/django/django/db/backends/utils.py", line 67, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/home/tim/code/django/django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/tim/code/django/django/db/backends/utils.py", line 82, in _execute
    return self.cursor.execute(sql)
  File "/home/tim/code/spanner-django/spanner/dbapi/autocommit_on_cursor.py", line 52, in execute
    self._run_prior_DDL_statements()
  File "/home/tim/code/spanner-django/spanner/dbapi/base_cursor.py", line 154, in _run_prior_DDL_statements
    return self._connection.run_prior_DDL_statements()
  File "/home/tim/code/spanner-django/spanner/dbapi/base_connection.py", line 58, in run_prior_DDL_statements
    return self.__handle_update_ddl(ddl_statements)
  File "/home/tim/code/spanner-django/spanner/dbapi/base_connection.py", line 35, in __handle_update_ddl
    return self._dbhandle.update_ddl(ddl_statements).result()
  File "/home/tim/.virtualenvs/django37/lib/python3.7/site-packages/google/api_core/future/polling.py", line 127, in result
    raise self._exception
google.api_core.exceptions.GoogleAPICallError: None Cannot drop column pony_id from table test_rfk_rider because it is used by index test_rfk_rider_pony_id_2ea40b1e.
```


Discussion:

Resolving patch:
diff --git a/django_spanner/features.py b/django_spanner/features.py
--- a/django_spanner/features.py
+++ b/django_spanner/features.py
@@ -228,6 +228,20 @@ class DatabaseFeatures(BaseDatabaseFeatures):
         'schema.tests.SchemaTests.test_alter_text_field_to_datetime_field',
         'schema.tests.SchemaTests.test_alter_text_field_to_time_field',
         # Spanner limitation: Cannot rename tables and columns.
+        'migrations.test_operations.OperationTests.test_alter_fk_non_fk',
+        'migrations.test_operations.OperationTests.test_alter_model_table',
+        'migrations.test_operations.OperationTests.test_alter_model_table_m2m',
+        'migrations.test_operations.OperationTests.test_rename_field',
+        'migrations.test_operations.OperationTests.test_rename_field_reloads_state_on_fk_target_changes',
+        'migrations.test_operations.OperationTests.test_rename_m2m_model_after_rename_field',
+        'migrations.test_operations.OperationTests.test_rename_m2m_target_model',
+        'migrations.test_operations.OperationTests.test_rename_m2m_through_model',
+        'migrations.test_operations.OperationTests.test_rename_model',
+        'migrations.test_operations.OperationTests.test_rename_model_with_m2m',
+        'migrations.test_operations.OperationTests.test_rename_model_with_self_referential_fk',
+        'migrations.test_operations.OperationTests.test_rename_model_with_self_referential_m2m',
+        'migrations.test_operations.OperationTests.test_rename_model_with_superclass_fk',
+        'migrations.test_operations.OperationTests.test_repoint_field_m2m',
         'schema.tests.SchemaTests.test_alter_db_table_case',
         'schema.tests.SchemaTests.test_alter_pk_with_self_referential_field',
         'schema.tests.SchemaTests.test_rename',
@@ -249,6 +263,12 @@ class DatabaseFeatures(BaseDatabaseFeatures):
         'schema.tests.SchemaTests.test_primary_key',
         # Spanner limitation:  Cannot remove a column from the primary key.
         'schema.tests.SchemaTests.test_alter_int_pk_to_int_unique',
+        # changing a not null constraint isn't allowed if it affects an index:
+        # https://github.com/orijtech/django-spanner/issues/378
+        'migrations.test_operations.OperationTests.test_alter_field_with_index',
+        # parsing INSERT with one inlined value and one placeholder fails:
+        # https://github.com/orijtech/django-spanner/issues/393
+        'migrations.test_operations.OperationTests.test_run_sql_params',
         # This test doesn't flush the database properly:
         # https://code.djangoproject.com/ticket/31398
         'multiple_database.tests.AuthTestCase',
diff --git a/django_spanner/schema.py b/django_spanner/schema.py
--- a/django_spanner/schema.py
+++ b/django_spanner/schema.py
@@ -128,11 +128,10 @@ def add_field(self, model, field):
         # defaults for this but Spanner doesn't support them.)
         effective_default = self.effective_default(field)
         if effective_default is not None:
-            self.execute('UPDATE %(table)s SET %(column)s=%(value)r' % {
+            self.execute('UPDATE %(table)s SET %(column)s=%%s' % {
                 "table": self.quote_name(model._meta.db_table),
                 "column": self.quote_name(field.column),
-                "value": effective_default,
-            })
+            }, (effective_default,))
         # Spanner doesn't support adding NOT NULL columns to existing tables.
         if not field.null:
             self.execute(self.sql_alter_column % {
@@ -152,6 +151,14 @@ def add_field(self, model, field):
         if field.remote_field and self.connection.features.supports_foreign_keys and field.db_constraint:
             self.deferred_sql.append(self._create_fk_sql(model, field, "_fk_%(to_table)s_%(to_column)s"))
 
+    def remove_field(self, model, field):
+        # Spanner requires dropping a column's indexes before dropping the
+        # column.
+        index_names = self._constraint_names(model, [field.column], index=True)
+        for index_name in index_names:
+            self.execute(self._delete_index_sql(model, index_name))
+        super().remove_field(model, field)
+
     def column_sql(self, model, field, include_default=False, exclude_not_null=False):
         """
         Take a field and return its column definition.


Test patch: