From c0fdce4dc2ffd7e75334a6697f3ed532e8c884b3 Mon Sep 17 00:00:00 2001 From: roman Date: Fri, 24 Jan 2025 13:23:57 +0300 Subject: [PATCH 1/4] added check db exception --- conn_http.go | 51 +++++++++++++++++++++++++++++++++++++++ conn_http_async_insert.go | 22 ++++++++++++++--- conn_http_batch.go | 21 +++++++++++++--- conn_http_exec.go | 22 ++++++++++++++--- 4 files changed, 107 insertions(+), 9 deletions(-) diff --git a/conn_http.go b/conn_http.go index c9f2f929b4..781689357d 100644 --- a/conn_http.go +++ b/conn_http.go @@ -32,6 +32,8 @@ import ( "net/http" "net/url" "os" + "regexp" + "strconv" "strings" "sync" "time" @@ -571,3 +573,52 @@ func (h *httpConnect) close() error { h.client = nil return nil } + +var ( + dbExceptionMainPattern = regexp.MustCompile(`Code:\s*(\d+)\.\s*DB::Exception:\s*(.*?)\s*\(([A-Z_]+)\)\s*\(version`) + dbExceptionFallbackPattern = regexp.MustCompile(`Code:\s*(\d+)\.\s*DB::Exception:\s*(.*)`) +) + +type DBException struct { + Code int + ErrorType string + ErrorMessage string +} + +func (e *DBException) Error() string { + return fmt.Sprintf("ClickHouse DB::Exception (Code: %d, Type: %s): %s", + e.Code, e.ErrorType, e.ErrorMessage) +} + +func checkDBException(body []byte) error { + text := string(body) + + matches := dbExceptionMainPattern.FindStringSubmatch(text) + if len(matches) == 4 { + code, err := strconv.Atoi(matches[1]) + if err != nil { + return nil + } + + return &DBException{ + Code: code, + ErrorType: matches[3], + ErrorMessage: strings.TrimSpace(matches[2]), + } + } + + fallbackMatches := dbExceptionFallbackPattern.FindStringSubmatch(text) + if len(fallbackMatches) == 3 { + code, err := strconv.Atoi(fallbackMatches[1]) + if err != nil { + return nil + } + + return &DBException{ + Code: code, + ErrorMessage: strings.TrimSpace(fallbackMatches[2]), + } + } + + return nil +} diff --git a/conn_http_async_insert.go b/conn_http_async_insert.go index 3e197f0bbd..98c49da815 100644 --- a/conn_http_async_insert.go +++ b/conn_http_async_insert.go @@ -39,11 +39,27 @@ func (h *httpConnect) asyncInsert(ctx context.Context, query string, wait bool, } res, err := h.sendQuery(ctx, query, &options, h.headers) - if res != nil { - defer res.Body.Close() + if res == nil { + return err + } + + defer res.Body.Close() + + if err != nil { // we don't care about result, so just discard it to reuse connection _, _ = io.Copy(io.Discard, res.Body) + + return err + } + + msg, err := h.readRawResponse(res) + if err != nil { + return err + } + + if err = checkDBException(msg); err != nil { + return err } - return err + return nil } diff --git a/conn_http_batch.go b/conn_http_batch.go index b4b27920c7..885ccbbffe 100644 --- a/conn_http_batch.go +++ b/conn_http_batch.go @@ -211,14 +211,29 @@ func (b *httpBatch) Send() (err error) { headers[k] = v } res, err := b.conn.sendStreamQuery(b.ctx, r, &options, headers) + if res == nil { + return err + } - if res != nil { - defer res.Body.Close() + defer res.Body.Close() + + if err != nil { // we don't care about result, so just discard it to reuse connection _, _ = io.Copy(io.Discard, res.Body) + + return err + } + + msg, err := b.conn.readRawResponse(res) + if err != nil { + return err } - return err + if err = checkDBException(msg); err != nil { + return err + } + + return nil } func (b *httpBatch) Rows() int { diff --git a/conn_http_exec.go b/conn_http_exec.go index 75198eb1b2..1d9322d28b 100644 --- a/conn_http_exec.go +++ b/conn_http_exec.go @@ -30,11 +30,27 @@ func (h *httpConnect) exec(ctx context.Context, query string, args ...any) error } res, err := h.sendQuery(ctx, query, &options, h.headers) - if res != nil { - defer res.Body.Close() + if res == nil { + return err + } + + defer res.Body.Close() + + if err != nil { // we don't care about result, so just discard it to reuse connection _, _ = io.Copy(io.Discard, res.Body) + + return err + } + + msg, err := h.readRawResponse(res) + if err != nil { + return err + } + + if err = checkDBException(msg); err != nil { + return err } - return err + return nil } From 0bb97e61d52d32283bbe3976119314382bef23f4 Mon Sep 17 00:00:00 2001 From: roman Date: Fri, 24 Jan 2025 14:50:47 +0300 Subject: [PATCH 2/4] added check db exception --- conn_http.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/conn_http.go b/conn_http.go index 781689357d..3a4e07b573 100644 --- a/conn_http.go +++ b/conn_http.go @@ -52,6 +52,11 @@ const ( queryIDParamName = "query_id" ) +var ( + dbExceptionMainPattern = regexp.MustCompile(`Code:\s*(\d+)\.\s*DB::Exception:\s*(.*?)\s*\(([A-Z_]+)\)\s*\(version`) + dbExceptionFallbackPattern = regexp.MustCompile(`Code:\s*(\d+)\.\s*DB::Exception:\s*(.*)`) +) + type Pool[T any] struct { pool *sync.Pool } @@ -574,11 +579,6 @@ func (h *httpConnect) close() error { return nil } -var ( - dbExceptionMainPattern = regexp.MustCompile(`Code:\s*(\d+)\.\s*DB::Exception:\s*(.*?)\s*\(([A-Z_]+)\)\s*\(version`) - dbExceptionFallbackPattern = regexp.MustCompile(`Code:\s*(\d+)\.\s*DB::Exception:\s*(.*)`) -) - type DBException struct { Code int ErrorType string From e4041f556fbafe4e74b3674727fa4f2de349a702 Mon Sep 17 00:00:00 2001 From: roman Date: Fri, 24 Jan 2025 18:25:48 +0300 Subject: [PATCH 3/4] added tests --- tests/issues/1468_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/issues/1468_test.go diff --git a/tests/issues/1468_test.go b/tests/issues/1468_test.go new file mode 100644 index 0000000000..57ad2ec8eb --- /dev/null +++ b/tests/issues/1468_test.go @@ -0,0 +1,33 @@ +package issues + +import ( + "context" + "testing" + + "github.com/ClickHouse/clickhouse-go/v2/tests" + "github.com/stretchr/testify/require" +) + +func TestIssue1468(t *testing.T) { + ctx := context.Background() + + conn, err := tests.GetConnection("issues", nil, nil, nil) + require.NoError(t, err) + defer conn.Close() + + const ddl = ` + CREATE TABLE IF NOT EXISTS issue_1468( + some_id UInt64, + some_title String + ) ENGINE = MergeTree PRIMARY KEY (some_id) ORDER BY (some_id) + ` + err = conn.Exec(ctx, ddl) + require.NoError(t, err) + defer conn.Exec(ctx, "DROP TABLE issue_1468") + + err = conn.Exec(ctx, "ALTER TABLE issue_1468 DROP COLUMN wrong") + require.Error(t, err) + + err = conn.Exec(ctx, "ALTER TABLE issue_1468 DROP COLUMN some_title") + require.NoError(t, err) +} From 01c1d99743073615cc3c1fe596f447fd6f0c7d24 Mon Sep 17 00:00:00 2001 From: roman Date: Fri, 24 Jan 2025 18:50:14 +0300 Subject: [PATCH 4/4] deleted wrong test --- tests/issues/1468_test.go | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 tests/issues/1468_test.go diff --git a/tests/issues/1468_test.go b/tests/issues/1468_test.go deleted file mode 100644 index 57ad2ec8eb..0000000000 --- a/tests/issues/1468_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package issues - -import ( - "context" - "testing" - - "github.com/ClickHouse/clickhouse-go/v2/tests" - "github.com/stretchr/testify/require" -) - -func TestIssue1468(t *testing.T) { - ctx := context.Background() - - conn, err := tests.GetConnection("issues", nil, nil, nil) - require.NoError(t, err) - defer conn.Close() - - const ddl = ` - CREATE TABLE IF NOT EXISTS issue_1468( - some_id UInt64, - some_title String - ) ENGINE = MergeTree PRIMARY KEY (some_id) ORDER BY (some_id) - ` - err = conn.Exec(ctx, ddl) - require.NoError(t, err) - defer conn.Exec(ctx, "DROP TABLE issue_1468") - - err = conn.Exec(ctx, "ALTER TABLE issue_1468 DROP COLUMN wrong") - require.Error(t, err) - - err = conn.Exec(ctx, "ALTER TABLE issue_1468 DROP COLUMN some_title") - require.NoError(t, err) -}