ClickHouse / clickhouse-go

Golang driver for ClickHouse
Apache License 2.0
2.88k stars 553 forks source link

An error occurs when querying fields of type Tuple(Tuple(UInt16, UInt16), Tuple(UInt16, UInt16)) #1245

Closed monchickey closed 6 months ago

monchickey commented 6 months ago

Describe the bug

When the field type in the data table is Tuple(Tuple(UInt16, UInt16), Tuple(UInt16, UInt16)), an error will be reported when querying using the database/sql interface. An error will also be reported when using the native interface to query without specifying a specific type.

Steps to reproduce

  1. CREATE TABLE
    CREATE TABLE test_tuple
    (
        `id` Int32,
        `segment` Tuple(Tuple(UInt16, UInt16), Tuple(UInt16, UInt16))
    )
    ENGINE = Memory
  2. INSERT samples data
    insert into test_tuple values (1, ((1,3),(8,9)))
    insert into test_tuple values (2, ((2,6),(10,10)))
  3. Verify write
    select * from test_tuple
    ┌─id─┬─segment─────────┐
    │  1 │ ((1,3),(8,9))   │
    │  2 │ ((2,6),(10,10)) │
    └────┴─────────────────┘

Expected behaviour

The following code runs the query and it should execute normally.

Code example

1. Query using database/sql interface

func TestSqlNestedTuple(t *testing.T) {
    conn := clickhouse.OpenDB(&clickhouse.Options{
        Addr: []string{"127.0.0.1:9000"},
        Auth: clickhouse.Auth{
            Database: "default",
            Username: "default",
            Password: "",
        },
        Debug: true,
        Debugf: func(format string, v ...any) {
            fmt.Printf(format+"\n", v...)
        },
    })
    defer conn.Close()

    rows, err := conn.Query("SELECT * FROM test_tuple")
    assert.Nil(t, err)
    defer rows.Close()
    for rows.Next() {
    }
}

The error message:

--- FAIL: TestSqlNestedTuple (0.01s)
panic: reflect: call of reflect.Value.Set on zero Value [recovered]
        panic: reflect: call of reflect.Value.Set on zero Value

goroutine 6 [running]:
testing.tRunner.func1.2({0x9f5720, 0xc000012240})
        /usr/local/go/src/testing/testing.go:1545 +0x238
testing.tRunner.func1()
        /usr/local/go/src/testing/testing.go:1548 +0x397
panic({0x9f5720?, 0xc000012240?})
        /usr/local/go/src/runtime/panic.go:914 +0x21f
reflect.flag.mustBeExportedSlow(0xc00009d350?)
        /usr/local/go/src/reflect/value.go:247 +0xb6
reflect.flag.mustBeExported(...)
        /usr/local/go/src/reflect/value.go:241
reflect.Value.Set({0x9c2160?, 0xc000012210?, 0x9c2160?}, {0x0?, 0x0?, 0xc0000e7b50?})
        /usr/local/go/src/reflect/value.go:2255 +0x9a
github.com/ClickHouse/clickhouse-go/v2/lib/column.(*Tuple).ScanRow(0xc00003f140?, {0xc00003f140?, 0xc000012210?}, 0xa06f60?)
        /opt/gopath/pkg/mod/github.com/!click!house/clickhouse-go/v2@v2.22.2/lib/column/tuple.go:481 +0x11f
github.com/ClickHouse/clickhouse-go/v2/lib/column.(*Tuple).Row(0xc000092690, 0x9ccb40?, 0x0)
        /opt/gopath/pkg/mod/github.com/!click!house/clickhouse-go/v2@v2.22.2/lib/column/tuple.go:139 +0xce
github.com/ClickHouse/clickhouse-go/v2.(*stdRows).Next(0xc00021e170, {0xc00021c2a0, 0x2, 0x0?})
        /opt/gopath/pkg/mod/github.com/!click!house/clickhouse-go/v2@v2.22.2/clickhouse_std.go:396 +0x2e8
database/sql.(*Rows).nextLocked(0xc000234090)
        /usr/local/go/src/database/sql/sql.go:3019 +0x107
database/sql.(*Rows).Next.func1()
        /usr/local/go/src/database/sql/sql.go:2994 +0x29
database/sql.withLock({0xbdc6a8, 0xc0002340c8}, 0xc0000e7d90)
        /usr/local/go/src/database/sql/sql.go:3502 +0x82
database/sql.(*Rows).Next(0xc000234090)
        /usr/local/go/src/database/sql/sql.go:2993 +0x85
ch-go-tuple_test.TestSqlNestedTuple(0x0?)
        /opt/programming/ch-go-tuple/main_test.go:92 +0x1a5
testing.tRunner(0xc0000ec4e0, 0xb43920)
        /usr/local/go/src/testing/testing.go:1595 +0xff
created by testing.(*T).Run in goroutine 1
        /usr/local/go/src/testing/testing.go:1648 +0x3ad

