Skip to content

Commit

Permalink
Fix panic on tuple scan on []any (#1249)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkaflik authored Mar 25, 2024
1 parent 09e7e4e commit f860696
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 13 deletions.
2 changes: 1 addition & 1 deletion lib/column/tuple.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ func (col *Tuple) scan(targetType reflect.Type, row int) (reflect.Value, error)
//tuples can be scanned into slices - specifically default for unnamed tuples
rSlice, err := col.scanSlice(targetType, row)
if err != nil {
return reflect.Value{}, nil
return reflect.Value{}, err
}
return rSlice, nil
case reflect.Interface:
Expand Down
60 changes: 60 additions & 0 deletions tests/issues/1245_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package issues

import (
"context"
"testing"

clickhouse_tests "github.com/ClickHouse/clickhouse-go/v2/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test1245Native(t *testing.T) {
testEnv, err := clickhouse_tests.GetTestEnvironment("issues")
require.NoError(t, err)
conn, err := clickhouse_tests.TestClientWithDefaultSettings(testEnv)
require.NoError(t, err)
ctx := context.Background()
const ddl = "CREATE TABLE IF NOT EXISTS test_1245 (`id` Int32, `segment` Tuple(Tuple(UInt16, UInt16), Tuple(UInt16, UInt16))) Engine = Memory"
require.NoError(t, conn.Exec(ctx, ddl))

defer func() {
require.NoError(t, conn.Exec(ctx, "DROP TABLE IF EXISTS test_1245"))
}()

require.NoError(t, conn.Exec(ctx, "INSERT INTO test_1245 VALUES (1, ((1,3),(8,9)))"))

rows, err := conn.Query(ctx, "SELECT id, segment FROM test_1245")
require.NoError(t, err)
defer rows.Close()
assert.True(t, rows.Next())
var id int32
var segment []any
assert.Errorf(t, rows.Scan(&id, &segment), "cannot use interface for unnamed tuples, use slice")
}

func Test1245DatabaseSQLDriver(t *testing.T) {
testEnv, err := clickhouse_tests.GetTestEnvironment("issues")
require.NoError(t, err)
conn, err := clickhouse_tests.TestDatabaseSQLClientWithDefaultSettings(testEnv)
require.NoError(t, err)
const ddl = "CREATE TABLE IF NOT EXISTS test_1245 (`id` Int32, `segment` Tuple(Tuple(UInt16, UInt16), Tuple(UInt16, UInt16))) Engine = Memory"
_, err = conn.Exec(ddl)
require.NoError(t, err)

defer func() {
_, err = conn.Exec("DROP TABLE IF EXISTS test_1245")
require.NoError(t, err)
}()

_, err = conn.Exec("INSERT INTO test_1245 VALUES (1, ((1,3),(8,9)))")
require.NoError(t, err)

rows, err := conn.Query("SELECT id, segment FROM test_1245")
require.NoError(t, err)
defer rows.Close()
assert.True(t, rows.Next())
var id int32
var segment []any
assert.Errorf(t, rows.Scan(&id, &segment), "cannot use interface for unnamed tuples, use slice")
}
146 changes: 134 additions & 12 deletions tests/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,13 @@ package tests
import (
"context"
"crypto/tls"
"database/sql"
"encoding/json"
"errors"
"fmt"
"github.com/ClickHouse/clickhouse-go/v2"
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
"github.com/ClickHouse/clickhouse-go/v2/lib/proto"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
"math/rand"
"net"
"net/url"
"os"
"path"
"path/filepath"
Expand All @@ -43,6 +35,17 @@ import (
"strings"
"testing"
"time"

"github.com/ClickHouse/clickhouse-go/v2"
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
"github.com/ClickHouse/clickhouse-go/v2/lib/proto"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)

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

func TestClientWithDefaultSettings(env ClickHouseTestEnvironment) (driver.Conn, error) {
func TestClientDefaultSettings(env ClickHouseTestEnvironment) clickhouse.Settings {
settings := clickhouse.Settings{}

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

return testClientWithDefaultOptions(env, settings)
return settings
}

func TestClientWithDefaultSettings(env ClickHouseTestEnvironment) (driver.Conn, error) {
return testClientWithDefaultOptions(env, TestClientDefaultSettings(env))
}

func TestDatabaseSQLClientWithDefaultOptions(env ClickHouseTestEnvironment, settings clickhouse.Settings) (*sql.DB, error) {
opts := ClientOptionsFromEnv(env, settings)
return sql.Open("clickhouse", optionsToDSN(&opts))
}

func TestDatabaseSQLClientWithDefaultSettings(env ClickHouseTestEnvironment) (*sql.DB, error) {
return TestDatabaseSQLClientWithDefaultOptions(env, TestClientDefaultSettings(env))
}

func GetConnection(testSet string, settings clickhouse.Settings, tlsConfig *tls.Config, compression *clickhouse.Compression) (driver.Conn, error) {
Expand Down Expand Up @@ -627,3 +643,109 @@ func CreateTinyProxyTestEnvironment(t *testing.T) (TinyProxyTestEnvironment, err
Container: container,
}, nil
}

func optionsToDSN(o *clickhouse.Options) string {
var u url.URL

if o.Protocol == clickhouse.Native {
u.Scheme = "clickhouse"
} else {
if o.TLS != nil {
u.Scheme = "https"
} else {
u.Scheme = "http"
}
}

u.Host = strings.Join(o.Addr, ",")
u.User = url.UserPassword(o.Auth.Username, o.Auth.Password)
u.Path = fmt.Sprintf("/%s", o.Auth.Database)

params := u.Query()

if o.TLS != nil {
params.Set("secure", "true")
}

if o.TLS != nil && o.TLS.InsecureSkipVerify {
params.Set("skip_verify", "true")
}

if o.Debug {
params.Set("debug", "true")
}

if o.Compression != nil {
params.Set("compress", o.Compression.Method.String())
if o.Compression.Level > 0 {
params.Set("compress_level", strconv.Itoa(o.Compression.Level))
}
}

if o.MaxCompressionBuffer > 0 {
params.Set("max_compression_buffer", strconv.Itoa(o.MaxCompressionBuffer))
}

if o.DialTimeout > 0 {
params.Set("dial_timeout", o.DialTimeout.String())
}

if o.BlockBufferSize > 0 {
params.Set("block_buffer_size", strconv.Itoa(int(o.BlockBufferSize)))
}

if o.ReadTimeout > 0 {
params.Set("read_timeout", o.ReadTimeout.String())
}

if o.ConnOpenStrategy != 0 {
var strategy string
switch o.ConnOpenStrategy {
case clickhouse.ConnOpenInOrder:
strategy = "in_order"
case clickhouse.ConnOpenRoundRobin:
strategy = "round_robin"
}

params.Set("connection_open_strategy", strategy)
}

if o.MaxOpenConns > 0 {
params.Set("max_open_conns", strconv.Itoa(o.MaxOpenConns))
}

if o.MaxIdleConns > 0 {
params.Set("max_idle_conns", strconv.Itoa(o.MaxIdleConns))
}

if o.ConnMaxLifetime > 0 {
params.Set("conn_max_lifetime", o.ConnMaxLifetime.String())
}

if o.ClientInfo.Products != nil {
var products []string
for _, product := range o.ClientInfo.Products {
products = append(products, fmt.Sprintf("%s/%s", product.Name, product.Version))
}
params.Set("client_info_product", strings.Join(products, ","))
}

for k, v := range o.Settings {
switch v := v.(type) {
case bool:
if v {
params.Set(k, "true")
} else {
params.Set(k, "false")
}
case int:
params.Set(k, strconv.Itoa(v))
case string:
params.Set(k, v)
}
}

u.RawQuery = params.Encode()

return u.String()
}

0 comments on commit f860696

Please sign in to comment.