34
34
from google .rpc .code_pb2 import ABORTED
35
35
36
36
37
- AUTOCOMMIT_MODE_WARNING = "This method is non-operational in autocommit mode"
37
+ CLIENT_TRANSACTION_NOT_STARTED_WARNING = (
38
+ "This method is non-operational as transaction has not started"
39
+ )
38
40
MAX_INTERNAL_RETRIES = 50
39
41
40
42
@@ -104,6 +106,7 @@ def __init__(self, instance, database=None, read_only=False):
104
106
self ._read_only = read_only
105
107
self ._staleness = None
106
108
self .request_priority = None
109
+ self ._transaction_begin_marked = False
107
110
108
111
@property
109
112
def autocommit (self ):
@@ -122,7 +125,7 @@ def autocommit(self, value):
122
125
:type value: bool
123
126
:param value: New autocommit mode state.
124
127
"""
125
- if value and not self ._autocommit and self .inside_transaction :
128
+ if value and not self ._autocommit and self ._spanner_transaction_started :
126
129
self .commit ()
127
130
128
131
self ._autocommit = value
@@ -137,17 +140,35 @@ def database(self):
137
140
return self ._database
138
141
139
142
@property
140
- def inside_transaction (self ):
141
- """Flag: transaction is started.
143
+ def _spanner_transaction_started (self ):
144
+ """Flag: whether transaction started at Spanner. This means that we had
145
+ made atleast one call to Spanner. Property client_transaction_started
146
+ would always be true if this is true as transaction has to start first
147
+ at clientside than at Spanner
142
148
143
149
Returns:
144
- bool: True if transaction begun , False otherwise.
150
+ bool: True if Spanner transaction started , False otherwise.
145
151
"""
146
152
return (
147
153
self ._transaction
148
154
and not self ._transaction .committed
149
155
and not self ._transaction .rolled_back
150
- )
156
+ ) or (self ._snapshot is not None )
157
+
158
+ @property
159
+ def inside_transaction (self ):
160
+ """Deprecated property which won't be supported in future versions.
161
+ Please use spanner_transaction_started property instead."""
162
+ return self ._spanner_transaction_started
163
+
164
+ @property
165
+ def _client_transaction_started (self ):
166
+ """Flag: whether transaction started at client side.
167
+
168
+ Returns:
169
+ bool: True if transaction started, False otherwise.
170
+ """
171
+ return (not self ._autocommit ) or self ._transaction_begin_marked
151
172
152
173
@property
153
174
def instance (self ):
@@ -175,7 +196,7 @@ def read_only(self, value):
175
196
Args:
176
197
value (bool): True for ReadOnly mode, False for ReadWrite.
177
198
"""
178
- if self .inside_transaction :
199
+ if self ._spanner_transaction_started :
179
200
raise ValueError (
180
201
"Connection read/write mode can't be changed while a transaction is in progress. "
181
202
"Commit or rollback the current transaction and try again."
@@ -213,7 +234,7 @@ def staleness(self, value):
213
234
Args:
214
235
value (dict): Staleness type and value.
215
236
"""
216
- if self .inside_transaction :
237
+ if self ._spanner_transaction_started :
217
238
raise ValueError (
218
239
"`staleness` option can't be changed while a transaction is in progress. "
219
240
"Commit or rollback the current transaction and try again."
@@ -331,15 +352,16 @@ def transaction_checkout(self):
331
352
"""Get a Cloud Spanner transaction.
332
353
333
354
Begin a new transaction, if there is no transaction in
334
- this connection yet. Return the begun one otherwise.
355
+ this connection yet. Return the started one otherwise.
335
356
336
- The method is non operational in autocommit mode.
357
+ This method is a no-op if the connection is in autocommit mode and no
358
+ explicit transaction has been started
337
359
338
360
:rtype: :class:`google.cloud.spanner_v1.transaction.Transaction`
339
361
:returns: A Cloud Spanner transaction object, ready to use.
340
362
"""
341
- if not self .autocommit :
342
- if not self .inside_transaction :
363
+ if not self .read_only and self . _client_transaction_started :
364
+ if not self ._spanner_transaction_started :
343
365
self ._transaction = self ._session_checkout ().transaction ()
344
366
self ._transaction .begin ()
345
367
@@ -354,7 +376,7 @@ def snapshot_checkout(self):
354
376
:rtype: :class:`google.cloud.spanner_v1.snapshot.Snapshot`
355
377
:returns: A Cloud Spanner snapshot object, ready to use.
356
378
"""
357
- if self .read_only and not self .autocommit :
379
+ if self .read_only and self ._client_transaction_started :
358
380
if not self ._snapshot :
359
381
self ._snapshot = Snapshot (
360
382
self ._session_checkout (), multi_use = True , ** self .staleness
@@ -369,55 +391,80 @@ def close(self):
369
391
The connection will be unusable from this point forward. If the
370
392
connection has an active transaction, it will be rolled back.
371
393
"""
372
- if self .inside_transaction :
394
+ if self ._spanner_transaction_started and not self . read_only :
373
395
self ._transaction .rollback ()
374
396
375
397
if self ._own_pool and self .database :
376
398
self .database ._pool .clear ()
377
399
378
400
self .is_closed = True
379
401
402
+ @check_not_closed
403
+ def begin (self ):
404
+ """
405
+ Marks the transaction as started.
406
+
407
+ :raises: :class:`InterfaceError`: if this connection is closed.
408
+ :raises: :class:`OperationalError`: if there is an existing transaction that has begin or is running
409
+ """
410
+ if self ._transaction_begin_marked :
411
+ raise OperationalError ("A transaction has already started" )
412
+ if self ._spanner_transaction_started :
413
+ raise OperationalError (
414
+ "Beginning a new transaction is not allowed when a transaction is already running"
415
+ )
416
+ self ._transaction_begin_marked = True
417
+
380
418
def commit (self ):
381
419
"""Commits any pending transaction to the database.
382
420
383
- This method is non-operational in autocommit mode .
421
+ This is a no-op if there is no active client transaction .
384
422
"""
385
423
if self .database is None :
386
424
raise ValueError ("Database needs to be passed for this operation" )
387
- self ._snapshot = None
388
425
389
- if self ._autocommit :
390
- warnings .warn (AUTOCOMMIT_MODE_WARNING , UserWarning , stacklevel = 2 )
426
+ if not self ._client_transaction_started :
427
+ warnings .warn (
428
+ CLIENT_TRANSACTION_NOT_STARTED_WARNING , UserWarning , stacklevel = 2
429
+ )
391
430
return
392
431
393
432
self .run_prior_DDL_statements ()
394
- if self .inside_transaction :
433
+ if self ._spanner_transaction_started :
395
434
try :
396
- if not self .read_only :
435
+ if self .read_only :
436
+ self ._snapshot = None
437
+ else :
397
438
self ._transaction .commit ()
398
439
399
440
self ._release_session ()
400
441
self ._statements = []
442
+ self ._transaction_begin_marked = False
401
443
except Aborted :
402
444
self .retry_transaction ()
403
445
self .commit ()
404
446
405
447
def rollback (self ):
406
448
"""Rolls back any pending transaction.
407
449
408
- This is a no-op if there is no active transaction or if the connection
409
- is in autocommit mode.
450
+ This is a no-op if there is no active client transaction.
410
451
"""
411
- self ._snapshot = None
412
452
413
- if self ._autocommit :
414
- warnings .warn (AUTOCOMMIT_MODE_WARNING , UserWarning , stacklevel = 2 )
415
- elif self ._transaction :
416
- if not self .read_only :
453
+ if not self ._client_transaction_started :
454
+ warnings .warn (
455
+ CLIENT_TRANSACTION_NOT_STARTED_WARNING , UserWarning , stacklevel = 2
456
+ )
457
+ return
458
+
459
+ if self ._spanner_transaction_started :
460
+ if self .read_only :
461
+ self ._snapshot = None
462
+ else :
417
463
self ._transaction .rollback ()
418
464
419
465
self ._release_session ()
420
466
self ._statements = []
467
+ self ._transaction_begin_marked = False
421
468
422
469
@check_not_closed
423
470
def cursor (self ):
0 commit comments