@@ -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