Skip to content

fix: preserve multi-host HA DSN addresses (Go 1.26) - #1946

Open
sankalpsthakur wants to merge 8 commits into
ClickHouse:mainfrom
sankalpsthakur:fix/1784-ha-dsn-go126
Open

fix: preserve multi-host HA DSN addresses (Go 1.26)#1946
sankalpsthakur wants to merge 8 commits into
ClickHouse:mainfrom
sankalpsthakur:fix/1784-ha-dsn-go126

Conversation

@sankalpsthakur

@sankalpsthakur sankalpsthakur commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #1784

Hardens multi-host (HA) DSN address parsing so host lists stay intact on Go 1.26.

DSN parsing uses net/url.Parse. When Go 1.26 rejects or collapses a comma-separated host list (http(s) with GODEBUG=urlstrictcolons, or bracketed IPv6 lists), a single-host rewrite is parsed and peers are restored with strings.Split and net.SplitHostPort.

  • IPv4 multi-host keeps every peer
  • Bracketed IPv6 multi-host keeps every peer
  • Auth is cluster-wide (userinfo, username/password query override, and post-ParseDSN Options.Auth mutation)

Validation

  • go test for ParseDSN / SplitHostList / MultiHostAuth / HTTP strict-colons (Go 1.25.6 and Go 1.26.7)
  • Integration: TestMultiHostDSNFailover and TestMultiHostDSNAuthOverride (existing tests/conn_test.go pattern; not run locally without ClickHouse)

AI/LLM disclosure

  • AI coding tools (including Grok and/or Codex agent-assisted editing) were used to help draft or modify code and this PR description.
  • I reviewed the complete change, understand the reasoning, and ran the reported local tests before submitting.
  • This submission is original work of authorship under the project CLA / contributor terms; AI output was not pasted unreviewed.

Comment thread clickhouse_options_test.go Outdated
Comment thread clickhouse_options_test.go
Comment thread clickhouse_options.go Outdated

@chernser chernser left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • correct implementation - use std lib
  • documentation how it works and changelog
  • more tests with authentication
  • ideally integration test

@sankalpsthakur

sankalpsthakur commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @chernser, addressed the feedback in the latest push. Documented lib/churl and refactored splitHostList to use net.SplitHostPort and JoinHostPort, expanded docs and added a changelog entry. Added more auth tests for multi-host cases including query overrides and IPv6. Unit tests pass. Let me know if you want integration coverage as well.

@sankalpsthakur

Copy link
Copy Markdown
Contributor Author

Thanks @chernser, pushed c54255c to address the checklist. Please take another look when you have a moment.

Comment thread clickhouse_options.go Outdated
Comment thread clickhouse_options_test.go
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

🤖 Claude review

