diff --git a/README.rst b/README.rst index 2eb77dff66..ac50d61863 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,8 @@ +THIS IS A FORK +============== +This exists for a bug fix. Waiting for corporate to sign the CLA. Once that is done, then a PR is made, and then accepted, this fork should be deleted. + + Python Client for Cloud Spanner =============================== diff --git a/google/cloud/spanner_dbapi/cursor.py b/google/cloud/spanner_dbapi/cursor.py index dccbf04dc8..a85e025b9a 100644 --- a/google/cloud/spanner_dbapi/cursor.py +++ b/google/cloud/spanner_dbapi/cursor.py @@ -206,13 +206,18 @@ def execute(self, sql, args=None): # self._run_prior_DDL_statements() self.connection.run_prior_DDL_statements() + # Statements run with existing transaction if not self.connection.autocommit: - if classification == parse_utils.STMT_UPDATING: - sql = parse_utils.ensure_where_clause(sql) - if classification != parse_utils.STMT_INSERT: sql, args = sql_pyformat_args_to_spanner(sql, args or None) + # Update statement + if classification == parse_utils.STMT_UPDATING: + sql = parse_utils.ensure_where_clause(sql) + transaction = self.connection.transaction_checkout() + self._do_execute_update(transaction, sql, args) + + # Other statements that either are insert or should be treated as inserts statement = Statement( sql, args, @@ -233,6 +238,7 @@ def execute(self, sql, args=None): self.connection.retry_transaction() return + # Statements run with out existing transactions if classification == parse_utils.STMT_NON_UPDATING: self._handle_DQL(sql, args or None) elif classification == parse_utils.STMT_INSERT: @@ -473,3 +479,5 @@ def get_table_column_schema(self, table_name): null_ok=is_nullable == "YES", spanner_type=spanner_type ) return column_details + +# This is a test diff --git a/tests/system/test_dbapi.py b/tests/system/test_dbapi.py index 210a4f5e90..790b49b567 100644 --- a/tests/system/test_dbapi.py +++ b/tests/system/test_dbapi.py @@ -357,3 +357,23 @@ def test_ping(shared_instance, dbapi_database): conn = Connection(shared_instance, dbapi_database) conn.validate() conn.close() + + +def test_update_non_autocommit(shared_instance, dbapi_database): + setup_rows = """ +INSERT INTO contacts (contact_id, first_name, last_name, email) +VALUES +(1, 'first-name', 'last-name', 'get@domen.ru'), +(2, 'first-name', 'last-name', 'get@domen.ru'), +(3, 'first-name', 'last-name', 'ignore@domen.ru') + """ + conn = Connection(shared_instance, dbapi_database) + cursor = conn.cursor() + cursor.execute(setup_rows) + conn.commit() + + cursor.execute( + "UPDATE contacts SET first_name='changed' WHERE email='get@domen.ru'" + ) + conn.commit() + assert cursor.rowcount == 2 diff --git a/tests/unit/spanner_dbapi/test_cursor.py b/tests/unit/spanner_dbapi/test_cursor.py index 07deffd707..3983ecca5c 100644 --- a/tests/unit/spanner_dbapi/test_cursor.py +++ b/tests/unit/spanner_dbapi/test_cursor.py @@ -168,6 +168,25 @@ def test_execute_insert_statement_autocommit_off(self): self.assertIsInstance(cursor._result_set, mock.MagicMock) self.assertIsInstance(cursor._itr, PeekIterator) + def test_execute_update_statement_autocommit_off(self): + from google.cloud.spanner_dbapi import parse_utils + + connection = self._make_connection(self.INSTANCE, mock.MagicMock()) + cursor = self._make_one(connection) + cursor.connection._autocommit = False + cursor.connection.transaction_checkout = mock.MagicMock(autospec=True) + + with mock.patch( + "google.cloud.spanner_dbapi.parse_utils.classify_stmt", + return_value=parse_utils.STMT_UPDATING, + ): + with mock.patch( + "google.cloud.spanner_dbapi.cursor.Cursor._do_execute_update", + return_value=mock.MagicMock(), + ): + cursor.execute(sql='UPDATE bogus row1="a" WHERE row2="b"') + self.assertIsInstance(cursor._result_set, mock.MagicMock) + def test_execute_statement(self): from google.cloud.spanner_dbapi import parse_utils