3434from google .rpc .code_pb2 import ABORTED
3535
3636
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+ )
3840MAX_INTERNAL_RETRIES = 50
3941
4042
@@ -104,6 +106,7 @@ def __init__(self, instance, database=None, read_only=False):
104106 self ._read_only = read_only
105107 self ._staleness = None
106108 self .request_priority = None
109+ self ._transaction_begin_marked = False
107110
108111 @property
109112 def autocommit (self ):
@@ -122,7 +125,7 @@ def autocommit(self, value):
122125 :type value: bool
123126 :param value: New autocommit mode state.
124127 """
125- if value and not self ._autocommit and self .inside_transaction :
128+ if value and not self ._autocommit and self ._spanner_transaction_started :
126129 self .commit ()
127130
128131 self ._autocommit = value
@@ -137,17 +140,35 @@ def database(self):
137140 return self ._database
138141
139142 @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
142148
143149 Returns:
144- bool: True if transaction begun , False otherwise.
150+ bool: True if Spanner transaction started , False otherwise.
145151 """
146152 return (
147153 self ._transaction
148154 and not self ._transaction .committed
149155 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
151172
152173 @property
153174 def instance (self ):
@@ -175,7 +196,7 @@ def read_only(self, value):
175196 Args:
176197 value (bool): True for ReadOnly mode, False for ReadWrite.
177198 """
178- if self .inside_transaction :
199+ if self ._spanner_transaction_started :
179200 raise ValueError (
180201 "Connection read/write mode can't be changed while a transaction is in progress. "
181202 "Commit or rollback the current transaction and try again."
@@ -213,7 +234,7 @@ def staleness(self, value):
213234 Args:
214235 value (dict): Staleness type and value.
215236 """
216- if self .inside_transaction :
237+ if self ._spanner_transaction_started :
217238 raise ValueError (
218239 "`staleness` option can't be changed while a transaction is in progress. "
219240 "Commit or rollback the current transaction and try again."
@@ -331,15 +352,16 @@ def transaction_checkout(self):
331352 """Get a Cloud Spanner transaction.
332353
333354 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.
335356
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
337359
338360 :rtype: :class:`google.cloud.spanner_v1.transaction.Transaction`
339361 :returns: A Cloud Spanner transaction object, ready to use.
340362 """
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 :
343365 self ._transaction = self ._session_checkout ().transaction ()
344366 self ._transaction .begin ()
345367
@@ -354,7 +376,7 @@ def snapshot_checkout(self):
354376 :rtype: :class:`google.cloud.spanner_v1.snapshot.Snapshot`
355377 :returns: A Cloud Spanner snapshot object, ready to use.
356378 """
357- if self .read_only and not self .autocommit :
379+ if self .read_only and self ._client_transaction_started :
358380 if not self ._snapshot :
359381 self ._snapshot = Snapshot (
360382 self ._session_checkout (), multi_use = True , ** self .staleness
@@ -369,55 +391,80 @@ def close(self):
369391 The connection will be unusable from this point forward. If the
370392 connection has an active transaction, it will be rolled back.
371393 """
372- if self .inside_transaction :
394+ if self ._spanner_transaction_started and not self . read_only :
373395 self ._transaction .rollback ()
374396
375397 if self ._own_pool and self .database :
376398 self .database ._pool .clear ()
377399
378400 self .is_closed = True
379401
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+
380418 def commit (self ):
381419 """Commits any pending transaction to the database.
382420
383- This method is non-operational in autocommit mode .
421+ This is a no-op if there is no active client transaction .
384422 """
385423 if self .database is None :
386424 raise ValueError ("Database needs to be passed for this operation" )
387- self ._snapshot = None
388425
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+ )
391430 return
392431
393432 self .run_prior_DDL_statements ()
394- if self .inside_transaction :
433+ if self ._spanner_transaction_started :
395434 try :
396- if not self .read_only :
435+ if self .read_only :
436+ self ._snapshot = None
437+ else :
397438 self ._transaction .commit ()
398439
399440 self ._release_session ()
400441 self ._statements = []
442+ self ._transaction_begin_marked = False
401443 except Aborted :
402444 self .retry_transaction ()
403445 self .commit ()
404446
405447 def rollback (self ):
406448 """Rolls back any pending transaction.
407449
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.
410451 """
411- self ._snapshot = None
412452
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 :
417463 self ._transaction .rollback ()
418464
419465 self ._release_session ()
420466 self ._statements = []
467+ self ._transaction_begin_marked = False
421468
422469 @check_not_closed
423470 def cursor (self ):
0 commit comments