panic at rows.Next

2. Query using native interface (Use reflection type to receive)

func TestNativeNestedTuple(t *testing.T) {
    conn, err := clickhouse.Open(&clickhouse.Options{
        Addr: []string{"127.0.0.1:9000"},
        Auth: clickhouse.Auth{
            Database: "default",
            Username: "default",
            Password: "",
        },
        Debug: true,
        Debugf: func(format string, v ...any) {
            fmt.Printf(format+"\n", v...)
        },
    })
    assert.Nil(t, err)
    defer conn.Close()

    rows, err := conn.Query(context.Background(), "SELECT * FROM test_tuple")
    assert.Nil(t, err)
    defer rows.Close()
    for rows.Next() {
        columns := rows.Columns()
        columnTypes := rows.ColumnTypes()
        results := make([]interface{}, len(columns))
        for i, columnType := range columnTypes {
            results[i] = reflect.New(columnType.ScanType()).Interface()

            fmt.Printf("column: %s, type: %s, value type: %v\n",
                columnType.Name(), columnType.ScanType(), columnType.DatabaseTypeName())
        }

        err := rows.Scan(results...)
        assert.Nil(t, err)
    }
}

The error message:

column: id, type: int32, value type: Int32
column: segment, type: []interface {}, value type: Tuple(Tuple(UInt16, UInt16), Tuple(UInt16, UInt16))
--- FAIL: TestNativeNestedTuple (0.01s)
panic: reflect: call of reflect.Value.Set on zero Value [recovered]
        panic: reflect: call of reflect.Value.Set on zero Value

goroutine 6 [running]:
testing.tRunner.func1.2({0x9f56e0, 0xc000012258})
        /usr/local/go/src/testing/testing.go:1545 +0x238
testing.tRunner.func1()
        /usr/local/go/src/testing/testing.go:1548 +0x397
panic({0x9f56e0?, 0xc000012258?})
        /usr/local/go/src/runtime/panic.go:914 +0x21f
reflect.flag.mustBeExportedSlow(0x67?)
        /usr/local/go/src/reflect/value.go:247 +0xb6
reflect.flag.mustBeExported(...)
        /usr/local/go/src/reflect/value.go:241
reflect.Value.Set({0x9c20e0?, 0xc000012228?, 0x9c20e0?}, {0x0?, 0x0?, 0x0?})
        /usr/local/go/src/reflect/value.go:2255 +0x9a
github.com/ClickHouse/clickhouse-go/v2/lib/column.(*Tuple).ScanRow(0xc000024120?, {0xc00003f1c0?, 0xc000012228?}, 0x4b6ef1?)
        /opt/gopath/pkg/mod/github.com/!click!house/clickhouse-go/v2@v2.22.2/lib/column/tuple.go:481 +0x11f
github.com/ClickHouse/clickhouse-go/v2.scan(0xc00003f040, 0x1, {0xc000080b40?, 0x2, 0x0?})
        /opt/gopath/pkg/mod/github.com/!click!house/clickhouse-go/v2@v2.22.2/scan.go:82 +0x19d
github.com/ClickHouse/clickhouse-go/v2.(*rows).Scan(0xbda700?, {0xc000080b40?, 0xab2c6f?, 0x25?})
        /opt/gopath/pkg/mod/github.com/!click!house/clickhouse-go/v2@v2.22.2/clickhouse_rows.go:78 +0xa5
ch-go-tuple_test.TestNativeNestedTuple(0x0?)
        /opt/programming/ch-go-tuple/main_test.go:66 +0x309
testing.tRunner(0xc0000ec820, 0xb438f0)
        /usr/local/go/src/testing/testing.go:1595 +0xff
created by testing.(*T).Run in goroutine 1
        /usr/local/go/src/testing/testing.go:1648 +0x3ad

panic at rows.Scan, The ScanType of column segment is returned as []interface{}, Using the [][]interface{} type receives normally

    for rows.Next() {
        columns := rows.Columns()
        columnTypes := rows.ColumnTypes()
        results := make([]interface{}, len(columns))
        for i, columnType := range columnTypes {
            if columnType.Name() == "segment" {
                var seg [][]interface{}
                results[i] = reflect.New(reflect.TypeOf(seg)).Interface()
            } else {
                results[i] = reflect.New(columnType.ScanType()).Interface()
            }

            fmt.Printf("column: %s, type: %s, value type: %v\n",
                columnType.Name(), columnType.ScanType(), columnType.DatabaseTypeName())
        }

        err := rows.Scan(results...)
        assert.Nil(t, err)
    }

This code can run successfully.

Configuration

Environment

ClickHouse server

jkaflik commented 6 months ago

Hi @monchickey

I've addressed panic in this issue: https://github.com/ClickHouse/clickhouse-go/pull/1249

Unnamed tuple scan is supported only against struct, map or slice. If you want to use any/interface{} use named tuple.