Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add methods for unwrapping Spanner client #1914

Merged
merged 1 commit into from
Feb 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions clirr-ignored-differences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Comment on lines +74 to +81
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interested to understand if there is a customer usecase where they would like to use databaseclient directly instead of using the connection.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is more of an 'escape hatch'. We try to support most features directly in the JDBC driver, but some Spanner features don't have a good equivalent in JDBC. In those cases, it can be easier to just use the database client directly. One such example could for example be BatchWrite. Although we can also create a method in the JDBC Connection that just delegates the call directly to the underlying database client, giving the option of just unwrapping the client ensures that all features are usable, even if there is no such delegate method.


/** Return the corresponding {@link com.google.cloud.spanner.connection.Connection} */
com.google.cloud.spanner.connection.Connection getSpannerConnection() {
return spanner;
Expand All @@ -82,7 +94,8 @@ ConnectionOptions getConnectionOptions() {
return options;
}

Spanner getSpanner() {
@Override
public Spanner getSpanner() {
return this.spanner.getSpanner();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@
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;
Expand All @@ -47,6 +50,28 @@
*/
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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can customer do something like below?

CloudSpannerJdbcConnection spannerJdbcConnection =
          connection.unwrap(CloudSpannerJdbcConnection.class);
      List<String> ddl =
          spannerJdbcConnection
              .getSpanner()
              .getDatabaseClient()

Is there any difference between this and the exposed method? I mean do we need to expose a seperate method for getDatabaseClient?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, there is no difference, as the only key that is used to determine which DatabaseClient to get from a Spanner instance is the DatabaseId. But this might change in the future, for example when #1856 is resolved. With this, the JDBC driver might use additional properties to determine which DatabaseClient is used for a connection.

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,18 @@
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;
Expand All @@ -50,6 +53,29 @@ private Connection createConnection(boolean autoCommit) throws SQLException {
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;
Expand Down Expand Up @@ -100,6 +101,16 @@ private ConnectionOptions mockOptions() {
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();
Expand Down