Skip to content

Commit 556a2c5

Browse files
committed
feat: support option for setting client-id
1 parent 7f6b43c commit 556a2c5

File tree

6 files changed

+106
-10
lines changed

6 files changed

+106
-10
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/Spanner.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,29 @@ public interface Spanner extends Service<SpannerOptions>, AutoCloseable {
125125
*/
126126
DatabaseClient getDatabaseClient(DatabaseId db);
127127

128+
129+
/**
130+
* Returns a {@code DatabaseClient} for the given database and given client id. It uses a pool of sessions to talk to
131+
* the database.
132+
* <!--SNIPPET get_db_client-->
133+
*
134+
* <pre>{@code
135+
* SpannerOptions options = SpannerOptions.newBuilder().build();
136+
* Spanner spanner = options.getService();
137+
* final String project = "test-project";
138+
* final String instance = "test-instance";
139+
* final String database = "example-db";
140+
* final String client_id = "client_id"
141+
* DatabaseId db =
142+
* DatabaseId.of(project, instance, database);
143+
*
144+
* DatabaseClient dbClient = spanner.getDatabaseClient(db, client_id);
145+
* }</pre>
146+
*
147+
* <!--SNIPPET get_db_client-->
148+
*/
149+
DatabaseClient getDatabaseClient(DatabaseId db, String clientId);
150+
128151
/**
129152
* Returns a {@code BatchClient} to do batch operations on Cloud Spanner databases. Batch client
130153
* is useful when one wants to read/query a large amount of data from Cloud Spanner across

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,12 @@ public InstanceAdminClient getInstanceAdminClient() {
254254

255255
@Override
256256
public DatabaseClient getDatabaseClient(DatabaseId db) {
257+
return getDatabaseClient(db, null);
258+
}
259+
260+
public DatabaseClient getDatabaseClient(DatabaseId db, String clientId) {
257261
synchronized (this) {
258262
checkClosed();
259-
String clientId = null;
260263
if (dbClients.containsKey(db) && !dbClients.get(db).isValid()) {
261264
// Close the invalidated client and remove it.
262265
dbClients.get(db).closeAsync(new ClosedException());

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
import java.util.Iterator;
109109
import java.util.LinkedList;
110110
import java.util.List;
111+
import java.util.Map;
111112
import java.util.Set;
112113
import java.util.Stack;
113114
import java.util.UUID;
@@ -157,6 +158,7 @@ class ConnectionImpl implements Connection {
157158
private static final ParsedStatement RELEASE_STATEMENT =
158159
AbstractStatementParser.getInstance(Dialect.GOOGLE_STANDARD_SQL)
159160
.parse(Statement.of("RELEASE s1"));
161+
private static final String CLIENT_ID = "client_id";
160162

161163
/**
162164
* Exception that is used to register the stacktrace of the code that opened a {@link Connection}.
@@ -251,8 +253,8 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
251253
}
252254
}
253255

254-
private StatementExecutor.StatementTimeout statementTimeout =
255-
new StatementExecutor.StatementTimeout();
256+
private StatementTimeout statementTimeout =
257+
new StatementTimeout();
256258
private boolean closed = false;
257259

258260
private final Spanner spanner;
@@ -323,7 +325,28 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
323325
EmulatorUtil.maybeCreateInstanceAndDatabase(
324326
spanner, options.getDatabaseId(), options.getDialect());
325327
}
326-
this.dbClient = spanner.getDatabaseClient(options.getDatabaseId());
328+
DatabaseClient tempDbClient = null;
329+
try {
330+
final Map<String, ConnectionPropertyValue<?>> propertyValueMap = options.getInitialConnectionPropertyValues();
331+
String clientIdString = null;
332+
if (propertyValueMap != null) {
333+
final ConnectionPropertyValue<?> clientIdProp = propertyValueMap.get(CLIENT_ID);
334+
if (clientIdProp != null) {
335+
Object value = clientIdProp.getValue();
336+
if (value != null) {
337+
clientIdString = value.toString();
338+
}
339+
}
340+
if (clientIdString != null && !clientIdString.isEmpty()) {
341+
tempDbClient = spanner.getDatabaseClient(options.getDatabaseId(), clientIdString);
342+
}
343+
} else {
344+
tempDbClient = spanner.getDatabaseClient(options.getDatabaseId());
345+
}
346+
} catch(Exception e) {
347+
tempDbClient = spanner.getDatabaseClient(options.getDatabaseId());
348+
}
349+
this.dbClient = tempDbClient;
327350
this.batchClient = spanner.getBatchClient(options.getDatabaseId());
328351
this.ddlClient = createDdlClient();
329352
this.connectionState =
@@ -411,7 +434,7 @@ static Attributes createOpenTelemetryAttributes(DatabaseId databaseId) {
411434
}
412435

413436
@VisibleForTesting
414-
ConnectionState.Type getConnectionStateType() {
437+
Type getConnectionStateType() {
415438
return this.connectionState.getType();
416439
}
417440

@@ -500,7 +523,7 @@ private void reset(Context context, boolean inTransaction) {
500523

501524
this.connectionState.resetValue(AUTOCOMMIT_DML_MODE, context, inTransaction);
502525
this.statementTag = null;
503-
this.statementTimeout = new StatementExecutor.StatementTimeout();
526+
this.statementTimeout = new StatementTimeout();
504527
this.connectionState.resetValue(DIRECTED_READ, context, inTransaction);
505528
this.connectionState.resetValue(SAVEPOINT_SUPPORT, context, inTransaction);
506529
this.protoDescriptors = null;
@@ -542,7 +565,7 @@ public boolean isClosed() {
542565
}
543566

544567
private <T> T getConnectionPropertyValue(
545-
com.google.cloud.spanner.connection.ConnectionProperty<T> property) {
568+
ConnectionProperty<T> property) {
546569
return this.connectionState.getValue(property).getValue();
547570
}
548571

@@ -563,8 +586,8 @@ private <T> void setConnectionPropertyValue(
563586
* Sets a connection property value only for the duration of the current transaction. The effects
564587
* of this will be undone once the transaction ends, regardless whether the transaction is
565588
* committed or rolled back. 'Local' properties are supported for both {@link
566-
* com.google.cloud.spanner.connection.ConnectionState.Type#TRANSACTIONAL} and {@link
567-
* com.google.cloud.spanner.connection.ConnectionState.Type#NON_TRANSACTIONAL} connection states.
589+
* Type#TRANSACTIONAL} and {@link
590+
* Type#NON_TRANSACTIONAL} connection states.
568591
*
569592
* <p>NOTE: This feature is not yet exposed in the public API.
570593
*/

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import static com.google.cloud.spanner.connection.ConnectionProperties.RETURN_COMMIT_STATS;
4747
import static com.google.cloud.spanner.connection.ConnectionProperties.ROUTE_TO_LEADER;
4848
import static com.google.cloud.spanner.connection.ConnectionProperties.TRACING_PREFIX;
49+
import static com.google.cloud.spanner.connection.ConnectionProperties.CLIENT_ID;
4950
import static com.google.cloud.spanner.connection.ConnectionProperties.TRACK_CONNECTION_LEAKS;
5051
import static com.google.cloud.spanner.connection.ConnectionProperties.TRACK_SESSION_LEAKS;
5152
import static com.google.cloud.spanner.connection.ConnectionProperties.USER_AGENT;
@@ -539,6 +540,11 @@ public Builder setTracingPrefix(String tracingPrefix) {
539540
return this;
540541
}
541542

543+
public Builder setClientId(String clientId) {
544+
setConnectionPropertyValue(CLIENT_ID, clientId);
545+
return this;
546+
}
547+
542548
/** @return the {@link ConnectionOptions} */
543549
public ConnectionOptions build() {
544550
Preconditions.checkState(this.uri != null, "Connection URI is required");
@@ -603,7 +609,6 @@ private ConnectionOptions(Builder builder) {
603609

604610
// Create the initial connection state from the parsed properties in the connection URL.
605611
this.initialConnectionState = new ConnectionState(connectionPropertyValues);
606-
607612
// Check that at most one of credentials location, encoded credentials, credentials provider and
608613
// OUAuth token has been specified in the connection URI.
609614
Preconditions.checkArgument(

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ public class ConnectionProperties {
135135

136136
private static final Boolean[] BOOLEANS = new Boolean[] {Boolean.TRUE, Boolean.FALSE};
137137

138+
static final ConnectionProperty<String> CLIENT_ID =
139+
create(
140+
"client_id",
141+
"Client Id to use for this connection. Can only be set at the start up time",
142+
null,
143+
StringValueConverter.INSTANCE,
144+
Context.STARTUP);
145+
138146
static final ConnectionProperty<ConnectionState.Type> CONNECTION_STATE_TYPE =
139147
create(
140148
"connection_state_type",

google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerImplTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,40 @@ public void testCreateInstanceAdminClient_whenMockAdminSettings_assertException(
358358
assertNotNull(instanceAdminClient);
359359
}
360360

361+
@Test
362+
public void testGetDatabaseClient_when_clientId_is_not_null() {
363+
String dbName =
364+
String.format("projects/p1/instances/i1/databases/%s", UUID.randomUUID().toString());
365+
DatabaseId db = DatabaseId.of(dbName);
366+
367+
Mockito.when(spannerOptions.getTransportOptions())
368+
.thenReturn(GrpcTransportOptions.newBuilder().build());
369+
Mockito.when(spannerOptions.getSessionPoolOptions())
370+
.thenReturn(SessionPoolOptions.newBuilder().setMinSessions(0).build());
371+
Mockito.when(spannerOptions.getDatabaseRole()).thenReturn("role");
372+
373+
DatabaseClientImpl databaseClient = (DatabaseClientImpl) impl.getDatabaseClient(db, "clientId-1");
374+
assertThat(databaseClient.clientId).isEqualTo("clientId-1");
375+
376+
// Get same db client again.
377+
DatabaseClientImpl databaseClient1 = (DatabaseClientImpl) impl.getDatabaseClient(db, "clientId-1");
378+
assertThat(databaseClient1.clientId).isEqualTo(databaseClient.clientId);
379+
380+
// Get a db client for a different database.
381+
String dbName2 =
382+
String.format("projects/p1/instances/i1/databases/%s", UUID.randomUUID().toString());
383+
DatabaseId db2 = DatabaseId.of(dbName2);
384+
DatabaseClientImpl databaseClient2 = (DatabaseClientImpl) impl.getDatabaseClient(db2, "clientId-1");
385+
assertThat(databaseClient2.clientId).isEqualTo("clientId-1");
386+
387+
// Getting a new database client for an invalidated database should use the same client id.
388+
databaseClient.pool.setResourceNotFoundException(
389+
new DatabaseNotFoundException(DoNotConstructDirectly.ALLOWED, "not found", null, null));
390+
DatabaseClientImpl revalidated = (DatabaseClientImpl) impl.getDatabaseClient(db, "clientId-1");
391+
assertThat(revalidated).isNotSameInstanceAs(databaseClient);
392+
assertThat(revalidated.clientId).isEqualTo(databaseClient.clientId);
393+
}
394+
361395
private void closeSpannerAndIncludeStacktrace(Spanner spanner) {
362396
spanner.close();
363397
}

0 commit comments

Comments
 (0)