From eea6c9e8901e29ff998d7996c819f3aa793ce2df Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Sun, 13 Aug 2023 15:08:13 +0000 Subject: [PATCH 1/5] chore(main): release 2.11.8-SNAPSHOT (#1314) :robot: I have created a release *beep* *boop* --- ### Updating meta-information for bleeding-edge SNAPSHOT release. --- This PR was generated with [Release Please](https://togithub.com/googleapis/release-please). See [documentation](https://togithub.com/googleapis/release-please#release-please). --- pom.xml | 2 +- samples/snapshot/pom.xml | 2 +- versions.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 4847ce9d5..3378a232f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 google-cloud-spanner-jdbc - 2.11.7 + 2.11.8-SNAPSHOT jar Google Cloud Spanner JDBC https://github.com/googleapis/java-spanner-jdbc diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 74c89c6c2..99c50943d 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,7 +28,7 @@ com.google.cloud google-cloud-spanner-jdbc - 2.11.7 + 2.11.8-SNAPSHOT diff --git a/versions.txt b/versions.txt index 874269315..5459f1d3b 100644 --- a/versions.txt +++ b/versions.txt @@ -1,4 +1,4 @@ # Format: # module:released-version:current-version -google-cloud-spanner-jdbc:2.11.7:2.11.7 +google-cloud-spanner-jdbc:2.11.7:2.11.8-SNAPSHOT From aaf89de2b569cf5f86c456c18fcc79643cc6c94b Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sun, 13 Aug 2023 18:32:12 +0200 Subject: [PATCH 2/5] chore(deps): update dependency com.google.cloud:google-cloud-spanner-jdbc to v2.11.7 (#1315) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [com.google.cloud:google-cloud-spanner-jdbc](https://togithub.com/googleapis/java-spanner-jdbc) | `2.11.6` -> `2.11.7` | [![age](https://developer.mend.io/api/mc/badges/age/maven/com.google.cloud:google-cloud-spanner-jdbc/2.11.7?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.google.cloud:google-cloud-spanner-jdbc/2.11.7?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.google.cloud:google-cloud-spanner-jdbc/2.11.6/2.11.7?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.google.cloud:google-cloud-spanner-jdbc/2.11.6/2.11.7?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
googleapis/java-spanner-jdbc (com.google.cloud:google-cloud-spanner-jdbc) ### [`v2.11.7`](https://togithub.com/googleapis/java-spanner-jdbc/blob/HEAD/CHANGELOG.md#2117-2023-08-13) [Compare Source](https://togithub.com/googleapis/java-spanner-jdbc/compare/v2.11.6...v2.11.7) ##### Dependencies - Update dependency com.google.cloud:google-cloud-spanner-bom to v6.45.1 ([#​1312](https://togithub.com/googleapis/java-spanner-jdbc/issues/1312)) ([2e99e35](https://togithub.com/googleapis/java-spanner-jdbc/commit/2e99e357c9688c89881433e77b3167924442abaa))
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/googleapis/java-spanner-jdbc). --- samples/install-without-bom/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index e5c2fd93b..192263619 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -29,7 +29,7 @@ com.google.cloud google-cloud-spanner-jdbc - 2.11.6 + 2.11.7 From 1953ea245889f54e3c803d6bf5e739141a48678e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 14 Aug 2023 21:01:45 +0200 Subject: [PATCH 3/5] chore: method for adding returning clause to statements (#1311) * chore: method for adding returning clause to statements Adds a method to JdbcStatement for appending a THEN RETURN/RETURNING clause to the statement. This will be used to modify statements that request generated keys to be returned. * feat: support return all columns * fix: only add THEN RETURN * from DML --- .../cloud/spanner/jdbc/JdbcStatement.java | 81 +++++++++ .../cloud/spanner/jdbc/JdbcStatementTest.java | 156 ++++++++++++++++++ 2 files changed, 237 insertions(+) diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcStatement.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcStatement.java index 051ed9d6d..3e7c86f9f 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcStatement.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcStatement.java @@ -23,9 +23,11 @@ import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.Type; import com.google.cloud.spanner.Type.StructField; +import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement; import com.google.cloud.spanner.connection.StatementResult; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.rpc.Code; import java.sql.ResultSet; import java.sql.SQLException; @@ -33,9 +35,13 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nullable; /** Implementation of {@link java.sql.Statement} for Google Cloud Spanner. */ class JdbcStatement extends AbstractJdbcStatement { + static final ImmutableList ALL_COLUMNS = ImmutableList.of("*"); + enum BatchType { NONE, DML, @@ -98,6 +104,81 @@ public long executeLargeUpdate(String sql) throws SQLException { } } + /** + * Adds a THEN RETURN/RETURNING clause to the given statement if the following conditions are all + * met: + * + *
    + *
  1. The generatedKeysColumns is not null or empty + *
  2. The statement is a DML statement + *
  3. The DML statement does not already contain a THEN RETURN/RETURNING clause + *
+ */ + Statement addReturningToStatement( + Statement statement, @Nullable ImmutableList generatedKeysColumns) + throws SQLException { + if (generatedKeysColumns == null || generatedKeysColumns.isEmpty()) { + return statement; + } + // Check if the statement is a DML statement or not. + ParsedStatement parsedStatement = getConnection().getParser().parse(statement); + if (parsedStatement.isUpdate() && !parsedStatement.hasReturningClause()) { + if (generatedKeysColumns.size() == 1 + && ALL_COLUMNS.get(0).equals(generatedKeysColumns.get(0))) { + // Add a 'THEN RETURN/RETURNING *' clause to the statement. + return statement + .toBuilder() + .replace(statement.getSql() + getReturningAllColumnsClause()) + .build(); + } + // Add a 'THEN RETURN/RETURNING col1, col2, ...' to the statement. + // The column names will be quoted using the dialect-specific identifier quoting character. + return statement + .toBuilder() + .replace( + generatedKeysColumns.stream() + .map(this::quoteColumn) + .collect( + Collectors.joining( + ", ", statement.getSql() + getReturningClause() + " ", ""))) + .build(); + } + return statement; + } + + /** Returns the dialect-specific clause for returning values from a DML statement. */ + String getReturningAllColumnsClause() { + switch (getConnection().getDialect()) { + case POSTGRESQL: + return "\nRETURNING *"; + case GOOGLE_STANDARD_SQL: + default: + return "\nTHEN RETURN *"; + } + } + + /** Returns the dialect-specific clause for returning values from a DML statement. */ + String getReturningClause() { + switch (getConnection().getDialect()) { + case POSTGRESQL: + return "\nRETURNING"; + case GOOGLE_STANDARD_SQL: + default: + return "\nTHEN RETURN"; + } + } + + /** Adds dialect-specific quotes to the given column name. */ + String quoteColumn(String column) { + switch (getConnection().getDialect()) { + case POSTGRESQL: + return "\"" + column + "\""; + case GOOGLE_STANDARD_SQL: + default: + return "`" + column + "`"; + } + } + @Override public boolean execute(String sql) throws SQLException { checkClosed(); diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java index c7b6a6cbd..d44583587 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -39,6 +40,7 @@ import com.google.cloud.spanner.connection.StatementResult; import com.google.cloud.spanner.connection.StatementResult.ResultType; import com.google.cloud.spanner.jdbc.JdbcSqlExceptionFactory.JdbcSqlExceptionImpl; +import com.google.common.collect.ImmutableList; import com.google.rpc.Code; import java.sql.ResultSet; import java.sql.SQLException; @@ -47,6 +49,7 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -599,4 +602,157 @@ public void testConvertUpdateCountsToSuccessNoInfo() throws SQLException { (long) Statement.SUCCESS_NO_INFO); } } + + @Test + public void testAddReturningToStatement() throws SQLException { + JdbcConnection connection = mock(JdbcConnection.class); + when(connection.getDialect()).thenReturn(dialect); + when(connection.getParser()).thenReturn(AbstractStatementParser.getInstance(dialect)); + try (JdbcStatement statement = new JdbcStatement(connection)) { + assertAddReturningSame(statement, "insert into test (id, value) values (1, 'One')", null); + assertAddReturningSame( + statement, "insert into test (id, value) values (1, 'One')", ImmutableList.of()); + assertAddReturningEquals( + statement, + dialect == Dialect.POSTGRESQL + ? "insert into test (id, value) values (1, 'One')\nRETURNING \"id\"" + : "insert into test (id, value) values (1, 'One')\nTHEN RETURN `id`", + "insert into test (id, value) values (1, 'One')", + ImmutableList.of("id")); + assertAddReturningEquals( + statement, + dialect == Dialect.POSTGRESQL + ? "insert into test (id, value) values (1, 'One')\nRETURNING \"id\", \"value\"" + : "insert into test (id, value) values (1, 'One')\nTHEN RETURN `id`, `value`", + "insert into test (id, value) values (1, 'One')", + ImmutableList.of("id", "value")); + assertAddReturningEquals( + statement, + dialect == Dialect.POSTGRESQL + ? "insert into test (id, value) values (1, 'One')\nRETURNING *" + : "insert into test (id, value) values (1, 'One')\nTHEN RETURN *", + "insert into test (id, value) values (1, 'One')", + ImmutableList.of("*")); + // Requesting generated keys for a DML statement that already contains a returning clause is a + // no-op. + assertAddReturningSame( + statement, + "insert into test (id, value) values (1, 'One') " + + statement.getReturningClause() + + " value", + ImmutableList.of("id")); + // Requesting generated keys for a query is a no-op. + for (ImmutableList keys : + ImmutableList.of( + ImmutableList.of("id"), ImmutableList.of("id", "value"), ImmutableList.of("*"))) { + assertAddReturningSame(statement, "select id, value from test", keys); + } + + // Update statements may also request generated keys. + assertAddReturningSame(statement, "update test set value='Two' where id=1", null); + assertAddReturningSame( + statement, "update test set value='Two' where id=1", ImmutableList.of()); + assertAddReturningEquals( + statement, + dialect == Dialect.POSTGRESQL + ? "update test set value='Two' where id=1\nRETURNING \"value\"" + : "update test set value='Two' where id=1\nTHEN RETURN `value`", + "update test set value='Two' where id=1", + ImmutableList.of("value")); + assertAddReturningEquals( + statement, + dialect == Dialect.POSTGRESQL + ? "update test set value='Two' where id=1\nRETURNING \"value\", \"id\"" + : "update test set value='Two' where id=1\nTHEN RETURN `value`, `id`", + "update test set value='Two' where id=1", + ImmutableList.of("value", "id")); + assertAddReturningEquals( + statement, + dialect == Dialect.POSTGRESQL + ? "update test set value='Two' where id=1\nRETURNING *" + : "update test set value='Two' where id=1\nTHEN RETURN *", + "update test set value='Two' where id=1", + ImmutableList.of("*")); + // Requesting generated keys for a DML statement that already contains a returning clause is a + // no-op. + assertAddReturningSame( + statement, + "update test set value='Two' where id=1 " + statement.getReturningClause() + " value", + ImmutableList.of("value")); + + // Delete statements may also request generated keys. + assertAddReturningSame(statement, "delete test where id=1", null); + assertAddReturningSame(statement, "delete test where id=1", ImmutableList.of()); + assertAddReturningEquals( + statement, + dialect == Dialect.POSTGRESQL + ? "delete test where id=1\nRETURNING \"value\"" + : "delete test where id=1\nTHEN RETURN `value`", + "delete test where id=1", + ImmutableList.of("value")); + assertAddReturningEquals( + statement, + dialect == Dialect.POSTGRESQL + ? "delete test where id=1\nRETURNING \"id\", \"value\"" + : "delete test where id=1\nTHEN RETURN `id`, `value`", + "delete test where id=1", + ImmutableList.of("id", "value")); + assertAddReturningEquals( + statement, + dialect == Dialect.POSTGRESQL + ? "delete test where id=1\nRETURNING *" + : "delete test where id=1\nTHEN RETURN *", + "delete test where id=1", + ImmutableList.of("*")); + // Requesting generated keys for a DML statement that already contains a returning clause is a + // no-op. + for (ImmutableList keys : + ImmutableList.of( + ImmutableList.of("id"), ImmutableList.of("id", "value"), ImmutableList.of("*"))) { + assertAddReturningSame( + statement, + "delete test where id=1 " + + (dialect == Dialect.POSTGRESQL + ? "delete test where id=1\nRETURNING" + : "delete test where id=1\nTHEN RETURN") + + " value", + keys); + } + + // Requesting generated keys for DDL is a no-op. + for (ImmutableList keys : + ImmutableList.of( + ImmutableList.of("id"), ImmutableList.of("id", "value"), ImmutableList.of("*"))) { + assertAddReturningSame( + statement, + dialect == Dialect.POSTGRESQL + ? "create table test (id bigint primary key, value varchar)" + : "create table test (id int64, value string(max)) primary key (id)", + keys); + } + } + } + + private void assertAddReturningSame( + JdbcStatement statement, String sql, @Nullable ImmutableList generatedKeysColumns) + throws SQLException { + com.google.cloud.spanner.Statement spannerStatement = + com.google.cloud.spanner.Statement.of(sql); + assertSame( + spannerStatement, + statement.addReturningToStatement(spannerStatement, generatedKeysColumns)); + } + + private void assertAddReturningEquals( + JdbcStatement statement, + String expectedSql, + String sql, + @Nullable ImmutableList generatedKeysColumns) + throws SQLException { + com.google.cloud.spanner.Statement spannerStatement = + com.google.cloud.spanner.Statement.of(sql); + assertEquals( + com.google.cloud.spanner.Statement.of(expectedSql), + statement.addReturningToStatement(spannerStatement, generatedKeysColumns)); + } } From e9241787b94cb614f658f5e6c977ffc008fd3397 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 15 Aug 2023 07:08:48 +0200 Subject: [PATCH 4/5] deps: update dependency com.google.cloud:google-cloud-spanner-bom to v6.45.2 (#1318) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3378a232f..9cc747b20 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ com.google.cloud google-cloud-spanner-bom - 6.45.1 + 6.45.2 pom import From 4f1472705f8642a8227d9a42bdb2b3285dd942f4 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 15 Aug 2023 06:32:13 +0000 Subject: [PATCH 5/5] chore(main): release 2.11.8 (#1319) :robot: I have created a release *beep* *boop* --- ## [2.11.8](https://togithub.com/googleapis/java-spanner-jdbc/compare/v2.11.7...v2.11.8) (2023-08-15) ### Dependencies * Update dependency com.google.cloud:google-cloud-spanner-bom to v6.45.2 ([#1318](https://togithub.com/googleapis/java-spanner-jdbc/issues/1318)) ([e924178](https://togithub.com/googleapis/java-spanner-jdbc/commit/e9241787b94cb614f658f5e6c977ffc008fd3397)) --- This PR was generated with [Release Please](https://togithub.com/googleapis/release-please). See [documentation](https://togithub.com/googleapis/release-please#release-please). --- CHANGELOG.md | 7 +++++++ pom.xml | 2 +- samples/snapshot/pom.xml | 2 +- versions.txt | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af3d292a3..a1f93aa82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [2.11.8](https://github.com/googleapis/java-spanner-jdbc/compare/v2.11.7...v2.11.8) (2023-08-15) + + +### Dependencies + +* Update dependency com.google.cloud:google-cloud-spanner-bom to v6.45.2 ([#1318](https://github.com/googleapis/java-spanner-jdbc/issues/1318)) ([e924178](https://github.com/googleapis/java-spanner-jdbc/commit/e9241787b94cb614f658f5e6c977ffc008fd3397)) + ## [2.11.7](https://github.com/googleapis/java-spanner-jdbc/compare/v2.11.6...v2.11.7) (2023-08-13) diff --git a/pom.xml b/pom.xml index 9cc747b20..7e85f5a45 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 google-cloud-spanner-jdbc - 2.11.8-SNAPSHOT + 2.11.8 jar Google Cloud Spanner JDBC https://github.com/googleapis/java-spanner-jdbc diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 99c50943d..73e594f27 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,7 +28,7 @@ com.google.cloud google-cloud-spanner-jdbc - 2.11.8-SNAPSHOT + 2.11.8 diff --git a/versions.txt b/versions.txt index 5459f1d3b..e9d28b678 100644 --- a/versions.txt +++ b/versions.txt @@ -1,4 +1,4 @@ # Format: # module:released-version:current-version -google-cloud-spanner-jdbc:2.11.7:2.11.8-SNAPSHOT +google-cloud-spanner-jdbc:2.11.8:2.11.8