This is an automated email from the ASF dual-hosted git repository.

HTHou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iotdb-client-go.git


The following commit(s) were added to refs/heads/main by this push:
     new d8b1027  Support IPv6 node URLs in the cluster session (#170)
d8b1027 is described below

commit d8b1027c14404e07cfe40a745907f7f0cca33783
Author: ZIHAN DAI <[email protected]>
AuthorDate: Fri Jul 24 16:30:48 2026 +1000

    Support IPv6 node URLs in the cluster session (#170)
    
    Cluster node URLs were split with strings.Split(url, ":"), which mis-parses
    an IPv6 endpoint: "[::1]:6667" resolves to host "[" and an empty port 
instead
    of host "::1" / port "6667", while the client's own output path already
    brackets IPv6 via net.JoinHostPort.
    
    Parse node URLs with net.SplitHostPort via a new parseNodeURL helper, which
    accepts the bracketed [ipv6]:port form (consistent with apache/iotdb#18162)
    in addition to IPv4 and hostnames, and rejects malformed URLs with an error
    instead of silently mis-parsing them. Add unit tests covering IPv4, 
hostname,
    and bracketed IPv6, plus malformed inputs.
    
    Signed-off-by: Zihan Dai <[email protected]>
---
 client/session.go      | 28 +++++++++++++++++++----
 client/session_test.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 85 insertions(+), 5 deletions(-)

diff --git a/client/session.go b/client/session.go
index 76275dc..787bd93 100644
--- a/client/session.go
+++ b/client/session.go
@@ -26,9 +26,9 @@ import (
        "errors"
        "fmt"
        "log"
+       "net"
        "reflect"
        "sort"
-       "strings"
        "time"
 
        "github.com/apache/thrift/lib/go/thrift"
@@ -1334,14 +1334,32 @@ func NewClusterSession(clusterConfig *ClusterConfig) 
(Session, error) {
        return newClusterSessionWithSqlDialect(clusterConfig)
 }
 
+// parseNodeURL splits a "host:port" node URL into a host and port, accepting
+// bracketed IPv6 forms such as "[::1]:6667" and "[2001:db8::1]:6667" in
+// addition to IPv4 and hostname URLs, consistent with the [ipv6]:port endpoint
+// format standardized in apache/iotdb#18162. Malformed URLs (missing port,
+// empty host, unbalanced brackets, or a bare IPv6 address without brackets) 
are
+// rejected with an error rather than silently mis-parsed.
+func parseNodeURL(nodeURL string) (endPoint, error) {
+       host, port, err := net.SplitHostPort(nodeURL)
+       if err != nil {
+               return endPoint{}, fmt.Errorf("invalid node url %q: %w", 
nodeURL, err)
+       }
+       if host == "" || port == "" {
+               return endPoint{}, fmt.Errorf("invalid node url %q: host and 
port must be non-empty", nodeURL)
+       }
+       return endPoint{Host: host, Port: port}, nil
+}
+
 func newClusterSessionWithSqlDialect(clusterConfig *ClusterConfig) (Session, 
error) {
        session := Session{}
        session.endPointList = make([]endPoint, len(clusterConfig.NodeUrls))
        for i := 0; i < len(clusterConfig.NodeUrls); i++ {
-               node := endPoint{}
-               node.Host = strings.Split(clusterConfig.NodeUrls[i], ":")[0]
-               node.Port = strings.Split(clusterConfig.NodeUrls[i], ":")[1]
-               session.endPointList[i] = node
+               ep, err := parseNodeURL(clusterConfig.NodeUrls[i])
+               if err != nil {
+                       return session, err
+               }
+               session.endPointList[i] = ep
        }
        var err error
        var lastErr error
diff --git a/client/session_test.go b/client/session_test.go
new file mode 100644
index 0000000..67f83e9
--- /dev/null
+++ b/client/session_test.go
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package client
+
+import "testing"
+
+func TestParseNodeURL(t *testing.T) {
+       tests := []struct {
+               name     string
+               nodeURL  string
+               wantHost string
+               wantPort string
+               wantErr  bool
+       }{
+               {"ipv4", "127.0.0.1:6667", "127.0.0.1", "6667", false},
+               {"hostname", "localhost:6667", "localhost", "6667", false},
+               {"ipv6 loopback", "[::1]:6667", "::1", "6667", false},
+               {"ipv6 full", "[2001:db8::1]:6667", "2001:db8::1", "6667", 
false},
+               // Bare (unbracketed) IPv6 with a port is ambiguous and 
rejected; the
+               // bracketed [ipv6]:port form must be used (see 
apache/iotdb#18162).
+               {"bare ipv6 rejected", "::1:6667", "", "", true},
+               {"ipv6 missing port", "[::1]6667", "", "", true},
+               {"ipv6 unbalanced bracket", "[::1:6667", "", "", true},
+               {"no colon", "nocolon", "", "", true},
+               {"empty port bracketed", "[::1]:", "", "", true},
+               {"empty port", "host:", "", "", true},
+               {"empty host", ":6667", "", "", true},
+               {"empty", "", "", "", true},
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       ep, err := parseNodeURL(tt.nodeURL)
+                       if (err != nil) != tt.wantErr {
+                               t.Fatalf("parseNodeURL(%q) error = %v, wantErr 
%v", tt.nodeURL, err, tt.wantErr)
+                       }
+                       if tt.wantErr {
+                               return
+                       }
+                       if ep.Host != tt.wantHost || ep.Port != tt.wantPort {
+                               t.Errorf("parseNodeURL(%q) = {host %q, port 
%q}, want {host %q, port %q}",
+                                       tt.nodeURL, ep.Host, ep.Port, 
tt.wantHost, tt.wantPort)
+                       }
+               })
+       }
+}

Reply via email to