Skip to content

Commit 4cf5261

Browse files
authored
feat: add TransactionMutationLimitExceededException as cause to SpannerBatchUpdateException (#3723)
If a DML batch fails due to a TransactionMutationLimitExceededException, then add that specific exception as the cause to the SpannerBatchUpdateException. This makes it easier to detect this specific error, and potentially retry the individual statements as PDML.
1 parent 97f4544 commit 4cf5261

File tree

5 files changed

+50
-7
lines changed

5 files changed

+50
-7
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerBatchUpdateException.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,16 @@
1717
package com.google.cloud.spanner;
1818

1919
public class SpannerBatchUpdateException extends SpannerException {
20-
private long[] updateCounts;
20+
private final long[] updateCounts;
21+
2122
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
2223
SpannerBatchUpdateException(
23-
DoNotConstructDirectly token, ErrorCode code, String message, long[] counts) {
24-
super(token, code, false, message, null);
24+
DoNotConstructDirectly token,
25+
ErrorCode code,
26+
String message,
27+
long[] counts,
28+
Throwable cause) {
29+
super(token, code, false, message, cause);
2530
updateCounts = counts;
2631
}
2732

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,11 @@ public static SpannerException newSpannerException(Throwable cause) {
118118
public static SpannerBatchUpdateException newSpannerBatchUpdateException(
119119
ErrorCode code, String message, long[] updateCounts) {
120120
DoNotConstructDirectly token = DoNotConstructDirectly.ALLOWED;
121-
return new SpannerBatchUpdateException(token, code, message, updateCounts);
121+
SpannerException cause = null;
122+
if (isTransactionMutationLimitException(code, message)) {
123+
cause = new TransactionMutationLimitExceededException(token, code, message, null, null);
124+
}
125+
return new SpannerBatchUpdateException(token, code, message, updateCounts, cause);
122126
}
123127

124128
/** Constructs a specific error that */

google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionMutationLimitExceededException.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
public class TransactionMutationLimitExceededException extends SpannerException {
2727
private static final long serialVersionUID = 1L;
2828

29+
private static final String ERROR_MESSAGE = "The transaction contains too many mutations.";
30+
2931
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
3032
TransactionMutationLimitExceededException(
3133
DoNotConstructDirectly token,
@@ -36,10 +38,14 @@ public class TransactionMutationLimitExceededException extends SpannerException
3638
super(token, errorCode, /*retryable = */ false, message, cause, apiException);
3739
}
3840

41+
static boolean isTransactionMutationLimitException(ErrorCode code, String message) {
42+
return code == ErrorCode.INVALID_ARGUMENT && message != null && message.contains(ERROR_MESSAGE);
43+
}
44+
3945
static boolean isTransactionMutationLimitException(Throwable cause) {
4046
if (cause == null
4147
|| cause.getMessage() == null
42-
|| !cause.getMessage().contains("The transaction contains too many mutations.")) {
48+
|| !cause.getMessage().contains(ERROR_MESSAGE)) {
4349
return false;
4450
}
4551
// Spanner includes a hint that points to the Spanner limits documentation page when the error

google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1070,7 +1070,7 @@ public long[] batchUpdate(Iterable<Statement> statements, UpdateOption... update
10701070
// In all other cases, we should throw a BatchUpdateException.
10711071
if (response.getStatus().getCode() == Code.ABORTED_VALUE) {
10721072
throw createAbortedExceptionForBatchDml(response);
1073-
} else if (response.getStatus().getCode() != 0) {
1073+
} else if (response.getStatus().getCode() != Code.OK_VALUE) {
10741074
throw newSpannerBatchUpdateException(
10751075
ErrorCode.fromRpcStatus(response.getStatus()),
10761076
response.getStatus().getMessage(),
@@ -1137,7 +1137,7 @@ public ApiFuture<long[]> batchUpdateAsync(
11371137
// In all other cases, we should throw a BatchUpdateException.
11381138
if (batchDmlResponse.getStatus().getCode() == Code.ABORTED_VALUE) {
11391139
throw createAbortedExceptionForBatchDml(batchDmlResponse);
1140-
} else if (batchDmlResponse.getStatus().getCode() != 0) {
1140+
} else if (batchDmlResponse.getStatus().getCode() != Code.OK_VALUE) {
11411141
throw newSpannerBatchUpdateException(
11421142
ErrorCode.fromRpcStatus(batchDmlResponse.getStatus()),
11431143
batchDmlResponse.getStatus().getMessage(),

google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RetryDmlAsPartitionedDmlMockServerTest.java

+28
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static org.junit.Assert.assertEquals;
2020
import static org.junit.Assert.assertFalse;
21+
import static org.junit.Assert.assertNotNull;
2122
import static org.junit.Assert.assertThrows;
2223
import static org.junit.Assert.assertTrue;
2324

@@ -26,6 +27,7 @@
2627
import com.google.cloud.spanner.MockSpannerServiceImpl;
2728
import com.google.cloud.spanner.MockSpannerServiceImpl.SimulatedExecutionTime;
2829
import com.google.cloud.spanner.ResultSet;
30+
import com.google.cloud.spanner.SpannerBatchUpdateException;
2931
import com.google.cloud.spanner.SpannerException;
3032
import com.google.cloud.spanner.Statement;
3133
import com.google.cloud.spanner.TransactionMutationLimitExceededException;
@@ -34,6 +36,7 @@
3436
import com.google.rpc.Help.Link;
3537
import com.google.spanner.v1.BeginTransactionRequest;
3638
import com.google.spanner.v1.CommitRequest;
39+
import com.google.spanner.v1.ExecuteBatchDmlRequest;
3740
import com.google.spanner.v1.ExecuteSqlRequest;
3841
import io.grpc.Metadata;
3942
import io.grpc.Status;
@@ -219,4 +222,29 @@ public void testSqlStatements() {
219222
}
220223
}
221224
}
225+
226+
@Test
227+
public void testTransactionMutationLimitExceeded_isWrappedAsCauseOfBatchUpdateException() {
228+
String sql = "update test set value=1 where true";
229+
Statement statement = Statement.of(sql);
230+
mockSpanner.putStatementResult(
231+
MockSpannerServiceImpl.StatementResult.exception(
232+
statement, createTransactionMutationLimitExceededException()));
233+
234+
try (Connection connection = createConnection()) {
235+
connection.setAutocommit(true);
236+
assertEquals(AutocommitDmlMode.TRANSACTIONAL, connection.getAutocommitDmlMode());
237+
238+
connection.startBatchDml();
239+
connection.execute(statement);
240+
SpannerBatchUpdateException batchUpdateException =
241+
assertThrows(SpannerBatchUpdateException.class, connection::runBatch);
242+
assertNotNull(batchUpdateException.getCause());
243+
assertEquals(
244+
TransactionMutationLimitExceededException.class,
245+
batchUpdateException.getCause().getClass());
246+
}
247+
assertEquals(1, mockSpanner.countRequestsOfType(ExecuteBatchDmlRequest.class));
248+
assertEquals(0, mockSpanner.countRequestsOfType(CommitRequest.class));
249+
}
222250
}

0 commit comments

Comments
 (0)