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)
+ }
+ })
+ }
+}