Skip to content

Commit f6a8ba6

Browse files
authored
feat: allow lenient mode for connection properties (#671)
* feat: allow lenient mode for connection properties Some applications automatically add additional properties to connection strings that are unknown to the Spanner Connection API (and thereby also the Spanner JDBC driver). This causes the connection attempt to fail. This change allows a user to specify 'lenient' mode where unknown properties only generate a warning instead of an error. Fixes dropwizard/dropwizard#3461 Fixes googleapis/google-cloud-java#6671 Fixes googleapis/java-spanner-jdbc#283 * fix: add credentials to prevent tests from trying to use env credentials
1 parent 3f9f74a commit f6a8ba6

File tree

2 files changed

+71
-6
lines changed

2 files changed

+71
-6
lines changed

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

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.util.Set;
4343
import java.util.regex.Matcher;
4444
import java.util.regex.Pattern;
45+
import javax.annotation.Nullable;
4546

4647
/**
4748
* Internal connection API for Google Cloud Spanner. This class may introduce breaking changes
@@ -152,6 +153,7 @@ public String[] getValidValues() {
152153
private static final String DEFAULT_NUM_CHANNELS = null;
153154
private static final String DEFAULT_USER_AGENT = null;
154155
private static final String DEFAULT_OPTIMIZER_VERSION = "";
156+
private static final boolean DEFAULT_LENIENT = false;
155157

156158
private static final String PLAIN_TEXT_PROTOCOL = "http:";
157159
private static final String HOST_PROTOCOL = "https:";
@@ -176,6 +178,8 @@ public String[] getValidValues() {
176178
private static final String USER_AGENT_PROPERTY_NAME = "userAgent";
177179
/** Query optimizer version to use for a connection. */
178180
private static final String OPTIMIZER_VERSION_PROPERTY_NAME = "optimizerVersion";
181+
/** Name of the 'lenientMode' connection property. */
182+
public static final String LENIENT_PROPERTY_NAME = "lenient";
179183

180184
/** All valid connection properties. */
181185
public static final Set<ConnectionProperty> VALID_PROPERTIES =
@@ -212,7 +216,11 @@ public String[] getValidValues() {
212216
"The custom user-agent property name to use when communicating with Cloud Spanner. This property is intended for internal library usage, and should not be set by applications."),
213217
ConnectionProperty.createStringProperty(
214218
OPTIMIZER_VERSION_PROPERTY_NAME,
215-
"Sets the default query optimizer version to use for this connection."))));
219+
"Sets the default query optimizer version to use for this connection."),
220+
ConnectionProperty.createBooleanProperty(
221+
LENIENT_PROPERTY_NAME,
222+
"Silently ignore unknown properties in the connection string/properties (true/false)",
223+
DEFAULT_LENIENT))));
216224

