From e09fd368a97a27db246d21b2b82dfb48068534a7 Mon Sep 17 00:00:00 2001 From: chaewonkong Date: Tue, 21 Apr 2026 23:24:22 +0900 Subject: [PATCH 1/7] fix: reset skippedValueTokens between enum values --- lib/column/enum.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/column/enum.go b/lib/column/enum.go index 74ef3ec43d..aba7589ee0 100644 --- a/lib/column/enum.go +++ b/lib/column/enum.go @@ -75,7 +75,7 @@ func extractEnumNamedValues(chType Type) (typ string, values []string, indexes [ var skippedValueTokens []int var indexFound bool var valueFound bool - var valueIndex = 0 + valueIndex := 0 for c := 0; c < len(src); c++ { token := src[c] @@ -152,6 +152,7 @@ func extractEnumNamedValues(chType Type) (typ string, values []string, indexes [ values = append(values, string(foundName)) indexFound = false valueFound = false + skippedValueTokens = skippedValueTokens[:0] } } From 76ec0e15139da4e428490003d3c08c0d733bac10 Mon Sep 17 00:00:00 2001 From: chaewonkong Date: Tue, 21 Apr 2026 23:26:06 +0900 Subject: [PATCH 2/7] fix: adjust skipped positions for prior removals when unescaping enum values --- lib/column/enum.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/column/enum.go b/lib/column/enum.go index aba7589ee0..30dce8ad4a 100644 --- a/lib/column/enum.go +++ b/lib/column/enum.go @@ -144,8 +144,8 @@ func extractEnumNamedValues(chType Type) (typ string, values []string, indexes [ } foundName := src[foundValueOffset : foundValueOffset+foundValueLen] - for _, skipped := range skippedValueTokens { - foundName = append(foundName[:skipped], foundName[skipped+1:]...) + for i, skipped := range skippedValueTokens { + foundName = append(foundName[:skipped-i], foundName[skipped-i+1:]...) } indexes = append(indexes, valueIndex) From 86f16f7d73adeba6a7c4fab8a9a475ad8ef9af27 Mon Sep 17 00:00:00 2001 From: chaewonkong Date: Tue, 21 Apr 2026 23:30:55 +0900 Subject: [PATCH 3/7] test: add cases for escaped quotes in enum value parsing --- lib/column/enum_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/column/enum_test.go b/lib/column/enum_test.go index 51f199e023..10d1142658 100644 --- a/lib/column/enum_test.go +++ b/lib/column/enum_test.go @@ -159,6 +159,24 @@ func TestExtractEnumNamedValues(t *testing.T) { 0: "b", }, }, + { + name: "Enum8 with escaped quote in multiple values", + chType: `Enum8('a\'b'=1,'c\'d'=2)`, + expectedType: "Enum8", + expectedValues: map[int]string{ + 1: "a'b", + 2: "c'd", + }, + }, + + { + name: "Enum8 with multiple escaped quotes in one value", + chType: `Enum8('a\'b\'c'=1)`, + expectedType: "Enum8", + expectedValues: map[int]string{ + 1: "a'b'c", + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From b6e2ae09c7ffd42df90bb7d322b33fbe2d8153df Mon Sep 17 00:00:00 2001 From: chaewonkong Date: Wed, 22 Apr 2026 22:52:13 +0900 Subject: [PATCH 4/7] test: remove extra blank line in enum_test.go --- lib/column/enum_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/column/enum_test.go b/lib/column/enum_test.go index 10d1142658..17266c3bda 100644 --- a/lib/column/enum_test.go +++ b/lib/column/enum_test.go @@ -168,7 +168,6 @@ func TestExtractEnumNamedValues(t *testing.T) { 2: "c'd", }, }, - { name: "Enum8 with multiple escaped quotes in one value", chType: `Enum8('a\'b\'c'=1)`, From 4d65f0dc541273279b1e65015dd9136db0cd393b Mon Sep 17 00:00:00 2001 From: chaewonkong Date: Wed, 22 Apr 2026 22:52:39 +0900 Subject: [PATCH 5/7] test: add integration test for escaped quotes in enum parsing - 1839_test.go --- tests/issues/1839_test.go | 49 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/issues/1839_test.go diff --git a/tests/issues/1839_test.go b/tests/issues/1839_test.go new file mode 100644 index 0000000000..9d6ff57fdd --- /dev/null +++ b/tests/issues/1839_test.go @@ -0,0 +1,49 @@ +package issues + +import ( + "context" + "testing" + + clickhouse_tests "github.com/ClickHouse/clickhouse-go/v2/tests" + "github.com/stretchr/testify/require" +) + +func Test1839(t *testing.T) { + testEnv, err := clickhouse_tests.GetTestEnvironment("issues") + require.NoError(t, err) + conn, err := clickhouse_tests.TestClientWithDefaultSettings(testEnv) + require.NoError(t, err) + t.Cleanup(func() { conn.Close() }) + + require.NoError(t, conn.Exec(t.Context(), ` + CREATE TABLE test_1839 ( + col Enum8('a\'b' = 1, 'c\'d' = 2, 'a\'b\'c' = 3) + ) ENGINE = MergeTree ORDER BY tuple() + `), "Create table failed") + + t.Cleanup(func() { + if err := conn.Exec(context.Background(), "DROP TABLE IF EXISTS test_1839"); err != nil { + t.Logf("DROP TABLE test_1839 failed: %v", err) + } + }) + + batch, err := conn.PrepareBatch(t.Context(), "INSERT INTO test_1839") + require.NoError(t, err, "PrepareBatch failed") + require.NoError(t, batch.Append("a'b"), "Append failed for %q", "a'b") + require.NoError(t, batch.Append("c'd"), "Append failed for %q", "c'd") + require.NoError(t, batch.Append("a'b'c"), "Append failed for %q", "a'b'c") + require.NoError(t, batch.Send(), "Send failed") + + rows, err := conn.Query(t.Context(), "SELECT col FROM test_1839 ORDER BY col") + require.NoError(t, err, "SELECT col failed") + defer rows.Close() + + var results []string + for rows.Next() { + var s string + require.NoError(t, rows.Scan(&s)) + results = append(results, s) + } + require.NoError(t, rows.Err(), "Scan failed") + require.Equal(t, []string{"a'b", "c'd", "a'b'c"}, results) +} From 4722017285091cc6e652e6bcdf3ab43316cab9fa Mon Sep 17 00:00:00 2001 From: chaewonkong Date: Thu, 23 Apr 2026 23:50:44 +0900 Subject: [PATCH 6/7] test: add Enum16 test cases --- lib/column/enum_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/column/enum_test.go b/lib/column/enum_test.go index 17266c3bda..3bddbcc887 100644 --- a/lib/column/enum_test.go +++ b/lib/column/enum_test.go @@ -176,6 +176,23 @@ func TestExtractEnumNamedValues(t *testing.T) { 1: "a'b'c", }, }, + { + name: "Enum16 with escaped quote in multiple values", + chType: `Enum16('a\'b'=1,'c\'d'=2)`, + expectedType: "Enum16", + expectedValues: map[int]string{ + 1: "a'b", + 2: "c'd", + }, + }, + { + name: "Enum16 with multiple escaped quotes in one value", + chType: `Enum16('a\'b\'c'=1)`, + expectedType: "Enum16", + expectedValues: map[int]string{ + 1: "a'b'c", + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 5d0ac85c0aac6417ec0ab47e089c2450d4610132 Mon Sep 17 00:00:00 2001 From: chaewonkong Date: Tue, 5 May 2026 15:45:02 +0900 Subject: [PATCH 7/7] test: add HTTP protocol coverage for issue 1839 --- tests/issues/1839_test.go | 48 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/tests/issues/1839_test.go b/tests/issues/1839_test.go index 9d6ff57fdd..6cc9c26aaf 100644 --- a/tests/issues/1839_test.go +++ b/tests/issues/1839_test.go @@ -4,8 +4,10 @@ import ( "context" "testing" - clickhouse_tests "github.com/ClickHouse/clickhouse-go/v2/tests" "github.com/stretchr/testify/require" + + "github.com/ClickHouse/clickhouse-go/v2" + clickhouse_tests "github.com/ClickHouse/clickhouse-go/v2/tests" ) func Test1839(t *testing.T) { @@ -16,10 +18,10 @@ func Test1839(t *testing.T) { t.Cleanup(func() { conn.Close() }) require.NoError(t, conn.Exec(t.Context(), ` - CREATE TABLE test_1839 ( + CREATE TABLE test_1839 ( col Enum8('a\'b' = 1, 'c\'d' = 2, 'a\'b\'c' = 3) ) ENGINE = MergeTree ORDER BY tuple() - `), "Create table failed") + `), "Create table failed") t.Cleanup(func() { if err := conn.Exec(context.Background(), "DROP TABLE IF EXISTS test_1839"); err != nil { @@ -47,3 +49,43 @@ func Test1839(t *testing.T) { require.NoError(t, rows.Err(), "Scan failed") require.Equal(t, []string{"a'b", "c'd", "a'b'c"}, results) } + +func Test1839HTTP(t *testing.T) { + testEnv, err := clickhouse_tests.GetTestEnvironment("issues") + require.NoError(t, err) + conn, err := clickhouse_tests.GetConnection("issues", t, clickhouse.HTTP, clickhouse_tests.TestClientDefaultSettings(testEnv), nil, nil) + require.NoError(t, err) + t.Cleanup(func() { conn.Close() }) + + require.NoError(t, conn.Exec(t.Context(), ` + CREATE TABLE test_1839_http ( + col Enum8('a\'b' = 1, 'c\'d' = 2, 'a\'b\'c' = 3) + ) ENGINE = MergeTree ORDER BY tuple() + `), "Create table failed") + + t.Cleanup(func() { + if err := conn.Exec(context.Background(), "DROP TABLE IF EXISTS test_1839_http"); err != nil { + t.Logf("DROP TABLE test_1839_http failed: %v", err) + } + }) + + batch, err := conn.PrepareBatch(t.Context(), "INSERT INTO test_1839_http") + require.NoError(t, err, "PrepareBatch failed") + require.NoError(t, batch.Append("a'b"), "Append failed for %q", "a'b") + require.NoError(t, batch.Append("c'd"), "Append failed for %q", "c'd") + require.NoError(t, batch.Append("a'b'c"), "Append failed for %q", "a'b'c") + require.NoError(t, batch.Send(), "Send failed") + + rows, err := conn.Query(t.Context(), "SELECT col FROM test_1839_http ORDER BY col") + require.NoError(t, err, "SELECT col failed") + defer rows.Close() + + var results []string + for rows.Next() { + var s string + require.NoError(t, rows.Scan(&s)) + results = append(results, s) + } + require.NoError(t, rows.Err(), "Scan failed") + require.Equal(t, []string{"a'b", "c'd", "a'b'c"}, results) +}