Skip to content

fix: bind Int128/UInt128/Int256/UInt256 (big.Int) params as numeric values#1922

Open
polyglotAI-bot wants to merge 2 commits into
mainfrom
polyglot/fix-bigint-bind-params
Open

fix: bind Int128/UInt128/Int256/UInt256 (big.Int) params as numeric values#1922
polyglotAI-bot wants to merge 2 commits into
mainfrom
polyglot/fix-bigint-bind-params

Conversation

@polyglotAI-bot

Copy link
Copy Markdown
Collaborator

Description

Fixes #1917.

*big.Int / big.Int are the Go types behind ClickHouse Int128, UInt128, Int256 and UInt256. In the client-side SQL bind rewrite (?, $1, @name), formatValue() matched *big.Int on the case fmt.Stringer: arm and emitted it as a quoted string literal ('170141183460469231731687303715884105727'). The server then saw a StringtoTypeName(?) returned String instead of a number — so type-inference contexts (arithmetic, function dispatch, toTypeName) broke. A big.Int passed by value was worse still: it fell through to fmt.Sprint and produced garbage struct text.

Emitting a bare decimal is not enough: ClickHouse infers an integer literal wider than 64 bits as Float64, losing precision (a WHERE on an Int128 column then matches nothing). The fix instead wraps the exact decimal in the narrowest wide-integer conversion that holds ittoInt128('..'), toUInt128('..'), toInt256('..') or toUInt256('..') — mirroring how the driver already binds times as toDateTime(...) and floats as cast(..., 'Float64'). This keeps both the exact value and an integer type. Server-side {name:Type} query parameters already declare the type, so there the bare decimal is sent unchanged.

Changes

  • bind.go: handle big.Int and *big.Int in formatValue() before the fmt.Stringer arm. New helpers formatBigInt / bigIntConvFunc pick the narrowest of Int128/UInt128/Int256/UInt256 that holds the value, or return an actionable error if it fits none. nil *big.IntNULL.
  • bind_test.go: TestFormatBigInt (magnitudes, spill boundaries, out-of-range, both format modes, value + pointer), TestBindBigInt (all three placeholder styles, arrays, tuples, maps, nil, and contrast cases that must stay quoted: string, another fmt.Stringer, decimal.Decimal), TestBindBigIntQueryParameter (server-side {name:Type} path).
  • tests/issues/1917_test.go: integration test over Native + HTTP — parameter binds as a wide integer, round-trips exactly, matches an Int128 column, and works via server-side query parameters.
  • tests/std/1917_test.go: the same round-trip through the database/sql surface on both protocols.

Test

Verified against ClickHouse 26.5 that the new tests fail on main (*big.Int'…'String; big.Int value → fmt.Sprint garbage) and pass with the fix. toInt128('…') etc. confirmed to yield the exact value and the expected wide-integer type on the server. gofmt/go vet clean on changed files; full root-package unit suite green.

Pre-PR validation gate

  • Deterministic repro confirmed (tests fail on unpatched bind.go, pass with fix)
  • Root cause documented above
  • Fix targets the root cause
  • Test fails without fix, passes with fix
  • No existing tests weakened or broken
  • Convention compliance (AGENTS.md / CONTRIBUTING.md — CHANGELOG.md is auto-generated, not hand-edited; regression test under tests/issues/; t.Cleanup + version-gated server-side subtest)

…alues

A *big.Int (the Go type behind Int128/UInt128/Int256/UInt256) implements
fmt.Stringer, so client-side binding matched the `case fmt.Stringer:` arm in
formatValue and emitted the value as a quoted string literal. The server then
saw a String -- `toTypeName(?)` returned `String`, and server-side
`{name:Int128}` parameters failed to parse the quoted value.

Emitting the bare decimal instead is not enough: ClickHouse reads a bare decimal
literal wider than 64 bits as Float64, losing precision (a WHERE on an Int128
column would match nothing). So in SQL mode the value is wrapped in the narrowest
wide-integer conversion that holds it exactly -- toInt128/toUInt128/toInt256/
toUInt256('<decimal>') -- mirroring how times bind as toDateTime(...) and floats
as cast(..., 'Float64'). In query-parameter text mode the {name:Type} placeholder
already declares the type, so the bare decimal is sent as-is.

Also fixes a nil *big.Int binding as '<nil>' instead of NULL.

Fixes: #1917
Add a tests/std regression test that drives a big.Int bind parameter
through the database/sql surface (rebind -> bind) on both Native and
HTTP, and add tuple and map nesting cases to the unit test, so every
entry point and container type that reaches the big.Int bind path in
issue #1917 is asserted.
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.

[backfill: ClickHouse/clickhouse-go] Int128/UInt128 bind parameters serialized as quoted strings, not integer literals

1 participant