From 7da50a3649e7714150e3f9160c5994241328f7d5 Mon Sep 17 00:00:00 2001 From: "guoqiang.wang" Date: Tue, 30 Apr 2024 09:24:02 +0800 Subject: [PATCH 1/4] fxied issue 1280 --- conn_batch.go | 9 ++-- tests/issues/1280_test.go | 96 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 tests/issues/1280_test.go diff --git a/conn_batch.go b/conn_batch.go index f463e4f91c..85899b749d 100644 --- a/conn_batch.go +++ b/conn_batch.go @@ -32,7 +32,7 @@ import ( "github.com/ClickHouse/clickhouse-go/v2/lib/proto" ) -var splitInsertRe = regexp.MustCompile(`(?i)\sVALUES\s*\(`) +var splitInsertRe = regexp.MustCompile(`(?i)\sVALUES\s*\(?`) var columnMatch = regexp.MustCompile(`INSERT INTO .+\s\((?P.+)\)$`) func (c *connect) prepareBatch(ctx context.Context, query string, opts driver.PrepareBatchOptions, release func(*connect, error), acquire func(context.Context) (*connect, error)) (driver.Batch, error) { @@ -41,7 +41,7 @@ func (c *connect) prepareBatch(ctx context.Context, query string, opts driver.Pr // fmt.Printf("panic occurred on %d:\n", c.num) // } //}() - query = splitInsertRe.Split(query, -1)[0] + query = strings.TrimSpace(splitInsertRe.Split(query, -1)[0]) colMatch := columnMatch.FindStringSubmatch(query) var columns []string if len(colMatch) == 2 { @@ -52,9 +52,8 @@ func (c *connect) prepareBatch(ctx context.Context, query string, opts driver.Pr columns[i] = strings.Trim(strings.Trim(strings.TrimSpace(columns[i]), "\""), "`") } } - if !strings.HasSuffix(strings.TrimSpace(strings.ToUpper(query)), "VALUES") { - query += " VALUES" - } + query += " VALUES" + options := queryOptions(ctx) if deadline, ok := ctx.Deadline(); ok { c.conn.SetDeadline(deadline) diff --git a/tests/issues/1280_test.go b/tests/issues/1280_test.go new file mode 100644 index 0000000000..f760f705d3 --- /dev/null +++ b/tests/issues/1280_test.go @@ -0,0 +1,96 @@ +package issues + +import ( + "context" + "reflect" + "regexp" + "testing" + + "github.com/ClickHouse/clickhouse-go/v2" + clickhouse_tests "github.com/ClickHouse/clickhouse-go/v2/tests" + "github.com/stretchr/testify/require" +) + +func Test1280(t *testing.T) { + var ( + conn, err = clickhouse_tests.GetConnection("issues", clickhouse.Settings{ + "max_execution_time": 60, + "allow_experimental_object_type": true, + }, nil, &clickhouse.Compression{ + Method: clickhouse.CompressionLZ4, + }) + ) + ctx := context.Background() + require.NoError(t, err) + const ddl = "CREATE TABLE test_1280_values (`id` Int32) Engine = Memory" + require.NoError(t, conn.Exec(ctx, ddl)) + defer func() { + conn.Exec(ctx, "DROP TABLE IF EXISTS test_1280_values") + }() + + _, err = conn.PrepareBatch(context.Background(), "INSERT INTO test_1280_values") + require.NoError(t, err) +} + +func Test1280SplitInsertRe(t *testing.T) { + var splitInsertRe = regexp.MustCompile(`(?i)\sVALUES\s*\(?`) + + // 定义测试用例 + testCases := []struct { + input string + expected []string + }{ + { + input: "INSERT INTO table_name VALUES (1, 'hello')", + expected: []string{"INSERT INTO table_name", "1, 'hello')"}, + }, + { + input: "INSERT INTO table_name VALUES (1, 'hello')", + expected: []string{"INSERT INTO table_name ", "1, 'hello')"}, + }, + { + input: "INSERT INTO table_name\tVALUES\t(2, 'world')", + expected: []string{"INSERT INTO table_name", "2, 'world')"}, + }, + { + input: "INSERT INTO table_name\t\tVALUES\t\t(2, 'world')", + expected: []string{"INSERT INTO table_name\t", "2, 'world')"}, + }, + { + input: "INSERT INTO table_name \tVALUES\t(2, 'world')", + expected: []string{"INSERT INTO table_name ", "2, 'world')"}, + }, + { + input: "INSERT INTO table_name\t VALUES\t(2, 'world')", + expected: []string{"INSERT INTO table_name\t", "2, 'world')"}, + }, + { + input: "INSERT INTO table_name\nVALUES\n(3, 'foo')", + expected: []string{"INSERT INTO table_name", "3, 'foo')"}, + }, + { + input: "INSERT INTO table_name\rVALUES\r(4, 'bar')", + expected: []string{"INSERT INTO table_name", "4, 'bar')"}, + }, + { + input: "INSERT INTO table_name VALUES", + expected: []string{"INSERT INTO table_name", ""}, + }, + { + input: "INSERT INTO table_name ", + expected: []string{"INSERT INTO table_name "}, + }, + { + input: "INSERT INTO table_name", + expected: []string{"INSERT INTO table_name"}, + }, + } + + // 遍历测试用例并执行测试 + for _, tc := range testCases { + result := splitInsertRe.Split(tc.input, -1) + if !reflect.DeepEqual(result, tc.expected) { + t.Errorf("Input: %q, \nExpected: %v Got: %v", tc.input, tc.expected, result) + } + } +} From 41af1202ff105be074d1636b96e4c39514ce716d Mon Sep 17 00:00:00 2001 From: "guoqiang.wang" Date: Tue, 30 Apr 2024 09:30:56 +0800 Subject: [PATCH 2/4] Update 1280_test.go --- tests/issues/1280_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/issues/1280_test.go b/tests/issues/1280_test.go index f760f705d3..aa2339ae20 100644 --- a/tests/issues/1280_test.go +++ b/tests/issues/1280_test.go @@ -45,7 +45,7 @@ func Test1280SplitInsertRe(t *testing.T) { expected: []string{"INSERT INTO table_name", "1, 'hello')"}, }, { - input: "INSERT INTO table_name VALUES (1, 'hello')", + input: "INSERT INTO table_name values (1, 'hello')", expected: []string{"INSERT INTO table_name ", "1, 'hello')"}, }, { @@ -84,6 +84,10 @@ func Test1280SplitInsertRe(t *testing.T) { input: "INSERT INTO table_name", expected: []string{"INSERT INTO table_name"}, }, + { + input: "INSERT INTO table_values", + expected: []string{"INSERT INTO table_values"}, + }, } // 遍历测试用例并执行测试 From 1cd5ab6ac18b3d7e96124348089fcf7d1502d3af Mon Sep 17 00:00:00 2001 From: "guoqiang.wang" Date: Tue, 30 Apr 2024 09:39:06 +0800 Subject: [PATCH 3/4] Update 1280_test.go --- tests/issues/1280_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/issues/1280_test.go b/tests/issues/1280_test.go index aa2339ae20..6abb415615 100644 --- a/tests/issues/1280_test.go +++ b/tests/issues/1280_test.go @@ -35,7 +35,6 @@ func Test1280(t *testing.T) { func Test1280SplitInsertRe(t *testing.T) { var splitInsertRe = regexp.MustCompile(`(?i)\sVALUES\s*\(?`) - // 定义测试用例 testCases := []struct { input string expected []string @@ -90,7 +89,6 @@ func Test1280SplitInsertRe(t *testing.T) { }, } - // 遍历测试用例并执行测试 for _, tc := range testCases { result := splitInsertRe.Split(tc.input, -1) if !reflect.DeepEqual(result, tc.expected) { From fe31f99426e614e726a54568efefa6f57335319e Mon Sep 17 00:00:00 2001 From: "guoqiang.wang" Date: Thu, 2 May 2024 12:23:05 +0800 Subject: [PATCH 4/4] fixed issue #1280 --- conn_batch.go | 10 +++- tests/issues/1280_test.go | 107 +++++++++++++++++--------------------- 2 files changed, 57 insertions(+), 60 deletions(-) diff --git a/conn_batch.go b/conn_batch.go index 85899b749d..6d8b1ce5f0 100644 --- a/conn_batch.go +++ b/conn_batch.go @@ -32,7 +32,7 @@ import ( "github.com/ClickHouse/clickhouse-go/v2/lib/proto" ) -var splitInsertRe = regexp.MustCompile(`(?i)\sVALUES\s*\(?`) +var insertMatch = regexp.MustCompile(`(?i)(INSERT\s+INTO\s+\S+(?:\s*\([^()]*(?:\([^()]*\)[^()]*)*\))?)(?:\s*VALUES)?`) var columnMatch = regexp.MustCompile(`INSERT INTO .+\s\((?P.+)\)$`) func (c *connect) prepareBatch(ctx context.Context, query string, opts driver.PrepareBatchOptions, release func(*connect, error), acquire func(context.Context) (*connect, error)) (driver.Batch, error) { @@ -41,7 +41,13 @@ func (c *connect) prepareBatch(ctx context.Context, query string, opts driver.Pr // fmt.Printf("panic occurred on %d:\n", c.num) // } //}() - query = strings.TrimSpace(splitInsertRe.Split(query, -1)[0]) + subMatches := insertMatch.FindStringSubmatch(query) + if len(subMatches) > 1 { + query = subMatches[1] + } else { + return nil, errors.New("invalid query") + } + colMatch := columnMatch.FindStringSubmatch(query) var columns []string if len(colMatch) == 2 { diff --git a/tests/issues/1280_test.go b/tests/issues/1280_test.go index 6abb415615..a50197d4a5 100644 --- a/tests/issues/1280_test.go +++ b/tests/issues/1280_test.go @@ -2,8 +2,6 @@ package issues import ( "context" - "reflect" - "regexp" "testing" "github.com/ClickHouse/clickhouse-go/v2" @@ -13,7 +11,7 @@ import ( func Test1280(t *testing.T) { var ( - conn, err = clickhouse_tests.GetConnection("issues", clickhouse.Settings{ + conn, err = clickhouse_tests.GetConnection(testSet, clickhouse.Settings{ "max_execution_time": 60, "allow_experimental_object_type": true, }, nil, &clickhouse.Compression{ @@ -22,77 +20,70 @@ func Test1280(t *testing.T) { ) ctx := context.Background() require.NoError(t, err) - const ddl = "CREATE TABLE test_1280_values (`id` Int32) Engine = Memory" - require.NoError(t, conn.Exec(ctx, ddl)) - defer func() { - conn.Exec(ctx, "DROP TABLE IF EXISTS test_1280_values") - }() - - _, err = conn.PrepareBatch(context.Background(), "INSERT INTO test_1280_values") - require.NoError(t, err) -} -func Test1280SplitInsertRe(t *testing.T) { - var splitInsertRe = regexp.MustCompile(`(?i)\sVALUES\s*\(?`) + ddl := "CREATE TABLE values (`id` Int32, `values` Int32) Engine = Memory" + require.NoError(t, conn.Exec(ctx, ddl)) + defer conn.Exec(ctx, "DROP TABLE IF EXISTS values") - testCases := []struct { - input string - expected []string + testCases1 := []struct { + input string }{ { - input: "INSERT INTO table_name VALUES (1, 'hello')", - expected: []string{"INSERT INTO table_name", "1, 'hello')"}, - }, - { - input: "INSERT INTO table_name values (1, 'hello')", - expected: []string{"INSERT INTO table_name ", "1, 'hello')"}, - }, - { - input: "INSERT INTO table_name\tVALUES\t(2, 'world')", - expected: []string{"INSERT INTO table_name", "2, 'world')"}, - }, - { - input: "INSERT INTO table_name\t\tVALUES\t\t(2, 'world')", - expected: []string{"INSERT INTO table_name\t", "2, 'world')"}, + input: "INSERT INTO values (values)", }, { - input: "INSERT INTO table_name \tVALUES\t(2, 'world')", - expected: []string{"INSERT INTO table_name ", "2, 'world')"}, + input: "INSERT INTO values (values) values", }, { - input: "INSERT INTO table_name\t VALUES\t(2, 'world')", - expected: []string{"INSERT INTO table_name\t", "2, 'world')"}, - }, - { - input: "INSERT INTO table_name\nVALUES\n(3, 'foo')", - expected: []string{"INSERT INTO table_name", "3, 'foo')"}, - }, - { - input: "INSERT INTO table_name\rVALUES\r(4, 'bar')", - expected: []string{"INSERT INTO table_name", "4, 'bar')"}, - }, - { - input: "INSERT INTO table_name VALUES", - expected: []string{"INSERT INTO table_name", ""}, + input: "INSERT INTO values (`values`) values", }, + } + + for i, tc := range testCases1 { + batch, err := conn.PrepareBatch(context.Background(), tc.input) + require.NoError(t, err) + appendErr := batch.Append(i) + require.NoError(t, appendErr) + err = batch.Send() + require.NoError(t, err) + } + + testCases2 := []struct { + input string + }{ { - input: "INSERT INTO table_name ", - expected: []string{"INSERT INTO table_name "}, + input: ` + INSERT + INTO + values + ( + id, + values + )`, }, { - input: "INSERT INTO table_name", - expected: []string{"INSERT INTO table_name"}, + input: `INSERT + INTO + values + (id, + values) + values`, }, { - input: "INSERT INTO table_values", - expected: []string{"INSERT INTO table_values"}, + input: ` + INSERT + INTO + values + (id,values) values (1,2)`, }, } - for _, tc := range testCases { - result := splitInsertRe.Split(tc.input, -1) - if !reflect.DeepEqual(result, tc.expected) { - t.Errorf("Input: %q, \nExpected: %v Got: %v", tc.input, tc.expected, result) - } + for i, tc := range testCases2 { + batch, err := conn.PrepareBatch(context.Background(), tc.input) + require.NoError(t, err) + appendErr := batch.Append(i, i) + require.NoError(t, appendErr) + err = batch.Send() + require.NoError(t, err) } }