From 91688c7281cf501a8d5ef3bd41e66b0f9aba39c3 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 12:35:56 +0100 Subject: [PATCH 01/11] chore(main): release 2.35.1-SNAPSHOT (#2362) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- 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 d8ac3be2c..f0ea9a46f 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.35.0 + 2.35.1-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 f190545bb..62997c229 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -29,7 +29,7 @@ com.google.cloud google-cloud-spanner-jdbc - 2.35.0 + 2.35.1-SNAPSHOT diff --git a/versions.txt b/versions.txt index 26ac9e854..f6c5b1fa8 100644 --- a/versions.txt +++ b/versions.txt @@ -1,4 +1,4 @@ # Format: # module:released-version:current-version -google-cloud-spanner-jdbc:2.35.0:2.35.0 +google-cloud-spanner-jdbc:2.35.0:2.35.1-SNAPSHOT From 7429508b82b26a55b6d7910416f3c72cfa63af2e Mon Sep 17 00:00:00 2001 From: "rayudu.alp" Date: Tue, 20 Jan 2026 15:01:08 +0530 Subject: [PATCH 02/11] fix: accept Arrays of Integer, Short, Byte for Array (#2365) --- .../google/cloud/spanner/jdbc/JdbcArray.java | 38 ++++++++-- .../cloud/spanner/jdbc/JdbcDataType.java | 2 +- .../spanner/jdbc/JdbcParameterStore.java | 2 + .../cloud/spanner/jdbc/JdbcArrayTest.java | 76 +++++++++++++++++++ 4 files changed, 109 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java index 1dd83f817..3cb523186 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java @@ -92,19 +92,41 @@ private JdbcArray(JdbcDataType type, Object[] elements) throws SQLException { this.data = java.lang.reflect.Array.newInstance( elements.getClass().getComponentType(), elements.length); + System.arraycopy(elements, 0, this.data, 0, elements.length); + } else if (type == JdbcDataType.INT64 && requiresWideningToLong(elements)) { + // Convert Byte[], Short[], and Integer[] to Long[] for INT64 type + // since Spanner only supports ARRAY + this.data = convertToLongArray(elements); } else { this.data = java.lang.reflect.Array.newInstance(type.getJavaClass(), elements.length); + try { + System.arraycopy(elements, 0, this.data, 0, elements.length); + } catch (Exception e) { + throw JdbcSqlExceptionFactory.of( + "Could not copy array elements. Make sure the supplied array only contains elements of class " + + type.getJavaClass().getName(), + Code.UNKNOWN, + e); + } } - try { - System.arraycopy(elements, 0, this.data, 0, elements.length); - } catch (Exception e) { - throw JdbcSqlExceptionFactory.of( - "Could not copy array elements. Make sure the supplied array only contains elements of class " - + type.getJavaClass().getName(), - Code.UNKNOWN, - e); + } + } + + private static boolean requiresWideningToLong(Object[] elements) { + Class componentType = elements.getClass().getComponentType(); + return componentType == Byte.class + || componentType == Short.class + || componentType == Integer.class; + } + + private static Long[] convertToLongArray(Object[] elements) { + Long[] longElements = new Long[elements.length]; + for (int i = 0; i < elements.length; i++) { + if (elements[i] != null) { + longElements[i] = ((Number) elements[i]).longValue(); } } + return longElements; } private JdbcArray(JdbcDataType type, List elements) { diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java index bb2f7c007..892c0057c 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java @@ -201,7 +201,7 @@ public Set getPostgreSQLAliases() { }, INT64 { private final Set> classes = - new HashSet<>(Arrays.asList(Byte.class, Integer.class, Long.class)); + new HashSet<>(Arrays.asList(Byte.class, Short.class, Integer.class, Long.class)); @Override public int getSqlType() { diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java index 0964db087..b43b44ddc 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java @@ -875,6 +875,8 @@ private Builder setArrayValue(ValueBinder binder, int type, Object valu return binder.toBoolArray((boolean[]) value); } else if (Boolean[].class.isAssignableFrom(value.getClass())) { return binder.toBoolArray(Arrays.asList((Boolean[]) value)); + } else if (Byte[].class.isAssignableFrom(value.getClass())) { + return binder.toInt64Array(toLongList((Byte[]) value)); } else if (short[].class.isAssignableFrom(value.getClass())) { long[] l = new long[((short[]) value).length]; for (int i = 0; i < l.length; i++) { diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java index bfae4e556..44dd1a78f 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java @@ -144,6 +144,82 @@ public void testCreateArrayTypeName() throws SQLException { assertThat(rs.next()).isFalse(); } + // Test that Byte[] arrays are automatically widened to Long[] for INT64 type + Long[] data; + array = JdbcArray.createArray("INT64", new Byte[] {1, 2, 3, null, Byte.MAX_VALUE}); + assertThat(array.getBaseType()).isEqualTo(Types.BIGINT); + // Data should be stored as Long[] + assertThat(array.getArray()).isInstanceOf(Long[].class); + data = (Long[]) array.getArray(); + assertThat(data[0]).isEqualTo(1L); + assertThat(data[1]).isEqualTo(2L); + assertThat(data[2]).isEqualTo(3L); + assertThat(data[3]).isNull(); + assertThat(data[4]).isEqualTo((long) Byte.MAX_VALUE); + + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getByte(2)).isEqualTo((byte) 1); + assertThat(rs.next()).isTrue(); + assertThat(rs.getByte(2)).isEqualTo((byte) 2); + assertThat(rs.next()).isTrue(); + assertThat(rs.getByte(2)).isEqualTo((byte) 3); + assertThat(rs.next()).isTrue(); + assertThat(rs.getByte(2)).isEqualTo((byte) 0); + assertTrue(rs.wasNull()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getByte(2)).isEqualTo(Byte.MAX_VALUE); + assertThat(rs.next()).isFalse(); + } + + // Test that Short[] arrays are automatically widened to Long[] for INT64 type + array = JdbcArray.createArray("INT64", new Short[] {100, 200, null, Short.MAX_VALUE}); + assertThat(array.getBaseType()).isEqualTo(Types.BIGINT); + // Data should be stored as Long[] + assertThat(array.getArray()).isInstanceOf(Long[].class); + data = (Long[]) array.getArray(); + assertThat(data[0]).isEqualTo(100L); + assertThat(data[1]).isEqualTo(200L); + assertThat(data[2]).isNull(); + assertThat(data[3]).isEqualTo((long) Short.MAX_VALUE); + + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getShort(2)).isEqualTo((short) 100); + assertThat(rs.next()).isTrue(); + assertThat(rs.getShort(2)).isEqualTo((short) 200); + assertThat(rs.next()).isTrue(); + assertThat(rs.getShort(2)).isEqualTo((short) 0); + assertTrue(rs.wasNull()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getShort(2)).isEqualTo(Short.MAX_VALUE); + assertThat(rs.next()).isFalse(); + } + + // Test that Integer[] arrays are automatically widened to Long[] for INT64 type + array = JdbcArray.createArray("INT64", new Integer[] {1000, 2000, null, Integer.MAX_VALUE}); + assertThat(array.getBaseType()).isEqualTo(Types.BIGINT); + // Data should be stored as Long[] + assertThat(array.getArray()).isInstanceOf(Long[].class); + data = (Long[]) array.getArray(); + assertThat(data[0]).isEqualTo(1000L); + assertThat(data[1]).isEqualTo(2000L); + assertThat(data[2]).isNull(); + assertThat(data[3]).isEqualTo((long) Integer.MAX_VALUE); + + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(2)).isEqualTo(1000); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(2)).isEqualTo(2000); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(2)).isEqualTo(0); + assertTrue(rs.wasNull()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(2)).isEqualTo(Integer.MAX_VALUE); + assertThat(rs.next()).isFalse(); + } + array = JdbcArray.createArray("NUMERIC", new BigDecimal[] {BigDecimal.ONE, null, BigDecimal.TEN}); assertThat(array.getBaseType()).isEqualTo(Types.NUMERIC); From 75cd38f1779eaca68a2b47020a618517add172dc Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 23 Jan 2026 16:00:07 +0000 Subject: [PATCH 03/11] chore(deps): update dependency com.google.cloud:google-cloud-spanner-jdbc to v2.35.0 (#2363) --- 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 78ae62ddd..865447824 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.34.1 + 2.35.0 From 61714fb5537d3676e7942738a86f08ec0b6e2707 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 26 Jan 2026 09:30:12 +0000 Subject: [PATCH 04/11] deps: update dependency org.springframework.boot:spring-boot-starter-data-jdbc to v4.0.2 (#2370) --- samples/spring-data-jdbc/googlesql/pom.xml | 2 +- samples/spring-data-jdbc/postgresql/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/spring-data-jdbc/googlesql/pom.xml b/samples/spring-data-jdbc/googlesql/pom.xml index d789c91a6..560f3ad44 100644 --- a/samples/spring-data-jdbc/googlesql/pom.xml +++ b/samples/spring-data-jdbc/googlesql/pom.xml @@ -59,7 +59,7 @@ org.springframework.boot spring-boot-starter-data-jdbc - 4.0.1 + 4.0.2 diff --git a/samples/spring-data-jdbc/postgresql/pom.xml b/samples/spring-data-jdbc/postgresql/pom.xml index 984ecf62a..61760a48b 100644 --- a/samples/spring-data-jdbc/postgresql/pom.xml +++ b/samples/spring-data-jdbc/postgresql/pom.xml @@ -59,7 +59,7 @@ org.springframework.boot spring-boot-starter-data-jdbc - 4.0.1 + 4.0.2 From 6c2b28e966dbb4c12a8c877b7eac2935146eaeb5 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 26 Jan 2026 09:30:42 +0000 Subject: [PATCH 05/11] deps: update dependency org.springframework.boot:spring-boot to v4.0.2 (#2369) --- samples/quickperf/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/quickperf/pom.xml b/samples/quickperf/pom.xml index 7fb46c05d..00d4c6a25 100644 --- a/samples/quickperf/pom.xml +++ b/samples/quickperf/pom.xml @@ -79,7 +79,7 @@ org.springframework.boot spring-boot - 4.0.1 + 4.0.2 test From db1755ca41b4c7fd0a232be94f66d5965677e46e Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 26 Jan 2026 09:31:12 +0000 Subject: [PATCH 06/11] deps: update dependency org.springframework.boot:spring-boot-starter-parent to v3.5.10 (#2368) --- samples/spring-data-mybatis/googlesql/pom.xml | 2 +- samples/spring-data-mybatis/postgresql/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/spring-data-mybatis/googlesql/pom.xml b/samples/spring-data-mybatis/googlesql/pom.xml index 0917b8df9..1efa0d298 100644 --- a/samples/spring-data-mybatis/googlesql/pom.xml +++ b/samples/spring-data-mybatis/googlesql/pom.xml @@ -13,7 +13,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.9 + 3.5.10 diff --git a/samples/spring-data-mybatis/postgresql/pom.xml b/samples/spring-data-mybatis/postgresql/pom.xml index f5f1c33b0..061f92696 100644 --- a/samples/spring-data-mybatis/postgresql/pom.xml +++ b/samples/spring-data-mybatis/postgresql/pom.xml @@ -13,7 +13,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.9 + 3.5.10 From 8e4ad8ac941ecef260e48e97b9d0685fd19fbc5c Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 26 Jan 2026 09:31:43 +0000 Subject: [PATCH 07/11] chore(deps): update dependency com.google.cloud:libraries-bom to v26.74.0 (#2366) --- samples/quickperf/pom.xml | 2 +- samples/snippets/pom.xml | 2 +- samples/spring-data-jdbc/googlesql/pom.xml | 2 +- samples/spring-data-jdbc/postgresql/pom.xml | 2 +- samples/spring-data-mybatis/googlesql/pom.xml | 2 +- samples/spring-data-mybatis/postgresql/pom.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/quickperf/pom.xml b/samples/quickperf/pom.xml index 00d4c6a25..4ff92403b 100644 --- a/samples/quickperf/pom.xml +++ b/samples/quickperf/pom.xml @@ -27,7 +27,7 @@ com.google.cloud libraries-bom - 26.73.0 + 26.74.0 pom import diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index c52c6f644..666ba5664 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -26,7 +26,7 @@ com.google.cloud libraries-bom - 26.73.0 + 26.74.0 pom import diff --git a/samples/spring-data-jdbc/googlesql/pom.xml b/samples/spring-data-jdbc/googlesql/pom.xml index 560f3ad44..5029fe0e7 100644 --- a/samples/spring-data-jdbc/googlesql/pom.xml +++ b/samples/spring-data-jdbc/googlesql/pom.xml @@ -41,7 +41,7 @@ com.google.cloud libraries-bom - 26.73.0 + 26.74.0 import pom diff --git a/samples/spring-data-jdbc/postgresql/pom.xml b/samples/spring-data-jdbc/postgresql/pom.xml index 61760a48b..58978949e 100644 --- a/samples/spring-data-jdbc/postgresql/pom.xml +++ b/samples/spring-data-jdbc/postgresql/pom.xml @@ -41,7 +41,7 @@ com.google.cloud libraries-bom - 26.73.0 + 26.74.0 import pom diff --git a/samples/spring-data-mybatis/googlesql/pom.xml b/samples/spring-data-mybatis/googlesql/pom.xml index 1efa0d298..3fa032f64 100644 --- a/samples/spring-data-mybatis/googlesql/pom.xml +++ b/samples/spring-data-mybatis/googlesql/pom.xml @@ -46,7 +46,7 @@ com.google.cloud libraries-bom - 26.73.0 + 26.74.0 import pom diff --git a/samples/spring-data-mybatis/postgresql/pom.xml b/samples/spring-data-mybatis/postgresql/pom.xml index 061f92696..eb262a031 100644 --- a/samples/spring-data-mybatis/postgresql/pom.xml +++ b/samples/spring-data-mybatis/postgresql/pom.xml @@ -35,7 +35,7 @@ com.google.cloud libraries-bom - 26.73.0 + 26.74.0 import pom From a4b5c21a449fca70bab45eb756addcba57f0b080 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 26 Jan 2026 09:32:09 +0000 Subject: [PATCH 08/11] deps: update dependency com.fasterxml.jackson.core:jackson-databind to v2.21.0 (#2364) --- samples/quickperf/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/quickperf/pom.xml b/samples/quickperf/pom.xml index 4ff92403b..4fe8c079e 100644 --- a/samples/quickperf/pom.xml +++ b/samples/quickperf/pom.xml @@ -67,7 +67,7 @@ com.fasterxml.jackson.core jackson-databind - 2.20.1 + 2.21.0 From 832064ca2bca0d2d29e00c6b1417c7a4ce22f847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 26 Jan 2026 16:19:47 +0100 Subject: [PATCH 09/11] perf: optimize JdbcDataSource#getConnection() (#2371) The `JdbcDataSource#getConnection()` method repeatedly executed a number of steps that were not necessary for each new connection. This has now been optimized, so they are only executed once, as long as the properties of the DataSource do not change. --- pom.xml | 2 + .../cloud/spanner/jdbc/JdbcDataSource.java | 38 +++++++++-- .../google/cloud/spanner/jdbc/JdbcDriver.java | 63 ++++++++++--------- .../spanner/jdbc/JdbcDataSourceTest.java | 63 +++++++++++++++++++ src/test/resources/logging.properties | 7 +++ 5 files changed, 137 insertions(+), 36 deletions(-) create mode 100644 src/test/java/com/google/cloud/spanner/jdbc/JdbcDataSourceTest.java create mode 100644 src/test/resources/logging.properties diff --git a/pom.xml b/pom.xml index f0ea9a46f..dd0d25374 100644 --- a/pom.xml +++ b/pom.xml @@ -260,6 +260,7 @@ projects/gcloud-devel/instances/spanner-testing-east1 + logging.properties @@ -274,6 +275,7 @@ projects/gcloud-devel/instances/spanner-testing-east1 + logging.properties 2400 4 diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataSource.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataSource.java index d4f415d27..f0f073aad 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataSource.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataSource.java @@ -16,11 +16,14 @@ package com.google.cloud.spanner.jdbc; +import static com.google.cloud.spanner.jdbc.JdbcDriver.appendPropertiesToUrl; +import static com.google.cloud.spanner.jdbc.JdbcDriver.buildConnectionOptions; +import static com.google.cloud.spanner.jdbc.JdbcDriver.maybeAddUserAgent; + import com.google.cloud.spanner.connection.ConnectionOptions; import com.google.rpc.Code; import java.io.PrintWriter; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.Properties; @@ -35,6 +38,8 @@ public class JdbcDataSource extends AbstractJdbcWrapper implements DataSource { private Boolean readonly; private Boolean retryAbortsInternally; + private volatile ConnectionOptions cachedConnectionOptions; + // Make sure the JDBC driver class is loaded. static { try { @@ -76,12 +81,22 @@ public Connection getConnection() throws SQLException { throw JdbcSqlExceptionFactory.of( "There is no URL specified for this data source", Code.FAILED_PRECONDITION); } - if (!JdbcDriver.getRegisteredDriver().acceptsURL(getUrl())) { - throw JdbcSqlExceptionFactory.of( - "The URL " + getUrl() + " is not valid for the data source " + getClass().getName(), - Code.FAILED_PRECONDITION); + if (cachedConnectionOptions == null) { + synchronized (this) { + if (cachedConnectionOptions == null) { + if (!JdbcDriver.getRegisteredDriver().acceptsURL(getUrl())) { + throw JdbcSqlExceptionFactory.of( + "The URL " + getUrl() + " is not valid for the data source " + getClass().getName(), + Code.FAILED_PRECONDITION); + } + Properties properties = createProperties(); + maybeAddUserAgent(properties); + String connectionUri = appendPropertiesToUrl(url.substring(5), properties); + cachedConnectionOptions = buildConnectionOptions(connectionUri, properties); + } + } } - return DriverManager.getConnection(getUrl(), createProperties()); + return new JdbcConnection(getUrl(), cachedConnectionOptions); } @Override @@ -114,6 +129,12 @@ public boolean isClosed() { return false; } + private void clearCachedConnectionOptions() { + synchronized (this) { + cachedConnectionOptions = null; + } + } + /** * @return the JDBC URL to use for this {@link DataSource}. */ @@ -125,6 +146,7 @@ public String getUrl() { * @param url The JDBC URL to use for this {@link DataSource}. */ public void setUrl(String url) { + clearCachedConnectionOptions(); this.url = url; } @@ -143,6 +165,7 @@ public String getCredentials() { * connection URL will be used. */ public void setCredentials(String credentials) { + clearCachedConnectionOptions(); this.credentials = credentials; } @@ -161,6 +184,7 @@ public Boolean getAutocommit() { * the connection URL will be used. */ public void setAutocommit(Boolean autocommit) { + clearCachedConnectionOptions(); this.autocommit = autocommit; } @@ -179,6 +203,7 @@ public Boolean getReadonly() { * URL will be used. */ public void setReadonly(Boolean readonly) { + clearCachedConnectionOptions(); this.readonly = readonly; } @@ -197,6 +222,7 @@ public Boolean getRetryAbortsInternally() { * this property, the value in the connection URL will be used. */ public void setRetryAbortsInternally(Boolean retryAbortsInternally) { + clearCachedConnectionOptions(); this.retryAbortsInternally = retryAbortsInternally; } } diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java index 120880fee..8e8bd0726 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java @@ -37,6 +37,7 @@ import java.sql.SQLWarning; import java.util.Map.Entry; import java.util.Properties; +import java.util.function.Supplier; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -247,7 +248,7 @@ public Connection connect(String url, Properties info) throws SQLException { return null; } - private ConnectionOptions buildConnectionOptions(String connectionUrl, Properties info) { + static ConnectionOptions buildConnectionOptions(String connectionUrl, Properties info) { ConnectionOptions.Builder builder = ConnectionOptions.newBuilder().setTracingPrefix("JDBC").setUri(connectionUrl); if (info.containsKey(OPEN_TELEMETRY_PROPERTY_KEY) @@ -272,40 +273,42 @@ static void maybeAddUserAgent(Properties properties) { } } - static boolean isHibernate() { - // Cache the result as the check is relatively expensive, and we also don't want to create - // multiple different Spanner instances just to get the correct user-agent in every case. - return Suppliers.memoize( - () -> { - try { - // First check if the Spanner Hibernate dialect is on the classpath. If it is, then - // we assume that Hibernate will (eventually) be used. - Class.forName( - "com.google.cloud.spanner.hibernate.SpannerDialect", - /* initialize= */ false, - JdbcDriver.class.getClassLoader()); - return true; - } catch (Throwable ignore) { - } + private static final Supplier isHibernate = + Suppliers.memoize( + () -> { + try { + // First check if the Spanner Hibernate dialect is on the classpath. If it is, then + // we assume that Hibernate will (eventually) be used. + Class.forName( + "com.google.cloud.spanner.hibernate.SpannerDialect", + /* initialize= */ false, + JdbcDriver.class.getClassLoader()); + return true; + } catch (Throwable ignore) { + } - // If we did not find the Spanner Hibernate dialect on the classpath, then do a - // check if the connection is still being created by Hibernate using the built-in - // Spanner dialect in Hibernate. - try { - StackTraceElement[] callStack = Thread.currentThread().getStackTrace(); - for (StackTraceElement element : callStack) { - if (element.getClassName().contains(".hibernate.")) { - return true; - } + // If we did not find the Spanner Hibernate dialect on the classpath, then do a + // check if the connection is still being created by Hibernate using the built-in + // Spanner dialect in Hibernate. + try { + StackTraceElement[] callStack = Thread.currentThread().getStackTrace(); + for (StackTraceElement element : callStack) { + if (element.getClassName().contains(".hibernate.")) { + return true; } - } catch (Throwable ignore) { } - return false; - }) - .get(); + } catch (Throwable ignore) { + } + return false; + }); + + static boolean isHibernate() { + // Cache the result as the check is relatively expensive, and we also don't want to create + // multiple different Spanner instances just to get the correct user-agent in every case. + return isHibernate.get(); } - private String appendPropertiesToUrl(String url, Properties info) { + static String appendPropertiesToUrl(String url, Properties info) { StringBuilder res = new StringBuilder(url); for (Entry entry : info.entrySet()) { if (entry.getValue() instanceof String && !"".equals(entry.getValue())) { diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcDataSourceTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcDataSourceTest.java new file mode 100644 index 000000000..196cb9169 --- /dev/null +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcDataSourceTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2026 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 + * + * http://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.jdbc; + +import static org.junit.Assert.assertEquals; + +import com.google.cloud.spanner.connection.AbstractMockServerTest; +import java.sql.Connection; +import java.sql.SQLException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class JdbcDataSourceTest extends AbstractMockServerTest { + + @Override + protected String getBaseUrl() { + return String.format( + "jdbc:cloudspanner://localhost:%d/projects/p/instances/i/databases/d?usePlainText=true", + getPort()); + } + + @Test + public void testGetConnectionFromNewDataSource() throws SQLException { + for (boolean autoCommit : new boolean[] {true, false}) { + JdbcDataSource dataSource = new JdbcDataSource(); + dataSource.setUrl(getBaseUrl()); + dataSource.setAutocommit(autoCommit); + try (Connection connection = dataSource.getConnection()) { + assertEquals(autoCommit, connection.getAutoCommit()); + } + } + } + + @Test + public void testGetConnectionFromCachedDataSource() throws SQLException { + JdbcDataSource dataSource = new JdbcDataSource(); + dataSource.setUrl(getBaseUrl()); + for (boolean autoCommit : new boolean[] {true, false}) { + // Changing a property on the DataSource should invalidate the internally cached + // ConnectionOptions. + dataSource.setAutocommit(autoCommit); + try (Connection connection = dataSource.getConnection()) { + assertEquals(autoCommit, connection.getAutoCommit()); + } + } + } +} diff --git a/src/test/resources/logging.properties b/src/test/resources/logging.properties new file mode 100644 index 000000000..c817ab7ac --- /dev/null +++ b/src/test/resources/logging.properties @@ -0,0 +1,7 @@ +.level=INFO +.handlers=java.util.logging.ConsoleHandler +java.util.logging.ConsoleHandler.level=INFO +java.util.logging.Logger.useParentHandlers=true + +# Set log level to WARN for SpannerImpl to prevent log spamming of the Spanner configuration. +com.google.cloud.spanner.SpannerImpl.LEVEL=WARN From 094ab5a62a723cd895a6ea54259a3bbef98d56fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 26 Jan 2026 16:20:03 +0100 Subject: [PATCH 10/11] docs: update connection properties docs with DCP (#2372) --- documentation/connection_properties.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/documentation/connection_properties.md b/documentation/connection_properties.md index 3e0f5efe3..99e3e1d16 100644 --- a/documentation/connection_properties.md +++ b/documentation/connection_properties.md @@ -22,6 +22,9 @@ The 'Context' value indicates whether the property can only be set when a connec | credentialsprovider | The class name of the com.google.api.gax.core.CredentialsProvider implementation that should be used to obtain credentials for connections. | | | STARTUP | | databaserole | Sets the database role to use for this connection. The default is privileges assigned to IAM role | | | STARTUP | | databoostenabled | Enable data boost for all partitioned queries that are executed by this connection. This setting is only used for partitioned queries and is ignored by all other statements. | false | true, false | USER | +| dcpinitialchannels | The initial number of channels in the dynamic channel pool. Only used when enableDynamicChannelPool is true. The default is SpannerOptions.DEFAULT_DYNAMIC_POOL_INITIAL_SIZE (4). | | | STARTUP | +| dcpmaxchannels | The maximum number of channels in the dynamic channel pool. Only used when enableDynamicChannelPool is true. The default is SpannerOptions.DEFAULT_DYNAMIC_POOL_MAX_CHANNELS (10). | | | STARTUP | +| dcpminchannels | The minimum number of channels in the dynamic channel pool. Only used when enableDynamicChannelPool is true. The default is SpannerOptions.DEFAULT_DYNAMIC_POOL_MIN_CHANNELS (2). | | | STARTUP | | ddlintransactionmode | Determines how the connection should handle DDL statements in a read/write transaction. | ALLOW_IN_EMPTY_TRANSACTION | FAIL, ALLOW_IN_EMPTY_TRANSACTION, AUTO_COMMIT_TRANSACTION | USER | | default_isolation_level | The transaction isolation level that is used by default for read/write transactions. The default is isolation_level_unspecified, which means that the connection will use the default isolation level of the database that it is connected to. | ISOLATION_LEVEL_UNSPECIFIED | ISOLATION_LEVEL_UNSPECIFIED, SERIALIZABLE, REPEATABLE_READ | USER | | defaultsequencekind | The default sequence kind that should be used for the database. This property is only used when a DDL statement that requires a default sequence kind is executed on this connection. | | | USER | @@ -30,6 +33,7 @@ The 'Context' value indicates whether the property can only be set when a connec | directed_read | The directed read options to apply to read-only transactions. | | | USER | | enableapitracing | Add OpenTelemetry traces for each individual RPC call. Enable this to get a detailed view of each RPC that is being executed by your application, or if you want to debug potential latency problems caused by RPCs that are being retried. | | true, false | STARTUP | | enabledirectaccess | Configure the connection to try to connect to Spanner using DirectPath (true/false). The client will try to connect to Spanner using a direct Google network connection. DirectPath will work only if the client is trying to establish a connection from a Google Cloud VM. Otherwise it will automatically fallback to the standard network path. NOTE: The default for this property is currently false, but this could be changed in the future. | | true, false | STARTUP | +| enabledynamicchannelpool | Enable dynamic channel pooling for automatic gRPC channel scaling. When enabled, the client will automatically scale the number of channels based on load. Setting numChannels will disable dynamic channel pooling even if this is set to true. The default is currently false (disabled), but this may change to true in a future version. Set this property explicitly to ensure consistent behavior. | | true, false | STARTUP | | enableendtoendtracing | Enable end-to-end tracing (true/false) to generate traces for both the time that is spent in the client, as well as time that is spent in the Spanner server. Server side traces can only go to Google Cloud Trace, so to see end to end traces, the application should configure an exporter that exports the traces to Google Cloud Trace. | false | true, false | STARTUP | | enableextendedtracing | Include the SQL string in the OpenTelemetry traces that are generated by this connection. The SQL string is added as the standard OpenTelemetry attribute 'db.statement'. | | true, false | STARTUP | | encodedcredentials | Base64-encoded credentials to use for this connection. If neither this property or a credentials location are set, the connection will use the default Google Cloud credentials for the runtime environment. WARNING: Enabling this property without proper validation can expose the application to security risks. It is intended for use with credentials from a trusted source only, as it could otherwise allow end-users to supply arbitrary credentials. For more information, seehttps://cloud.google.com/docs/authentication/client-libraries#external-credentials | | | STARTUP | From ed74a8e3d6eba6d1a9c997713e3c2a246963f9e6 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:15:07 +0100 Subject: [PATCH 11/11] chore(main): release 2.35.1 (#2367) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 25 +++++++++++++++++++++++++ README.md | 6 +++--- pom.xml | 2 +- samples/snapshot/pom.xml | 2 +- versions.txt | 2 +- 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3298d7655..d7895ebe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## [2.35.1](https://github.com/googleapis/java-spanner-jdbc/compare/v2.35.0...v2.35.1) (2026-01-26) + + +### Bug Fixes + +* Accept Arrays of Integer, Short, Byte for Array<Int64> ([#2365](https://github.com/googleapis/java-spanner-jdbc/issues/2365)) ([7429508](https://github.com/googleapis/java-spanner-jdbc/commit/7429508b82b26a55b6d7910416f3c72cfa63af2e)) + + +### Performance Improvements + +* Optimize JdbcDataSource#getConnection() ([#2371](https://github.com/googleapis/java-spanner-jdbc/issues/2371)) ([832064c](https://github.com/googleapis/java-spanner-jdbc/commit/832064ca2bca0d2d29e00c6b1417c7a4ce22f847)) + + +### Dependencies + +* Update dependency com.fasterxml.jackson.core:jackson-databind to v2.21.0 ([#2364](https://github.com/googleapis/java-spanner-jdbc/issues/2364)) ([a4b5c21](https://github.com/googleapis/java-spanner-jdbc/commit/a4b5c21a449fca70bab45eb756addcba57f0b080)) +* Update dependency org.springframework.boot:spring-boot to v4.0.2 ([#2369](https://github.com/googleapis/java-spanner-jdbc/issues/2369)) ([6c2b28e](https://github.com/googleapis/java-spanner-jdbc/commit/6c2b28e966dbb4c12a8c877b7eac2935146eaeb5)) +* Update dependency org.springframework.boot:spring-boot-starter-data-jdbc to v4.0.2 ([#2370](https://github.com/googleapis/java-spanner-jdbc/issues/2370)) ([61714fb](https://github.com/googleapis/java-spanner-jdbc/commit/61714fb5537d3676e7942738a86f08ec0b6e2707)) +* Update dependency org.springframework.boot:spring-boot-starter-parent to v3.5.10 ([#2368](https://github.com/googleapis/java-spanner-jdbc/issues/2368)) ([db1755c](https://github.com/googleapis/java-spanner-jdbc/commit/db1755ca41b4c7fd0a232be94f66d5965677e46e)) + + +### Documentation + +* Update connection properties docs with DCP ([#2372](https://github.com/googleapis/java-spanner-jdbc/issues/2372)) ([094ab5a](https://github.com/googleapis/java-spanner-jdbc/commit/094ab5a62a723cd895a6ea54259a3bbef98d56fc)) + ## [2.35.0](https://github.com/googleapis/java-spanner-jdbc/compare/v2.34.1...v2.35.0) (2026-01-16) diff --git a/README.md b/README.md index c7e4a8b7b..5893ab94b 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.35.0 + 2.35.1 ``` @@ -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.35.0' +implementation 'com.google.cloud:google-cloud-spanner-jdbc:2.35.1' ``` @@ -38,7 +38,7 @@ If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-spanner-jdbc" % "2.35.0" +libraryDependencies += "com.google.cloud" % "google-cloud-spanner-jdbc" % "2.35.1" ``` diff --git a/pom.xml b/pom.xml index dd0d25374..f299e9002 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.35.1-SNAPSHOT + 2.35.1 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 62997c229..fc6ff5f0c 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -29,7 +29,7 @@ com.google.cloud google-cloud-spanner-jdbc - 2.35.1-SNAPSHOT + 2.35.1 diff --git a/versions.txt b/versions.txt index f6c5b1fa8..0bd58ebcf 100644 --- a/versions.txt +++ b/versions.txt @@ -1,4 +1,4 @@ # Format: # module:released-version:current-version -google-cloud-spanner-jdbc:2.35.0:2.35.1-SNAPSHOT +google-cloud-spanner-jdbc:2.35.1:2.35.1