feat(dbapi): use inline begin to eliminate BeginTransaction RPC by waiho-gumloop · Pull Request #1502 · googleapis/python-spanner

@product-auto-label bot added size: s

Pull request size is small.

api: spanner

Issues related to the googleapis/python-spanner API.

labels

Feb 26, 2026

gemini-code-assist[bot]

@waiho-gumloop

@waiho-gumloop

…ests

Add test_dbapi_inline_begin.py with 7 mockserver tests that verify:
- Read-write DBAPI transactions send no BeginTransactionRequest
- First ExecuteSqlRequest uses TransactionSelector(begin=...)
- Read + write + commit request sequence is correct
- DML-only transactions use inline begin
- Read-only transactions still use explicit BeginTransaction
- Transaction retry after abort works with inline begin

Update existing mockserver tests that expected BeginTransactionRequest
for read-write DBAPI transactions:
- test_tags.py: Remove BeginTransactionRequest from expected sequences
  for all read-write tag tests, adjust tag index offsets
- test_dbapi_isolation_level.py: Verify isolation level on the inline
  begin field of ExecuteSqlRequest instead of BeginTransactionRequest

Made-with: Cursor

@waiho-gumloop

- Consolidate 3 redundant single-read tests into one comprehensive test
  that verifies: no BeginTransactionRequest, inline begin on first
  ExecuteSqlRequest, correct request sequence, and correct query results
- Rename test_second_statement_uses_transaction_id to
  test_read_then_write_full_lifecycle with additional assertions:
  CommitRequest.transaction_id matches the transaction ID from inline begin
- Strengthen test_rollback to verify RollbackRequest is sent with a
  non-empty transaction_id (was only checking no BeginTransactionRequest)
- Add CommitRequest assertions to abort retry test: both the aborted
  and successful commits carry valid transaction IDs
- Assert cursor.fetchall() return values in read tests to verify inline
  begin doesn't corrupt result set metadata
- Add RollbackRequest import

Made-with: Cursor

@rahul2393

@olavloite olavloite added the do not merge

Indicates a pull request not ready for merge, due to either quality or timing.

label

Mar 13, 2026

@olavloite

Add retries if the first statement in a read/write transaction fails, as the
statement then does not return a transaction ID. In order to ensure that we
get a transaction ID, we first execute an explicit BeginTransaction RPC and
then retry the original statement. We return the response of the retry to
the application, regardless whether the retry fails or succeeds.

The reason that we do a retry with a BeginTransaction AND include the
first statement, is to guarantee transaction consistency. If we were to
leave the first statement out of the transaction, then it will not be
guaranteed that the error condition that cause the failure in the first
place is actually still true when the transaction commits. This would break
the transaction guarantees.

Example (pseudo-code):

```sql
-- The following statement fails with ALREADY_EXISTS
insert into some_table (id, value) values (1, 'One');

-- Execute an explicit BeginTransaction RPC.
begin;
-- Retry the initial statement. This ensures that
-- whatever the response is, this response will be
-- valid for the entire transaction.
insert into some_table (id, value) values (1, 'One');

-- This is guaranteed to return a row.
select * from some_table where id=1;

-- ... execute the rest of the transaction ...
commit;
```

If we had not included the initial insert statement in the retried transaction,
then there is no guarantee that the select statement would actually return any
rows, as other transactions could in theory have deleted it in the meantime.

@olavloite olavloite removed the do not merge

Indicates a pull request not ready for merge, due to either quality or timing.

label

Mar 13, 2026

@olavloite

sinhasubham

sinhasubham

olavloite

olavloite