You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
sev:1 — Easy client-side workaround (cast in SQL, e.g. SELECT CAST(enum_col AS Int8), or scan into a sql.Scanner wrapper). The bug is "missing functionality" rather than wrong-value/blocking-error on a default path. No silent corruption, no default-path hang. Users can always retrieve the integer form by changing the SQL.
Description
ClickHouse Enum8 / Enum16 values are conceptually pairs of (name, number). A complete client should let the caller materialize either form on read:
the string name ('open', 'closed', …) — what the user wrote in the DDL, or
the underlying integer ordinal (int8 / int16) — what is sent on the wire.
In clickhouse-go's native columnar reader, only the string form is exposed. Enum8.ScanRow / Enum16.ScanRow accept only *string and **string destinations; any integer destination falls through to ColumnConverterError. The Row() helper also returns the string form unconditionally.
This mirrors the source bug filed against the Java v2 client (ClickHouse/clickhouse-java#2026), which asks for both representations to be supported on read.
lib/column/enum16.go:52-71 is structurally identical and has the same limitation for int8/int16/int destinations.
Note that the write side already accepts both forms (see Enum8.AppendRow / Enum8.Append, also covered by the merged PR #802 "Support inserting Enums as int8/int16/int values"). Read-side support was never added.
ClickHouse server version
Code analysis only; not verified against a running server (the dev-env ClickHouse at http://localhost:8123 refused connection during this investigation).
Reproduction
Minimal failing program against any ClickHouse server:
package main
import (
"context""fmt""github.com/ClickHouse/clickhouse-go/v2"
)
funcmain() {
ctx:=context.Background()
conn, err:=clickhouse.Open(&clickhouse.Options{
Addr: []string{"localhost:9000"},
})
iferr!=nil { panic(err) }
deferconn.Close()
iferr:=conn.Exec(ctx, `DROP TABLE IF EXISTS enum_scan_test`); err!=nil { panic(err) }
iferr:=conn.Exec(ctx, ` CREATE TABLE enum_scan_test ( v Enum8('a' = 1, 'b' = 2) ) ENGINE = Memory `); err!=nil { panic(err) }
iferr:=conn.Exec(ctx, `INSERT INTO enum_scan_test VALUES ('b')`); err!=nil { panic(err) }
// Works: scan into *string returns the constant name.varasStringstringiferr:=conn.QueryRow(ctx, `SELECT v FROM enum_scan_test`).Scan(&asString); err!=nil {
panic(err)
}
fmt.Printf("string: %q\n", asString) // string: "b"// Fails: scan into *int8 returns ColumnConverterError, even though the// underlying wire value is an int8 ordinal.varasIntint8err=conn.QueryRow(ctx, `SELECT v FROM enum_scan_test`).Scan(&asInt)
fmt.Printf("int8 err: %v\n", err)
// int8 err: clickhouse [ScanRow]: into=*int8, from=Enum8
}
Expected: scanning into *int8 / *int16 / *int returns the numeric ordinal (2 for 'b').
Actual: clickhouse [ScanRow]: into=*int8, from=Enum8.
Equivalent regression-style test (place under tests/issues/):
funcTestEnumScanIntoInt(t*testing.T) {
ctx:=context.Background()
conn, err:=GetNativeConnection(...)
require.NoError(t, err)
require.NoError(t, conn.Exec(ctx, `DROP TABLE IF EXISTS enum_scan_int`))
require.NoError(t, conn.Exec(ctx, ` CREATE TABLE enum_scan_int (v Enum8('a'=1,'b'=2)) ENGINE = Memory`))
require.NoError(t, conn.Exec(ctx, `INSERT INTO enum_scan_int VALUES ('b')`))
vargotint8require.NoError(t, conn.QueryRow(ctx, `SELECT v FROM enum_scan_int`).Scan(&got))
require.Equal(t, int8(2), got)
}
Suggested fix
Extend the ScanRow switch in lib/column/enum8.go and lib/column/enum16.go to accept the integer destinations the column already understands on the write side: *int8, **int8, *int16, **int16, *int32, *int64, *int, and their pointer-to-pointer variants. The numeric value is already available as col.col.Row(row) (typed proto.Enum8 / proto.Enum16, which are integer types) — no extra lookup is needed.
A round-trip test for both representations should be added in tests/, in the spirit of the Workflow rules in AGENTS.md.
Search performed (dedup)
gh issue list --repo ClickHouse/clickhouse-go --search "enum int" --state all --limit 20 → 0 results
gh issue list --repo ClickHouse/clickhouse-go --search "enum read" --state all --limit 20 → 1 result (invalid Enum value: 0 #285 — about invalid Enum value: 0, unrelated)
gh pr list --repo ClickHouse/clickhouse-go --search "enum int" --state all --limit 20 → 4 results, all unrelated except Support inserting Enums as int8/int16/int values #802 ("Support inserting Enums as int8/int16/int values"), which is the write-side counterpart, not read
gh issue list --repo ClickHouse/integrations-ai-playground --label backfill --label "target:clickhouse-go" --search "enum" --state all --limit 20 → 0 results
gh pr list --repo ClickHouse/integrations-ai-playground --search "enum int" --state all --limit 20 → 0 results
Target client repo
ClickHouse/clickhouse-goSeverity
sev:1— Easy client-side workaround (cast in SQL, e.g.SELECT CAST(enum_col AS Int8), or scan into asql.Scannerwrapper). The bug is "missing functionality" rather than wrong-value/blocking-error on a default path. No silent corruption, no default-path hang. Users can always retrieve the integer form by changing the SQL.Description
ClickHouse
Enum8/Enum16values are conceptually pairs of(name, number). A complete client should let the caller materialize either form on read:'open','closed', …) — what the user wrote in the DDL, orint8/int16) — what is sent on the wire.In
clickhouse-go's native columnar reader, only the string form is exposed.Enum8.ScanRow/Enum16.ScanRowaccept only*stringand**stringdestinations; any integer destination falls through toColumnConverterError. TheRow()helper also returns the string form unconditionally.This mirrors the source bug filed against the Java v2 client (ClickHouse/clickhouse-java#2026), which asks for both representations to be supported on read.
Relevant code:
lib/column/enum8.go:52-71lib/column/enum8.go:44-50lib/column/enum16.go:52-71is structurally identical and has the same limitation forint8/int16/intdestinations.Note that the write side already accepts both forms (see
Enum8.AppendRow/Enum8.Append, also covered by the merged PR #802 "Support inserting Enums as int8/int16/int values"). Read-side support was never added.ClickHouse server version
Code analysis only; not verified against a running server (the dev-env ClickHouse at
http://localhost:8123refused connection during this investigation).Reproduction
Minimal failing program against any ClickHouse server:
Expected: scanning into
*int8/*int16/*intreturns the numeric ordinal (2for'b').Actual:
clickhouse [ScanRow]: into=*int8, from=Enum8.Equivalent regression-style test (place under
tests/issues/):Suggested fix
Extend the
ScanRowswitch inlib/column/enum8.goandlib/column/enum16.goto accept the integer destinations the column already understands on the write side:*int8,**int8,*int16,**int16,*int32,*int64,*int, and their pointer-to-pointer variants. The numeric value is already available ascol.col.Row(row)(typedproto.Enum8/proto.Enum16, which are integer types) — no extra lookup is needed.A round-trip test for both representations should be added in
tests/, in the spirit of the Workflow rules inAGENTS.md.Search performed (dedup)
gh issue list --repo ClickHouse/clickhouse-go --search "enum int" --state all --limit 20→ 0 resultsgh issue list --repo ClickHouse/clickhouse-go --search "enum scan" --state all --limit 20→ 1 result (Default value for Nullable(Enum8/16) Cannot be Read #198 — aboutNullable(Enum)default values, unrelated)gh issue list --repo ClickHouse/clickhouse-go --search "enum read" --state all --limit 20→ 1 result (invalid Enum value: 0 #285 — aboutinvalid Enum value: 0, unrelated)gh pr list --repo ClickHouse/clickhouse-go --search "enum int" --state all --limit 20→ 4 results, all unrelated except Support inserting Enums as int8/int16/int values #802 ("Support inserting Enums as int8/int16/int values"), which is the write-side counterpart, not readgh issue list --repo ClickHouse/integrations-ai-playground --label backfill --label "target:clickhouse-go" --search "enum" --state all --limit 20→ 0 resultsgh pr list --repo ClickHouse/integrations-ai-playground --search "enum int" --state all --limit 20→ 0 resultsSource bug
ClickHouse/clickhouse-java#2026