Repository: googleapis/java-spanner-jdbc
Pull request #1914 (2025-02-14 15:06:22)

feat: add methods for unwrapping Spanner client
Adds methods for unwrapping the underlying DatabaseClient and Spanner instance that is used by a JDBC connection.


Patch:
diff --git a/clirr-ignored-differences.xml b/clirr-ignored-differences.xml
index e82c992..547c9c4 100644
--- a/clirr-ignored-differences.xml
+++ b/clirr-ignored-differences.xml
@@ -108,4 +108,19 @@
     <className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
     <method>void setAutoBatchDmlUpdateCountVerification(boolean)</method>
   </difference>
+  <difference>
+    <differenceType>7012</differenceType>
+    <className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
+    <method>com.google.cloud.spanner.DatabaseClient getDatabaseClient()</method>
+  </difference>
+  <difference>
+    <differenceType>7012</differenceType>
+    <className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
+    <method>com.google.cloud.spanner.Spanner getSpanner()</method>
+  </difference>
+  <difference>
+    <differenceType>7012</differenceType>
+    <className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
+    <method>com.google.cloud.spanner.DatabaseId getDatabaseId()</method>
+  </difference>
 </differences>
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcConnection.java b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcConnection.java
index 122d489..38430cf 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcConnection.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcConnection.java
@@ -16,6 +16,8 @@
 
 package com.google.cloud.spanner.jdbc;
 
+import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.DatabaseId;
 import com.google.cloud.spanner.Dialect;
 import com.google.cloud.spanner.Spanner;
 import com.google.cloud.spanner.SpannerException;
@@ -68,6 +70,16 @@ abstract class AbstractJdbcConnection extends AbstractJdbcWrapper
     this.usesDirectExecutor = ConnectionOptionsHelper.usesDirectExecutor(options);
   }
 
+  @Override
+  public DatabaseId getDatabaseId() {
+    return this.options.getDatabaseId();
+  }
+
+  @Override
+  public DatabaseClient getDatabaseClient() {
+    return getSpannerConnection().getDatabaseClient();
+  }
+
   /** Return the corresponding {@link com.google.cloud.spanner.connection.Connection} */
   com.google.cloud.spanner.connection.Connection getSpannerConnection() {
     return spanner;
@@ -82,7 +94,8 @@ abstract class AbstractJdbcConnection extends AbstractJdbcWrapper
     return options;
   }
 
-  Spanner getSpanner() {
+  @Override
+  public Spanner getSpanner() {
     return this.spanner.getSpanner();
   }
 
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection.java b/src/main/java/com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection.java
index b8491d1..359854c 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection.java
@@ -20,10 +20,13 @@ import com.google.cloud.spanner.AbortedDueToConcurrentModificationException;
 import com.google.cloud.spanner.AbortedException;
 import com.google.cloud.spanner.CommitResponse;
 import com.google.cloud.spanner.CommitStats;
+import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.DatabaseId;
 import com.google.cloud.spanner.Dialect;
 import com.google.cloud.spanner.Mutation;
 import com.google.cloud.spanner.Options.QueryOption;
 import com.google.cloud.spanner.PartitionOptions;
+import com.google.cloud.spanner.Spanner;
 import com.google.cloud.spanner.TimestampBound;
 import com.google.cloud.spanner.connection.AutocommitDmlMode;
 import com.google.cloud.spanner.connection.SavepointSupport;
@@ -47,6 +50,28 @@ import javax.annotation.Nonnull;
  */
 public interface CloudSpannerJdbcConnection extends Connection {
 
+  /**
+   * Returns the {@link DatabaseId} of the database that this {@link Connection} is connected to.
+   */
+  default DatabaseId getDatabaseId() {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Returns the underlying {@link DatabaseClient} that is used by this connection. Operations that
+   * are executed on the {@link DatabaseClient} that is returned has no impact on this {@link
+   * Connection}, e.g. starting a read/write transaction on the {@link DatabaseClient} will not
+   * start a transaction on this connection.
+   */
+  default DatabaseClient getDatabaseClient() {
+    throw new UnsupportedOperationException();
+  }
+
+  /** Returns the underlying {@link Spanner} instance that is used by this connection. */
+  default Spanner getSpanner() {
+    throw new UnsupportedOperationException();
+  }
+
   /**
    * Sets the transaction tag to use for the current transaction. This method may only be called
    * when in a transaction, and before the transaction is actually started, i.e. before any


Test patch:
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/DdlMockServerTest.java b/src/test/java/com/google/cloud/spanner/jdbc/DdlMockServerTest.java
index 75f9d9e..7bca236 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/DdlMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/DdlMockServerTest.java
@@ -24,15 +24,18 @@ import static org.junit.Assert.assertTrue;
 import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
 import com.google.cloud.spanner.Statement;
 import com.google.cloud.spanner.connection.AbstractMockServerTest;
+import com.google.common.collect.ImmutableList;
 import com.google.longrunning.Operation;
 import com.google.protobuf.Any;
 import com.google.protobuf.Empty;
 import com.google.rpc.Code;
+import com.google.spanner.admin.database.v1.GetDatabaseDdlResponse;
 import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -50,6 +53,29 @@ public class DdlMockServerTest extends AbstractMockServerTest {
     return DriverManager.getConnection(createUrl(autoCommit));
   }
 
+  @Test
+  public void testGetDatabaseDdl() throws SQLException {
+    List<String> expectedDdl =
+        ImmutableList.of(
+            "create table foo (id int64) primary key (id)",
+            "create table bar (id int64) primary key (id)");
+    mockDatabaseAdmin.addResponse(
+        GetDatabaseDdlResponse.newBuilder().addAllStatements(expectedDdl).build());
+
+    try (Connection connection = createConnection(/* autoCommit = */ true)) {
+      CloudSpannerJdbcConnection spannerJdbcConnection =
+          connection.unwrap(CloudSpannerJdbcConnection.class);
+      List<String> ddl =
+          spannerJdbcConnection
+              .getSpanner()
+              .getDatabaseAdminClient()
+              .getDatabaseDdl(
+                  spannerJdbcConnection.getDatabaseId().getInstanceId().getInstance(),
+                  spannerJdbcConnection.getDatabaseId().getDatabase());
+      assertEquals(expectedDdl, ddl);
+    }
+  }
+
   @Test
   public void testDdlInAutoCommitIsTrue_succeeds() throws SQLException {
     mockDatabaseAdmin.addResponse(
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcConnectionTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcConnectionTest.java
index a54c179..e0ea49f 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcConnectionTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcConnectionTest.java
@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -100,6 +101,16 @@ public class JdbcConnectionTest {
     return options;
   }
 
+  @Test
+  public void testGetDatabaseClient() throws SQLException {
+    ConnectionOptions options = mockOptions();
+    try (Connection connection = createConnection(options)) {
+      CloudSpannerJdbcConnection spannerJdbcConnection =
+          connection.unwrap(CloudSpannerJdbcConnection.class);
+      assertNotNull(spannerJdbcConnection.getDatabaseClient());
+    }
+  }
+
   @Test
   public void testAutoCommit() throws SQLException {
     ConnectionOptions options = mockOptions();