13
13
# limitations under the License.
14
14
15
15
"""DB-API Connection for the Google Cloud Spanner."""
16
- import time
17
16
import warnings
18
17
19
18
from google .api_core .exceptions import Aborted
23
22
from google .cloud .spanner_dbapi .batch_dml_executor import BatchMode , BatchDmlExecutor
24
23
from google .cloud .spanner_dbapi .parse_utils import _get_statement_type
25
24
from google .cloud .spanner_dbapi .parsed_statement import (
26
- ParsedStatement ,
27
- Statement ,
28
25
StatementType ,
29
26
)
30
27
from google .cloud .spanner_dbapi .partition_helper import PartitionId
28
+ from google .cloud .spanner_dbapi .parsed_statement import ParsedStatement , Statement
29
+ from google .cloud .spanner_dbapi .transaction_helper import TransactionRetryHelper
30
+ from google .cloud .spanner_dbapi .cursor import Cursor
31
31
from google .cloud .spanner_v1 import RequestOptions
32
- from google .cloud .spanner_v1 .session import _get_retry_delay
33
32
from google .cloud .spanner_v1 .snapshot import Snapshot
34
33
from deprecated import deprecated
35
34
36
- from google .cloud .spanner_dbapi .checksum import _compare_checksums
37
- from google .cloud .spanner_dbapi .checksum import ResultsChecksum
38
- from google .cloud .spanner_dbapi .cursor import Cursor
39
35
from google .cloud .spanner_dbapi .exceptions import (
40
36
InterfaceError ,
41
37
OperationalError ,
44
40
from google .cloud .spanner_dbapi .version import DEFAULT_USER_AGENT
45
41
from google .cloud .spanner_dbapi .version import PY_VERSION
46
42
47
- from google .rpc .code_pb2 import ABORTED
48
-
49
43
50
44
CLIENT_TRANSACTION_NOT_STARTED_WARNING = (
51
45
"This method is non-operational as a transaction has not been started."
52
46
)
53
- MAX_INTERNAL_RETRIES = 50
54
47
55
48
56
49
def check_not_closed (function ):
@@ -106,9 +99,6 @@ def __init__(self, instance, database=None, read_only=False):
106
99
self ._transaction = None
107
100
self ._session = None
108
101
self ._snapshot = None
109
- # SQL statements, which were executed
110
- # within the current transaction
111
- self ._statements = []
112
102
113
103
self .is_closed = False
114
104
self ._autocommit = False
@@ -125,6 +115,7 @@ def __init__(self, instance, database=None, read_only=False):
125
115
self ._spanner_transaction_started = False
126
116
self ._batch_mode = BatchMode .NONE
127
117
self ._batch_dml_executor : BatchDmlExecutor = None
118
+ self ._transaction_helper = TransactionRetryHelper (self )
128
119
129
120
@property
130
121
def autocommit (self ):
@@ -288,76 +279,6 @@ def _release_session(self):
288
279
self .database ._pool .put (self ._session )
289
280
self ._session = None
290
281
291
- def retry_transaction (self ):
292
- """Retry the aborted transaction.
293
-
294
- All the statements executed in the original transaction
295
- will be re-executed in new one. Results checksums of the
296
- original statements and the retried ones will be compared.
297
-
298
- :raises: :class:`google.cloud.spanner_dbapi.exceptions.RetryAborted`
299
- If results checksum of the retried statement is
300
- not equal to the checksum of the original one.
301
- """
302
- attempt = 0
303
- while True :
304
- self ._spanner_transaction_started = False
305
- attempt += 1
306
- if attempt > MAX_INTERNAL_RETRIES :
307
- raise
308
-
309
- try :
310
- self ._rerun_previous_statements ()
311
- break
312
- except Aborted as exc :
313
- delay = _get_retry_delay (exc .errors [0 ], attempt )
314
- if delay :
315
- time .sleep (delay )
316
-
317
- def _rerun_previous_statements (self ):
318
- """
319
- Helper to run all the remembered statements
320
- from the last transaction.
321
- """
322
- for statement in self ._statements :
323
- if isinstance (statement , list ):
324
- statements , checksum = statement
325
-
326
- transaction = self .transaction_checkout ()
327
- statements_tuple = []
328
- for single_statement in statements :
329
- statements_tuple .append (single_statement .get_tuple ())
330
- status , res = transaction .batch_update (statements_tuple )
331
-
332
- if status .code == ABORTED :
333
- raise Aborted (status .details )
334
-
335
- retried_checksum = ResultsChecksum ()
336
- retried_checksum .consume_result (res )
337
- retried_checksum .consume_result (status .code )
338
-
339
- _compare_checksums (checksum , retried_checksum )
340
- else :
341
- res_iter , retried_checksum = self .run_statement (statement , retried = True )
342
- # executing all the completed statements
343
- if statement != self ._statements [- 1 ]:
344
- for res in res_iter :
345
- retried_checksum .consume_result (res )
346
-
347
- _compare_checksums (statement .checksum , retried_checksum )
348
- # executing the failed statement
349
- else :
350
- # streaming up to the failed result or
351
- # to the end of the streaming iterator
352
- while len (retried_checksum ) < len (statement .checksum ):
353
- try :
354
- res = next (iter (res_iter ))
355
- retried_checksum .consume_result (res )
356
- except StopIteration :
357
- break
358
-
359
- _compare_checksums (statement .checksum , retried_checksum )
360
-
361
282
def transaction_checkout (self ):
362
283
"""Get a Cloud Spanner transaction.
363
284
@@ -433,12 +354,10 @@ def begin(self):
433
354
434
355
def commit (self ):
435
356
"""Commits any pending transaction to the database.
436
-
437
357
This is a no-op if there is no active client transaction.
438
358
"""
439
359
if self .database is None :
440
360
raise ValueError ("Database needs to be passed for this operation" )
441
-
442
361
if not self ._client_transaction_started :
443
362
warnings .warn (
444
363
CLIENT_TRANSACTION_NOT_STARTED_WARNING , UserWarning , stacklevel = 2
@@ -450,33 +369,31 @@ def commit(self):
450
369
if self ._spanner_transaction_started and not self ._read_only :
451
370
self ._transaction .commit ()
452
371
except Aborted :
453
- self .retry_transaction ()
372
+ self ._transaction_helper . retry_transaction ()
454
373
self .commit ()
455
374
finally :
456
- self ._release_session ()
457
- self ._statements = []
458
- self ._transaction_begin_marked = False
459
- self ._spanner_transaction_started = False
375
+ self ._reset_post_commit_or_rollback ()
460
376
461
377
def rollback (self ):
462
378
"""Rolls back any pending transaction.
463
-
464
379
This is a no-op if there is no active client transaction.
465
380
"""
466
381
if not self ._client_transaction_started :
467
382
warnings .warn (
468
383
CLIENT_TRANSACTION_NOT_STARTED_WARNING , UserWarning , stacklevel = 2
469
384
)
470
385
return
471
-
472
386
try :
473
387
if self ._spanner_transaction_started and not self ._read_only :
474
388
self ._transaction .rollback ()
475
389
finally :
476
- self ._release_session ()
477
- self ._statements = []
478
- self ._transaction_begin_marked = False
479
- self ._spanner_transaction_started = False
390
+ self ._reset_post_commit_or_rollback ()
391
+
392
+ def _reset_post_commit_or_rollback (self ):
393
+ self ._release_session ()
394
+ self ._transaction_helper .reset ()
395
+ self ._transaction_begin_marked = False
396
+ self ._spanner_transaction_started = False
480
397
481
398
@check_not_closed
482
399
def cursor (self ):
@@ -493,7 +410,7 @@ def run_prior_DDL_statements(self):
493
410
494
411
return self .database .update_ddl (ddl_statements ).result ()
495
412
496
- def run_statement (self , statement : Statement , retried = False ):
413
+ def run_statement (self , statement : Statement ):
497
414
"""Run single SQL statement in begun transaction.
498
415
499
416
This method is never used in autocommit mode. In
@@ -513,17 +430,11 @@ def run_statement(self, statement: Statement, retried=False):
513
430
checksum of this statement results.
514
431
"""
515
432
transaction = self .transaction_checkout ()
516
- if not retried :
517
- self ._statements .append (statement )
518
-
519
- return (
520
- transaction .execute_sql (
521
- statement .sql ,
522
- statement .params ,
523
- param_types = statement .param_types ,
524
- request_options = self .request_options ,
525
- ),
526
- ResultsChecksum () if retried else statement .checksum ,
433
+ return transaction .execute_sql (
434
+ statement .sql ,
435
+ statement .params ,
436
+ param_types = statement .param_types ,
437
+ request_options = self .request_options ,
527
438
)
528
439
529
440
@check_not_closed
0 commit comments