@@ -463,13 +463,9 @@ TransactionSelector getTransactionSelector() {
463
463
// Aborted error if the call that included the BeginTransaction option fails. The
464
464
// Aborted error will cause the entire transaction to be retried, and the retry will use
465
465
// a separate BeginTransaction RPC.
466
- if (trackTransactionStarter ) {
467
- TransactionSelector .newBuilder ()
468
- .setId (tx .get (waitForTransactionTimeoutMillis , TimeUnit .MILLISECONDS ))
469
- .build ();
470
- } else {
471
- TransactionSelector .newBuilder ().setId (tx .get ()).build ();
472
- }
466
+ TransactionSelector .newBuilder ()
467
+ .setId (tx .get (waitForTransactionTimeoutMillis , TimeUnit .MILLISECONDS ))
468
+ .build ();
473
469
}
474
470
} catch (ExecutionException e ) {
475
471
if (e .getCause () instanceof AbortedException ) {
@@ -479,11 +475,15 @@ TransactionSelector getTransactionSelector() {
479
475
}
480
476
throw SpannerExceptionFactory .newSpannerException (e .getCause ());
481
477
} catch (TimeoutException e ) {
478
+ // Throw an ABORTED exception to force a retry of the transaction if no transaction
479
+ // has been returned by the first statement.
482
480
SpannerException se =
483
481
SpannerExceptionFactory .newSpannerException (
484
- ErrorCode .DEADLINE_EXCEEDED ,
485
- "Timeout while waiting for a transaction to be returned by another statement. "
486
- + "See the suppressed exception for the stacktrace of the caller that should return a transaction" ,
482
+ ErrorCode .ABORTED ,
483
+ "Timeout while waiting for a transaction to be returned by another statement."
484
+ + (trackTransactionStarter
485
+ ? " See the suppressed exception for the stacktrace of the caller that should return a transaction"
486
+ : "" ),
487
487
e );
488
488
if (transactionStarter != null ) {
489
489
se .addSuppressed (transactionStarter );
@@ -498,12 +498,20 @@ TransactionSelector getTransactionSelector() {
498
498
}
499
499
500
500
@ Override
501
- public void onTransactionMetadata (Transaction transaction ) {
502
- // A transaction has been returned by a statement that was executed. Set the id of the
503
- // transaction on this instance and release the lock to allow other statements to proceed.
504
- if (this .transactionId == null && transaction != null && transaction .getId () != null ) {
505
- this .transactionId = transaction .getId ();
506
- this .transactionIdFuture .set (transaction .getId ());
501
+ public void onTransactionMetadata (Transaction transaction , boolean shouldIncludeId ) {
502
+ Preconditions .checkNotNull (transaction );
503
+ if (transaction .getId () != ByteString .EMPTY ) {
504
+ // A transaction has been returned by a statement that was executed. Set the id of the
505
+ // transaction on this instance and release the lock to allow other statements to proceed.
506
+ if ((transactionIdFuture == null || !this .transactionIdFuture .isDone ())
507
+ && this .transactionId == null ) {
508
+ this .transactionId = transaction .getId ();
509
+ this .transactionIdFuture .set (transaction .getId ());
510
+ }
511
+ } else if (shouldIncludeId ) {
512
+ // The statement should have returned a transaction.
513
+ throw SpannerExceptionFactory .newSpannerException (
514
+ ErrorCode .FAILED_PRECONDITION , AbstractReadContext .NO_TRANSACTION_RETURNED_MSG );
507
515
}
508
516
}
509
517
@@ -580,17 +588,18 @@ public long executeUpdate(Statement statement, UpdateOption... options) {
580
588
com .google .spanner .v1 .ResultSet resultSet =
581
589
rpc .executeQuery (builder .build (), session .getOptions ());
582
590
if (resultSet .getMetadata ().hasTransaction ()) {
583
- onTransactionMetadata (resultSet .getMetadata ().getTransaction ());
591
+ onTransactionMetadata (
592
+ resultSet .getMetadata ().getTransaction (), builder .getTransaction ().hasBegin ());
584
593
}
585
594
if (!resultSet .hasStats ()) {
586
595
throw new IllegalArgumentException (
587
596
"DML response missing stats possibly due to non-DML statement as input" );
588
597
}
589
598
// For standard DML, using the exact row count.
590
599
return resultSet .getStats ().getRowCountExact ();
591
- } catch (SpannerException e ) {
592
- onError (e , builder . hasTransaction () && builder .getTransaction ().hasBegin ());
593
- throw e ;
600
+ } catch (Throwable t ) {
601
+ onError (SpannerExceptionFactory . asSpannerException ( t ), builder .getTransaction ().hasBegin ());
602
+ throw t ;
594
603
}
595
604
}
596
605
@@ -621,6 +630,12 @@ public Long apply(ResultSet input) {
621
630
ErrorCode .INVALID_ARGUMENT ,
622
631
"DML response missing stats possibly due to non-DML statement as input" );
623
632
}
633
+ if (builder .getTransaction ().hasBegin ()
634
+ && !(input .getMetadata ().hasTransaction ()
635
+ && input .getMetadata ().getTransaction ().getId () != ByteString .EMPTY )) {
636
+ throw SpannerExceptionFactory .newSpannerException (
637
+ ErrorCode .FAILED_PRECONDITION , NO_TRANSACTION_RETURNED_MSG );
638
+ }
624
639
// For standard DML, using the exact row count.
625
640
return input .getStats ().getRowCountExact ();
626
641
}
@@ -633,8 +648,8 @@ public Long apply(ResultSet input) {
633
648
new ApiFunction <Throwable , Long >() {
634
649
@ Override
635
650
public Long apply (Throwable input ) {
636
- SpannerException e = SpannerExceptionFactory .newSpannerException (input );
637
- onError (e , builder .hasTransaction () && builder . getTransaction ().hasBegin ());
651
+ SpannerException e = SpannerExceptionFactory .asSpannerException (input );
652
+ onError (e , builder .getTransaction ().hasBegin ());
638
653
throw e ;
639
654
}
640
655
},
@@ -645,9 +660,11 @@ public Long apply(Throwable input) {
645
660
public void run () {
646
661
try {
647
662
if (resultSet .get ().getMetadata ().hasTransaction ()) {
648
- onTransactionMetadata (resultSet .get ().getMetadata ().getTransaction ());
663
+ onTransactionMetadata (
664
+ resultSet .get ().getMetadata ().getTransaction (),
665
+ builder .getTransaction ().hasBegin ());
649
666
}
650
- } catch (ExecutionException | InterruptedException e ) {
667
+ } catch (Throwable e ) {
651
668
// Ignore this error here as it is handled by the future that is returned by the
652
669
// executeUpdateAsync method.
653
670
}
@@ -670,7 +687,9 @@ public long[] batchUpdate(Iterable<Statement> statements, UpdateOption... option
670
687
for (int i = 0 ; i < response .getResultSetsCount (); ++i ) {
671
688
results [i ] = response .getResultSets (i ).getStats ().getRowCountExact ();
672
689
if (response .getResultSets (i ).getMetadata ().hasTransaction ()) {
673
- onTransactionMetadata (response .getResultSets (i ).getMetadata ().getTransaction ());
690
+ onTransactionMetadata (
691
+ response .getResultSets (i ).getMetadata ().getTransaction (),
692
+ builder .getTransaction ().hasBegin ());
674
693
}
675
694
}
676
695
@@ -686,8 +705,8 @@ public long[] batchUpdate(Iterable<Statement> statements, UpdateOption... option
686
705
results );
687
706
}
688
707
return results ;
689
- } catch (SpannerException e ) {
690
- onError (e , builder . hasTransaction () && builder .getTransaction ().hasBegin ());
708
+ } catch (Throwable e ) {
709
+ onError (SpannerExceptionFactory . asSpannerException ( e ), builder .getTransaction ().hasBegin ());
691
710
throw e ;
692
711
}
693
712
}
@@ -718,7 +737,9 @@ public long[] apply(ExecuteBatchDmlResponse input) {
718
737
for (int i = 0 ; i < input .getResultSetsCount (); ++i ) {
719
738
results [i ] = input .getResultSets (i ).getStats ().getRowCountExact ();
720
739
if (input .getResultSets (i ).getMetadata ().hasTransaction ()) {
721
- onTransactionMetadata (input .getResultSets (i ).getMetadata ().getTransaction ());
740
+ onTransactionMetadata (
741
+ input .getResultSets (i ).getMetadata ().getTransaction (),
742
+ builder .getTransaction ().hasBegin ());
722
743
}
723
744
}
724
745
// If one of the DML statements was aborted, we should throw an aborted exception.
@@ -743,10 +764,8 @@ public long[] apply(ExecuteBatchDmlResponse input) {
743
764
new ApiFunction <Throwable , long []>() {
744
765
@ Override
745
766
public long [] apply (Throwable input ) {
746
- SpannerException e = SpannerExceptionFactory .newSpannerException (input );
747
- onError (
748
- SpannerExceptionFactory .newSpannerException (e .getCause ()),
749
- builder .hasTransaction () && builder .getTransaction ().hasBegin ());
767
+ SpannerException e = SpannerExceptionFactory .asSpannerException (input );
768
+ onError (e , builder .getTransaction ().hasBegin ());
750
769
throw e ;
751
770
}
752
771
},
0 commit comments