From dad58a4544ff17a24b97c497682ac0ba25aa5b71 Mon Sep 17 00:00:00 2001 From: Kuba Kaflik Date: Thu, 13 Apr 2023 12:00:43 +0200 Subject: [PATCH 1/3] Create release.yml --- .github/release.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/release.yml diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000000..d641b35610 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,22 @@ +changelog: + exclude: + labels: + - ignore-for-release + authors: + - dependabot + categories: + - title: Breaking Changes 🚨 + labels: + - Semver-Major + - breaking-change + - title: Enhancements 🎉 + labels: + - Semver-Minor + - enhancement + - title: Fixes 🐛 + labels: + - Semver-Patch + - fix + - title: Other Changes 🛠 + labels: + - "*" From 87d694ab6198b79d1ed145ab9727fcc327baecc7 Mon Sep 17 00:00:00 2001 From: LIU Chao <42240939+xiaochaoren1@users.noreply.github.com> Date: Thu, 13 Apr 2023 18:44:35 +0800 Subject: [PATCH 2/3] Reset the pointer to the nullable field (#964) When the field is nullable type, the pointer is not reset, this pull request manually resets the pointer to fix this problem --- lib/column/nullable.go | 26 ++++++++++++++++++ tests/issues/955_test.go | 58 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 tests/issues/955_test.go diff --git a/lib/column/nullable.go b/lib/column/nullable.go index 50759e01fa..6e9019a015 100644 --- a/lib/column/nullable.go +++ b/lib/column/nullable.go @@ -90,6 +90,32 @@ func (col *Nullable) ScanRow(dest interface{}, row int) error { if col.enable { switch col.nulls.Row(row) { case 1: + switch v := dest.(type) { + case **uint64: + *v = nil + case **int64: + *v = nil + case **uint32: + *v = nil + case **int32: + *v = nil + case **uint16: + *v = nil + case **int16: + *v = nil + case **uint8: + *v = nil + case **int8: + *v = nil + case **string: + *v = nil + case **float32: + *v = nil + case **float64: + *v = nil + case **time.Time: + *v = nil + } if scan, ok := dest.(sql.Scanner); ok { return scan.Scan(nil) } diff --git a/tests/issues/955_test.go b/tests/issues/955_test.go new file mode 100644 index 0000000000..ee16c75e84 --- /dev/null +++ b/tests/issues/955_test.go @@ -0,0 +1,58 @@ +package issues + +import ( + "context" + "github.com/ClickHouse/clickhouse-go/v2" + "github.com/ClickHouse/clickhouse-go/v2/lib/driver" + clickhouse_tests "github.com/ClickHouse/clickhouse-go/v2/tests" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "reflect" + "testing" +) + +func Test955(t *testing.T) { + var ( + conn, err = clickhouse_tests.GetConnection("issues", clickhouse.Settings{ + "max_execution_time": 60, + }, nil, &clickhouse.Compression{ + Method: clickhouse.CompressionLZ4, + }) + ) + ctx := context.Background() + require.NoError(t, err) + const ddl = ` + CREATE TABLE test_955 ( + Col1 Nullable(UInt64) + ) Engine MergeTree() ORDER BY tuple() + ` + defer func() { + conn.Exec(ctx, "DROP TABLE IF EXISTS test_955") + }() + require.NoError(t, conn.Exec(ctx, ddl)) + const baseValues = ` + INSERT INTO test_955 VALUES (123), (NULL) + ` + require.NoError(t, conn.Exec(ctx, baseValues)) + + rows, err := conn.Query(ctx, "SELECT * FROM test_955") + require.NoError(t, err) + defer func(rows driver.Rows) { + _ = rows.Close() + }(rows) + + records := make([][]any, 0) + for rows.Next() { + record := make([]any, 0, len(rows.ColumnTypes())) + for _, ct := range rows.ColumnTypes() { + record = append(record, reflect.New(ct.ScanType()).Interface()) + } + err = rows.Scan(record...) + require.NoError(t, err) + + records = append(records, record) + } + var value *uint64 + value = nil + assert.Equal(t, value, *records[1][0].(**uint64)) +} From d14aec822026b6718dcbb8353a6e7849483b685b Mon Sep 17 00:00:00 2001 From: Zhehao Wu Date: Thu, 13 Apr 2023 18:45:06 +0800 Subject: [PATCH 3/3] Enable to use ternary operator with named arguments (#965) * check type of all query parameters before binding * fix check logic issue --------- Co-authored-by: Kuba Kaflik --- bind.go | 35 +++++++++++++++++++++++++---------- bind_test.go | 17 ++++++++++++++++- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/bind.go b/bind.go index 8e9567c389..ce4106805c 100644 --- a/bind.go +++ b/bind.go @@ -66,32 +66,47 @@ func bind(tz *time.Location, query string, args ...interface{}) (string, error) return query, nil } var ( - haveNamed bool haveNumeric bool havePositional bool ) + + allArgumentsNamed, err := checkAllNamedArguments(args...) + if err != nil { + return "", err + } + + if allArgumentsNamed { + return bindNamed(tz, query, args...) + } + haveNumeric = bindNumericRe.MatchString(query) havePositional = bindPositionalRe.MatchString(query) if haveNumeric && havePositional { return "", ErrBindMixedParamsFormats } + if haveNumeric { + return bindNumeric(tz, query, args...) + } + return bindPositional(tz, query, args...) +} + +func checkAllNamedArguments(args ...interface{}) (bool, error) { + var ( + haveNamed bool + haveAnonymous bool + ) for _, v := range args { switch v.(type) { case driver.NamedValue, driver.NamedDateValue: haveNamed = true default: + haveAnonymous = true } - if haveNamed && (haveNumeric || havePositional) { - return "", ErrBindMixedParamsFormats + if haveNamed && haveAnonymous { + return haveNamed, ErrBindMixedParamsFormats } } - if haveNamed { - return bindNamed(tz, query, args...) - } - if haveNumeric { - return bindNumeric(tz, query, args...) - } - return bindPositional(tz, query, args...) + return haveNamed, nil } var bindPositionCharRe = regexp.MustCompile(`[?]`) diff --git a/bind_test.go b/bind_test.go index 057bb2a18c..ca9054680f 100644 --- a/bind_test.go +++ b/bind_test.go @@ -225,13 +225,14 @@ func TestBindPositional(t *testing.T) { ANS col4 = ? AND null_coll = ? ) - `, 1, 2, "I'm a string param", nil) + `, 1, 2, "I'm a string param", nil, Named("namedArg", nil)) assert.Error(t, err) var nilPtr *bool = nil var nilPtrPtr **interface{} = nil valuedPtr := &([]interface{}{123}[0]) nilValuePtr := &([]interface{}{nil}[0]) + _, err = bind(time.Local, ` SELECT * FROM t WHERE col = ? AND col2 = ? @@ -330,6 +331,20 @@ func TestFormatMap(t *testing.T) { assert.Equal(t, "map('a', 1)", val) } +func TestBindNamedWithTernaryOperator(t *testing.T) { + sqls := []string{ + `SELECT if(@arg1,@arg2,@arg3)`, // correct + `SELECT @arg1?@arg2:@arg3`, // failed here + } + for _, sql := range sqls { + _, err := bind(time.Local, sql, + Named("arg1", 0), + Named("arg2", 1), + Named("arg3", 2)) + assert.NoError(t, err) + } +} + func BenchmarkBindNumeric(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ {