Skip to content

panic: makeslice: len out of range when reading JSON column with deprecated v0 wire format #1854

Description

@amalakar

Observed

  1. Create a JSON column and insert 4 rows with heterogeneous value types across the same JSON
    paths (e.g. latency_ms is an array in one row, a string in another).
  2. SELECT the column back using the Native protocol with clickhouse-go v2.
  3. The driver panics or returns a wire decode error:
    • panic: runtime error: makeslice: len out of range (in production with large datasets)
    • read data: failed to decode block: unexpected value N for boolean
    • clickhouse [process]: unexpected packet N

Expected behaviour

The SELECT should return all rows without error.

Code example

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/ClickHouse/clickhouse-go/v2"
)

// rows contains heterogeneous JSON documents. The same key appears with
// different value types across rows (Int64, String, Array, Bool, Float64,
// nested object, timestamp) so that each JSON path acquires multiple
// inferred CH types — the condition that triggers the discriminator
// index shift in the v0 deprecated decoder.
var rows = []string{
	`{"latency_ms":[]}`,
	`{"cpu_pct":1,"region":"x","request_id":1,"response_times":[],"service":"x","tags":[]}`,
	`{"counts":[],"created_at":"x","ended_at":"x","errors":[],"labels":[],"span_ids":[],"started_at":"x","trace_id":"x","user_id":"x"}`,
	`{"attachments":[],"config":{"host":"host-149","nested":{"extra":null,"name":"svc-147","parent":null},"value":null},"context":{"name":"ctx-273","parent":null,"root":false},"durations":[],"logged_at":"x","metadata":{"active":true,"ids":[null,null],"ts":"2026-05-04T15:18:01Z"},"metrics":{"primary":{"count":542265,"enabled":true,"parent":null},"samples":[18485,18893],"secondary":{"extra":null,"label":"label-166","parent":null}},"org_id":"x","stack_frames":[],"updated_at":"x"}`,
}

func main() {
	conn, err := clickhouse.Open(&clickhouse.Options{
		Addr:     []string{"127.0.0.1:9002"},
		Auth:     clickhouse.Auth{Database: "default", Username: "default", Password: "password"},
		Protocol: clickhouse.Native,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	ctx := context.Background()
	if err := conn.Ping(ctx); err != nil {
		log.Fatalf("ping: %v", err)
	}

	exec := func(q string) {
		if err := conn.Exec(ctx, q); err != nil {
			log.Fatalf("%s\n  %v", q, err)
		}
	}
	exec("DROP TABLE IF EXISTS test_json_table")
	exec("CREATE TABLE test_json_table (raw JSON(max_dynamic_paths=32)) ENGINE = MergeTree ORDER BY tuple()")

	batch, err := conn.PrepareBatch(ctx, "INSERT INTO test_json_table (raw)")
	if err != nil {
		log.Fatal(err)
	}
	for _, row := range rows {
		if err := batch.Append(row); err != nil {
			log.Fatal(err)
		}
	}
	if err := batch.Send(); err != nil {
		log.Fatal(err)
	}

	result, err := conn.Query(ctx, "SELECT raw FROM test_json_table SETTINGS max_memory_usage=0")
	if err != nil {
		log.Fatal(err)
	}
	defer result.Close()

	var n int
	for result.Next() {
		var raw clickhouse.JSON
		if err := result.Scan(&raw); err != nil {
			log.Fatal(err)
		}
		n++
	}
	if err := result.Err(); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("read %d rows OK\n", n)
}

Run with:

docker run --rm -p 9002:9000 -p 8123:8123 \
  -e CLICKHOUSE_PASSWORD=password \
  clickhouse/clickhouse-server:26.2

go run .

Error log

read data: failed to decode block from 127.0.0.1:9002 (conn_id=1, compression=none): unexpected value 97 for boolean

In production with larger datasets the panic form is observed:

panic: runtime error: makeslice: len out of range

goroutine 120050 [running]:
  github.com/ClickHouse/clickhouse-go/v2/lib/column.(*JSON).decodeObjectData_v1
    lib/column/json_deprecated.go:111
  github.com/ClickHouse/clickhouse-go/v2/lib/column.(*JSON).Decode
    lib/column/json.go:768
  github.com/ClickHouse/clickhouse-go/v2/lib/column.(*Dynamic).decodeData_v1
    lib/column/dynamic_deprecated.go:174
  github.com/ClickHouse/clickhouse-go/v2/lib/column.(*Dynamic).Decode
  github.com/ClickHouse/clickhouse-go/v2/lib/column.(*Array).Decode
    lib/column/array.go:227
  github.com/ClickHouse/clickhouse-go/v2/lib/proto.(*Block).Decode
    lib/proto/block.go:232
  github.com/ClickHouse/clickhouse-go/v2.(*connect).readData
    conn_process.go:107

Root cause (hypothesis)

ClickHouse 26.2 sends JSON column data in the deprecated v0 wire format
(output_format_native_use_flattened_dynamic_and_json_serialization=0 by default). In this format
each JSON path with multiple inferred types is encoded as a Dynamic column with N variant types.

Dynamic.decodeHeader_v1 reads the N type names from the wire, then inserts "SharedVariant" and
re-sorts the list alphabetically. This shifts the driver's discriminator→column index mapping
relative to what the server wrote. For example, if a path has variants ["Array(Int64)", "String"],
the server uses discriminator 1 = String, but the driver maps discriminator 1 → SharedVariant
(which sorts between "Array..." and "String..."). Every subsequent column read is then offset,
causing the decoder to consume the wrong bytes — producing a makeslice panic, an unexpected-boolean
error, or an unexpected-packet error depending on what those bytes look like to the next decoder.

Details

Environment

  • clickhouse-go version: v2.45.0
  • Interface: ClickHouse API (clickhouse.Open)
  • Go version: 1.26.0
  • Operating system: macOS / darwin arm64
  • ClickHouse version: 26.2.15.4
  • Is it a ClickHouse Cloud? No (also reproduces on self-hosted)
  • ClickHouse Server non-default settings: none
    (output_format_native_use_flattened_dynamic_and_json_serialization=0 is the default)
  • CREATE TABLE statement:
CREATE TABLE test_json_table (raw JSON(max_dynamic_paths=32))
ENGINE = MergeTree ORDER BY tuple()

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions