Skip to content

Commit c9be29c

Browse files
authored
feat: add endpoint connection URL property (#2969)
Adds an 'endpoint' connection URL property for the Connection API. This property can be used instead of adding the endpoint to the host group part of the Connection URL, which again removes the need to actually change the connection URL when connecting to for example the emulator from the JDBC driver. The latter can instead just add the endpoint to the Properties set that is given to the JDBC driver.
1 parent 44c6a26 commit c9be29c

File tree

2 files changed

+87
-7
lines changed

2 files changed

+87
-7
lines changed

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

+30-7
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ public String[] getValidValues() {
175175
private static final String DEFAULT_MIN_SESSIONS = null;
176176
private static final String DEFAULT_MAX_SESSIONS = null;
177177
private static final String DEFAULT_NUM_CHANNELS = null;
178+
static final String DEFAULT_ENDPOINT = null;
178179
private static final String DEFAULT_CHANNEL_PROVIDER = null;
179180
private static final String DEFAULT_DATABASE_ROLE = null;
180181
private static final String DEFAULT_USER_AGENT = null;
@@ -234,6 +235,8 @@ public String[] getValidValues() {
234235
public static final String MAX_SESSIONS_PROPERTY_NAME = "maxSessions";
235236
/** Name of the 'numChannels' connection property. */
236237
public static final String NUM_CHANNELS_PROPERTY_NAME = "numChannels";
238+
/** Name of the 'endpoint' connection property. */
239+
public static final String ENDPOINT_PROPERTY_NAME = "endpoint";
237240
/** Name of the 'channelProvider' connection property. */
238241
public static final String CHANNEL_PROVIDER_PROPERTY_NAME = "channelProvider";
239242

@@ -332,6 +335,12 @@ private static String generateGuardedConnectionPropertyError(
332335
ConnectionProperty.createStringProperty(
333336
NUM_CHANNELS_PROPERTY_NAME,
334337
"The number of gRPC channels to use to communicate with Cloud Spanner. The default is 4."),
338+
ConnectionProperty.createStringProperty(
339+
ENDPOINT_PROPERTY_NAME,
340+
"The endpoint that the JDBC driver should connect to. "
341+
+ "The default is the default Spanner production endpoint when autoConfigEmulator=false, "
342+
+ "and the default Spanner emulator endpoint (localhost:9010) when autoConfigEmulator=true. "
343+
+ "This property takes precedence over any host name at the start of the connection URL."),
335344
ConnectionProperty.createStringProperty(
336345
CHANNEL_PROVIDER_PROPERTY_NAME,
337346
"The name of the channel provider class. The name must reference an implementation of ExternalChannelProvider. If this property is not set, the connection will use the default grpc channel provider."),
@@ -738,7 +747,9 @@ private ConnectionOptions(Builder builder) {
738747
this.autoConfigEmulator = parseAutoConfigEmulator(this.uri);
739748
this.dialect = parseDialect(this.uri);
740749
this.usePlainText = this.autoConfigEmulator || parseUsePlainText(this.uri);
741-
this.host = determineHost(matcher, autoConfigEmulator, usePlainText, System.getenv());
750+
this.host =
751+
determineHost(
752+
matcher, parseEndpoint(this.uri), autoConfigEmulator, usePlainText, System.getenv());
742753
this.rpcPriority = parseRPCPriority(this.uri);
743754
this.delayTransactionStartUntilFirstWrite = parseDelayTransactionStartUntilFirstWrite(this.uri);
744755
this.trackSessionLeaks = parseTrackSessionLeaks(this.uri);
@@ -829,10 +840,12 @@ private ConnectionOptions(Builder builder) {
829840
@VisibleForTesting
830841
static String determineHost(
831842
Matcher matcher,
843+
String endpoint,
832844
boolean autoConfigEmulator,
833845
boolean usePlainText,
834846
Map<String, String> environment) {
835-
if (matcher.group(Builder.HOST_GROUP) == null) {
847+
String host;
848+
if (Objects.equals(endpoint, DEFAULT_ENDPOINT) && matcher.group(Builder.HOST_GROUP) == null) {
836849
if (autoConfigEmulator) {
837850
if (Strings.isNullOrEmpty(environment.get(SPANNER_EMULATOR_HOST_ENV_VAR))) {
838851
return DEFAULT_EMULATOR_HOST;
@@ -842,13 +855,18 @@ static String determineHost(
842855
} else {
843856
return DEFAULT_HOST;
844857
}
858+
} else if (!Objects.equals(endpoint, DEFAULT_ENDPOINT)) {
859+
// Add '//' at the start of the endpoint to conform to the standard URL specification.
860+
host = "//" + endpoint;
845861
} else {
846-
if (usePlainText) {
847-
return PLAIN_TEXT_PROTOCOL + matcher.group(Builder.HOST_GROUP);
848-
} else {
849-
return HOST_PROTOCOL + matcher.group(Builder.HOST_GROUP);
850-
}
862+
// The leading '//' is already included in the regex for the connection URL, so we don't need
863+
// to add the leading '//' to the host name here.
864+
host = matcher.group(Builder.HOST_GROUP);
851865
}
866+
if (usePlainText) {
867+
return PLAIN_TEXT_PROTOCOL + host;
868+
}
869+
return HOST_PROTOCOL + host;
852870
}
853871

854872
private static Integer parseIntegerProperty(String propertyName, String value) {
@@ -1013,6 +1031,11 @@ static String parseNumChannels(String uri) {
10131031
return value != null ? value : DEFAULT_NUM_CHANNELS;
10141032
}
10151033

1034+
private static String parseEndpoint(String uri) {
1035+
String value = parseUriProperty(uri, ENDPOINT_PROPERTY_NAME);
1036+
return value != null ? value : DEFAULT_ENDPOINT;
1037+
}
1038+
10161039
@VisibleForTesting
10171040
static String parseChannelProvider(String uri) {
10181041
String value = parseUriProperty(uri, CHANNEL_PROVIDER_PROPERTY_NAME);

google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java

+57
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.cloud.spanner.connection;
1818

1919
import static com.google.cloud.spanner.connection.ConnectionOptions.Builder.SPANNER_URI_PATTERN;
20+
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENDPOINT;
2021
import static com.google.cloud.spanner.connection.ConnectionOptions.determineHost;
2122
import static com.google.common.truth.Truth.assertThat;
2223
import static org.junit.Assert.assertEquals;
@@ -172,41 +173,47 @@ public void testDetermineHost() {
172173
DEFAULT_HOST,
173174
determineHost(
174175
matcherWithoutHost,
176+
DEFAULT_ENDPOINT,
175177
/* autoConfigEmulator= */ false,
176178
/* usePlainText= */ false,
177179
ImmutableMap.of()));
178180
assertEquals(
179181
DEFAULT_HOST,
180182
determineHost(
181183
matcherWithoutHost,
184+
DEFAULT_ENDPOINT,
182185
/* autoConfigEmulator= */ false,
183186
/* usePlainText= */ false,
184187
ImmutableMap.of("FOO", "bar")));
185188
assertEquals(
186189
"http://localhost:9010",
187190
determineHost(
188191
matcherWithoutHost,
192+
DEFAULT_ENDPOINT,
189193
/* autoConfigEmulator= */ true,
190194
/* usePlainText= */ false,
191195
ImmutableMap.of()));
192196
assertEquals(
193197
"http://localhost:9011",
194198
determineHost(
195199
matcherWithoutHost,
200+
DEFAULT_ENDPOINT,
196201
/* autoConfigEmulator= */ true,
197202
/* usePlainText= */ false,
198203
ImmutableMap.of("SPANNER_EMULATOR_HOST", "localhost:9011")));
199204
assertEquals(
200205
"http://localhost:9010",
201206
determineHost(
202207
matcherWithoutHost,
208+
DEFAULT_ENDPOINT,
203209
/* autoConfigEmulator= */ true,
204210
/* usePlainText= */ true,
205211
ImmutableMap.of()));
206212
assertEquals(
207213
"http://localhost:9011",
208214
determineHost(
209215
matcherWithoutHost,
216+
DEFAULT_ENDPOINT,
210217
/* autoConfigEmulator= */ true,
211218
/* usePlainText= */ true,
212219
ImmutableMap.of("SPANNER_EMULATOR_HOST", "localhost:9011")));
@@ -216,44 +223,80 @@ public void testDetermineHost() {
216223
"https://custom.host.domain:1234",
217224
determineHost(
218225
matcherWithHost,
226+
DEFAULT_ENDPOINT,
219227
/* autoConfigEmulator= */ false,
220228
/* usePlainText= */ false,
221229
ImmutableMap.of()));
222230
assertEquals(
223231
"http://custom.host.domain:1234",
224232
determineHost(
225233
matcherWithHost,
234+
DEFAULT_ENDPOINT,
226235
/* autoConfigEmulator= */ false,
227236
/* usePlainText= */ true,
228237
ImmutableMap.of()));
229238
assertEquals(
230239
"http://custom.host.domain:1234",
231240
determineHost(
232241
matcherWithHost,
242+
DEFAULT_ENDPOINT,
233243
/* autoConfigEmulator= */ false,
234244
/* usePlainText= */ true,
235245
ImmutableMap.of()));
236246
assertEquals(
237247
"https://custom.host.domain:1234",
238248
determineHost(
239249
matcherWithHost,
250+
DEFAULT_ENDPOINT,
240251
/* autoConfigEmulator= */ true,
241252
/* usePlainText= */ false,
242253
ImmutableMap.of()));
243254
assertEquals(
244255
"http://custom.host.domain:1234",
245256
determineHost(
246257
matcherWithHost,
258+
DEFAULT_ENDPOINT,
247259
/* autoConfigEmulator= */ false,
248260
/* usePlainText= */ true,
249261
ImmutableMap.of("SPANNER_EMULATOR_HOST", "localhost:9011")));
250262
assertEquals(
251263
"https://custom.host.domain:1234",
252264
determineHost(
253265
matcherWithHost,
266+
DEFAULT_ENDPOINT,
254267
/* autoConfigEmulator= */ true,
255268
/* usePlainText= */ false,
256269
ImmutableMap.of("SPANNER_EMULATOR_HOST", "localhost:9011")));
270+
271+
// The 'endpoint' connection URL property can also be used to connect to the emulator.
272+
// Using this property is sometimes easier than adding the URL to the host part of the
273+
// connection string, for example because it can be added to the Properties object that
274+
// is used by JDBC.
275+
assertEquals(
276+
"http://localhost:9010",
277+
determineHost(
278+
matcherWithoutHost,
279+
"localhost:9010",
280+
/* autoConfigEmulator= */ false,
281+
/* usePlainText= */ true,
282+
ImmutableMap.of()));
283+
// A value for the 'endpoint' connection property overrides any value in the host group.
284+
assertEquals(
285+
"https://my.endpoint:1234",
286+
determineHost(
287+
matcherWithHost,
288+
"my.endpoint:1234",
289+
/* autoConfigEmulator= */ false,
290+
/* usePlainText= */ false,
291+
ImmutableMap.of("SPANNER_EMULATOR_HOST", "localhost:9011")));
292+
assertEquals(
293+
"http://my.endpoint.local:1234",
294+
determineHost(
295+
matcherWithHost,
296+
"my.endpoint.local:1234",
297+
/* autoConfigEmulator= */ false,
298+
/* usePlainText= */ true,
299+
ImmutableMap.of()));
257300
}
258301

259302
@Test
@@ -291,6 +334,20 @@ public void testBuildWithAutoConfigEmulatorAndHost() {
291334
assertTrue(options.isUsePlainText());
292335
}
293336

337+
@Test
338+
public void testBuildWithAutoConfigEmulatorAndEndpoint() {
339+
ConnectionOptions.Builder builder = ConnectionOptions.newBuilder();
340+
builder.setUri(
341+
"cloudspanner:/projects/test-project-123/instances/test-instance-123/databases/test-database-123?autoConfigEmulator=true;endpoint=central-emulator.local:8080");
342+
ConnectionOptions options = builder.build();
343+
assertEquals("http://central-emulator.local:8080", options.getHost());
344+
assertEquals("test-project-123", options.getProjectId());
345+
assertEquals("test-instance-123", options.getInstanceId());
346+
assertEquals("test-database-123", options.getDatabaseName());
347+
assertEquals(NoCredentials.getInstance(), options.getCredentials());
348+
assertTrue(options.isUsePlainText());
349+
}
350+
294351
@Test
295352
public void testBuildWithDefaultProjectPlaceholder() {
296353
ConnectionOptions.Builder builder = ConnectionOptions.newBuilder();

0 commit comments

Comments
 (0)