Skip to content

Commit 07af52c

Browse files
committed
fix: correctly retry if a failure happens in the middle
1 parent 25708d3 commit 07af52c

File tree

6 files changed

+65
-14
lines changed

6 files changed

+65
-14
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlBatch.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,15 +249,18 @@ public ApiFuture<long[]> runBatchAsync(CallType callType) {
249249
new AtomicReference<>();
250250
try {
251251
ddlClient.runWithRetryForMissingDefaultSequenceKind(
252-
() -> {
252+
restartIndex -> {
253253
OperationFuture<Void, UpdateDatabaseDdlMetadata> operation =
254-
ddlClient.executeDdl(statements, protoDescriptors);
254+
ddlClient.executeDdl(
255+
statements.subList(restartIndex, statements.size()),
256+
protoDescriptors);
255257
operationReference.set(operation);
256258
// Wait until the operation has finished.
257259
getWithStatementTimeout(operation, RUN_BATCH_STATEMENT);
258260
},
259261
connectionState.getValue(DEFAULT_SEQUENCE_KIND).getValue(),
260-
dbClient.getDialect());
262+
dbClient.getDialect(),
263+
operationReference);
261264
long[] updateCounts = new long[statements.size()];
262265
Arrays.fill(updateCounts, 1L);
263266
state = UnitOfWorkState.RAN;

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlClient.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import java.util.Collections;
3333
import java.util.List;
3434
import java.util.concurrent.ExecutionException;
35+
import java.util.concurrent.atomic.AtomicReference;
36+
import java.util.function.Consumer;
3537

3638
/**
3739
* Convenience class for executing Data Definition Language statements on transactions that support
@@ -136,15 +138,26 @@ static boolean isCreateDatabaseStatement(String statement) {
136138
}
137139

138140
void runWithRetryForMissingDefaultSequenceKind(
139-
Runnable runnable, String defaultSequenceKind, Dialect dialect) {
141+
Consumer<Integer> runnable,
142+
String defaultSequenceKind,
143+
Dialect dialect,
144+
AtomicReference<OperationFuture<Void, UpdateDatabaseDdlMetadata>> operationReference) {
140145
try {
141-
runnable.run();
146+
runnable.accept(0);
142147
} catch (Throwable t) {
143148
SpannerException spannerException = SpannerExceptionFactory.asSpannerException(t);
144149
if (!Strings.isNullOrEmpty(defaultSequenceKind)
145150
&& spannerException instanceof MissingDefaultSequenceKindException) {
146151
setDefaultSequenceKind(defaultSequenceKind, dialect);
147-
runnable.run();
152+
int restartIndex = 0;
153+
if (operationReference.get() != null) {
154+
try {
155+
UpdateDatabaseDdlMetadata metadata = operationReference.get().getMetadata().get();
156+
restartIndex = metadata.getCommitTimestampsCount();
157+
} catch (Throwable ignore) {
158+
}
159+
}
160+
runnable.accept(restartIndex);
148161
return;
149162
}
150163
throw t;

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import java.util.Arrays;
6565
import java.util.UUID;
6666
import java.util.concurrent.Callable;
67+
import java.util.concurrent.atomic.AtomicReference;
6768
import javax.annotation.Nonnull;
6869

6970
/**
@@ -387,13 +388,14 @@ public ApiFuture<Void> executeDdlAsync(CallType callType, final ParsedStatement
387388
executeCreateDatabase(ddl);
388389
} else {
389390
ddlClient.runWithRetryForMissingDefaultSequenceKind(
390-
() -> {
391+
restartIndex -> {
391392
OperationFuture<?, ?> operation =
392393
ddlClient.executeDdl(ddl.getSqlWithoutComments(), protoDescriptors);
393394
getWithStatementTimeout(operation, ddl);
394395
},
395396
connectionState.getValue(DEFAULT_SEQUENCE_KIND).getValue(),
396-
dbClient.getDialect());
397+
dbClient.getDialect(),
398+
new AtomicReference<>());
397399
}
398400
state = UnitOfWorkState.COMMITTED;
399401
return null;

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ private DdlClient createDefaultMockDdlClient(
119119
when(ddlClient.executeDdl(anyList(), any())).thenReturn(operation);
120120
doCallRealMethod()
121121
.when(ddlClient)
122-
.runWithRetryForMissingDefaultSequenceKind(any(), any(), any());
122+
.runWithRetryForMissingDefaultSequenceKind(any(), any(), any(), any());
123123
return ddlClient;
124124
} catch (Exception e) {
125125
throw new RuntimeException(e);
@@ -265,7 +265,9 @@ public void testGetStateAndIsActive() {
265265
SpannerException exception =
266266
SpannerExceptionFactory.newSpannerException(ErrorCode.FAILED_PRECONDITION, "test");
267267
doThrow(exception).when(client).executeDdl(anyList(), isNull());
268-
doCallRealMethod().when(client).runWithRetryForMissingDefaultSequenceKind(any(), any(), any());
268+
doCallRealMethod()
269+
.when(client)
270+
.runWithRetryForMissingDefaultSequenceKind(any(), any(), any(), any());
269271
batch = createSubject(client);
270272
assertThat(batch.getState(), is(UnitOfWorkState.STARTED));
271273
assertThat(batch.isActive(), is(true));
@@ -479,7 +481,9 @@ public void testFailedUpdateCount() throws InterruptedException, ExecutionExcept
479481
new ExecutionException(
480482
"ddl statement failed", Status.INVALID_ARGUMENT.asRuntimeException()));
481483
when(operationFuture.getMetadata()).thenReturn(metadataFuture);
482-
doCallRealMethod().when(client).runWithRetryForMissingDefaultSequenceKind(any(), any(), any());
484+
doCallRealMethod()
485+
.when(client)
486+
.runWithRetryForMissingDefaultSequenceKind(any(), any(), any(), any());
483487
when(client.executeDdl(argThat(isListOfStringsWithSize(2)), isNull()))
484488
.thenReturn(operationFuture);
485489
DdlBatch batch =
@@ -511,7 +515,9 @@ public void testFailedUpdateCount() throws InterruptedException, ExecutionExcept
511515
@Test
512516
public void testFailedAfterFirstStatement() throws InterruptedException, ExecutionException {
513517
DdlClient client = mock(DdlClient.class);
514-
doCallRealMethod().when(client).runWithRetryForMissingDefaultSequenceKind(any(), any(), any());
518+
doCallRealMethod()
519+
.when(client)
520+
.runWithRetryForMissingDefaultSequenceKind(any(), any(), any(), any());
515521
UpdateDatabaseDdlMetadata metadata =
516522
UpdateDatabaseDdlMetadata.newBuilder()
517523
.addCommitTimestamps(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ private DdlClient createDefaultMockDdlClient() {
314314
when(ddlClient.executeDdl(anyList(), any())).thenReturn(operation);
315315
doCallRealMethod()
316316
.when(ddlClient)
317-
.runWithRetryForMissingDefaultSequenceKind(any(), any(), any());
317+
.runWithRetryForMissingDefaultSequenceKind(any(), any(), any(), any());
318318
return ddlClient;
319319
} catch (Exception e) {
320320
throw new RuntimeException(e);

google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITDdlTest.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import com.google.cloud.spanner.connection.ConnectionOptions;
3737
import com.google.cloud.spanner.connection.ITAbstractSpannerTest;
3838
import com.google.cloud.spanner.connection.SqlScriptVerifier;
39+
import com.google.cloud.spanner.testing.EmulatorSpannerHelper;
3940
import java.util.Arrays;
4041
import java.util.Collections;
4142
import org.junit.Before;
@@ -172,9 +173,35 @@ public void testDefaultSequenceKindInBatch() {
172173
connection.execute(statement2);
173174
SpannerBatchUpdateException exception =
174175
assertThrows(SpannerBatchUpdateException.class, connection::runBatch);
175-
assertEquals(0, Arrays.stream(exception.getUpdateCounts()).sum());
176+
long updateCount = Arrays.stream(exception.getUpdateCounts()).sum();
177+
// The emulator refuses the entire batch. Spanner executes the first statement and fails on
178+
// the second statement.
179+
if (EmulatorSpannerHelper.isUsingEmulator()) {
180+
assertEquals(0, updateCount);
181+
} else {
182+
assertEquals(1, updateCount);
183+
}
176184

177185
// Setting a default sequence kind on the connection should make the statement succeed.
186+
connection.setDefaultSequenceKind("bit_reversed_positive");
187+
connection.startBatchDdl();
188+
if (updateCount == 0) {
189+
connection.execute(statement1);
190+
}
191+
connection.execute(statement2);
192+
connection.runBatch();
193+
}
194+
}
195+
196+
@Test
197+
public void testDefaultSequenceKindRetriesBatchCorrectly() {
198+
try (Connection connection = createConnection()) {
199+
Statement statement1 =
200+
Statement.of("create table testseq1 (id1 int64 primary key, value string(max))");
201+
Statement statement2 =
202+
Statement.of(
203+
"create table testseq2 (id2 int64 auto_increment primary key, value string(max))");
204+
178205
connection.setDefaultSequenceKind("bit_reversed_positive");
179206
connection.startBatchDdl();
180207
connection.execute(statement1);

0 commit comments

Comments
 (0)