From 70482ed0e3b5fe2e694648ebd58d7a1218c1f610 Mon Sep 17 00:00:00 2001 From: Aleksandr Razumov Date: Thu, 19 May 2022 19:48:16 +0300 Subject: [PATCH 01/22] fix(proto): fix TraceID and SpanID encode ClickHouse is using little endian on the wire, so swap endianness to match raw value. Ref: ClickHouse/ClickHouse#34369 Ref: ClickHouse/ClickHouse#33723 Ref: go-faster/ch#49 --- lib/proto/query.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/proto/query.go b/lib/proto/query.go index b88bbd9045..492f5c6d03 100644 --- a/lib/proto/query.go +++ b/lib/proto/query.go @@ -18,6 +18,7 @@ package proto import ( + stdbin "encoding/binary" "fmt" "os" @@ -65,6 +66,13 @@ func (q *Query) Encode(encoder *binary.Encoder, revision uint64) error { return encoder.String(q.Body) } +func swap64(b []byte) { + for i := 0; i < len(b); i += 8 { + u := stdbin.BigEndian.Uint64(b[i:]) + stdbin.LittleEndian.PutUint64(b[i:], u) + } +} + func (q *Query) encodeClientInfo(encoder *binary.Encoder, revision uint64) error { encoder.Byte(ClientQueryInitial) encoder.String(q.InitialUser) // initial_user @@ -97,10 +105,12 @@ func (q *Query) encodeClientInfo(encoder *binary.Encoder, revision uint64) error encoder.Byte(1) { v := q.Span.TraceID() + swap64(v[:]) // https://github.com/ClickHouse/ClickHouse/issues/34369 encoder.Raw(v[:]) } { v := q.Span.SpanID() + swap64(v[:]) // https://github.com/ClickHouse/ClickHouse/issues/34369 encoder.Raw(v[:]) } encoder.String(q.Span.TraceState().String()) From be0202e87086f409548304274f60d39ca54e99ad Mon Sep 17 00:00:00 2001 From: Vitaly Orlov Date: Wed, 25 May 2022 17:18:13 +0300 Subject: [PATCH 02/22] Run test in all subdirectories --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 582eaaa7b7..07f90a6a5a 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -47,5 +47,5 @@ jobs: - name: Run tests run: | go test -v . - go test -v ./tests + go test -v ./tests/.. go test -v ./lib/... From b289a28bd0759148d25491e70493bf6175bb26d7 Mon Sep 17 00:00:00 2001 From: Vitaly Orlov Date: Wed, 25 May 2022 17:31:03 +0300 Subject: [PATCH 03/22] One command --- .github/workflows/run-tests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 07f90a6a5a..a3195d65ce 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -46,6 +46,4 @@ jobs: - name: Run tests run: | - go test -v . - go test -v ./tests/.. - go test -v ./lib/... + go test -v ./... From 13697d7cc3331a367d68a451f0d4eb9ca977fbed Mon Sep 17 00:00:00 2001 From: Vitaly Orlov Date: Wed, 25 May 2022 17:36:49 +0300 Subject: [PATCH 04/22] FIx --- .github/workflows/run-tests.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index a3195d65ce..205e68a8fc 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -46,4 +46,7 @@ jobs: - name: Run tests run: | - go test -v ./... + go test -v . + go test -v ./tests/... + go test -v ./lib/... + From 1ef06927b2ea4283e69c8182851880779a42fb31 Mon Sep 17 00:00:00 2001 From: Vitaly Orlov Date: Wed, 25 May 2022 17:37:07 +0300 Subject: [PATCH 05/22] Fix From dd42d5cb73046c5504ac256872d48f30a5402809 Mon Sep 17 00:00:00 2001 From: Vitaly Orlov Date: Wed, 25 May 2022 17:37:31 +0300 Subject: [PATCH 06/22] Fix --- .github/workflows/run-tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 205e68a8fc..8bcb143e87 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -49,4 +49,3 @@ jobs: go test -v . go test -v ./tests/... go test -v ./lib/... - From e8018eb023c948f8febabe702d2770c181e0b1e6 Mon Sep 17 00:00:00 2001 From: Vitaly Orlov Date: Thu, 26 May 2022 11:47:23 +0300 Subject: [PATCH 07/22] Add TestConnFailoverConnOpenRoundRobin test --- tests/conn_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/conn_test.go b/tests/conn_test.go index 6c62b07950..c1140fed6d 100644 --- a/tests/conn_test.go +++ b/tests/conn_test.go @@ -93,6 +93,31 @@ func TestConnFailover(t *testing.T) { } } } +func TestConnFailoverConnOpenRoundRobin(t *testing.T) { + conn, err := clickhouse.Open(&clickhouse.Options{ + Addr: []string{ + "127.0.0.1:9001", + "127.0.0.1:9002", + "127.0.0.1:9000", + }, + Auth: clickhouse.Auth{ + Database: "default", + Username: "default", + Password: "", + }, + Compression: &clickhouse.Compression{ + Method: clickhouse.CompressionLZ4, + }, + ConnOpenStrategy: clickhouse.ConnOpenRoundRobin, + // Debug: true, + }) + if assert.NoError(t, err) { + if err := conn.Ping(context.Background()); assert.NoError(t, err) { + t.Log(conn.ServerVersion()) + t.Log(conn.Ping(context.Background())) + } + } +} func TestPingDeadline(t *testing.T) { conn, err := clickhouse.Open(&clickhouse.Options{ Addr: []string{"127.0.0.1:9000"}, From 96327a5b14c0ae769ee93bfa6188f60300ad0906 Mon Sep 17 00:00:00 2001 From: Gregory Petrosyan Date: Fri, 20 May 2022 12:38:03 +0300 Subject: [PATCH 08/22] Enable scanning of String columns into encoding.BinaryUnmarshaler This adds to String columns functionality already present for FixedString ones. --- lib/column/string.go | 3 +++ lib/column/string_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 lib/column/string_test.go diff --git a/lib/column/string.go b/lib/column/string.go index f66c5f4e5a..602aebb003 100644 --- a/lib/column/string.go +++ b/lib/column/string.go @@ -18,6 +18,7 @@ package column import ( + "encoding" "fmt" "reflect" @@ -54,6 +55,8 @@ func (col *String) ScanRow(dest interface{}, row int) error { case **string: *d = new(string) **d = v[row] + case encoding.BinaryUnmarshaler: + return d.UnmarshalBinary(binary.Str2Bytes(v[row])) default: return &ColumnConverterError{ Op: "ScanRow", diff --git a/lib/column/string_test.go b/lib/column/string_test.go new file mode 100644 index 0000000000..8ced86ce96 --- /dev/null +++ b/lib/column/string_test.go @@ -0,0 +1,29 @@ +package column + +import "testing" + +type binaryUnmarshaler struct { + data []byte +} + +func (b *binaryUnmarshaler) UnmarshalBinary(data []byte) error { + b.data = append(b.data[:0], data...) + return nil +} + +func TestString_ScanRow(t *testing.T) { + t.Run("encoding.BinaryUnmarshaler", func(t *testing.T) { + col := String([]string{"hello", "world"}) + + var dest binaryUnmarshaler + for i, s := range col { + err := col.ScanRow(&dest, i) + if err != nil { + t.Fatalf("unexpected ScanRow error: %v", err) + } + if string(dest.data) != s { + t.Fatalf("ScanRow resulted in %q instead of %q", dest.data, s) + } + } + }) +} From 94bc7f1adf3849c14620fb398ab36daaf24a03e9 Mon Sep 17 00:00:00 2001 From: Vitaly Orlov Date: Thu, 26 May 2022 12:49:27 +0300 Subject: [PATCH 09/22] Correct failover retry with ConnOpenRoundRobin --- clickhouse.go | 10 +++++++--- clickhouse_std.go | 10 +++++++--- tests/std/conn_test.go | 7 +++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/clickhouse.go b/clickhouse.go index 8994d73558..5b4a36d3fb 100644 --- a/clickhouse.go +++ b/clickhouse.go @@ -187,9 +187,13 @@ func (ch *clickhouse) Stats() driver.Stats { func (ch *clickhouse) dial(ctx context.Context) (conn *connect, err error) { connID := int(atomic.AddInt64(&ch.connID, 1)) - for num := range ch.opt.Addr { - if ch.opt.ConnOpenStrategy == ConnOpenRoundRobin { - num = int(connID) % len(ch.opt.Addr) + for i := range ch.opt.Addr { + var num int + switch ch.opt.ConnOpenStrategy { + case ConnOpenInOrder: + num = i + case ConnOpenRoundRobin: + num = (int(connID) + i) % len(ch.opt.Addr) } if conn, err = dial(ctx, ch.opt.Addr[num], connID, ch.opt); err == nil { return conn, nil diff --git a/clickhouse_std.go b/clickhouse_std.go index 62c5def696..12e99d7c0b 100644 --- a/clickhouse_std.go +++ b/clickhouse_std.go @@ -50,9 +50,13 @@ func (o *stdConnOpener) Connect(ctx context.Context) (_ driver.Conn, err error) conn *connect connID = int(atomic.AddInt64(&globalConnID, 1)) ) - for num := range o.opt.Addr { - if o.opt.ConnOpenStrategy == ConnOpenRoundRobin { - num = int(connID) % len(o.opt.Addr) + for i := range o.opt.Addr { + var num int + switch o.opt.ConnOpenStrategy { + case ConnOpenInOrder: + num = i + case ConnOpenRoundRobin: + num = (int(connID) + i) % len(o.opt.Addr) } if conn, err = dial(ctx, o.opt.Addr[num], connID, o.opt); err == nil { return &stdDriver{ diff --git a/tests/std/conn_test.go b/tests/std/conn_test.go index 4023df8dea..cc13ece035 100644 --- a/tests/std/conn_test.go +++ b/tests/std/conn_test.go @@ -45,6 +45,13 @@ func TestStdConnFailover(t *testing.T) { } } } +func TestStdConnFailoverConnOpenRoundRobin(t *testing.T) { + if conn, err := sql.Open("clickhouse", "clickhouse://127.0.0.1:9001,127.0.0.1:9002,127.0.0.1:9003,127.0.0.1:9004,127.0.0.1:9005,127.0.0.1:9006,127.0.0.1:9000/?connection_open_strategy=round_robin"); assert.NoError(t, err) { + if err := conn.PingContext(context.Background()); assert.NoError(t, err) { + t.Log(conn.PingContext(context.Background())) + } + } +} func TestStdPingDeadline(t *testing.T) { if conn, err := sql.Open("clickhouse", "clickhouse://127.0.0.1:9000"); assert.NoError(t, err) { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-time.Second)) From 42e6959ff45a3a3c9b5452e39f6262b0b3fd23f9 Mon Sep 17 00:00:00 2001 From: Ivan Blinkov Date: Fri, 27 May 2022 17:16:01 +0300 Subject: [PATCH 10/22] Update README.md --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index ad4c71842d..717faba01a 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ This minimal tls.Config is normally all that is necessary to connect to the secu If additional TLS parameters are necessary the application code should set the desired fields in the tls.Config struct. That can include specific cipher suites, forcing a particular TLS version (like 1.2 or 1.3), adding an internal CA certificate chain, adding a client certificate (and private key) if required by the ClickHouse server, and most of the other options that come with a more specialized security setup. -## Alternatives +## Third-party alternatives * Database drivers * [mailru/go-clickhouse](https://github.com/mailru/go-clickhouse) (uses the HTTP protocol) @@ -156,8 +156,3 @@ If additional TLS parameters are necessary the application code should set the d * Insert collectors: * [KittenHouse](https://github.com/YuriyNasretdinov/kittenhouse) * [nikepan/clickhouse-bulk](https://github.com/nikepan/clickhouse-bulk) - -### Useful projects - -* [clickhouse-backup](https://github.com/AlexAkulov/clickhouse-backup) -* [go-graphite](https://github.com/go-graphite) From 21a27227c9248e04cd48c7a52d0af2f70e2d45ff Mon Sep 17 00:00:00 2001 From: Ivan Blinkov Date: Wed, 1 Jun 2022 10:32:07 +0300 Subject: [PATCH 11/22] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 717faba01a..a56d9765c9 100644 --- a/README.md +++ b/README.md @@ -146,10 +146,10 @@ If additional TLS parameters are necessary the application code should set the d ## Third-party alternatives -* Database drivers +* Database drivers: * [mailru/go-clickhouse](https://github.com/mailru/go-clickhouse) (uses the HTTP protocol) * [uptrace/go-clickhouse](https://github.com/uptrace/go-clickhouse) (uses the native TCP protocol with `database/sql`-like API) - * drivers with columnar interface : + * Drivers with columnar interface: * [vahid-sohrabloo/chconn](https://github.com/vahid-sohrabloo/chconn) * [go-faster/ch](https://github.com/go-faster/ch) From 0180082f7d190a5865e93455ddf2b0bb06bd2d9d Mon Sep 17 00:00:00 2001 From: Dale McDiarmid Date: Mon, 6 Jun 2022 13:42:03 +0100 Subject: [PATCH 12/22] Correct test matrix --- .github/workflows/run-tests.yml | 8 ++++---- README.md | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 582eaaa7b7..e56c90c1a0 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -22,10 +22,10 @@ jobs: - 1.17 - 1.18 clickhouse: - - 19.11 - - 20.1 - - 21.11 - - 22.1 + - 21.8 + - 22.3 + - 22.4 + - 22.5 - latest services: diff --git a/README.md b/README.md index a56d9765c9..1cbcd10f4c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ There are two version of this driver, v1 and v2, available as separate branches. Users should use v2 which is production ready and [significantly faster than v1](#benchmark). +## Supported ClickHouse Versions + +The driver is tested against the currently [supported versions](https://github.com/ClickHouse/ClickHouse/blob/master/SECURITY.md) of ClickHouse + ## Key features * Uses native ClickHouse TCP client-server protocol From 46b3545548349ae6e2ac120846589157679b5324 Mon Sep 17 00:00:00 2001 From: Dale McDiarmid Date: Mon, 6 Jun 2022 13:48:53 +0100 Subject: [PATCH 13/22] Clickhouse images --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index e56c90c1a0..9dbab3d1b8 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -30,7 +30,7 @@ jobs: services: clickhouse: - image: yandex/clickhouse-server:${{ matrix.clickhouse }} + image: clickhouse/clickhouse-server:${{ matrix.clickhouse }} ports: - 9000:9000 options: --ulimit nofile=262144:262144 From 45911ffdf9606c71826b9ff33f35a0dbc161f674 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jun 2022 15:31:12 +0000 Subject: [PATCH 14/22] Bump github.com/stretchr/testify from 1.7.1 to 1.7.2 Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.1 to 1.7.2. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.7.1...v1.7.2) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f05365702d..84162855d9 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/pierrec/lz4/v4 v4.1.14 github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/shopspring/decimal v1.3.1 - github.com/stretchr/testify v1.7.1 + github.com/stretchr/testify v1.7.2 github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect go.opentelemetry.io/otel/trace v1.7.0 diff --git a/go.sum b/go.sum index 37b6cb87ae..e00360477c 100644 --- a/go.sum +++ b/go.sum @@ -49,8 +49,9 @@ github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= @@ -98,5 +99,6 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From f72607477cf5112f33af8bb89c138f8bdafcb67e Mon Sep 17 00:00:00 2001 From: Dale McDiarmid Date: Mon, 6 Jun 2022 17:43:36 +0100 Subject: [PATCH 15/22] Support maps in values --- bind.go | 73 ++++++++++++++++++++++++++++---------- tests/issues/584_test.go | 75 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 19 deletions(-) create mode 100644 tests/issues/584_test.go diff --git a/bind.go b/bind.go index 8169a2bd58..04adb3661d 100644 --- a/bind.go +++ b/bind.go @@ -84,7 +84,10 @@ func bindPositional(tz *time.Location, query string, args ...interface{}) (_ str return "", nil } } - params[i] = format(tz, v) + params[i], err = format(tz, v) + if err != nil { + return "", err + } } i := 0 query = bindPositionalRe.ReplaceAllStringFunc(query, func(n string) string { @@ -116,7 +119,11 @@ func bindNumeric(tz *time.Location, query string, args ...interface{}) (_ string return "", nil } } - params[fmt.Sprintf("$%d", i+1)] = format(tz, v) + val, err := format(tz, v) + if err != nil { + return "", err + } + params[fmt.Sprintf("$%d", i+1)] = val } query = bindNumericRe.ReplaceAllStringFunc(query, func(n string) string { if _, found := params[n]; !found { @@ -147,7 +154,11 @@ func bindNamed(tz *time.Location, query string, args ...interface{}) (_ string, return "", err } } - params["@"+v.Name] = format(tz, value) + val, err := format(tz, value) + if err != nil { + return "", err + } + params["@"+v.Name] = val } } query = bindNamedRe.ReplaceAllStringFunc(query, func(n string) string { @@ -163,49 +174,73 @@ func bindNamed(tz *time.Location, query string, args ...interface{}) (_ string, return query, nil } -func format(tz *time.Location, v interface{}) string { +func format(tz *time.Location, v interface{}) (string, error) { quote := func(v string) string { return "'" + strings.NewReplacer(`\`, `\\`, `'`, `\'`).Replace(v) + "'" } switch v := v.(type) { case nil: - return "NULL" + return "NULL", nil case string: - return quote(v) + return quote(v), nil case time.Time: switch v.Location().String() { case "Local": - return fmt.Sprintf("toDateTime(%d)", v.Unix()) + return fmt.Sprintf("toDateTime(%d)", v.Unix()), nil case tz.String(): - return v.Format("toDateTime('2006-01-02 15:04:05')") + return v.Format("toDateTime('2006-01-02 15:04:05')"), nil } - return v.Format("toDateTime('2006-01-02 15:04:05', '" + v.Location().String() + "')") + return v.Format("toDateTime('2006-01-02 15:04:05', '" + v.Location().String() + "')"), nil case []interface{}: // tuple elements := make([]string, 0, len(v)) for _, e := range v { - elements = append(elements, format(tz, e)) + val, err := format(tz, e) + if err != nil { + return "", err + } + elements = append(elements, val) } - return "(" + strings.Join(elements, ", ") + ")" + return "(" + strings.Join(elements, ", ") + ")", nil case [][]interface{}: items := make([]string, 0, len(v)) for _, t := range v { - items = append(items, format(tz, t)) + val, err := format(tz, t) + if err != nil { + return "", err + } + items = append(items, val) } - return strings.Join(items, ", ") + return strings.Join(items, ", "), nil case fmt.Stringer: - return quote(v.String()) + return quote(v.String()), nil } switch v := reflect.ValueOf(v); v.Kind() { case reflect.String: - return quote(v.String()) - case reflect.Slice: + return quote(v.String()), nil + case reflect.Slice: // array values := make([]string, 0, v.Len()) for i := 0; i < v.Len(); i++ { - values = append(values, format(tz, v.Index(i).Interface())) + val, err := format(tz, v.Index(i).Interface()) + if err != nil { + return "", err + } + values = append(values, val) + } + return "[" + strings.Join(values, ", ") + "]", nil + case reflect.Map: // map + values := make([]string, 0, len(v.MapKeys())) + for _, key := range v.MapKeys() { + name := key.Interface().(string) + val, err := format(tz, v.MapIndex(key).Interface()) + if err != nil { + return "", err + } + values = append(values, fmt.Sprintf("'%s': %s", name, val)) } - return strings.Join(values, ", ") + return "{" + strings.Join(values, ", ") + "}", nil + } - return fmt.Sprint(v) + return fmt.Sprint(v), nil } func rebind(in []std_driver.NamedValue) []interface{} { diff --git a/tests/issues/584_test.go b/tests/issues/584_test.go new file mode 100644 index 0000000000..2a162b960c --- /dev/null +++ b/tests/issues/584_test.go @@ -0,0 +1,75 @@ +package issues + +import ( + "context" + "github.com/ClickHouse/clickhouse-go/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +func Test584(t *testing.T) { + conn, err := clickhouse.Open(&clickhouse.Options{ + Addr: []string{"127.0.0.1:9000"}, + Compression: &clickhouse.Compression{ + Method: clickhouse.CompressionLZ4, + }, + Settings: clickhouse.Settings{ + "max_execution_time": 60, + }, + }) + require.NoError(t, err) + defer require.NoError(t, conn.Exec(context.Background(), "DROP TABLE issue_584")) + + const ddl = ` + CREATE TABLE issue_584 ( + Col1 Map(String, String) + ) Engine Memory + ` + require.NoError(t, conn.Exec(context.Background(), "DROP TABLE IF EXISTS issue_584")) + require.NoError(t, conn.Exec(context.Background(), ddl)) + require.NoError(t, conn.Exec(context.Background(), "INSERT INTO issue_584 values($1)", map[string]string{ + "key": "value", + })) + var event map[string]string + require.NoError(t, conn.QueryRow(context.Background(), "SELECT * FROM issue_584").Scan(&event)) + assert.Equal(t, map[string]string{ + "key": "value", + }, event) +} + +func Test584Complex(t *testing.T) { + conn, err := clickhouse.Open(&clickhouse.Options{ + Addr: []string{"127.0.0.1:9000"}, + Compression: &clickhouse.Compression{ + Method: clickhouse.CompressionLZ4, + }, + Settings: clickhouse.Settings{ + "max_execution_time": 60, + }, + }) + require.NoError(t, err) + defer require.NoError(t, conn.Exec(context.Background(), "DROP TABLE issue_584")) + + const ddl = ` + CREATE TABLE issue_584 ( + Col1 Map(String, Map(String, Array(UInt8))) + ) Engine Memory + ` + require.NoError(t, conn.Exec(context.Background(), "DROP TABLE IF EXISTS issue_584")) + require.NoError(t, conn.Exec(context.Background(), ddl)) + col1 := map[string]map[string][]uint8{ + "a": { + "b": []uint8{1, 2, 3, 4}, + "c": []uint8{5, 6, 7, 8}, + }, + "d": { + "e": []uint8{10, 11, 12, 13}, + }, + } + require.NoError(t, conn.Exec(context.Background(), "INSERT INTO issue_584 values($1)", col1)) + var event map[string]map[string][]uint8 + require.NoError(t, conn.QueryRow(context.Background(), "SELECT * FROM issue_584").Scan(&event)) + assert.Equal(t, col1, event) + +} From 04816b30ac4011b71d134d84dc30392dd7cdcd68 Mon Sep 17 00:00:00 2001 From: Dale McDiarmid Date: Mon, 6 Jun 2022 18:11:38 +0100 Subject: [PATCH 16/22] fix bind --- bind.go | 8 ++++++-- bind_test.go | 21 ++++++++++++++------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/bind.go b/bind.go index 04adb3661d..34a84b425f 100644 --- a/bind.go +++ b/bind.go @@ -217,7 +217,7 @@ func format(tz *time.Location, v interface{}) (string, error) { switch v := reflect.ValueOf(v); v.Kind() { case reflect.String: return quote(v.String()), nil - case reflect.Slice: // array + case reflect.Slice: values := make([]string, 0, v.Len()) for i := 0; i < v.Len(); i++ { val, err := format(tz, v.Index(i).Interface()) @@ -226,7 +226,7 @@ func format(tz *time.Location, v interface{}) (string, error) { } values = append(values, val) } - return "[" + strings.Join(values, ", ") + "]", nil + return strings.Join(values, ", "), nil case reflect.Map: // map values := make([]string, 0, len(v.MapKeys())) for _, key := range v.MapKeys() { @@ -235,6 +235,10 @@ func format(tz *time.Location, v interface{}) (string, error) { if err != nil { return "", err } + if v.MapIndex(key).Kind() == reflect.Slice { + // assume slices in maps are arrays + val = fmt.Sprintf("[%s]", val) + } values = append(values, fmt.Sprintf("'%s': %s", name, val)) } return "{" + strings.Join(values, ", ") + "}", nil diff --git a/bind_test.go b/bind_test.go index b9eaa9d78a..3ecad9bcc5 100644 --- a/bind_test.go +++ b/bind_test.go @@ -186,8 +186,10 @@ func TestFormatTime(t *testing.T) { tz, err = time.LoadLocation("Europe/London") ) if assert.NoError(t, err) { - if assert.Equal(t, "toDateTime('2022-01-12 15:00:00')", format(t1.Location(), t1)) { - assert.Equal(t, "toDateTime('2022-01-12 15:00:00', 'UTC')", format(tz, t1)) + val, _ := format(t1.Location(), t1) + if assert.Equal(t, "toDateTime('2022-01-12 15:00:00')", val) { + val, _ = format(tz, t1) + assert.Equal(t, "toDateTime('2022-01-12 15:00:00', 'UTC')", val) } } } @@ -197,19 +199,24 @@ func TestStringBasedType(t *testing.T) { SupperString string SupperSupperString string ) - require.Equal(t, "'a'", format(time.UTC, SupperString("a"))) - require.Equal(t, "'a'", format(time.UTC, SupperSupperString("a"))) - require.Equal(t, "'a', 'b', 'c'", format(time.UTC, []SupperSupperString{"a", "b", "c"})) + val, _ := format(time.UTC, SupperString("a")) + require.Equal(t, "'a'", val) + val, _ = format(time.UTC, SupperSupperString("a")) + require.Equal(t, "'a'", val) + val, _ = format(time.UTC, []SupperSupperString{"a", "b", "c"}) + require.Equal(t, "'a', 'b', 'c'", val) } func TestFormatTuple(t *testing.T) { - assert.Equal(t, "('A', 1)", format(time.UTC, []interface{}{"A", 1})) + val, _ := format(time.UTC, []interface{}{"A", 1}) + assert.Equal(t, "('A', 1)", val) { tuples := [][]interface{}{ []interface{}{"A", 1}, []interface{}{"B", 2}, } - assert.Equal(t, "('A', 1), ('B', 2)", format(time.UTC, tuples)) + val, _ = format(time.UTC, tuples) + assert.Equal(t, "('A', 1), ('B', 2)", val) } } From b2a60fff7f2ba78e03c28030a26f5bef967f3cd4 Mon Sep 17 00:00:00 2001 From: Dale McDiarmid Date: Mon, 6 Jun 2022 18:24:04 +0100 Subject: [PATCH 17/22] Defer properly --- tests/issues/546_test.go | 1 - tests/issues/578_test.go | 3 +-- tests/issues/584_test.go | 16 ++++++++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/issues/546_test.go b/tests/issues/546_test.go index 8683fa9ae6..958dccd3f8 100644 --- a/tests/issues/546_test.go +++ b/tests/issues/546_test.go @@ -18,7 +18,6 @@ func Test546(t *testing.T) { Username: "default", Password: "", }, - Debug: true, DialTimeout: time.Second, MaxOpenConns: 10, MaxIdleConns: 5, diff --git a/tests/issues/578_test.go b/tests/issues/578_test.go index 3e3d100f38..086320529f 100644 --- a/tests/issues/578_test.go +++ b/tests/issues/578_test.go @@ -10,8 +10,7 @@ import ( func Test578(t *testing.T) { ctx := context.Background() conn, err := clickhouse.Open(&clickhouse.Options{ - Addr: []string{"127.0.0.1:9000"}, - Debug: true, + Addr: []string{"127.0.0.1:9000"}, }) assert.NoError(t, err) diff --git a/tests/issues/584_test.go b/tests/issues/584_test.go index 2a162b960c..b0e22942d4 100644 --- a/tests/issues/584_test.go +++ b/tests/issues/584_test.go @@ -19,7 +19,9 @@ func Test584(t *testing.T) { }, }) require.NoError(t, err) - defer require.NoError(t, conn.Exec(context.Background(), "DROP TABLE issue_584")) + defer func() { + require.NoError(t, conn.Exec(context.Background(), "DROP TABLE issue_584_complex")) + }() const ddl = ` CREATE TABLE issue_584 ( @@ -49,14 +51,16 @@ func Test584Complex(t *testing.T) { }, }) require.NoError(t, err) - defer require.NoError(t, conn.Exec(context.Background(), "DROP TABLE issue_584")) + defer func() { + require.NoError(t, conn.Exec(context.Background(), "DROP TABLE issue_584_complex")) + }() const ddl = ` - CREATE TABLE issue_584 ( + CREATE TABLE issue_584_complex ( Col1 Map(String, Map(String, Array(UInt8))) ) Engine Memory ` - require.NoError(t, conn.Exec(context.Background(), "DROP TABLE IF EXISTS issue_584")) + require.NoError(t, conn.Exec(context.Background(), "DROP TABLE IF EXISTS issue_584_complex")) require.NoError(t, conn.Exec(context.Background(), ddl)) col1 := map[string]map[string][]uint8{ "a": { @@ -67,9 +71,9 @@ func Test584Complex(t *testing.T) { "e": []uint8{10, 11, 12, 13}, }, } - require.NoError(t, conn.Exec(context.Background(), "INSERT INTO issue_584 values($1)", col1)) + require.NoError(t, conn.Exec(context.Background(), "INSERT INTO issue_584_complex values($1)", col1)) var event map[string]map[string][]uint8 - require.NoError(t, conn.QueryRow(context.Background(), "SELECT * FROM issue_584").Scan(&event)) + require.NoError(t, conn.QueryRow(context.Background(), "SELECT * FROM issue_584_complex").Scan(&event)) assert.Equal(t, col1, event) } From ad9e0b774b1e7d5e9776238b95fb4992b9ff1255 Mon Sep 17 00:00:00 2001 From: Dale McDiarmid Date: Mon, 6 Jun 2022 18:33:22 +0100 Subject: [PATCH 18/22] Support non-string keys --- bind.go | 7 +++++-- tests/issues/584_test.go | 14 +++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/bind.go b/bind.go index 34a84b425f..ac30b5b1b6 100644 --- a/bind.go +++ b/bind.go @@ -230,7 +230,10 @@ func format(tz *time.Location, v interface{}) (string, error) { case reflect.Map: // map values := make([]string, 0, len(v.MapKeys())) for _, key := range v.MapKeys() { - name := key.Interface().(string) + name := fmt.Sprint(key.Interface()) + if key.Kind() == reflect.String { + name = fmt.Sprintf("'%s'", name) + } val, err := format(tz, v.MapIndex(key).Interface()) if err != nil { return "", err @@ -239,7 +242,7 @@ func format(tz *time.Location, v interface{}) (string, error) { // assume slices in maps are arrays val = fmt.Sprintf("[%s]", val) } - values = append(values, fmt.Sprintf("'%s': %s", name, val)) + values = append(values, fmt.Sprintf("%s: %s", name, val)) } return "{" + strings.Join(values, ", ") + "}", nil diff --git a/tests/issues/584_test.go b/tests/issues/584_test.go index b0e22942d4..61121a296e 100644 --- a/tests/issues/584_test.go +++ b/tests/issues/584_test.go @@ -20,7 +20,7 @@ func Test584(t *testing.T) { }) require.NoError(t, err) defer func() { - require.NoError(t, conn.Exec(context.Background(), "DROP TABLE issue_584_complex")) + require.NoError(t, conn.Exec(context.Background(), "DROP TABLE issue_584")) }() const ddl = ` @@ -57,22 +57,22 @@ func Test584Complex(t *testing.T) { const ddl = ` CREATE TABLE issue_584_complex ( - Col1 Map(String, Map(String, Array(UInt8))) + Col1 Map(String, Map(UInt8, Array(UInt8))) ) Engine Memory ` require.NoError(t, conn.Exec(context.Background(), "DROP TABLE IF EXISTS issue_584_complex")) require.NoError(t, conn.Exec(context.Background(), ddl)) - col1 := map[string]map[string][]uint8{ + col1 := map[string]map[uint8][]uint8{ "a": { - "b": []uint8{1, 2, 3, 4}, - "c": []uint8{5, 6, 7, 8}, + 100: []uint8{1, 2, 3, 4}, + 99: []uint8{5, 6, 7, 8}, }, "d": { - "e": []uint8{10, 11, 12, 13}, + 98: []uint8{10, 11, 12, 13}, }, } require.NoError(t, conn.Exec(context.Background(), "INSERT INTO issue_584_complex values($1)", col1)) - var event map[string]map[string][]uint8 + var event map[string]map[uint8][]uint8 require.NoError(t, conn.QueryRow(context.Background(), "SELECT * FROM issue_584_complex").Scan(&event)) assert.Equal(t, col1, event) From e8ade2f26e65b74273ad09478725ba8d0dc5a99f Mon Sep 17 00:00:00 2001 From: Dale McDiarmid Date: Tue, 7 Jun 2022 10:57:42 +0100 Subject: [PATCH 19/22] Support < 21.8 --- bind.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bind.go b/bind.go index ac30b5b1b6..b406e0ec14 100644 --- a/bind.go +++ b/bind.go @@ -242,7 +242,7 @@ func format(tz *time.Location, v interface{}) (string, error) { // assume slices in maps are arrays val = fmt.Sprintf("[%s]", val) } - values = append(values, fmt.Sprintf("%s: %s", name, val)) + values = append(values, fmt.Sprintf("%s : %s", name, val)) } return "{" + strings.Join(values, ", ") + "}", nil From ae5e334375d825776c7dcd04de466ab8289bfb62 Mon Sep 17 00:00:00 2001 From: Dale McDiarmid Date: Tue, 7 Jun 2022 12:23:26 +0100 Subject: [PATCH 20/22] Allow ipv4 to be passed in ipv6 --- lib/column/ipv6.go | 20 +++------ tests/ipv6_test.go | 104 ++++++++++++++++++++++++++++++++------------- 2 files changed, 80 insertions(+), 44 deletions(-) diff --git a/lib/column/ipv6.go b/lib/column/ipv6.go index c1435cef6c..a1f339477b 100644 --- a/lib/column/ipv6.go +++ b/lib/column/ipv6.go @@ -72,10 +72,7 @@ func (col *IPv6) Append(v interface{}) (nulls []uint8, err error) { nulls = make([]uint8, len(v)) for _, v := range v { if len(v) != net.IPv6len { - return nil, &Error{ - ColumnType: string(col.Type()), - Err: fmt.Errorf("invalid size. expected %d got %d", net.IPv6len, len(v)), - } + v = v.To16() } col.data = append(col.data, v[:]...) } @@ -84,13 +81,11 @@ func (col *IPv6) Append(v interface{}) (nulls []uint8, err error) { for i, v := range v { switch { case v != nil: - if len(*v) != net.IPv6len { - return nil, &Error{ - ColumnType: string(col.Type()), - Err: fmt.Errorf("invalid size. expected %d got %d", net.IPv6len, len(*v)), - } - } + //copy so we don't modify original value tmp := *v + if len(tmp) != net.IPv6len { + tmp = tmp.To16() + } col.data = append(col.data, tmp[:]...) default: col.data, nulls[i] = append(col.data, make([]byte, net.IPv6len)...), 1 @@ -128,10 +123,7 @@ func (col *IPv6) AppendRow(v interface{}) error { } } if len(ip) != net.IPv6len { - return &Error{ - ColumnType: string(col.Type()), - Err: fmt.Errorf("invalid size. expected %d got %d", net.IPv6len, len(ip)), - } + ip = ip.To16() } col.data = append(col.data, ip[:]...) return nil diff --git a/tests/ipv6_test.go b/tests/ipv6_test.go index 9a82ef8c93..0c766bf040 100644 --- a/tests/ipv6_test.go +++ b/tests/ipv6_test.go @@ -19,6 +19,7 @@ package tests import ( "context" + "github.com/stretchr/testify/require" "net" "testing" @@ -95,6 +96,52 @@ func TestIPv6(t *testing.T) { } } +func TestIPv4InIPv6(t *testing.T) { + var ( + ctx = context.Background() + conn, err = clickhouse.Open(&clickhouse.Options{ + Addr: []string{"127.0.0.1:9000"}, + Auth: clickhouse.Auth{ + Database: "default", + Username: "default", + Password: "", + }, + Compression: &clickhouse.Compression{ + Method: clickhouse.CompressionLZ4, + }, + //Debug: true, + }) + ) + if assert.NoError(t, err) { + const ddl = ` + CREATE TABLE test_ipv6 ( + Col1 IPv6 + , Col2 IPv6 + ) Engine Memory + ` + defer func() { + conn.Exec(ctx, "DROP TABLE test_ipv6") + }() + require.NoError(t, conn.Exec(ctx, ddl)) + batch, err := conn.PrepareBatch(ctx, "INSERT INTO test_ipv6") + require.NoError(t, err) + var ( + col1Data = net.ParseIP("127.0.0.1").To4() + col2Data = net.ParseIP("85.242.48.167").To4() + ) + require.NoError(t, batch.Append(col1Data, col2Data)) + require.NoError(t, batch.Send()) + var ( + col1 net.IP + col2 net.IP + ) + require.NoError(t, conn.QueryRow(ctx, "SELECT * FROM test_ipv6").Scan(&col1, &col2)) + assert.Equal(t, col1Data.To16(), col1) + assert.Equal(t, col2Data.To16(), col2) + } + +} + func TestNullableIPv6(t *testing.T) { var ( ctx = context.Background() @@ -191,36 +238,33 @@ func TestColumnarIPv6(t *testing.T) { defer func() { conn.Exec(ctx, "DROP TABLE test_ipv6") }() - if err := conn.Exec(ctx, ddl); assert.NoError(t, err) { - if batch, err := conn.PrepareBatch(ctx, "INSERT INTO test_ipv6"); assert.NoError(t, err) { - var ( - col1Data []*net.IP - col2Data []*net.IP - col3Data []*net.IP - v1, v2 = net.ParseIP("2001:44c8:129:2632:33:0:252:2"), net.ParseIP("2a02:e980:1e::1") - ) - col1Data = append(col1Data, &v1) - col2Data = append(col2Data, &v2) - col3Data = append(col3Data, nil) - { - batch.Column(0).Append(col1Data) - batch.Column(1).Append(col2Data) - batch.Column(2).Append(col3Data) - } - if assert.NoError(t, batch.Send()) { - var ( - col1 *net.IP - col2 *net.IP - col3 *net.IP - ) - if err := conn.QueryRow(ctx, "SELECT * FROM test_ipv6").Scan(&col1, &col2, &col3); assert.NoError(t, err) { - if assert.Nil(t, col3) { - assert.Equal(t, v1, *col1) - assert.Equal(t, v2, *col2) - } - } - } - } + + require.NoError(t, conn.Exec(ctx, ddl)) + batch, err := conn.PrepareBatch(ctx, "INSERT INTO test_ipv6") + require.NoError(t, err) + var ( + col1Data []*net.IP + col2Data []*net.IP + col3Data []*net.IP + v1, v2 = net.ParseIP("2001:44c8:129:2632:33:0:252:2"), net.ParseIP("192.168.1.1").To4() + ) + col1Data = append(col1Data, &v1) + col2Data = append(col2Data, &v2) + col3Data = append(col3Data, nil) + { + batch.Column(0).Append(col1Data) + batch.Column(1).Append(col2Data) + batch.Column(2).Append(col3Data) } + require.NoError(t, batch.Send()) + var ( + col1 *net.IP + col2 *net.IP + col3 *net.IP + ) + require.NoError(t, conn.QueryRow(ctx, "SELECT * FROM test_ipv6").Scan(&col1, &col2, &col3)) + require.Nil(t, col3) + require.Equal(t, v1, *col1) + require.Equal(t, v2.To16(), *col2) } } From 6b8865afc6546553005eb7d7dbd95b903fb9a4f5 Mon Sep 17 00:00:00 2001 From: Dale McDiarmid Date: Tue, 7 Jun 2022 14:41:11 +0100 Subject: [PATCH 21/22] Fix negative decimal --- lib/column/decimal.go | 4 +- tests/decimal_test.go | 88 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 75 insertions(+), 17 deletions(-) diff --git a/lib/column/decimal.go b/lib/column/decimal.go index 72dddd5f4e..a24fe1b9dc 100644 --- a/lib/column/decimal.go +++ b/lib/column/decimal.go @@ -155,7 +155,7 @@ func (col *Decimal) AppendRow(v interface{}) error { func (col *Decimal) Decode(decoder *binary.Decoder, rows int) error { switch col.nobits { case 32: - var base UInt32 + var base Int32 if err := base.Decode(decoder, rows); err != nil { return err } @@ -163,7 +163,7 @@ func (col *Decimal) Decode(decoder *binary.Decoder, rows int) error { col.values = append(col.values, decimal.New(int64(v), int32(-col.scale))) } case 64: - var base UInt64 + var base Int64 if err := base.Decode(decoder, rows); err != nil { return err } diff --git a/tests/decimal_test.go b/tests/decimal_test.go index c8a565c27a..ab14844146 100644 --- a/tests/decimal_test.go +++ b/tests/decimal_test.go @@ -19,6 +19,7 @@ package tests import ( "context" + "github.com/stretchr/testify/require" "testing" "github.com/ClickHouse/clickhouse-go/v2" @@ -52,11 +53,11 @@ func TestDecimal(t *testing.T) { } const ddl = ` CREATE TABLE test_decimal ( - Col1 Decimal32(5) - , Col2 Decimal(18,5) - , Col3 Decimal(15,3) - , Col4 Decimal128(5) - , Col5 Decimal256(5) + Col1 Decimal32(3) + , Col2 Decimal(18,6) + , Col3 Decimal(15,7) + , Col4 Decimal128(8) + , Col5 Decimal256(9) ) Engine Memory ` defer func() { @@ -65,11 +66,11 @@ func TestDecimal(t *testing.T) { if err := conn.Exec(ctx, ddl); assert.NoError(t, err) { if batch, err := conn.PrepareBatch(ctx, "INSERT INTO test_decimal"); assert.NoError(t, err) { if err := batch.Append( - decimal.New(25, 0), - decimal.New(30, 0), - decimal.New(35, 0), - decimal.New(135, 0), - decimal.New(256, 0), + decimal.New(25, 4), + decimal.New(30, 5), + decimal.New(35, 6), + decimal.New(135, 7), + decimal.New(256, 8), ); !assert.NoError(t, err) { return } @@ -82,11 +83,11 @@ func TestDecimal(t *testing.T) { col5 decimal.Decimal ) if err := conn.QueryRow(ctx, "SELECT * FROM test_decimal").Scan(&col1, &col2, &col3, &col4, &col5); assert.NoError(t, err) { - assert.True(t, decimal.New(25, 0).Equal(col1)) - assert.True(t, decimal.New(30, 0).Equal(col2)) - assert.True(t, decimal.New(35, 0).Equal(col3)) - assert.True(t, decimal.New(135, 0).Equal(col4)) - assert.True(t, decimal.New(256, 0).Equal(col5)) + assert.True(t, decimal.New(25, 4).Equal(col1)) + assert.True(t, decimal.New(30, 5).Equal(col2)) + assert.True(t, decimal.New(35, 6).Equal(col3)) + assert.True(t, decimal.New(135, 7).Equal(col4)) + assert.True(t, decimal.New(256, 8).Equal(col5)) } } } @@ -94,6 +95,63 @@ func TestDecimal(t *testing.T) { } } +func TestNegativeDecimal(t *testing.T) { + var ( + ctx = context.Background() + conn, err = clickhouse.Open(&clickhouse.Options{ + Addr: []string{"127.0.0.1:9000"}, + Auth: clickhouse.Auth{ + Database: "default", + Username: "default", + Password: "", + }, + Compression: &clickhouse.Compression{ + Method: clickhouse.CompressionLZ4, + }, + Settings: clickhouse.Settings{ + "allow_experimental_bigint_types": 1, + }, + //Debug: true, + }) + ) + require.NoError(t, err) + require.NoError(t, conn.Exec(ctx, "DROP TABLE IF EXISTS test_decimal")) + const ddl = ` + CREATE TABLE test_decimal ( + Col1 Nullable(Decimal(9,4)), + Col2 Nullable(Decimal(18,5)), + Col3 Nullable(Decimal(48,7)), + Col4 Nullable(Decimal(76,29)) + ) Engine Memory + ` + defer func() { + conn.Exec(ctx, "DROP TABLE test_decimal") + }() + if err := checkMinServerVersion(conn, 21, 1); err != nil { + t.Skip(err.Error()) + return + } + require.NoError(t, conn.Exec(ctx, ddl)) + batch, err := conn.PrepareBatch(ctx, "INSERT INTO test_decimal") + require.NoError(t, err) + require.NoError(t, batch.Append(decimal.RequireFromString("-0.0171"), + decimal.RequireFromString("-0.01171"), + decimal.RequireFromString("-3.0111"), + decimal.RequireFromString("-21111122.0111111111111111111171"))) + require.NoError(t, batch.Send()) + var ( + col1 decimal.Decimal + col2 decimal.Decimal + col3 decimal.Decimal + col4 decimal.Decimal + ) + require.NoError(t, conn.QueryRow(ctx, "SELECT * FROM test_decimal").Scan(&col1, &col2, &col3, &col4)) + assert.Equal(t, decimal.RequireFromString("-0.0171").String(), col1.String()) + assert.Equal(t, decimal.RequireFromString("-0.01171").String(), col2.String()) + assert.Equal(t, decimal.RequireFromString("-3.0111").String(), col3.String()) + assert.Equal(t, decimal.RequireFromString("-21111122.0111111111111111111171").String(), col4.String()) +} + func TestNullableDecimal(t *testing.T) { var ( ctx = context.Background() From 707e818e810c8fb2eaaa8b8b2991fd65d509ed29 Mon Sep 17 00:00:00 2001 From: Dale McDiarmid Date: Tue, 7 Jun 2022 15:46:02 +0100 Subject: [PATCH 22/22] Error if no address --- clickhouse.go | 4 ++++ tests/issues/592_test.go | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 tests/issues/592_test.go diff --git a/clickhouse.go b/clickhouse.go index 5b4a36d3fb..351cc6cfa6 100644 --- a/clickhouse.go +++ b/clickhouse.go @@ -44,6 +44,7 @@ var ( ErrAcquireConnTimeout = errors.New("clickhouse: acquire conn timeout. you can increase the number of max open conn or the dial timeout") ErrUnsupportedServerRevision = errors.New("clickhouse: unsupported server revision") ErrBindMixedParamsFormats = errors.New("clickhouse [bind]: mixed named, numeric or positional parameters") + ErrAcquireConnNoAddress = errors.New("clickhouse: no valid address supplied") ) type OpError struct { @@ -199,6 +200,9 @@ func (ch *clickhouse) dial(ctx context.Context) (conn *connect, err error) { return conn, nil } } + if err == nil { + err = ErrAcquireConnNoAddress + } return nil, err } diff --git a/tests/issues/592_test.go b/tests/issues/592_test.go new file mode 100644 index 0000000000..995c9a51e9 --- /dev/null +++ b/tests/issues/592_test.go @@ -0,0 +1,17 @@ +package issues + +import ( + "context" + "github.com/ClickHouse/clickhouse-go/v2" + "github.com/stretchr/testify/assert" + "testing" +) + +func Test592(t *testing.T) { + conn, err := clickhouse.Open(&clickhouse.Options{}) + assert.NoError(t, err) + + ctx := context.Background() + err = conn.Exec(ctx, "DROP TABLE test_connection") + assert.Error(t, err) +}