diff --git a/cloud-spanner-r2dbc/src/test/java/com/google/cloud/spanner/r2dbc/it/SpannerClientLibraryTestKit.java b/cloud-spanner-r2dbc/src/test/java/com/google/cloud/spanner/r2dbc/it/SpannerClientLibraryTestKit.java index 9ef81c96..04d201d3 100644 --- a/cloud-spanner-r2dbc/src/test/java/com/google/cloud/spanner/r2dbc/it/SpannerClientLibraryTestKit.java +++ b/cloud-spanner-r2dbc/src/test/java/com/google/cloud/spanner/r2dbc/it/SpannerClientLibraryTestKit.java @@ -20,6 +20,8 @@ import static com.google.cloud.spanner.r2dbc.SpannerConnectionFactoryProvider.INSTANCE; import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE; import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER; +import static io.r2dbc.spi.test.TestKit.TestStatement.INSERT_TWO_COLUMNS; +import static io.r2dbc.spi.test.TestKit.TestStatement.SELECT_VALUE_TWO_COLUMNS; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -37,13 +39,11 @@ import io.r2dbc.spi.Result; import io.r2dbc.spi.Statement; import io.r2dbc.spi.test.TestKit; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.function.Function; -import java.util.stream.IntStream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -52,7 +52,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.PreparedStatementCallback; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -85,28 +84,10 @@ static void setUp() { jdbcOperations = mock(JdbcOperations.class); doAnswer(invocation -> { - String query = invocation.getArgument(0); - executeDml(c -> c.createStatement(query.replace("INTO test ", "INTO test (value) ") - .replace("INTO test_two_column", "INTO test_two_column (col1,col2)"))); + executeDml(c -> c.createStatement(invocation.getArgument(0))); return null; }).when(jdbcOperations).execute((String) any()); - doAnswer(invocation -> { - String query = invocation.getArgument(0); - - // The TCK uses java.sql JDBC classes that we have no implemented, but only in two cases - // that we can detect and substitute here. - if (query.equalsIgnoreCase("INSERT INTO clob_test VALUES (?)")) { - executeDml(c -> c.createStatement("INSERT INTO clob_test (value) VALUES (@val)") - .bind("val", "test-value")); - } else if (query.equalsIgnoreCase("INSERT INTO blob_test VALUES (?)")) { - executeDml(c -> c.createStatement("INSERT INTO blob_test (value) VALUES (@val)").bind("val", - StandardCharsets.UTF_8.encode("test-value").array())); - } - - return null; - }).when(jdbcOperations).execute((String) any(), (PreparedStatementCallback) any()); - SpannerOptions options = SpannerOptions.newBuilder().build(); Spanner spanner = options.getService(); dbAdminClient = spanner.getDatabaseAdminClient(); @@ -120,7 +101,6 @@ static void setUp() { createTableIfNeeded(id, "clob_test", " ( value BYTES(MAX) ) PRIMARY KEY (value)"); } - private static void createTableIfNeeded(DatabaseId id, String tableName, String definition) { Boolean tableExists = Mono.from(connectionFactory.create()) .flatMapMany(c -> c.createStatement( @@ -227,38 +207,16 @@ public void bindFails() { */ } - // override to fix DDL for Spanner. - @Override - @Test - public void prepareStatement() { - Mono.from(getConnectionFactory().create()) - .delayUntil(c -> c.beginTransaction()) - .flatMapMany(connection -> { - Statement statement = connection.createStatement( - String.format("INSERT INTO test (value) VALUES(%s)", getPlaceholder(0))); - - IntStream.range(0, 10) - .forEach(i -> statement.bind(getIdentifier(0), i).add()); - - return Flux.from(statement - .execute()) - .concatWith(close(connection)); - }) - .as(StepVerifier::create) - .expectNextCount(10).as("values from insertions") - .verifyComplete(); - } - // override. column names are case-sensitive in Spanner. @Override @Test public void duplicateColumnNames() { - getJdbcOperations().execute("INSERT INTO test_two_column VALUES (100, 'hello')"); + getJdbcOperations().execute(expand(INSERT_TWO_COLUMNS)); Mono.from(getConnectionFactory().create()) .flatMapMany(connection -> Flux.from(connection - .createStatement("SELECT col1 AS value, col2 AS VALUE FROM test_two_column") + .createStatement(expand(SELECT_VALUE_TWO_COLUMNS)) .execute()) .flatMap(result -> result @@ -275,9 +233,8 @@ public void duplicateColumnNames() { // override. column names are case-sensitive in Spanner. @Override @Test - @Disabled // TODO: GH-252 public void columnMetadata() { - getJdbcOperations().execute("INSERT INTO test_two_column VALUES (100, 'hello')"); + getJdbcOperations().execute(expand(INSERT_TWO_COLUMNS)); Mono.from(getConnectionFactory().create()) .flatMapMany(connection -> Flux.from(connection @@ -399,28 +356,28 @@ public void prepareStatementWithIncompleteBatchFails() { */ } - // DML syntax needed to be fixed. + // Returns Long instead of Integer. Equivalent test in V1 did not need an override. @Override @Test public void transactionCommit() { - executeDml(c -> c.createStatement("INSERT INTO test (value) VALUES (100)")); + executeDml(c -> c.createStatement(expand(TestStatement.INSERT_VALUE100))); Mono.from(getConnectionFactory().create()) .flatMapMany(connection -> Mono.from(connection.beginTransaction()) - .thenMany(Flux.from(connection.createStatement("SELECT value FROM test") - .execute()) - .flatMap(this::extractColumnsLong)) + .thenMany( + Flux.from(connection.createStatement(expand(TestStatement.SELECT_VALUE)).execute()) + .flatMap(this::extractColumnsLong)) // NOTE: this defer is a from() in the original. needs a follow up to resolve .concatWith(Flux.from(connection.createStatement( - String.format("INSERT INTO test (value) VALUES (%s)", getPlaceholder(0))) + String.format(expand(TestStatement.INSERT_VALUE_PLACEHOLDER, getPlaceholder(0)))) .bind(getIdentifier(0), 200) .execute()) - .flatMap(TestKit::extractRowsUpdated)) - .concatWith(Flux.from(connection.createStatement("SELECT value FROM test") + .flatMap(this::extractRowsUpdated)) + .concatWith(Flux.from(connection.createStatement(expand(TestStatement.SELECT_VALUE)) .execute()) .flatMap(this::extractColumnsLong)) - .concatWith(Flux.from(connection.createStatement("SELECT value FROM test") + .concatWith(Flux.from(connection.createStatement(expand(TestStatement.SELECT_VALUE)) .execute()) .flatMap(this::extractColumnsLong)) .concatWith(close(connection))) @@ -449,25 +406,6 @@ Compound statements (statements with more than 1 semi-colon) are not supported. */ } - // DML syntax fix. - @Override - @Test - public void bindNull() { - Mono.from(getConnectionFactory().create()) - .delayUntil(c -> c.beginTransaction()) - .flatMapMany(connection -> Flux.from(connection - - .createStatement( - String.format("INSERT INTO test (value) VALUES(%s)", getPlaceholder(0))) - .bindNull(getIdentifier(0), Integer.class).add() - .execute()) - - .concatWith(close(connection))) - .as(StepVerifier::create) - .expectNextCount(1).as("rows inserted") - .verifyComplete(); - } - @Override @Test @Disabled // TODO: GH-275 @@ -477,11 +415,11 @@ public void changeAutoCommitCommitsTransaction() { Flux.from(connection.setAutoCommit(false)) .thenMany(connection.beginTransaction()) // DML syntax fix adding column list - .thenMany(connection.createStatement( - "INSERT INTO test (value) VALUES(200)").execute()) + .thenMany( + connection.createStatement(expand(TestStatement.INSERT_VALUE200)).execute()) .flatMap(Result::getRowsUpdated) .thenMany(connection.setAutoCommit(true)) - .thenMany(connection.createStatement("SELECT value FROM test").execute()) + .thenMany(connection.createStatement(expand(TestStatement.SELECT_VALUE)).execute()) .flatMap(it -> it.map((row, metadata) -> row.get("value"))) .concatWith(close(connection)) ) @@ -500,8 +438,8 @@ public void sameAutoCommitLeavesTransactionUnchanged() { .flatMapMany(connection -> Flux.from(connection.setAutoCommit(false)) .thenMany(connection.beginTransaction()) - .thenMany(connection.createStatement( - "INSERT INTO test (value) VALUES(200)").execute()) + .thenMany( + connection.createStatement(expand(TestStatement.INSERT_VALUE200)).execute()) .flatMap(Result::getRowsUpdated) .thenMany(connection.setAutoCommit(false)) .thenMany(connection.rollbackTransaction()) @@ -519,4 +457,8 @@ public void autoCommitByDefault() { } + @Override + public String expand(TestStatement statement, Object... args) { + return SpannerTestKitStatements.expand(statement, args); + } } diff --git a/cloud-spanner-r2dbc/src/test/java/com/google/cloud/spanner/r2dbc/it/SpannerTestKit.java b/cloud-spanner-r2dbc/src/test/java/com/google/cloud/spanner/r2dbc/it/SpannerTestKit.java index c531bcbd..4a5e0671 100644 --- a/cloud-spanner-r2dbc/src/test/java/com/google/cloud/spanner/r2dbc/it/SpannerTestKit.java +++ b/cloud-spanner-r2dbc/src/test/java/com/google/cloud/spanner/r2dbc/it/SpannerTestKit.java @@ -20,6 +20,10 @@ import static com.google.cloud.spanner.r2dbc.SpannerConnectionFactoryProvider.INSTANCE; import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE; import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER; +import static io.r2dbc.spi.test.TestKit.TestStatement.INSERT_TWO_COLUMNS; +import static io.r2dbc.spi.test.TestKit.TestStatement.INSERT_VALUE200; +import static io.r2dbc.spi.test.TestKit.TestStatement.SELECT_VALUE; +import static io.r2dbc.spi.test.TestKit.TestStatement.SELECT_VALUE_TWO_COLUMNS; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -37,12 +41,10 @@ import io.r2dbc.spi.Result; import io.r2dbc.spi.Statement; import io.r2dbc.spi.test.TestKit; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.function.Function; -import java.util.stream.IntStream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -51,13 +53,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.PreparedStatementCallback; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; /** - * R2DBC TCK test implementation. + * Cloud Spanner R2DBC TCK test implementation. */ public class SpannerTestKit implements TestKit { @@ -82,28 +83,10 @@ static void setUp() { jdbcOperations = mock(JdbcOperations.class); doAnswer(invocation -> { - String query = invocation.getArgument(0); - executeDml(c -> c.createStatement(query.replace("INTO test ", "INTO test (value) ") - .replace("INTO test_two_column", "INTO test_two_column (col1,col2)"))); + executeDml(c -> c.createStatement(invocation.getArgument(0))); return null; }).when(jdbcOperations).execute((String) any()); - doAnswer(invocation -> { - String query = invocation.getArgument(0); - - // The TCK uses java.sql JDBC classes that we have no implemented, but only in two cases - // that we can detect and substitute here. - if (query.equalsIgnoreCase("INSERT INTO clob_test VALUES (?)")) { - executeDml(c -> c.createStatement("INSERT INTO clob_test (value) VALUES (@val)") - .bind("val", "test-value")); - } else if (query.equalsIgnoreCase("INSERT INTO blob_test VALUES (?)")) { - executeDml(c -> c.createStatement("INSERT INTO blob_test (value) VALUES (@val)").bind("val", - StandardCharsets.UTF_8.encode("test-value").array())); - } - - return null; - }).when(jdbcOperations).execute((String) any(), (PreparedStatementCallback) any()); - SpannerOptions options = SpannerOptions.newBuilder().build(); Spanner spanner = options.getService(); dbAdminClient = spanner.getDatabaseAdminClient(); @@ -224,38 +207,19 @@ public void bindFails() { */ } - // override to fix DDL for Spanner. - @Override - @Test - public void prepareStatement() { - Mono.from(getConnectionFactory().create()) - .delayUntil(c -> c.beginTransaction()) - .flatMapMany(connection -> { - Statement statement = connection.createStatement( - String.format("INSERT INTO test (value) VALUES(%s)", getPlaceholder(0))); - - IntStream.range(0, 10) - .forEach(i -> statement.bind(getIdentifier(0), i).add()); - - return Flux.from(statement - .execute()) - .concatWith(close(connection)); - }) - .as(StepVerifier::create) - .expectNextCount(10).as("values from insertions") - .verifyComplete(); - } - - // override. column names are case-sensitive in Spanner. + /* Overrides parent test because + * 1) column names are case-sensitive in Spanner + * 2) Spanner returns Long instead of Integer + */ @Override @Test public void duplicateColumnNames() { - getJdbcOperations().execute("INSERT INTO test_two_column VALUES (100, 'hello')"); + getJdbcOperations().execute(expand(INSERT_TWO_COLUMNS)); Mono.from(getConnectionFactory().create()) .flatMapMany(connection -> Flux.from(connection - .createStatement("SELECT col1 AS value, col2 AS VALUE FROM test_two_column") + .createStatement(expand(SELECT_VALUE_TWO_COLUMNS)) .execute()) .flatMap(result -> result @@ -269,11 +233,11 @@ public void duplicateColumnNames() { .verifyComplete(); } - // override. column names are case-sensitive in Spanner. + /* Overrides parent test because column names are case-sensitive in Spanner */ @Override @Test public void columnMetadata() { - getJdbcOperations().execute("INSERT INTO test_two_column VALUES (100, 'hello')"); + getJdbcOperations().execute(expand(INSERT_TWO_COLUMNS)); Mono.from(getConnectionFactory().create()) .flatMapMany(connection -> Flux.from(connection @@ -389,39 +353,6 @@ public void prepareStatementWithIncompleteBatchFails() { */ } - // DML syntax needed to be fixed. - @Override - @Test - public void transactionCommit() { - executeDml(c -> c.createStatement("INSERT INTO test (value) VALUES (100)")); - - Mono.from(getConnectionFactory().create()) - .flatMapMany(connection -> Mono.from(connection.beginTransaction()) - .thenMany(Flux.from(connection.createStatement("SELECT value FROM test") - .execute()) - .flatMap(TestKit::extractColumns)) - - // NOTE: this defer is a from() in the original. needs a follow up to resolve - .concatWith(Flux.from(connection.createStatement( - String.format("INSERT INTO test (value) VALUES (%s)", getPlaceholder(0))) - .bind(getIdentifier(0), 200) - .execute()) - .flatMap(TestKit::extractRowsUpdated)) - .concatWith(Flux.from(connection.createStatement("SELECT value FROM test") - .execute()) - .flatMap(TestKit::extractColumns)) - .concatWith(Flux.from(connection.createStatement("SELECT value FROM test") - .execute()) - .flatMap(TestKit::extractColumns)) - .concatWith(close(connection))) - .as(StepVerifier::create) - .expectNext(Collections.singletonList(100)).as("value from select 1") - .expectNext(1).as("rows inserted") - .expectNext(Arrays.asList(100, 200)).as("values from select 2") - .expectNext(Arrays.asList(100, 200)).as("values from select 3") - .verifyComplete(); - } - @Override @Disabled @Test @@ -431,25 +362,7 @@ Compound statements (statements with more than 1 semi-colon) are not supported. */ } - // DML syntax fix. - @Override - @Test - public void bindNull() { - Mono.from(getConnectionFactory().create()) - .delayUntil(c -> c.beginTransaction()) - .flatMapMany(connection -> Flux.from(connection - - .createStatement( - String.format("INSERT INTO test (value) VALUES(%s)", getPlaceholder(0))) - .bindNull(getIdentifier(0), Integer.class).add() - .execute()) - - .concatWith(close(connection))) - .as(StepVerifier::create) - .expectNextCount(1).as("rows inserted") - .verifyComplete(); - } - + /* Overrides parent test because Spanner returns Long instead of Integer */ @Override @Test public void changeAutoCommitCommitsTransaction() { @@ -457,12 +370,10 @@ public void changeAutoCommitCommitsTransaction() { .flatMapMany(connection -> Flux.from(connection.setAutoCommit(false)) .thenMany(connection.beginTransaction()) - // DML syntax fix adding column list - .thenMany(connection.createStatement( - "INSERT INTO test (value) VALUES(200)").execute()) + .thenMany(connection.createStatement(expand(INSERT_VALUE200)).execute()) .flatMap(Result::getRowsUpdated) .thenMany(connection.setAutoCommit(true)) - .thenMany(connection.createStatement("SELECT value FROM test").execute()) + .thenMany(connection.createStatement(expand(SELECT_VALUE)).execute()) .flatMap(it -> it.map((row, metadata) -> row.get("value"))) .concatWith(close(connection)) ) @@ -474,23 +385,7 @@ public void changeAutoCommitCommitsTransaction() { } @Override - @Test - public void sameAutoCommitLeavesTransactionUnchanged() { - Mono.from(getConnectionFactory().create()) - .flatMapMany(connection -> - Flux.from(connection.setAutoCommit(false)) - .thenMany(connection.beginTransaction()) - .thenMany(connection.createStatement( - "INSERT INTO test (value) VALUES(200)").execute()) - .flatMap(Result::getRowsUpdated) - .thenMany(connection.setAutoCommit(false)) - .thenMany(connection.rollbackTransaction()) - .thenMany(connection.createStatement("SELECT value FROM test").execute()) - .flatMap(it -> it.map((row, metadata) -> row.get("value"))) - .concatWith(close(connection)) - ) - .as(StepVerifier::create) - .verifyComplete(); + public String expand(TestStatement statement, Object... args) { + return SpannerTestKitStatements.expand(statement, args); } - } diff --git a/cloud-spanner-r2dbc/src/test/java/com/google/cloud/spanner/r2dbc/it/SpannerTestKitStatements.java b/cloud-spanner-r2dbc/src/test/java/com/google/cloud/spanner/r2dbc/it/SpannerTestKitStatements.java new file mode 100644 index 00000000..32b95205 --- /dev/null +++ b/cloud-spanner-r2dbc/src/test/java/com/google/cloud/spanner/r2dbc/it/SpannerTestKitStatements.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019-2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.r2dbc.it; + +import static io.r2dbc.spi.test.TestKit.TestStatement.INSERT_BLOB_VALUE_PLACEHOLDER; +import static io.r2dbc.spi.test.TestKit.TestStatement.INSERT_CLOB_VALUE_PLACEHOLDER; +import static io.r2dbc.spi.test.TestKit.TestStatement.INSERT_TWO_COLUMNS; +import static io.r2dbc.spi.test.TestKit.TestStatement.INSERT_VALUE100; +import static io.r2dbc.spi.test.TestKit.TestStatement.INSERT_VALUE200; +import static io.r2dbc.spi.test.TestKit.TestStatement.INSERT_VALUE_PLACEHOLDER; +import static io.r2dbc.spi.test.TestKit.TestStatement.SELECT_VALUE_TWO_COLUMNS; + +import io.r2dbc.spi.test.TestKit.TestStatement; +import java.util.HashMap; +import java.util.Map; + +class SpannerTestKitStatements { + + static final Map STATEMENTS = new HashMap<>(); + + static { + STATEMENTS.put(INSERT_VALUE100,"INSERT INTO test (value) VALUES (100)"); + STATEMENTS.put(INSERT_VALUE200, "INSERT INTO test (value) VALUES (200)"); + STATEMENTS.put(INSERT_TWO_COLUMNS, + "INSERT INTO test_two_column (col1,col2) VALUES (100, 'hello')"); + STATEMENTS.put(INSERT_BLOB_VALUE_PLACEHOLDER, "INSERT INTO blob_test VALUES (?)"); + STATEMENTS.put(INSERT_CLOB_VALUE_PLACEHOLDER, "INSERT INTO clob_test VALUES (?)"); + STATEMENTS.put(INSERT_VALUE_PLACEHOLDER,"INSERT INTO test (value) VALUES (%s)"); + + // Spanner column names are case-sensitive + STATEMENTS.put(SELECT_VALUE_TWO_COLUMNS, + "SELECT col1 AS value, col2 AS VALUE FROM test_two_column"); + } + + static String expand(TestStatement statement, Object... args) { + String sql = STATEMENTS.get(statement); + if (sql == null) { + sql = statement.getSql(); + } + + return String.format(sql, args); + } +} diff --git a/pom.xml b/pom.xml index 3389c78a..fe52d8c7 100644 --- a/pom.xml +++ b/pom.xml @@ -86,8 +86,8 @@ 10.0.0 - 0.8.1.RELEASE - Dysprosium-SR5 + 0.8.3.RELEASE + Dysprosium-SR12 3.3.0 1.7.30