Skip to content

feat: support option for setting client_id while creating spanner database client #3832

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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

aakashanandg
Copy link

@aakashanandg aakashanandg commented Apr 23, 2025

Fixes googleapis/java-spanner-jdbc#1856.

Summary:

This PR adds support for specifying an optional client_Id when creating a Spanner DatabaseClient. This allows client applications (including JDBC connections using the client_id property) to identify themselves to the Spanner service, primarily for enhanced monitoring and logging capabilities.

Implementation:

  • Added the getDatabaseClient(DatabaseId db, String clientId) overload to the Spanner interface.
  • Modified connection setup logic to parse and utilize the clientId when obtaining the DatabaseClient.

Testing:

  • All unit tests pass (mvn test).

@aakashanandg aakashanandg requested review from a team as code owners April 23, 2025 12:50
@product-auto-label product-auto-label bot added size: m Pull request size is medium. api: spanner Issues related to the googleapis/java-spanner API. labels Apr 23, 2025
@aakashanandg aakashanandg requested a review from olavloite April 23, 2025 12:50
@aakashanandg aakashanandg changed the title feat: support option for setting client_id while creating Spanner DatabaseClient feat: support option for setting client_id while creating spanner database client Apr 24, 2025
Comment on lines -326 to +346
this.dbClient = spanner.getDatabaseClient(options.getDatabaseId());
DatabaseClient tempDbClient = null;
final DatabaseId databaseId = options.getDatabaseId();
try {
Optional<String> clientIdOpt = extractClientIdOptional(options);
if (clientIdOpt.isPresent() && !clientIdOpt.get().isEmpty()) {
if (this.spanner instanceof ExtendedSpanner) {
ExtendedSpanner extendedSpanner = (ExtendedSpanner) this.spanner;
tempDbClient = extendedSpanner.getDatabaseClient(databaseId, clientIdOpt.get());
}
}
} catch (Exception e) {
System.err.println(
"WARNING: Failed during DatabaseClient initialization (possibly getting specific ID), falling back to default. Error: "
+ e.getMessage());
}
if (tempDbClient == null) {
tempDbClient = spanner.getDatabaseClient(databaseId);
}
this.dbClient = tempDbClient;
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can simplify this entire block into one line like this:

    this.dbClient = spanner.getDatabaseClient(
        databaseId,
        options.getInitialConnectionPropertyValue(ConnectionProperties.CLIENT_ID));

Copy link
Collaborator

Choose a reason for hiding this comment

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

But to do this, you need to make sure that:

  1. getDatabaseClient(DatabaseId, String) accepts a null value for the second argument (which it should, as that is the default when calling the overload without a client id)
  2. Change the return type of SpannerPool#getSpanner(..) to an interface or class that supports the additional client-id argument. That means that you get an instance that you can use directly on line 315.

Comment on lines +363 to +370
private Optional<String> extractClientIdOptional(ConnectionOptions options) {
return Optional.ofNullable(options.getInitialConnectionPropertyValues())
.map(props -> props.get(CLIENT_ID))
.map(ConnectionPropertyValue::getValue)
.map(Object::toString)
.filter(id -> !id.isEmpty());
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove, see above

@@ -411,7 +439,7 @@ static Attributes createOpenTelemetryAttributes(DatabaseId databaseId) {
}

@VisibleForTesting
ConnectionState.Type getConnectionStateType() {
Type getConnectionStateType() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove the unrelated changes in this PR

null,
StringValueConverter.INSTANCE,
Context.STARTUP);

Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: remove empty line


DatabaseClientImpl databaseClient =
(DatabaseClientImpl) impl.getDatabaseClient(db, "clientId-1");
assertThat(databaseClient.clientId).isEqualTo("clientId-1");
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: prefer the use of junit assertions, so in this case assertEquals(expected, actual). This should also be the case if the test class that you are editing contains assertThat(..) style assertions.

@@ -157,6 +159,7 @@ class ConnectionImpl implements Connection {
private static final ParsedStatement RELEASE_STATEMENT =
AbstractStatementParser.getInstance(Dialect.GOOGLE_STANDARD_SQL)
.parse(Statement.of("RELEASE s1"));
private static final String CLIENT_ID = "client_id";
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: remove (see below)

}

@Override
public DatabaseClient getDatabaseClient(DatabaseId db, String clientId) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

  • What happens if you call this method twice with the same db and clientId? Does it return the same instance, both with the correct clientId? Add a test for that.
  • What happens if you call this method twice with the same db, but different clientIds? Does it return the same instance? Or two different instances with the correct clientId? Add a test for that.
  • What happens if you call this method twice with different dbs, but the same clientIds? Add a test.

Comment on lines +390 to +395
// Getting a new database client for an invalidated database should use the same client id.
databaseClient.pool.setResourceNotFoundException(
new DatabaseNotFoundException(DoNotConstructDirectly.ALLOWED, "not found", null, null));
DatabaseClientImpl revalidated = (DatabaseClientImpl) impl.getDatabaseClient(db, "clientId-1");
assertThat(revalidated).isNotSameInstanceAs(databaseClient);
assertThat(revalidated.clientId).isEqualTo(databaseClient.clientId);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems like a copy-pasted test from something else that is not really testing the new feature that is being added here.


package com.google.cloud.spanner;

public interface ExtendedSpanner extends Spanner {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that we should:

  1. Move this interface into the package com.google.cloud.spanner.connection
  2. Rename it to InternalSpanner
  3. Add the annotation @InternalApi
  4. Add a javadoc saying '/** This interface is used internally to create (JDBC) connections for Spanner and is only intended for internal usage. */`

Comment on lines +20 to +39
/**
* Returns a {@code DatabaseClient} for the given database and given client id. It uses a pool of
* sessions to talk to the database.
* <!--SNIPPET get_db_client-->
*
* <pre>{@code
* SpannerOptions options = SpannerOptions.newBuilder().build();
* Spanner spanner = options.getService();
* final String project = "test-project";
* final String instance = "test-instance";
* final String database = "example-db";
* final String client_id = "client_id"
* DatabaseId db =
* DatabaseId.of(project, instance, database);
*
* DatabaseClient dbClient = spanner.getDatabaseClient(db, client_id);
* }</pre>
*
* <!--SNIPPET get_db_client-->
*/
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove this documentation, as this is intended for internal usage. Also add an @InternalApi annotation to the method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: spanner Issues related to the googleapis/java-spanner API. size: m Pull request size is medium.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feat: support option for setting client-id
2 participants