Description
Two Go value types cannot be bound through the client-side parameter binder in bind.go. Neither is a ClickHouse limitation — the server stores both fine — but formatValue has no case for either, so both fall through to a generic branch that produces SQL the server cannot use.
1. []byte is serialized as Array(UInt8), not as a String literal
[]byte is a valid database/sql driver.Value and the natural Go type for a ClickHouse String holding arbitrary bytes (the String column implementation accepts it on the batch path). But in formatValue there is no case []byte, so it falls through to the reflection block and hits reflect.Slice, which renders each element as a number:
[]byte("A\x00B") -> [65, 0, 66]
The bound value becomes an Array(UInt8). Against a String column this is either a type error or — worse — silently the wrong value where the expression is permissive. There is no way to bind binary data as a string through the client-side binder.
This is the Go counterpart of the missing PEP 249 Binary constructor reported for clickhouse-connect (see link below).
2. time.Duration is serialized with Duration.String()
time.Duration is the Go type this driver itself maps the ClickHouse Time/Time64 types to — column.Time.ScanType() returns scanTypeDuration, and column.Time.AppendRow accepts time.Duration and converts it via proto.IntoTime32. So a round trip through a batch works.
Through the binder it does not. time.Duration implements fmt.Stringer, so it matches case fmt.Stringer in formatValue and is quoted with Go's duration syntax:
14*time.Hour + 30*time.Minute -> '14h30m0s'
'14h30m0s' is not parseable by the server as a Time (or as the underlying integer number of seconds). The read path and the batch-insert path understand time.Duration; the bind path emits a literal that can never work, so conn.Query/conn.Exec against a Time column is unusable with the type the driver otherwise standardises on.
ClickHouse server version
Code analysis plus a library-level unit test only; not verified against a running server (no ClickHouse instance was reachable in this environment). The defect is entirely in client-side SQL text generation, which the test below observes directly.
Reproduction
A test in the repository root (package clickhouse):
package clickhouse
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestBindDuration(t *testing.T) {
// time.Duration is the Go type this driver maps the ClickHouse Time /
// Time64 types to (column.Time.ScanType() == scanTypeDuration).
q, err := bind(time.UTC, "SELECT toTime(?)", 14*time.Hour+30*time.Minute)
assert.NoError(t, err)
assert.Equal(t, "SELECT toTime('14:30:00')", q)
}
func TestBindBytes(t *testing.T) {
// []byte is a valid database/sql driver.Value and the natural Go type for
// a ClickHouse String holding arbitrary bytes.
q, err := bind(time.UTC, "SELECT ?", []byte("A\x00B"))
assert.NoError(t, err)
assert.Equal(t, "SELECT 'A\\0B'", q)
}
$ go test -run 'TestBindDuration|TestBindBytes' -v .
=== RUN TestBindDuration
Error: Not equal:
expected: "SELECT toTime('14:30:00')"
actual : "SELECT toTime('14h30m0s')"
--- FAIL: TestBindDuration (0.00s)
=== RUN TestBindBytes
Error: Not equal:
expected: "SELECT 'A\0B'"
actual : "SELECT [65, 0, 66]"
--- FAIL: TestBindBytes (0.00s)
FAIL
Expected: a quoted String literal for []byte, and a Time-parseable literal (or the integer number of seconds) for time.Duration.
Actual: [65, 0, 66] and '14h30m0s'.
The same holds through the public surface — conn.Query(ctx, "SELECT ?", []byte(...)) and db.Query("SELECT ?", someDuration) both route through bind.
Suggested fix
In formatValue (bind.go:522), add explicit cases before the fmt.Stringer case and before the reflection fallback:
case []byte: — quote it as a String literal, using the same escaping as the string case (so \0, ' and \ survive).
case time.Duration: — render as the server expects for Time/Time64: HH:MM:SS[.frac], or the plain integer number of seconds. It must come before case fmt.Stringer, which currently swallows it. *time.Duration and the formatParamText mode (server-side {name:Type} parameters) need the same treatment.
Related but distinct: #1927 covers wrong serialization of other types in the server-side {name:Type} parameter path.
Link
Reported for the Python client as ClickHouse/clickhouse-connect#919 (missing PEP 249 Binary constructor; datetime.time bound as a bare 14:30:00 literal where the Time column expects seconds).
Description
Two Go value types cannot be bound through the client-side parameter binder in
bind.go. Neither is a ClickHouse limitation — the server stores both fine — butformatValuehas no case for either, so both fall through to a generic branch that produces SQL the server cannot use.1.
[]byteis serialized asArray(UInt8), not as aStringliteral[]byteis a validdatabase/sqldriver.Valueand the natural Go type for a ClickHouseStringholding arbitrary bytes (theStringcolumn implementation accepts it on the batch path). But informatValuethere is nocase []byte, so it falls through to the reflection block and hitsreflect.Slice, which renders each element as a number:The bound value becomes an
Array(UInt8). Against aStringcolumn this is either a type error or — worse — silently the wrong value where the expression is permissive. There is no way to bind binary data as a string through the client-side binder.This is the Go counterpart of the missing PEP 249
Binaryconstructor reported for clickhouse-connect (see link below).2.
time.Durationis serialized withDuration.String()time.Durationis the Go type this driver itself maps the ClickHouseTime/Time64types to —column.Time.ScanType()returnsscanTypeDuration, andcolumn.Time.AppendRowacceptstime.Durationand converts it viaproto.IntoTime32. So a round trip through a batch works.Through the binder it does not.
time.Durationimplementsfmt.Stringer, so it matchescase fmt.StringerinformatValueand is quoted with Go's duration syntax:'14h30m0s'is not parseable by the server as aTime(or as the underlying integer number of seconds). The read path and the batch-insert path understandtime.Duration; the bind path emits a literal that can never work, soconn.Query/conn.Execagainst aTimecolumn is unusable with the type the driver otherwise standardises on.ClickHouse server version
Code analysis plus a library-level unit test only; not verified against a running server (no ClickHouse instance was reachable in this environment). The defect is entirely in client-side SQL text generation, which the test below observes directly.
Reproduction
A test in the repository root (package
clickhouse):Expected: a quoted
Stringliteral for[]byte, and aTime-parseable literal (or the integer number of seconds) fortime.Duration.Actual:
[65, 0, 66]and'14h30m0s'.The same holds through the public surface —
conn.Query(ctx, "SELECT ?", []byte(...))anddb.Query("SELECT ?", someDuration)both route throughbind.Suggested fix
In
formatValue(bind.go:522), add explicit cases before thefmt.Stringercase and before the reflection fallback:case []byte:— quote it as aStringliteral, using the same escaping as thestringcase (so\0,'and\survive).case time.Duration:— render as the server expects forTime/Time64:HH:MM:SS[.frac], or the plain integer number of seconds. It must come beforecase fmt.Stringer, which currently swallows it.*time.Durationand theformatParamTextmode (server-side{name:Type}parameters) need the same treatment.Related but distinct: #1927 covers wrong serialization of other types in the server-side
{name:Type}parameter path.Link
Reported for the Python client as ClickHouse/clickhouse-connect#919 (missing PEP 249
Binaryconstructor;datetime.timebound as a bare14:30:00literal where theTimecolumn expects seconds).