Hardens multi-host HA DSN parsing (#1784) by extracting the host list from the raw DSN authority instead of relying on churl's parsed Host. The fix works for the reported cases (IPv4 multi-host, all-bracketed IPv6, auth variants) and the TestParseDSN coverage is solid, but the approach patches around the real bug in lib/churl and regresses percent-encoded hosts.

Key concerns:

  • The raw-authority extraction takes precedence for every DSN and skips percent-decoding, so previously-working encoded hosts (RFC 6874 IPv6 zone IDs like [fe80::1%25eth0]:9000) now land encoded in Options.Addr and fail to dial (inline on clickhouse_options.go).
  • Mixed bracketed + plain host lists ([::1]:9440,host2:9440) still fail end-to-end inside churl.Parse before the new code ever runs, while the new TestSplitHostList case suggests they are supported (inline on clickhouse_options_test.go).
  • The root cause is churl.parseHost mishandling bracketed IPv6 in comma lists; fixing it there would resolve both inline findings, keep dsn.Host correct for all consumers, and avoid a second parser that must stay in sync with churl (see general finding).

Blind spots: this review is static — the sandbox could not run go test; the churl.parseHost traces were verified by hand against the copied Go 1.25.7 code in lib/churl/churl.go.

Verdict: ⚠️ Request changes

General findings

  • ⚠️ Should fix — fix belongs in lib/churl.parseHost, not a second parser
    lib/churl exists precisely to keep comma-separated HA hosts parseable, yet parseHost still mishandles bracketed IPv6 lists: [a]:p,[b]:p silently collapses to the last host (it anchors on strings.LastIndex(host, "[")), and [a]:p,plain:p hard-errors on the port check. This PR works around that in fromDSN with a raw-string re-parse that must now agree with churl forever, while dsn.Host remains wrong for any other consumer of churl.Parse.

    Splitting the authority on top-level commas inside parseHost (reusing the bracket-depth logic from splitHostList) and parsing each host individually would fix both inline findings at the root, keep the existing strings.Split(dsn.Host, ",") in fromDSN working unchanged, and remove the ~95 new lines in clickhouse_options.go.

Inline comments are attached to the relevant lines. This summary updates in place on re-review.

@sankalpsthakur

sankalpsthakur commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

@chernser rebased on main. multi-host split lives in lib/churl.parseHost now (IPv6/mixed/zone IDs), fromDSN just uses SplitHostPort. more tests + multi-host failover. d1c8f2f.

@sankalpsthakur
sankalpsthakur force-pushed the fix/1784-ha-dsn-go126 branch from c54255c to d1c8f2f Compare August 7, 2026 17:40
@sankalpsthakur

sankalpsthakur commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

@chernser follow-up on 74b1af5: cluster-wide Auth test (userinfo, query override, post-ParseDSN Options mutation) and README notes for multi-host IPv6 / failover auth.

Earlier (d1c8f2f): multi-host split in lib/churl.parseHost; fromDSN only SplitHostPort/JoinHostPort. Integration: TestMultiHostDSNFailover. Ready when you have a moment.

@sankalpsthakur

Copy link
Copy Markdown
Contributor Author

Updated this for the stdlib-style multi-host parsing notes, auth/override cases, changelog, and renamed the tests off the issue number. Ready for another pass when you have time.

@sankalpsthakur

Copy link
Copy Markdown
Contributor Author

Merged latest main, normalized the CHANGELOG Unreleased note (CRLF), and documented multi-host DSN / cluster-wide Auth in docs/configuration.mdx, docs/database-sql-api.mdx, and docs/config-reference.mdx.

Review checklist remains covered: stdlib SplitHostPort/JoinHostPort after lib/churl.parseHost, auth override tests, integration TestMultiHostDSNFailover. Unit tests for ParseDSN / SplitHostList / MultiHostAuth / churl pass locally.

@sankalpsthakur

Copy link
Copy Markdown
Contributor Author

Pushed ae9f6d5. After churl.Parse, hosts split with strings.Split + SplitHostPort. TestMultiHostDSNAuthOverride covers replacing Options.Auth after ParseDSN, including failover to the live host.

Extract HA host lists from the raw DSN authority instead of only
splitting url.Host, so multi-host DSNs (including bracketed IPv6)
are not collapsed by URL host normalization. Add ParseDSN and churl
regression tests for the issue 1784 multi-host cases.
- Use stdlib net.SplitHostPort/net.JoinHostPort in splitHostList for
  per-host validation/normalization; document HA handling and Go 1.26
  net/url tightening (churl as Go 1.25.7 copy plus raw-authority
  extraction). Clarify single cluster-wide Auth and query override.
- Add CHANGELOG Unreleased entry describing fix.
- Expand ParseDSN tests: rename from issue-number style to functional
  names, add multi-host cases for auth overridden via query, auth via
  query only, encoded passwords, IPv6 with auth, and 3-host preservation.
- Expand host-list helper tests: rename, add auth-override case and
  additional splitHostList coverage.

Fixes ClickHouse#1784
Root cause of IPv6 multi-host collapse and mixed-list parse errors was
churl.parseHost anchoring on LastIndex of '['. Split authority on
top-level commas and parse each host so dsn.Host stays complete and
percent-decoding (zone IDs) still works.

fromDSN now only splits the parsed host with stdlib SplitHostPort.
Add mixed IPv6/plain + zone-ID ParseDSN cases, churl coverage, and a
multi-host DSN failover integration test.
Document that HA DSN credentials apply to every peer (userinfo, query
override, and post-ParseDSN Options.Auth mutation). Expand README DSN
section for multi-host IPv6 lists and cluster-wide auth.
Document cluster-wide Auth for multi-host DSNs in the Go docs, and keep
CHANGELOG Unreleased notes with upstream CRLF endings.

Signed-off-by: Sankalp Thakur <sankalphimself@gmail.com>
fromDSN already gets a comma-separated host list from churl.Parse.
Split with strings.Split and normalize via SplitHostPort/JoinHostPort.
Add TestMultiHostDSNAuthOverride for post-ParseDSN Options.Auth mutation.

Signed-off-by: Sankalp Thakur <sankalphimself@gmail.com>
@sankalpsthakur

Copy link
Copy Markdown
Contributor Author

Rebased onto current main to clear the merge conflict. Stdlib SplitHostPort after churl.Parse, cluster-wide auth tests, and the failover integration tests are unchanged.

Go 1.26 can reject or collapse comma-separated host lists. Multi-host
DSNs are parsed as a single-host URL, then peers are restored with
strings.Split and net.SplitHostPort. Auth remains cluster-wide.

Drop the extra HA parser in lib/churl.parseHost.
@sankalpsthakur

sankalpsthakur commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@chernser HA DSN parsing now uses net/url.Parse plus strings.Split and net.SplitHostPort, without a custom authority parser in lib/churl. Multi-host lists that Go 1.26 rejects or collapses are recovered by parsing a single-host rewrite. Auth is cluster-wide (userinfo, query username/password, Options.Auth after ParseDSN). Docs and changelog updated. Unit tests cover auth/IPv6 on Go 1.25 and 1.26; failover tests remain in tests/conn_test.go.

Use net/url.Parse as the primary path, then strings.Split and
net.SplitHostPort. Rewrite to a single host only when Parse rejects
or collapses the list (Go 1.26 urlstrictcolons, bracketed IPv6).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HA DSN parsing fails with Go 1.26

3 participants