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