Skip to content

Commit 9964bd5

Browse files
authored
test: fix the queryAndThen method in the mock test server (#2623)
The mock server that is used for testing contained a queryAndThen(..) method that did not do anything with the 'and then' part. This PR fixes that, and adds an additional test using that method. That test shows what happens with a bit-reversed sequence when a transaction is aborted.
1 parent 84cd62f commit 9964bd5

File tree

2 files changed

+76
-1
lines changed

2 files changed

+76
-1
lines changed

google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockSpannerServiceImpl.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ public static StatementResult query(Statement statement, ResultSet resultSet) {
260260
*/
261261
public static StatementResult queryAndThen(
262262
Statement statement, ResultSet resultSet, ResultSet next) {
263-
return new StatementResult(statement, resultSet);
263+
return new StatementResult(statement, resultSet, next);
264264
}
265265

266266
/** Creates a {@link StatementResult} for a read request. */

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

+75
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
import static org.hamcrest.CoreMatchers.is;
2222
import static org.hamcrest.MatcherAssert.assertThat;
2323
import static org.junit.Assert.assertEquals;
24+
import static org.junit.Assert.assertThrows;
2425
import static org.junit.Assert.fail;
2526

2627
import com.google.cloud.Timestamp;
28+
import com.google.cloud.spanner.AbortedDueToConcurrentModificationException;
2729
import com.google.cloud.spanner.ErrorCode;
2830
import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
2931
import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
@@ -35,14 +37,23 @@
3537
import com.google.cloud.spanner.connection.it.ITTransactionRetryTest.CountTransactionRetryListener;
3638
import com.google.common.collect.ImmutableList;
3739
import com.google.protobuf.ByteString;
40+
import com.google.protobuf.ListValue;
41+
import com.google.protobuf.Value;
3842
import com.google.spanner.v1.CommitRequest;
3943
import com.google.spanner.v1.ExecuteBatchDmlRequest;
4044
import com.google.spanner.v1.ExecuteSqlRequest;
4145
import com.google.spanner.v1.ExecuteSqlRequest.QueryMode;
46+
import com.google.spanner.v1.ResultSetMetadata;
47+
import com.google.spanner.v1.StructType;
48+
import com.google.spanner.v1.StructType.Field;
49+
import com.google.spanner.v1.Type;
50+
import com.google.spanner.v1.TypeCode;
4251
import io.grpc.Status;
4352
import io.grpc.StatusRuntimeException;
4453
import java.util.Collections;
4554
import java.util.List;
55+
import java.util.stream.Collectors;
56+
import java.util.stream.LongStream;
4657
import org.junit.Test;
4758
import org.junit.runner.RunWith;
4859
import org.junit.runners.JUnit4;
@@ -497,6 +508,7 @@ public void testRetryUsesTagsWithUpdateReturning() {
497508
assertEquals(2L, commitRequestCount);
498509
}
499510

511+
@Test
500512
public void testRetryUsesAnalyzeModeForUpdate() {
501513
mockSpanner.putStatementResult(
502514
StatementResult.query(SELECT_COUNT_STATEMENT, SELECT_COUNT_RESULTSET_BEFORE_INSERT));
@@ -531,6 +543,69 @@ public void testRetryUsesAnalyzeModeForUpdate() {
531543
assertEquals(QueryMode.NORMAL, requests.get(4).getQueryMode());
532544
}
533545

546+
@Test
547+
public void testAbortedWithBitReversedSequence() {
548+
// A bit-reversed sequence can only be used in a read/write transaction. However, calling
549+
// get_next_sequence_value will update the sequence durably, even if the transaction is aborted.
550+
// That means that retrying a transaction that called get_next_sequence_value will always fail.
551+
String getSequenceValuesSql =
552+
"WITH t AS (\n"
553+
+ "\tselect get_next_sequence_value(sequence enhanced_sequence) AS n\n"
554+
+ "\tUNION ALL\n"
555+
+ "\tselect get_next_sequence_value(sequence enhanced_sequence) AS n\n"
556+
+ "\tUNION ALL\n"
557+
+ "\tselect get_next_sequence_value(sequence enhanced_sequence) AS n\n"
558+
+ "\tUNION ALL\n"
559+
+ "\tselect get_next_sequence_value(sequence enhanced_sequence) AS n\n"
560+
+ "\tUNION ALL\n"
561+
+ "\tselect get_next_sequence_value(sequence enhanced_sequence) AS n\n"
562+
+ ")\n"
563+
+ "SELECT n FROM t";
564+
mockSpanner.putStatementResult(
565+
StatementResult.queryAndThen(
566+
Statement.of(getSequenceValuesSql),
567+
createBitReversedSequenceResultSet(1L, 5L),
568+
createBitReversedSequenceResultSet(6L, 10L)));
569+
long currentValue = 0L;
570+
try (ITConnection connection = createConnection()) {
571+
try (ResultSet resultSet = connection.executeQuery(Statement.of(getSequenceValuesSql))) {
572+
while (resultSet.next()) {
573+
assertEquals(Long.reverse(++currentValue), resultSet.getLong(0));
574+
}
575+
}
576+
mockSpanner.abortNextStatement();
577+
// The retry should fail, because the sequence will return new values during the retry.
578+
assertThrows(AbortedDueToConcurrentModificationException.class, connection::commit);
579+
}
580+
}
581+
582+
static com.google.spanner.v1.ResultSet createBitReversedSequenceResultSet(
583+
long startValue, long endValue) {
584+
return com.google.spanner.v1.ResultSet.newBuilder()
585+
.setMetadata(
586+
ResultSetMetadata.newBuilder()
587+
.setRowType(
588+
StructType.newBuilder()
589+
.addFields(
590+
Field.newBuilder()
591+
.setName("n")
592+
.setType(Type.newBuilder().setCode(TypeCode.INT64).build())
593+
.build())
594+
.build())
595+
.build())
596+
.addAllRows(
597+
LongStream.range(startValue, endValue)
598+
.map(Long::reverse)
599+
.mapToObj(
600+
id ->
601+
ListValue.newBuilder()
602+
.addValues(
603+
Value.newBuilder().setStringValue(String.valueOf(id)).build())
604+
.build())
605+
.collect(Collectors.toList()))
606+
.build();
607+
}
608+
534609
ITConnection createConnection(TransactionRetryListener listener) {
535610
ITConnection connection =
536611
super.createConnection(ImmutableList.of(), ImmutableList.of(listener));

0 commit comments

Comments
 (0)