Repository: googleapis/java-spanner-jdbc
Pull request #1884 (2025-01-08 06:09:12)

feat: support for spanner external host
Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:
- [x] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/java-spanner-jdbc/issues/new/choose) before writing your code!  That way we can discuss the change, evaluate designs, and agree on the general idea
- [x] Ensure the tests and linter pass
- [x] Code coverage does not decrease (if any source code was changed)
- [x] Appropriate docs were updated (if necessary)

Fixes #<issue_number_goes_here> ☕️

If you write sample code, please follow the [samples format](
https://github.com/GoogleCloudPlatform/java-docs-samples/blob/main/SAMPLE_FORMAT.md).



Patch:
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java
index 6bb7028..3fb7010 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java
@@ -25,6 +25,7 @@ import com.google.cloud.spanner.connection.ConnectionOptions;
 import com.google.cloud.spanner.connection.ConnectionOptionsHelper;
 import com.google.cloud.spanner.connection.ConnectionPropertiesHelper;
 import com.google.cloud.spanner.connection.ConnectionProperty;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.rpc.Code;
 import io.opentelemetry.api.OpenTelemetry;
 import java.sql.Connection;
@@ -145,6 +146,11 @@ public class JdbcDriver implements Driver {
   private static final String JDBC_URL_FORMAT =
       "jdbc:" + ConnectionOptions.Builder.SPANNER_URI_FORMAT;
   private static final Pattern URL_PATTERN = Pattern.compile(JDBC_URL_FORMAT);
+  private static final String JDBC_EXTERNAL_HOST_FORMAT =
+      "jdbc:" + ConnectionOptions.Builder.EXTERNAL_HOST_FORMAT;
+
+  @VisibleForTesting
+  static final Pattern EXTERNAL_HOST_URL_PATTERN = Pattern.compile(JDBC_EXTERNAL_HOST_FORMAT);
 
   @InternalApi
   public static String getClientLibToken() {
@@ -213,7 +219,8 @@ public class JdbcDriver implements Driver {
     if (url != null && url.startsWith("jdbc:cloudspanner")) {
       try {
         Matcher matcher = URL_PATTERN.matcher(url);
-        if (matcher.matches()) {
+        Matcher matcherExternalHost = EXTERNAL_HOST_URL_PATTERN.matcher(url);
+        if (matcher.matches() || matcherExternalHost.matches()) {
           // strip 'jdbc:' from the URL, add any extra properties and pass on to the generic
           // Connection API
           String connectionUri = appendPropertiesToUrl(url.substring(5), info);


Test patch:
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcDriverTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcDriverTest.java
index 020848e..b4ae04f 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcDriverTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcDriverTest.java
@@ -16,8 +16,10 @@
 
 package com.google.cloud.spanner.jdbc;
 
+import static com.google.cloud.spanner.jdbc.JdbcDriver.EXTERNAL_HOST_URL_PATTERN;
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -47,6 +49,7 @@ import java.sql.SQLException;
 import java.util.Collection;
 import java.util.Objects;
 import java.util.Properties;
+import java.util.regex.Matcher;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -211,4 +214,32 @@ public class JdbcDriverTest {
       assertThat(jdbc.getCode()).isEqualTo(Code.INVALID_ARGUMENT);
     }
   }
+
+  @Test
+  public void testJdbcExternalHostFormat() {
+    Matcher matcherWithoutInstance =
+        EXTERNAL_HOST_URL_PATTERN.matcher("jdbc:cloudspanner://localhost:15000/databases/test-db");
+    assertTrue(matcherWithoutInstance.matches());
+    assertEquals("test-db", matcherWithoutInstance.group("DATABASEGROUP"));
+    Matcher matcherWithProperty =
+        EXTERNAL_HOST_URL_PATTERN.matcher(
+            "jdbc:cloudspanner://localhost:15000/instances/default/databases/singers-db?usePlainText=true");
+    assertTrue(matcherWithProperty.matches());
+    assertEquals("default", matcherWithProperty.group("INSTANCEGROUP"));
+    assertEquals("singers-db", matcherWithProperty.group("DATABASEGROUP"));
+    Matcher matcherWithoutPort =
+        EXTERNAL_HOST_URL_PATTERN.matcher(
+            "jdbc:cloudspanner://localhost/instances/default/databases/test-db");
+    assertTrue(matcherWithoutPort.matches());
+    assertEquals("default", matcherWithoutPort.group("INSTANCEGROUP"));
+    assertEquals("test-db", matcherWithoutPort.group("DATABASEGROUP"));
+    Matcher matcherWithProject =
+        EXTERNAL_HOST_URL_PATTERN.matcher(
+            "jdbc:cloudspanner://localhost:15000/projects/default/instances/default/databases/singers-db");
+    assertFalse(matcherWithProject.matches());
+    Matcher matcherWithoutHost =
+        EXTERNAL_HOST_URL_PATTERN.matcher(
+            "jdbc:cloudspanner:/instances/default/databases/singers-db");
+    assertFalse(matcherWithoutHost.matches());
+  }
 }