@@ -463,13 +463,9 @@ TransactionSelector getTransactionSelector() {
463463 // Aborted error if the call that included the BeginTransaction option fails. The
464464 // Aborted error will cause the entire transaction to be retried, and the retry will use
465465 // 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 ();
473469 }
474470 } catch (ExecutionException e ) {
475471 if (e .getCause () instanceof AbortedException ) {
@@ -479,11 +475,15 @@ TransactionSelector getTransactionSelector() {
479475 }
480476 throw SpannerExceptionFactory .newSpannerException (e .getCause ());
481477 } 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.
482480 SpannerException se =
483481 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+ : "" ),
487487 e );
488488 if (transactionStarter != null ) {
489489 se .addSuppressed (transactionStarter );
@@ -498,12 +498,20 @@ TransactionSelector getTransactionSelector() {
498498 }
499499
500500 @ 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 );
507515 }
508516 }
509517
@@ -580,17 +588,18 @@ public long executeUpdate(Statement statement, UpdateOption... options) {
580588 com .google .spanner .v1 .ResultSet resultSet =
581589 rpc .executeQuery (builder .build (), session .getOptions ());
582590 if (resultSet .getMetadata ().hasTransaction ()) {
583- onTransactionMetadata (resultSet .getMetadata ().getTransaction ());
591+ onTransactionMetadata (
592+ resultSet .getMetadata ().getTransaction (), builder .getTransaction ().hasBegin ());
584593 }
585594 if (!resultSet .hasStats ()) {
586595 throw new IllegalArgumentException (
587596 "DML response missing stats possibly due to non-DML statement as input" );
588597 }
589598 // For standard DML, using the exact row count.
590599 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 ;
594603 }
595604 }
596605
@@ -621,6 +630,12 @@ public Long apply(ResultSet input) {
621630 ErrorCode .INVALID_ARGUMENT ,
622631 "DML response missing stats possibly due to non-DML statement as input" );
623632 }
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+ }
624639 // For standard DML, using the exact row count.
625640 return input .getStats ().getRowCountExact ();
626641 }
@@ -633,8 +648,8 @@ public Long apply(ResultSet input) {
633648 new ApiFunction <Throwable , Long >() {
634649 @ Override
635650 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 ());
638653 throw e ;
639654 }
640655 },
@@ -645,9 +660,11 @@ public Long apply(Throwable input) {
645660 public void run () {
646661 try {
647662 if (resultSet .get ().getMetadata ().hasTransaction ()) {
648- onTransactionMetadata (resultSet .get ().getMetadata ().getTransaction ());
663+ onTransactionMetadata (
664+ resultSet .get ().getMetadata ().getTransaction (),
665+ builder .getTransaction ().hasBegin ());
649666 }
650- } catch (ExecutionException | InterruptedException e ) {
667+ } catch (Throwable e ) {
651668 // Ignore this error here as it is handled by the future that is returned by the
652669 // executeUpdateAsync method.
653670 }
@@ -670,7 +687,9 @@ public long[] batchUpdate(Iterable<Statement> statements, UpdateOption... option
670687 for (int i = 0 ; i < response .getResultSetsCount (); ++i ) {
671688 results [i ] = response .getResultSets (i ).getStats ().getRowCountExact ();
672689 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 ());
674693 }
675694 }
676695
@@ -686,8 +705,8 @@ public long[] batchUpdate(Iterable<Statement> statements, UpdateOption... option
686705 results );
687706 }
688707 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 ());
691710 throw e ;
692711 }
693712 }
@@ -718,7 +737,9 @@ public long[] apply(ExecuteBatchDmlResponse input) {
718737 for (int i = 0 ; i < input .getResultSetsCount (); ++i ) {
719738 results [i ] = input .getResultSets (i ).getStats ().getRowCountExact ();
720739 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 ());
722743 }
723744 }
724745 // If one of the DML statements was aborted, we should throw an aborted exception.
@@ -743,10 +764,8 @@ public long[] apply(ExecuteBatchDmlResponse input) {
743764 new ApiFunction <Throwable , long []>() {
744765 @ Override
745766 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 ());
750769 throw e ;
751770 }
752771 },
0 commit comments