217225
private static final Set<ConnectionProperty> INTERNAL_PROPERTIES =
218226
Collections.unmodifiableSet(
@@ -416,6 +424,7 @@ public static Builder newBuilder() {
416424
}
417425

418426
private final String uri;
427+
private final String warnings;
419428
private final String credentialsUrl;
420429
private final String oauthToken;
421430
private final Credentials fixedCredentials;
@@ -441,7 +450,7 @@ private ConnectionOptions(Builder builder) {
441450
Matcher matcher = Builder.SPANNER_URI_PATTERN.matcher(builder.uri);
442451
Preconditions.checkArgument(
443452
matcher.find(), String.format("Invalid connection URI specified: %s", builder.uri));
444-
checkValidProperties(builder.uri);
453+
this.warnings = checkValidProperties(builder.uri);
445454

446455
this.uri = builder.uri;
447456
this.sessionPoolOptions = builder.sessionPoolOptions;
@@ -574,6 +583,12 @@ static String parseOptimizerVersion(String uri) {
574583
return value != null ? value : DEFAULT_OPTIMIZER_VERSION;
575584
}
576585

586+
@VisibleForTesting
587+
static boolean parseLenient(String uri) {
588+
String value = parseUriProperty(uri, LENIENT_PROPERTY_NAME);
589+
return value != null ? Boolean.valueOf(value) : DEFAULT_LENIENT;
590+
}
591+
577592
@VisibleForTesting
578593
static String parseUriProperty(String uri, String property) {
579594
Pattern pattern = Pattern.compile(String.format("(?is)(?:;|\\?)%s=(.*?)(?:;|$)", property));
@@ -586,9 +601,10 @@ static String parseUriProperty(String uri, String property) {
586601

587602
/** Check that only valid properties have been specified. */
588603
@VisibleForTesting
589-
static void checkValidProperties(String uri) {
604+
static String checkValidProperties(String uri) {
590605
String invalidProperties = "";
591606
List<String> properties = parseProperties(uri);
607+
boolean lenient = parseLenient(uri);
592608
for (String property : properties) {
593609
if (!INTERNAL_VALID_PROPERTIES.contains(ConnectionProperty.createEmptyProperty(property))) {
594610
if (invalidProperties.length() > 0) {
@@ -597,9 +613,17 @@ static void checkValidProperties(String uri) {
597613
invalidProperties = invalidProperties + property;
598614
}
599615
}
600-
Preconditions.checkArgument(
601-
invalidProperties.isEmpty(),
602-
"Invalid properties found in connection URI: " + invalidProperties.toString());
616+
if (lenient) {
617+
return String.format(
618+
"Invalid properties found in connection URI: %s", invalidProperties.toString());
619+
} else {
620+
Preconditions.checkArgument(
621+
invalidProperties.isEmpty(),
622+
String.format(
623+
"Invalid properties found in connection URI. Add lenient=true to the connection string to ignore unknown properties. Invalid properties: %s",
624+
invalidProperties.toString()));
625+
return null;
626+
}
603627
}
604628

605629
@VisibleForTesting
@@ -706,6 +730,12 @@ public boolean isRetryAbortsInternally() {
706730
return retryAbortsInternally;
707731
}
708732

733+
/** Any warnings that were generated while creating the {@link ConnectionOptions} instance. */
734+
@Nullable
735+
public String getWarnings() {
736+
return warnings;
737+
}
738+
709739
/** Use http instead of https. Only valid for (local) test servers. */
710740
boolean isUsePlainText() {
711741
return usePlainText;

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,4 +386,39 @@ public void testSetOAuthTokenAndCredentials() {
386386
assertThat(e.getMessage()).contains("Cannot specify both credentials and an OAuth token");
387387
}
388388
}
389+
390+
@Test
391+
public void testLenient() {
392+
ConnectionOptions options =
393+
ConnectionOptions.newBuilder()
394+
.setUri(
395+
"cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?lenient=true;foo=bar")
396+
.setCredentialsUrl(FILE_TEST_PATH)
397+
.build();
398+
assertThat(options.getWarnings()).isNotNull();
399+
assertThat(options.getWarnings()).contains("foo");
400+
assertThat(options.getWarnings()).doesNotContain("lenient");
401+
402+
options =
403+
ConnectionOptions.newBuilder()
404+
.setUri(
405+
"cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?bar=foo;lenient=true")
406+
.setCredentialsUrl(FILE_TEST_PATH)
407+
.build();
408+
assertThat(options.getWarnings()).isNotNull();
409+
assertThat(options.getWarnings()).contains("bar");
410+
assertThat(options.getWarnings()).doesNotContain("lenient");
411+
412+
try {
413+
options =
414+
ConnectionOptions.newBuilder()
415+
.setUri(
416+
"cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?bar=foo")
417+
.setCredentialsUrl(FILE_TEST_PATH)
418+
.build();
419+
fail("missing expected exception");
420+
} catch (IllegalArgumentException e) {
421+
assertThat(e.getMessage()).contains("bar");
422+
}
423+
}
389424
}

0 commit comments

Comments
 (0)