diff --git a/lib/column/enum.go b/lib/column/enum.go index 74ef3ec43d..30dce8ad4a 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] @@ -144,14 +144,15 @@ 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) values = append(values, string(foundName)) indexFound = false valueFound = false + skippedValueTokens = skippedValueTokens[:0] } } diff --git a/lib/column/enum_test.go b/lib/column/enum_test.go index 51f199e023..3bddbcc887 100644 --- a/lib/column/enum_test.go +++ b/lib/column/enum_test.go @@ -159,6 +159,40 @@ 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", + }, + }, + { + 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) { diff --git a/tests/issues/1839_test.go b/tests/issues/1839_test.go new file mode 100644 index 0000000000..6cc9c26aaf --- /dev/null +++ b/tests/issues/1839_test.go @@ -0,0 +1,91 @@ +package issues + +import ( + "context" + "testing" + + "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) { + 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) +} + +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) +}