@@ -628,11 +628,16 @@ private Builder() {}
628628 public static final String SPANNER_URI_FORMAT =
629629 "(?:cloudspanner:)(?<HOSTGROUP>//[\\ w.-]+(?:\\ .[\\ w\\ .-]+)*[\\ w\\ -\\ ._~:/?#\\ [\\ ]@!\\ $&'\\ (\\ )\\ *\\ +,;=.]+)?/projects/(?<PROJECTGROUP>(([a-z]|[-.:]|[0-9])+|(DEFAULT_PROJECT_ID)))(/instances/(?<INSTANCEGROUP>([a-z]|[-]|[0-9])+)(/databases/(?<DATABASEGROUP>([a-z]|[-]|[_]|[0-9])+))?)?(?:[?|;].*)?" ;
630630
631+ public static final String EXTERNAL_HOST_FORMAT =
632+ "(?:cloudspanner:)(?<HOSTGROUP>//[\\ w.-]+(?::\\ d+)?)(/instances/(?<INSTANCEGROUP>[a-z0-9-]+))?(/databases/(?<DATABASEGROUP>[a-z0-9_-]+))(?:[?;].*)?" ;
631633 private static final String SPANNER_URI_REGEX = "(?is)^" + SPANNER_URI_FORMAT + "$" ;
632634
633635 @ VisibleForTesting
634636 static final Pattern SPANNER_URI_PATTERN = Pattern .compile (SPANNER_URI_REGEX );
635637
638+ @ VisibleForTesting
639+ static final Pattern EXTERNAL_HOST_PATTERN = Pattern .compile (EXTERNAL_HOST_FORMAT );
640+
636641 private static final String HOST_GROUP = "HOSTGROUP" ;
637642 private static final String PROJECT_GROUP = "PROJECTGROUP" ;
638643 private static final String INSTANCE_GROUP = "INSTANCEGROUP" ;
@@ -643,6 +648,10 @@ private boolean isValidUri(String uri) {
643648 return SPANNER_URI_PATTERN .matcher (uri ).matches ();
644649 }
645650
651+ private boolean isValidExternalHostUri (String uri ) {
652+ return EXTERNAL_HOST_PATTERN .matcher (uri ).matches ();
653+ }
654+
646655 /**
647656 * Sets the URI of the Cloud Spanner database to connect to. A connection URI must be specified
648657 * in this format:
@@ -700,9 +709,11 @@ private boolean isValidUri(String uri) {
700709 * @return this builder
701710 */
702711 public Builder setUri (String uri ) {
703- Preconditions .checkArgument (
704- isValidUri (uri ),
705- "The specified URI is not a valid Cloud Spanner connection URI. Please specify a URI in the format \" cloudspanner:[//host[:port]]/projects/project-id[/instances/instance-id[/databases/database-name]][\\ ?property-name=property-value[;property-name=property-value]*]?\" " );
712+ if (!isValidExternalHostUri (uri )) {
713+ Preconditions .checkArgument (
714+ isValidUri (uri ),
715+ "The specified URI is not a valid Cloud Spanner connection URI. Please specify a URI in the format \" cloudspanner:[//host[:port]]/projects/project-id[/instances/instance-id[/databases/database-name]][\\ ?property-name=property-value[;property-name=property-value]*]?\" " );
716+ }
706717 ConnectionPropertyValue <Boolean > value =
707718 cast (ConnectionProperties .parseValues (uri ).get (LENIENT .getKey ()));
708719 checkValidProperties (value != null && value .getValue (), uri );
@@ -829,7 +840,14 @@ public static Builder newBuilder() {
829840 private final SpannerOptionsConfigurator configurator ;
830841
831842 private ConnectionOptions (Builder builder ) {
832- Matcher matcher = Builder .SPANNER_URI_PATTERN .matcher (builder .uri );
843+ Matcher matcher ;
844+ boolean isExternalHost = false ;
845+ if (builder .isValidExternalHostUri (builder .uri )) {
846+ matcher = Builder .EXTERNAL_HOST_PATTERN .matcher (builder .uri );
847+ isExternalHost = true ;
848+ } else {
849+ matcher = Builder .SPANNER_URI_PATTERN .matcher (builder .uri );
850+ }
833851 Preconditions .checkArgument (
834852 matcher .find (), String .format ("Invalid connection URI specified: %s" , builder .uri ));
835853
@@ -947,12 +965,18 @@ && getInitialConnectionPropertyValue(OAUTH_TOKEN) == null
947965 this .sessionPoolOptions = SessionPoolOptions .newBuilder ().setAutoDetectDialect (true ).build ();
948966 }
949967
950- String projectId = matcher .group (Builder .PROJECT_GROUP );
968+ String projectId = "default" ;
969+ String instanceId = matcher .group (Builder .INSTANCE_GROUP );
970+ if (!isExternalHost ) {
971+ projectId = matcher .group (Builder .PROJECT_GROUP );
972+ } else if (instanceId == null ) {
973+ instanceId = "default" ;
974+ }
951975 if (Builder .DEFAULT_PROJECT_ID_PLACEHOLDER .equalsIgnoreCase (projectId )) {
952976 projectId = getDefaultProjectId (this .credentials );
953977 }
954978 this .projectId = projectId ;
955- this .instanceId = matcher . group ( Builder . INSTANCE_GROUP ) ;
979+ this .instanceId = instanceId ;
956980 this .databaseName = matcher .group (Builder .DATABASE_GROUP );
957981 }
958982
@@ -981,6 +1005,10 @@ static String determineHost(
9811005 // The leading '//' is already included in the regex for the connection URL, so we don't need
9821006 // to add the leading '//' to the host name here.
9831007 host = matcher .group (Builder .HOST_GROUP );
1008+ if (Builder .EXTERNAL_HOST_FORMAT .equals (matcher .pattern ().pattern ())
1009+ && !host .matches (".*:\\ d+$" )) {
1010+ host = String .format ("%s:15000" , host );
1011+ }
9841012 }
9851013 if (usePlainText ) {
9861014 return PLAIN_TEXT_PROTOCOL + host ;
0 commit comments