diff --git a/README.md b/README.md index 73d8f22181..aa140bda1d 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ If you are using Maven with [BOM][libraries-bom], add this to your pom.xml file: com.google.cloud libraries-bom - 26.70.0 + 26.71.0 pom import @@ -41,7 +41,7 @@ If you are using Maven without the BOM, add this to your dependencies: com.google.cloud google-cloud-spanner - 6.102.0 + 6.102.1 ``` diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadWriteTransaction.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadWriteTransaction.java index 5b52214d11..c0e464ee5e 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadWriteTransaction.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadWriteTransaction.java @@ -689,7 +689,19 @@ public ApiFuture executeQueryAsync( InterceptorsUsage.IGNORE_INTERCEPTORS, ImmutableList.of(SpannerGrpc.getExecuteStreamingSqlMethod())); } else { - res = super.executeQueryAsync(callType, statement, analyzeMode, options); + // Handle both SELECT queries and DML with THEN RETURN without delegating to the base class, + // which rejects non-SELECT statements. + res = + executeStatementAsync( + callType, + statement, + () -> { + checkTimedOut(); + checkAborted(); + return DirectExecuteResultSet.ofResultSet( + internalExecuteQuery(statement, analyzeMode, options)); + }, + SpannerGrpc.getExecuteStreamingSqlMethod()); } ApiFutures.addCallback(res, new StatementResultCallback<>(), MoreExecutors.directExecutor()); return res; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java index cf546e9588..a78df0471e 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java @@ -110,7 +110,7 @@ public abstract class AbstractMockServerTest { .setMetadata(SINGLE_COL_INT64_RESULTSET_METADATA) .build(); public static final com.google.spanner.v1.ResultSet UPDATE_RETURNING_RESULTSET = - com.google.spanner.v1.ResultSet.newBuilder() + ResultSet.newBuilder() .setStats(ResultSetStats.newBuilder().setRowCountExact(1)) .setMetadata( ResultSetMetadata.newBuilder() @@ -121,6 +121,10 @@ public abstract class AbstractMockServerTest { .setName("col") .setType(Type.newBuilder().setCodeValue(TypeCode.INT64_VALUE)) .build()))) + .addRows( + ListValue.newBuilder() + .addValues(Value.newBuilder().setStringValue("1").build()) + .build()) .build(); protected static final ResultSet SELECT1_RESULTSET = diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AutoDmlBatchMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AutoDmlBatchMockServerTest.java index a7467997c6..b359bcb60c 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AutoDmlBatchMockServerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AutoDmlBatchMockServerTest.java @@ -97,6 +97,8 @@ public void testDmlWithReturningAfterDml() { // DML with a THEN RETURN clause cannot be batched. This therefore flushes the batch and // executes the INSERT ... THEN RETURN statement as a separate ExecuteSqlRequest. try (ResultSet resultSet = connection.executeQuery(INSERT_RETURNING_STATEMENT)) { + assertTrue(resultSet.next()); + assertEquals(1L, resultSet.getLong(0)); assertFalse(resultSet.next()); } @@ -123,6 +125,8 @@ public void testDmlWithReturningAfterDml_usingExecute() { StatementResult result = connection.execute(INSERT_RETURNING_STATEMENT); assertEquals(ResultType.RESULT_SET, result.getResultType()); try (ResultSet resultSet = result.getResultSet()) { + assertTrue(resultSet.next()); + assertEquals(1L, resultSet.getLong(0)); assertFalse(resultSet.next()); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java index 947fe9d33c..7d0fa94c9b 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java @@ -287,6 +287,21 @@ public void testExecuteUpdate() { assertThat(get(transaction.executeUpdateAsync(CallType.SYNC, parsedStatement)), is(1L)); } + @Test + public void testExecuteQueryWithDmlReturningWithoutRetry() { + ParsedStatement parsedStatement = mock(ParsedStatement.class); + when(parsedStatement.getType()).thenReturn(StatementType.UPDATE); + when(parsedStatement.isUpdate()).thenReturn(true); + when(parsedStatement.hasReturningClause()).thenReturn(true); + Statement statement = Statement.of("INSERT INTO TEST (ID, NAME) VALUES (1, 'x') THEN RETURN *"); + when(parsedStatement.getStatement()).thenReturn(statement); + + ReadWriteTransaction transaction = createSubject(/* commitBehavior= */ CommitBehavior.SUCCEED); + ResultSet rs = + get(transaction.executeQueryAsync(CallType.SYNC, parsedStatement, AnalyzeMode.NONE)); + assertThat(rs, is(notNullValue())); + } + @Test public void testGetCommitTimestampBeforeCommit() { ParsedStatement parsedStatement = mock(ParsedStatement.class); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/TransactionMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/TransactionMockServerTest.java index 31cc1acd19..45f68b11a5 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/TransactionMockServerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/TransactionMockServerTest.java @@ -31,6 +31,7 @@ import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.connection.ITAbstractSpannerTest.ITConnection; +import com.google.cloud.spanner.connection.StatementResult.ResultType; import com.google.spanner.v1.BeginTransactionRequest; import com.google.spanner.v1.CommitRequest; import com.google.spanner.v1.ExecuteBatchDmlRequest; @@ -357,5 +358,45 @@ public long nanoTime() { } @Test - public void testTransactionTimeoutInAutoCommit() {} + public void testCanUseAllMethodsWithInternalRetriesDisabled() { + // Verify that all query/update methods work as expected when internal retries have been + // disabled. + try (Connection connection = createConnection()) { + connection.setAutocommit(false); + connection.setRetryAbortsInternally(false); + + try (ResultSet result = connection.executeQuery(SELECT1_STATEMENT)) { + assertTrue(result.next()); + assertEquals(1L, result.getLong(0)); + assertFalse(result.next()); + } + assertEquals(1, connection.executeUpdate(INSERT_STATEMENT)); + try (ResultSet result = connection.executeQuery(INSERT_RETURNING_STATEMENT)) { + assertTrue(result.next()); + assertEquals(1L, result.getLong(0)); + assertFalse(result.next()); + } + + StatementResult statementResult = connection.execute(SELECT1_STATEMENT); + assertEquals(ResultType.RESULT_SET, statementResult.getResultType()); + try (ResultSet result = statementResult.getResultSet()) { + assertTrue(result.next()); + assertEquals(1L, result.getLong(0)); + assertFalse(result.next()); + } + + statementResult = connection.execute(INSERT_STATEMENT); + assertEquals(ResultType.UPDATE_COUNT, statementResult.getResultType()); + assertEquals(1L, statementResult.getUpdateCount().longValue()); + + statementResult = connection.execute(INSERT_RETURNING_STATEMENT); + assertEquals(ResultType.RESULT_SET, statementResult.getResultType()); + try (ResultSet result = statementResult.getResultSet()) { + assertTrue(result.next()); + assertEquals(1L, result.getLong(0)); + assertFalse(result.next()); + } + connection.commit(); + } + } }