diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e0afcedd..64e1e8f81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [2.25.3](https://github.com/googleapis/java-spanner-jdbc/compare/v2.25.2...v2.25.3) (2025-01-06) + + +### Bug Fixes + +* Clear interrupted flag after cancel ([#1880](https://github.com/googleapis/java-spanner-jdbc/issues/1880)) ([e1fd4e1](https://github.com/googleapis/java-spanner-jdbc/commit/e1fd4e131a039b80306991cc93c5c097f2538c90)), closes [#1879](https://github.com/googleapis/java-spanner-jdbc/issues/1879) + + +### Dependencies + +* Update dependency com.google.cloud:google-cloud-spanner-bom to v6.84.0 ([#1881](https://github.com/googleapis/java-spanner-jdbc/issues/1881)) ([42ffaad](https://github.com/googleapis/java-spanner-jdbc/commit/42ffaadf0e671806269ba6c0fba8ce470911b8fe)) +* Update dependency org.springframework.boot:spring-boot to v3.4.1 ([#1873](https://github.com/googleapis/java-spanner-jdbc/issues/1873)) ([c81941c](https://github.com/googleapis/java-spanner-jdbc/commit/c81941ca62face226804619e11b3e9de9b0aa801)) +* Update dependency org.springframework.boot:spring-boot-starter-data-jdbc to v3.4.1 ([#1874](https://github.com/googleapis/java-spanner-jdbc/issues/1874)) ([cc3fc3e](https://github.com/googleapis/java-spanner-jdbc/commit/cc3fc3e8a2f455909eb9687a81d742250decb8c3)) +* Update dependency org.springframework.boot:spring-boot-starter-parent to v3.4.1 ([#1876](https://github.com/googleapis/java-spanner-jdbc/issues/1876)) ([ea02e5d](https://github.com/googleapis/java-spanner-jdbc/commit/ea02e5da9d220782c2223bc7f2d4969e70a1b868)) + + +### Documentation + +* Add sample for using array of struct query param ([#1871](https://github.com/googleapis/java-spanner-jdbc/issues/1871)) ([d7cb90d](https://github.com/googleapis/java-spanner-jdbc/commit/d7cb90d264eaf0793422d3bfcaadd5be2ebd6412)) + ## [2.25.2](https://github.com/googleapis/java-spanner-jdbc/compare/v2.25.1...v2.25.2) (2024-12-19) diff --git a/README.md b/README.md index 12015e861..ec61e3622 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ If you are using Maven, add this to your pom.xml file: com.google.cloud google-cloud-spanner-jdbc - 2.25.2 + 2.25.3 ``` @@ -30,7 +30,7 @@ If you are using Gradle without BOM, add this to your dependencies ```Groovy -implementation 'com.google.cloud:google-cloud-spanner-jdbc:2.25.2' +implementation 'com.google.cloud:google-cloud-spanner-jdbc:2.25.3' ``` @@ -38,7 +38,7 @@ If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-spanner-jdbc" % "2.25.2" +libraryDependencies += "com.google.cloud" % "google-cloud-spanner-jdbc" % "2.25.3" ``` diff --git a/pom.xml b/pom.xml index fb07a0d69..306777953 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.25.2 + 2.25.3 jar Google Cloud Spanner JDBC https://github.com/googleapis/java-spanner-jdbc @@ -61,7 +61,7 @@ com.google.cloud google-cloud-spanner-bom - 6.83.0 + 6.84.0 pom import diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 6761e1c05..5b713984c 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.25.1 + 2.25.2 diff --git a/samples/quickperf/pom.xml b/samples/quickperf/pom.xml index b9f6a13a2..15b8b27da 100644 --- a/samples/quickperf/pom.xml +++ b/samples/quickperf/pom.xml @@ -79,7 +79,7 @@ org.springframework.boot spring-boot - 3.4.0 + 3.4.1 test diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 3f0e54f6b..0e135825b 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,7 +28,7 @@ com.google.cloud google-cloud-spanner-jdbc - 2.25.2 + 2.25.3 diff --git a/samples/snippets/src/main/java/com/example/spanner/jdbc/JdbcSample.java b/samples/snippets/src/main/java/com/example/spanner/jdbc/JdbcSample.java index b17c6b4d8..a597d7430 100644 --- a/samples/snippets/src/main/java/com/example/spanner/jdbc/JdbcSample.java +++ b/samples/snippets/src/main/java/com/example/spanner/jdbc/JdbcSample.java @@ -22,6 +22,10 @@ import com.google.cloud.spanner.Mutation; import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.Struct; +import com.google.cloud.spanner.Type; +import com.google.cloud.spanner.Type.StructField; +import com.google.cloud.spanner.Value; import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.cloud.spanner.admin.database.v1.DatabaseAdminSettings; import com.google.cloud.spanner.jdbc.CloudSpannerJdbcConnection; @@ -1519,6 +1523,59 @@ static void partitionedDmlPostgreSQL( } // [END spanner_postgresql_partitioned_dml] + static void arrayOfStructAsQueryParameter( + final String project, + final String instance, + final String database, + final Properties properties) throws SQLException { + try (Connection connection = + DriverManager.getConnection( + String.format( + "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s", + project, instance, database), + properties)) { + try (Statement statement = connection.createStatement()) { + statement.execute( + "create table if not exists my_table " + + "(col1 string(max), col2 int64) primary key (col1)"); + statement.execute( + "insert or update into my_table (col1, col2) " + + "values ('value1', 1), ('value2', 2), ('value3', 3)"); + } + + try (PreparedStatement statement = connection.prepareStatement( + "select * from my_table " + + "where STRUCT(col1, col2) " + + "in unnest (?)")) { + statement.setObject( + 1, + Value.structArray( + com.google.cloud.spanner.Type.struct( + StructField.of("col1", Type.string()), + StructField.of("col2", Type.int64())), + ImmutableList.of( + Struct.newBuilder() + .set("col1").to("value1") + .set("col2").to(1L) + .build(), + Struct.newBuilder() + .set("col1").to("value2") + .set("col2").to(2L) + .build()))); + try (java.sql.ResultSet resultSet = statement.executeQuery()) { + while (resultSet.next()) { + for (int col = 1; + col <= resultSet.getMetaData().getColumnCount(); + col++) { + System.out.printf("%s;", resultSet.getString(col)); + } + System.out.println(); + } + } + } + } + } + /** The expected number of command line arguments. */ private static final int NUM_EXPECTED_ARGS = 3; @@ -1697,6 +1754,13 @@ static boolean runGoogleSQLSample( database.getDatabase(), createProperties()); return true; + case "arrayofstructparam": + arrayOfStructAsQueryParameter( + database.getInstanceId().getProject(), + database.getInstanceId().getInstance(), + database.getDatabase(), + createProperties()); + return true; default: return false; } diff --git a/samples/snippets/src/test/java/com/example/spanner/jdbc/JdbcSampleTest.java b/samples/snippets/src/test/java/com/example/spanner/jdbc/JdbcSampleTest.java index 1b0328de9..c1275f457 100644 --- a/samples/snippets/src/test/java/com/example/spanner/jdbc/JdbcSampleTest.java +++ b/samples/snippets/src/test/java/com/example/spanner/jdbc/JdbcSampleTest.java @@ -18,6 +18,7 @@ import static com.example.spanner.jdbc.JdbcSample.addColumn; import static com.example.spanner.jdbc.JdbcSample.addColumnPostgreSQL; +import static com.example.spanner.jdbc.JdbcSample.arrayOfStructAsQueryParameter; import static com.example.spanner.jdbc.JdbcSample.createConnection; import static com.example.spanner.jdbc.JdbcSample.createConnectionWithEmulator; import static com.example.spanner.jdbc.JdbcSample.createDatabase; @@ -243,6 +244,10 @@ public void testGoogleSQLSamples() throws Exception { result = runSample(() -> partitionedDml(PROJECT_ID, INSTANCE_ID, DATABASE_ID, properties)); assertEquals("Updated at least 3 albums\n", result); + + result = runSample( + () -> arrayOfStructAsQueryParameter(PROJECT_ID, INSTANCE_ID, DATABASE_ID, properties)); + assertEquals("value1;1;\nvalue2;2;\n", result); } @Test diff --git a/samples/spring-data-jdbc/pom.xml b/samples/spring-data-jdbc/pom.xml index e8fc57ed0..2ac50a807 100644 --- a/samples/spring-data-jdbc/pom.xml +++ b/samples/spring-data-jdbc/pom.xml @@ -30,7 +30,7 @@ com.google.cloud google-cloud-spanner-bom - 6.83.0 + 6.84.0 import pom @@ -55,7 +55,7 @@ org.springframework.boot spring-boot-starter-data-jdbc - 3.4.0 + 3.4.1 diff --git a/samples/spring-data-mybatis/pom.xml b/samples/spring-data-mybatis/pom.xml index 6e294a1c7..73ff402e2 100644 --- a/samples/spring-data-mybatis/pom.xml +++ b/samples/spring-data-mybatis/pom.xml @@ -13,7 +13,7 @@ org.springframework.boot spring-boot-starter-parent - 3.4.0 + 3.4.1 diff --git a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcStatement.java b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcStatement.java index 5c15f3362..3b98591ab 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcStatement.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcStatement.java @@ -16,6 +16,7 @@ package com.google.cloud.spanner.jdbc; +import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.Options; import com.google.cloud.spanner.Options.QueryOption; import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode; @@ -34,6 +35,7 @@ import java.time.Duration; import java.util.Arrays; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; @@ -47,6 +49,7 @@ abstract class AbstractJdbcStatement extends AbstractJdbcWrapper implements Stat final AbstractStatementParser parser; private final Lock executingLock; private volatile Thread executingThread; + private final AtomicBoolean cancelled = new AtomicBoolean(); private boolean closed; private boolean closeOnCompletion; private boolean poolable; @@ -259,10 +262,18 @@ private T doWithStatementTimeout( connection.recordClientLibLatencyMetric(executionDuration.toMillis()); return result; } catch (SpannerException spannerException) { + if (this.cancelled.get() + && spannerException.getErrorCode() == ErrorCode.CANCELLED + && this.executingLock != null) { + // Clear the interrupted flag of the thread. + //noinspection ResultOfMethodCallIgnored + Thread.interrupted(); + } throw JdbcSqlExceptionFactory.of(spannerException); } finally { if (this.executingLock != null) { this.executingThread = null; + this.cancelled.set(false); this.executingLock.unlock(); } if (shouldResetTimeout.apply(result)) { @@ -374,8 +385,14 @@ public void cancel() throws SQLException { // This is a best-effort operation. It could be that the executing thread is set to null // between the if-check and the actual execution. Just ignore if that happens. try { + this.cancelled.set(true); this.executingThread.interrupt(); } catch (NullPointerException ignore) { + // ignore, this just means that the execution finished before we got to the point where we + // could interrupt the thread. + } catch (SecurityException securityException) { + throw JdbcSqlExceptionFactory.of( + securityException.getMessage(), Code.PERMISSION_DENIED, securityException); } } else { connection.getSpannerConnection().cancel(); diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTimeoutTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTimeoutTest.java index 2c8c43ca1..7ce51d362 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTimeoutTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTimeoutTest.java @@ -161,7 +161,6 @@ public void testCancel() throws Exception { message instanceof ExecuteSqlRequest && ((ExecuteSqlRequest) message).getSql().equals(sql), 5000L); - System.out.println("Cancelling statement"); statement.cancel(); return null; }); diff --git a/versions.txt b/versions.txt index fcaf7771d..d3b8ba19e 100644 --- a/versions.txt +++ b/versions.txt @@ -1,4 +1,4 @@ # Format: # module:released-version:current-version -google-cloud-spanner-jdbc:2.25.2:2.25.2 +google-cloud-spanner-jdbc:2.25.3:2.25.3