@@ -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