Skip to content

Add IPv6 support, AUTH_METHOD_NONE, and custom curl options#248

Merged
isublimity merged 10 commits intomasterfrom
feature/ipv6-auth-curl-options
Apr 4, 2026
Merged

Add IPv6 support, AUTH_METHOD_NONE, and custom curl options#248
isublimity merged 10 commits intomasterfrom
feature/ipv6-auth-curl-options

Conversation

@isublimity
Copy link
Copy Markdown
Contributor

Summary

  • IPv6 support: getUri() correctly wraps bare IPv6 addresses in brackets (e.g. http://[::1]:8123), distinguishing IPv6 from host:port by counting colons
  • AUTH_METHOD_NONE (0): new auth method constant for trusted/proxy setups where no credentials should be sent
  • Custom curl options: pass arbitrary CURLOPT_* via $connectParams['curl_options'] array or Http::setCurlOptions()
  • CurlerRequest::option() made public to support curl option injection from transport layer

Reimplements the ideas from #239 with proper IPv6 detection and full test coverage.

Test plan

  • IPv6UriTest — 8 cases: IPv6 with/without port, already bracketed, IPv4, hostname:port, hostname/path
  • AuthMethodNoneTest — constant value, presence in AUTH_METHODS_LIST, client construction
  • CurlOptionsTest — options via config and via setCurlOptions()
  • All 102 existing tests pass
  • PHPStan clean (0 errors)

🤖 Generated with Claude Code

isublimity and others added 10 commits April 4, 2026 02:13
- IPv6: getUri() wraps bare IPv6 addresses in brackets (e.g. [::1]:8123)
- AUTH_METHOD_NONE (0): skip authentication for trusted/proxy setups
- curl_options: pass arbitrary CURLOPT_* via config or setCurlOptions()
- CurlerRequest::option() made public for curl option injection
- Tests: IPv6UriTest, AuthMethodNoneTest, CurlOptionsTest

Inspired by PR #239, reimplemented with proper IPv6 detection and tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes #247 — streamRead() and streamWrite() accept key-value bindings,
not a string array.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eader

statistics() now falls back to the X-ClickHouse-Summary HTTP header when
the response body has no statistics (INSERT/write queries). New summary()
method provides direct access to the header data.

Fixes #233

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
docker-compose now runs two ClickHouse instances:
- clickhouse-21 (port 8123) — v21.9 for backward compat
- clickhouse-latest (port 8124) — v26.3.3.20

Run tests against either:
  CLICKHOUSE_PORT=8123 ./vendor/bin/phpunit  # v21.9
  CLICKHOUSE_PORT=8124 ./vendor/bin/phpunit  # v26.3

Known ClickHouse 26.3 behavioral changes (not library bugs):
- JSON returns numbers as native types, not strings (UInt64Test)
- Temporary tables work without session (SessionsTest)
- Mid-stream errors return 500 instead of 200 (StatementTest)
- Different compression ratios (GzipInsert/InsertCSV size assertions)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two separate PHPUnit configs:
- phpunit-ch21.xml (port 8123) — all original tests, 107 tests
- phpunit-ch26.xml (port 8124) — shared tests + CH26-adapted tests, 93 tests

CH26-specific tests in tests/ClickHouse26/ adapt for behavioral changes:
- Modern MergeTree syntax (ORDER BY instead of deprecated args)
- JSON returns native numeric types instead of strings (UInt64)
- Temporary tables work without sessions
- Mid-stream errors return HTTP 500 instead of 200
- No hardcoded compressed data sizes

Run: ./vendor/bin/phpunit -c phpunit-ch21.xml  # ClickHouse 21.9
     ./vendor/bin/phpunit -c phpunit-ch26.xml  # ClickHouse 26.3

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Five major initiatives planned:
1. Native Query Parameters ({name:Type} server-side binding)
2. Full ClickHouse type support (60+ types in 4 phases)
3. Structured exceptions (CH error name, query ID)
4. PHPStan level 1 → max (incremental)
5. Per-query settings override in select()/write()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rams, PHPStan 5

P0: Per-query settings override
- select(), selectAsync(), write() accept optional $querySettings array
- Per-query settings merge with global (per-query takes priority)
- Global settings remain unchanged after query

P0: Structured exceptions
- DatabaseException::fromClickHouse() factory with exception name + query ID
- Parse CH 22+ error format: (EXCEPTION_NAME) (version ...)
- Extract X-ClickHouse-Query-Id from response header
- getClickHouseExceptionName(), getQueryId() accessors

P1: Native Query Parameters
- selectWithParams() / writeWithParams() — server-side typed binding
- SQL uses {name:Type}, values passed as param_name in URL
- Supports: int, float, string, bool, null, DateTime, array
- SQL injection impossible at protocol level

P1: PHPStan level 1 → 5
- Raised from level 1 to level 5
- Baseline for 60 pre-existing errors (mostly curl layer type hints)
- All new code must pass level 5

Tests: 126 (CH21) + 112 (CH26) = all passing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
#234 Fix: hasErrorClickhouse no longer json_decode() large responses
- Bodies > 4KB: check only tail for ClickHouse error patterns
- Bodies <= 4KB: validate JSON as before (preserves #223 fix)
- Prevents OOM when streaming large JSON resultsets

#166 Feature: Generator-based row iteration
- Statement::rowsGenerator() — yields rows from already-fetched data
- Client::selectGenerator() — streams from CH via JSONEachRow, yields
  one row at a time without loading full resultset into memory
- Supports bindings and per-query settings

#176 Feature: GitHub Actions CI (replaces Travis CI)
- Matrix: PHP 8.0-8.4 x ClickHouse 21.9 + 26.3
- PHPStan and PHPCS jobs
- Docker services for both CH versions

Tests: 135 (CH21) + 121 (CH26) = all passing, PHPStan clean

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New types in src/Type/:
- Int64, Decimal — large numeric types (string-based, no overflow)
- UUID — UUID values for insert and native params
- IPv4, IPv6 — IP address types
- DateTime64 — sub-second precision, fromString() and fromDateTime()
- Date32 — extended date range
- MapType — Map(K, V) composite type
- TupleType — Tuple(T1, T2, ...) composite type

All types implement Type interface, work with:
- insert() via ValueFormatter pipeline
- selectWithParams() via convertParamValue()
- Bindings via getValue()

#191 Fix: progressFunction now works for write/insert operations
- Added wait_end_of_query=1 setting when progressFunction is enabled
- Required for ClickHouse to send progress headers during writes

Tests: 158 (CH21) + 144 (CH26) = all passing, PHPStan clean

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New docs in doc/:
- types.md — all 9 type classes with examples (insert, bindings, native params)
- native-params.md — server-side {name:Type} parameters
- per-query-settings.md — $querySettings override for select/write
- generators.md — selectGenerator() and rowsGenerator() for large results
- progress.md — progressFunction for SELECT and INSERT
- exceptions.md — structured exceptions with error codes and names

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@isublimity isublimity merged commit 0456967 into master Apr 4, 2026
2 of 13 checks passed
@isublimity isublimity deleted the feature/ipv6-auth-curl-options branch April 4, 2026 00:43
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.

1 participant