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 endpoint connection URL property #2969

Merged
merged 1 commit into from
Apr 2, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ public String[] getValidValues() {
private static final String DEFAULT_MIN_SESSIONS = null;
private static final String DEFAULT_MAX_SESSIONS = null;
private static final String DEFAULT_NUM_CHANNELS = null;
static final String DEFAULT_ENDPOINT = null;
private static final String DEFAULT_CHANNEL_PROVIDER = null;
private static final String DEFAULT_DATABASE_ROLE = null;
private static final String DEFAULT_USER_AGENT = null;
Expand Down Expand Up @@ -234,6 +235,8 @@ public String[] getValidValues() {
public static final String MAX_SESSIONS_PROPERTY_NAME = "maxSessions";
/** Name of the 'numChannels' connection property. */
public static final String NUM_CHANNELS_PROPERTY_NAME = "numChannels";
/** Name of the 'endpoint' connection property. */
public static final String ENDPOINT_PROPERTY_NAME = "endpoint";
/** Name of the 'channelProvider' connection property. */
public static final String CHANNEL_PROVIDER_PROPERTY_NAME = "channelProvider";

Expand Down Expand Up @@ -332,6 +335,12 @@ private static String generateGuardedConnectionPropertyError(
ConnectionProperty.createStringProperty(
NUM_CHANNELS_PROPERTY_NAME,
"The number of gRPC channels to use to communicate with Cloud Spanner. The default is 4."),
ConnectionProperty.createStringProperty(
ENDPOINT_PROPERTY_NAME,
"The endpoint that the JDBC driver should connect to. "
+ "The default is the default Spanner production endpoint when autoConfigEmulator=false, "
+ "and the default Spanner emulator endpoint (localhost:9010) when autoConfigEmulator=true. "
+ "This property takes precedence over any host name at the start of the connection URL."),
ConnectionProperty.createStringProperty(
CHANNEL_PROVIDER_PROPERTY_NAME,
"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."),
Expand Down Expand Up @@ -738,7 +747,9 @@ private ConnectionOptions(Builder builder) {
this.autoConfigEmulator = parseAutoConfigEmulator(this.uri);
this.dialect = parseDialect(this.uri);
this.usePlainText = this.autoConfigEmulator || parseUsePlainText(this.uri);
this.host = determineHost(matcher, autoConfigEmulator, usePlainText, System.getenv());
this.host =
determineHost(
matcher, parseEndpoint(this.uri), autoConfigEmulator, usePlainText, System.getenv());
this.rpcPriority = parseRPCPriority(this.uri);
this.delayTransactionStartUntilFirstWrite = parseDelayTransactionStartUntilFirstWrite(this.uri);
this.trackSessionLeaks = parseTrackSessionLeaks(this.uri);
Expand Down Expand Up @@ -829,10 +840,12 @@ private ConnectionOptions(Builder builder) {
@VisibleForTesting
static String determineHost(
Matcher matcher,
String endpoint,
boolean autoConfigEmulator,
boolean usePlainText,
Map<String, String> environment) {
if (matcher.group(Builder.HOST_GROUP) == null) {
String host;
if (Objects.equals(endpoint, DEFAULT_ENDPOINT) && matcher.group(Builder.HOST_GROUP) == null) {
if (autoConfigEmulator) {
if (Strings.isNullOrEmpty(environment.get(SPANNER_EMULATOR_HOST_ENV_VAR))) {
return DEFAULT_EMULATOR_HOST;
Expand All @@ -842,13 +855,18 @@ static String determineHost(
} else {
return DEFAULT_HOST;
}
} else if (!Objects.equals(endpoint, DEFAULT_ENDPOINT)) {
// Add '//' at the start of the endpoint to conform to the standard URL specification.
host = "//" + endpoint;
} else {
if (usePlainText) {
return PLAIN_TEXT_PROTOCOL + matcher.group(Builder.HOST_GROUP);
} else {
return HOST_PROTOCOL + matcher.group(Builder.HOST_GROUP);
}
// The leading '//' is already included in the regex for the connection URL, so we don't need
// to add the leading '//' to the host name here.
host = matcher.group(Builder.HOST_GROUP);
}
if (usePlainText) {
return PLAIN_TEXT_PROTOCOL + host;
}
return HOST_PROTOCOL + host;
}

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

private static String parseEndpoint(String uri) {
String value = parseUriProperty(uri, ENDPOINT_PROPERTY_NAME);
return value != null ? value : DEFAULT_ENDPOINT;
}

@VisibleForTesting
static String parseChannelProvider(String uri) {
String value = parseUriProperty(uri, CHANNEL_PROVIDER_PROPERTY_NAME);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.google.cloud.spanner.connection;

import static com.google.cloud.spanner.connection.ConnectionOptions.Builder.SPANNER_URI_PATTERN;
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENDPOINT;
import static com.google.cloud.spanner.connection.ConnectionOptions.determineHost;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
Expand Down Expand Up @@ -172,41 +173,47 @@ public void testDetermineHost() {
DEFAULT_HOST,
determineHost(
matcherWithoutHost,
DEFAULT_ENDPOINT,
/* autoConfigEmulator= */ false,
/* usePlainText= */ false,
ImmutableMap.of()));
assertEquals(
DEFAULT_HOST,
determineHost(
matcherWithoutHost,
DEFAULT_ENDPOINT,
/* autoConfigEmulator= */ false,
/* usePlainText= */ false,
ImmutableMap.of("FOO", "bar")));
assertEquals(
"http://localhost:9010",
determineHost(
matcherWithoutHost,
DEFAULT_ENDPOINT,
/* autoConfigEmulator= */ true,
/* usePlainText= */ false,
ImmutableMap.of()));
assertEquals(
"http://localhost:9011",
determineHost(
matcherWithoutHost,
DEFAULT_ENDPOINT,
/* autoConfigEmulator= */ true,
/* usePlainText= */ false,
ImmutableMap.of("SPANNER_EMULATOR_HOST", "localhost:9011")));
assertEquals(
"http://localhost:9010",
determineHost(
matcherWithoutHost,
DEFAULT_ENDPOINT,
/* autoConfigEmulator= */ true,
/* usePlainText= */ true,
ImmutableMap.of()));
assertEquals(
"http://localhost:9011",
determineHost(
matcherWithoutHost,
DEFAULT_ENDPOINT,
/* autoConfigEmulator= */ true,
/* usePlainText= */ true,
ImmutableMap.of("SPANNER_EMULATOR_HOST", "localhost:9011")));
Expand All @@ -216,44 +223,80 @@ public void testDetermineHost() {
"https://custom.host.domain:1234",
determineHost(
matcherWithHost,
DEFAULT_ENDPOINT,
/* autoConfigEmulator= */ false,
/* usePlainText= */ false,
ImmutableMap.of()));
assertEquals(
"http://custom.host.domain:1234",
determineHost(
matcherWithHost,
DEFAULT_ENDPOINT,
/* autoConfigEmulator= */ false,
/* usePlainText= */ true,
ImmutableMap.of()));
assertEquals(
"http://custom.host.domain:1234",
determineHost(
matcherWithHost,
DEFAULT_ENDPOINT,
/* autoConfigEmulator= */ false,
/* usePlainText= */ true,
ImmutableMap.of()));
assertEquals(
"https://custom.host.domain:1234",
determineHost(
matcherWithHost,
DEFAULT_ENDPOINT,
/* autoConfigEmulator= */ true,
/* usePlainText= */ false,
ImmutableMap.of()));
assertEquals(
"http://custom.host.domain:1234",
determineHost(
matcherWithHost,
DEFAULT_ENDPOINT,
/* autoConfigEmulator= */ false,
/* usePlainText= */ true,
ImmutableMap.of("SPANNER_EMULATOR_HOST", "localhost:9011")));
assertEquals(
"https://custom.host.domain:1234",
determineHost(
matcherWithHost,
DEFAULT_ENDPOINT,
/* autoConfigEmulator= */ true,
/* usePlainText= */ false,
ImmutableMap.of("SPANNER_EMULATOR_HOST", "localhost:9011")));

// The 'endpoint' connection URL property can also be used to connect to the emulator.
// Using this property is sometimes easier than adding the URL to the host part of the
// connection string, for example because it can be added to the Properties object that
// is used by JDBC.
assertEquals(
"http://localhost:9010",
determineHost(
matcherWithoutHost,
"localhost:9010",
/* autoConfigEmulator= */ false,
/* usePlainText= */ true,
ImmutableMap.of()));
// A value for the 'endpoint' connection property overrides any value in the host group.
assertEquals(
"https://my.endpoint:1234",
determineHost(
matcherWithHost,
"my.endpoint:1234",
/* autoConfigEmulator= */ false,
/* usePlainText= */ false,
ImmutableMap.of("SPANNER_EMULATOR_HOST", "localhost:9011")));
assertEquals(
"http://my.endpoint.local:1234",
determineHost(
matcherWithHost,
"my.endpoint.local:1234",
/* autoConfigEmulator= */ false,
/* usePlainText= */ true,
ImmutableMap.of()));
}

@Test
Expand Down Expand Up @@ -291,6 +334,20 @@ public void testBuildWithAutoConfigEmulatorAndHost() {
assertTrue(options.isUsePlainText());
}

@Test
public void testBuildWithAutoConfigEmulatorAndEndpoint() {
ConnectionOptions.Builder builder = ConnectionOptions.newBuilder();
builder.setUri(
"cloudspanner:/projects/test-project-123/instances/test-instance-123/databases/test-database-123?autoConfigEmulator=true;endpoint=central-emulator.local:8080");
ConnectionOptions options = builder.build();
assertEquals("http://central-emulator.local:8080", options.getHost());
assertEquals("test-project-123", options.getProjectId());
assertEquals("test-instance-123", options.getInstanceId());
assertEquals("test-database-123", options.getDatabaseName());
assertEquals(NoCredentials.getInstance(), options.getCredentials());
assertTrue(options.isUsePlainText());
}

@Test
public void testBuildWithDefaultProjectPlaceholder() {
ConnectionOptions.Builder builder = ConnectionOptions.newBuilder();
Expand Down