Repository: googleapis/python-spanner-django

Issue:
django-test-suite: custom_pk.tests.CustomPKTests.{test_required_pk, test_unique_pk}: AssertionError: IntegrityError not raised 
- [ ] custom_pk.tests.CustomPKTests.test_required_pk
- [ ] custom_pk.tests.CustomPKTests.test_unique_pk

```shell
======================================================================
FAIL: test_required_pk (custom_pk.tests.CustomPKTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/emmanuelodeke/Desktop/spanner-orm/django_tests/django/django/test/testcases.py", line 1229, in skip_wrapper
    return test_func(*args, **kwargs)
  File "/Users/emmanuelodeke/Desktop/spanner-orm/django_tests/django/tests/custom_pk/tests.py", line 232, in test_required_pk
    Employee.objects.create(first_name="Tom", last_name="Smith")
AssertionError: IntegrityError not raised

======================================================================
FAIL: test_unique_pk (custom_pk.tests.CustomPKTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/emmanuelodeke/Desktop/spanner-orm/django_tests/django/tests/custom_pk/tests.py", line 201, in test_unique_pk
    Employee.objects.create(employee_code=123, first_name="Fred", last_name="Jones")
AssertionError: IntegrityError not raised

----------------------------------------------------------------------
Ran 6 tests in 42.325s

FAILED (failures=2)
Preserving test database for alias 'default' ('travisdb1')...

Dropped travisdb1
Dropped travisdb1-other

real	3m32.496s
user	0m9.269s
sys	0m7.792s
```


Discussion:

Resolving patch:
diff --git a/spanner/dbapi/cursor.py b/spanner/dbapi/cursor.py
--- a/spanner/dbapi/cursor.py
+++ b/spanner/dbapi/cursor.py
@@ -92,29 +92,15 @@ def execute(self, sql, args=None):
             if classification == STMT_DDL:
                 self.__handle_update_ddl(sql)
             elif classification == STMT_NON_UPDATING:
-                self.__handle_DQL(
-                    sql,
-                    args or None,
-                    param_types=param_types,
-                )
+                self.__handle_DQL(sql, args or None, param_types=param_types)
             elif classification == STMT_INSERT:
-                self.__handle_insert(
-                    sql,
-                    args or None,
-                )
+                self.__handle_insert(sql, args or None)
             else:
-                self.__handle_update(
-                    sql,
-                    args or None,
-                    param_types=param_types,
-                )
-
-        except grpc_exceptions.AlreadyExists as e:
+                self.__handle_update(sql, args or None, param_types=param_types)
+        except (grpc_exceptions.AlreadyExists, grpc_exceptions.FailedPrecondition) as e:
             raise IntegrityError(e.details if hasattr(e, 'details') else e)
-
         except grpc_exceptions.InvalidArgument as e:
             raise ProgrammingError(e.details if hasattr(e, 'details') else e)
-
         except grpc_exceptions.InternalServerError as e:
             raise OperationalError(e.details if hasattr(e, 'details') else e)
 
@@ -143,6 +129,8 @@ def __do_execute_update(self, transaction, sql, params, param_types=None):
         return res
 
     def __handle_insert(self, sql, params):
+        self.__commit_preceding_batch(OP_DDL)
+
         # There are 3 variants of an INSERT statement:
         # a) INSERT INTO <table> (columns...) VALUES (<inlined values>): no params
         # b) INSERT INTO <table> (columns...) SELECT_STMT:               no params
@@ -154,13 +142,16 @@ def __handle_insert(self, sql, params):
             # Case c)
             rows = rows_for_insert_or_update(columns, params, parts.get('values_pyformat'))
 
-        self.__db_handle.append_to_batch_stack(
-            op=OP_INSERT,
-            table=parts.get('table'),
-            columns=columns,
-            values=rows,
+        self.__db_handle.in_transaction(
+            self.__do_execute_insert,
+            parts.get('table'),
+            columns,
+            rows,
         )
 
+    def __do_execute_insert(self, transaction, table, columns, values):
+        return transaction.insert(table, columns, values)
+
     def __execute_insert_no_params(self, transaction, sql):
         return transaction.execute_update(sql)
 
diff --git a/spanner/django/features.py b/spanner/django/features.py
--- a/spanner/django/features.py
+++ b/spanner/django/features.py
@@ -74,9 +74,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
         'bulk_create.tests.BulkCreateTests.test_bulk_insert_expressions',
         # To be investigated: https://github.com/orijtech/spanner-orm/issues/135
         'admin_changelist.tests.ChangeListTests.test_multiuser_edit',
-        # FailedPrecondition and AlreadyExists should be raised as IntegrityError:
-        # https://github.com/orijtech/spanner-orm/issues/154
-        'model_fields.test_booleanfield.BooleanFieldTests.test_null_default',
         # Implement DatabaseOperations.datetime_cast_date_sql():
         # https://github.com/orijtech/spanner-orm/issues/170
         'model_fields.test_datetimefield.DateTimeFieldTests.test_lookup_date_with_use_tz',


Test patch: