Skip to content

Commit f860696

Browse files
authored
Fix panic on tuple scan on []any (#1249)
1 parent 09e7e4e commit f860696

File tree

3 files changed

+195
-13
lines changed

3 files changed

+195
-13
lines changed

lib/column/tuple.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ func (col *Tuple) scan(targetType reflect.Type, row int) (reflect.Value, error)
447447
//tuples can be scanned into slices - specifically default for unnamed tuples
448448
rSlice, err := col.scanSlice(targetType, row)
449449
if err != nil {
450-
return reflect.Value{}, nil
450+
return reflect.Value{}, err
451451
}
452452
return rSlice, nil
453453
case reflect.Interface:

tests/issues/1245_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package issues
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
clickhouse_tests "github.com/ClickHouse/clickhouse-go/v2/tests"
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func Test1245Native(t *testing.T) {
13+
testEnv, err := clickhouse_tests.GetTestEnvironment("issues")
14+
require.NoError(t, err)
15+
conn, err := clickhouse_tests.TestClientWithDefaultSettings(testEnv)
16+
require.NoError(t, err)
17+
ctx := context.Background()
18+
const ddl = "CREATE TABLE IF NOT EXISTS test_1245 (`id` Int32, `segment` Tuple(Tuple(UInt16, UInt16), Tuple(UInt16, UInt16))) Engine = Memory"
19+
require.NoError(t, conn.Exec(ctx, ddl))
20+
21+
defer func() {
22+
require.NoError(t, conn.Exec(ctx, "DROP TABLE IF EXISTS test_1245"))
23+
}()
24+
25+
require.NoError(t, conn.Exec(ctx, "INSERT INTO test_1245 VALUES (1, ((1,3),(8,9)))"))
26+
27+
rows, err := conn.Query(ctx, "SELECT id, segment FROM test_1245")
28+
require.NoError(t, err)
29+
defer rows.Close()
30+
assert.True(t, rows.Next())
31+
var id int32
32+
var segment []any
33+
assert.Errorf(t, rows.Scan(&id, &segment), "cannot use interface for unnamed tuples, use slice")
34+
}
35+
36+
func Test1245DatabaseSQLDriver(t *testing.T) {
37+
testEnv, err := clickhouse_tests.GetTestEnvironment("issues")
38+
require.NoError(t, err)
39+
conn, err := clickhouse_tests.TestDatabaseSQLClientWithDefaultSettings(testEnv)
40+
require.NoError(t, err)
41+
const ddl = "CREATE TABLE IF NOT EXISTS test_1245 (`id` Int32, `segment` Tuple(Tuple(UInt16, UInt16), Tuple(UInt16, UInt16))) Engine = Memory"
42+
_, err = conn.Exec(ddl)
43+
require.NoError(t, err)
44+
45+
defer func() {
46+
_, err = conn.Exec("DROP TABLE IF EXISTS test_1245")
47+
require.NoError(t, err)
48+
}()
49+
50+
_, err = conn.Exec("INSERT INTO test_1245 VALUES (1, ((1,3),(8,9)))")
51+
require.NoError(t, err)
52+
53+
rows, err := conn.Query("SELECT id, segment FROM test_1245")
54+
require.NoError(t, err)
55+
defer rows.Close()
56+
assert.True(t, rows.Next())
57+
var id int32
58+
var segment []any
59+
assert.Errorf(t, rows.Scan(&id, &segment), "cannot use interface for unnamed tuples, use slice")
60+
}

tests/utils.go

+134-12
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,13 @@ package tests
2020
import (
2121
"context"
2222
"crypto/tls"
23+
"database/sql"
2324
"encoding/json"
2425
"errors"
2526
"fmt"
26-
"github.com/ClickHouse/clickhouse-go/v2"
27-
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
28-
"github.com/ClickHouse/clickhouse-go/v2/lib/proto"
29-
"github.com/docker/docker/api/types/container"
30-
"github.com/docker/go-connections/nat"
31-
"github.com/docker/go-units"
32-
"github.com/google/uuid"
33-
"github.com/stretchr/testify/require"
34-
"github.com/testcontainers/testcontainers-go"
35-
"github.com/testcontainers/testcontainers-go/wait"
3627
"math/rand"
3728
"net"
29+
"net/url"
3830
"os"
3931
"path"
4032
"path/filepath"
@@ -43,6 +35,17 @@ import (
4335
"strings"
4436
"testing"
4537
"time"
38+
39+
"github.com/ClickHouse/clickhouse-go/v2"
40+
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
41+
"github.com/ClickHouse/clickhouse-go/v2/lib/proto"
42+
"github.com/docker/docker/api/types/container"
43+
"github.com/docker/go-connections/nat"
44+
"github.com/docker/go-units"
45+
"github.com/google/uuid"
46+
"github.com/stretchr/testify/require"
47+
"github.com/testcontainers/testcontainers-go"
48+
"github.com/testcontainers/testcontainers-go/wait"
4649
)
4750

4851
var testUUID = uuid.NewString()[0:12]
@@ -291,7 +294,7 @@ func testClientWithDefaultOptions(env ClickHouseTestEnvironment, settings clickh
291294
return clickhouse.Open(&opts)
292295
}
293296

294-
func TestClientWithDefaultSettings(env ClickHouseTestEnvironment) (driver.Conn, error) {
297+
func TestClientDefaultSettings(env ClickHouseTestEnvironment) clickhouse.Settings {
295298
settings := clickhouse.Settings{}
296299

297300
if proto.CheckMinVersion(proto.Version{
@@ -305,7 +308,20 @@ func TestClientWithDefaultSettings(env ClickHouseTestEnvironment) (driver.Conn,
305308
settings["insert_quorum_parallel"] = 0
306309
settings["select_sequential_consistency"] = 1
307310

308-
return testClientWithDefaultOptions(env, settings)
311+
return settings
312+
}
313+
314+
func TestClientWithDefaultSettings(env ClickHouseTestEnvironment) (driver.Conn, error) {
315+
return testClientWithDefaultOptions(env, TestClientDefaultSettings(env))
316+
}
317+
318+
func TestDatabaseSQLClientWithDefaultOptions(env ClickHouseTestEnvironment, settings clickhouse.Settings) (*sql.DB, error) {
319+
opts := ClientOptionsFromEnv(env, settings)
320+
return sql.Open("clickhouse", optionsToDSN(&opts))
321+
}
322+
323+
func TestDatabaseSQLClientWithDefaultSettings(env ClickHouseTestEnvironment) (*sql.DB, error) {
324+
return TestDatabaseSQLClientWithDefaultOptions(env, TestClientDefaultSettings(env))
309325
}
310326

311327
func GetConnection(testSet string, settings clickhouse.Settings, tlsConfig *tls.Config, compression *clickhouse.Compression) (driver.Conn, error) {
@@ -627,3 +643,109 @@ func CreateTinyProxyTestEnvironment(t *testing.T) (TinyProxyTestEnvironment, err
627643
Container: container,
628644
}, nil
629645
}
646+
647+
func optionsToDSN(o *clickhouse.Options) string {
648+
var u url.URL
649+
650+
if o.Protocol == clickhouse.Native {
651+
u.Scheme = "clickhouse"
652+
} else {
653+
if o.TLS != nil {
654+
u.Scheme = "https"
655+
} else {
656+
u.Scheme = "http"
657+
}
658+
}
659+
660+
u.Host = strings.Join(o.Addr, ",")
661+
u.User = url.UserPassword(o.Auth.Username, o.Auth.Password)
662+
u.Path = fmt.Sprintf("/%s", o.Auth.Database)
663+
664+
params := u.Query()
665+
666+
if o.TLS != nil {
667+
params.Set("secure", "true")
668+
}
669+
670+
if o.TLS != nil && o.TLS.InsecureSkipVerify {
671+
params.Set("skip_verify", "true")
672+
}
673+
674+
if o.Debug {
675+
params.Set("debug", "true")
676+
}
677+
678+
if o.Compression != nil {
679+
params.Set("compress", o.Compression.Method.String())
680+
if o.Compression.Level > 0 {
681+
params.Set("compress_level", strconv.Itoa(o.Compression.Level))
682+
}
683+
}
684+
685+
if o.MaxCompressionBuffer > 0 {
686+
params.Set("max_compression_buffer", strconv.Itoa(o.MaxCompressionBuffer))
687+
}
688+
689+
if o.DialTimeout > 0 {
690+
params.Set("dial_timeout", o.DialTimeout.String())
691+
}
692+
693+
if o.BlockBufferSize > 0 {
694+
params.Set("block_buffer_size", strconv.Itoa(int(o.BlockBufferSize)))
695+
}
696+
697+
if o.ReadTimeout > 0 {
698+
params.Set("read_timeout", o.ReadTimeout.String())
699+
}
700+
701+
if o.ConnOpenStrategy != 0 {
702+
var strategy string
703+
switch o.ConnOpenStrategy {
704+
case clickhouse.ConnOpenInOrder:
705+
strategy = "in_order"
706+
case clickhouse.ConnOpenRoundRobin:
707+
strategy = "round_robin"
708+
}
709+
710+
params.Set("connection_open_strategy", strategy)
711+
}
712+
713+
if o.MaxOpenConns > 0 {
714+
params.Set("max_open_conns", strconv.Itoa(o.MaxOpenConns))
715+
}
716+
717+
if o.MaxIdleConns > 0 {
718+
params.Set("max_idle_conns", strconv.Itoa(o.MaxIdleConns))
719+
}
720+
721+
if o.ConnMaxLifetime > 0 {
722+
params.Set("conn_max_lifetime", o.ConnMaxLifetime.String())
723+
}
724+
725+
if o.ClientInfo.Products != nil {
726+
var products []string
727+
for _, product := range o.ClientInfo.Products {
728+
products = append(products, fmt.Sprintf("%s/%s", product.Name, product.Version))
729+
}
730+
params.Set("client_info_product", strings.Join(products, ","))
731+
}
732+
733+
for k, v := range o.Settings {
734+
switch v := v.(type) {
735+
case bool:
736+
if v {
737+
params.Set(k, "true")
738+
} else {
739+
params.Set(k, "false")
740+
}
741+
case int:
742+
params.Set(k, strconv.Itoa(v))
743+
case string:
744+
params.Set(k, v)
745+
}
746+
}
747+
748+
u.RawQuery = params.Encode()
749+
750+
return u.String()
751+
}

0 commit comments

Comments
 (0)