Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## (next)

### Fixes

- Improved error classification to mark all ClickHouse errors as downstream errors, including errors wrapped in HTTP response bodies and multi-error chains

## 4.11.1

### Fixes
Expand Down
40 changes: 38 additions & 2 deletions pkg/plugin/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,13 +271,49 @@ func (h *Clickhouse) Macros() sqlds.Macros {

// MutateQueryError marks ClickHouse errors as downstream errors
func (h *Clickhouse) MutateQueryError(err error) backend.ErrorWithSource {
var wrappedException *clickhouse.Exception
if errors.As(err, &wrappedException) {
// Check if any error in the error chain (including multi-errors) is a clickhouse.Exception
if containsClickHouseException(err) {
return backend.NewErrorWithSource(err, backend.ErrorSourceDownstream)
}
return backend.NewErrorWithSource(err, backend.DefaultErrorSource)
}

// containsClickHouseException checks if err or any error in its chain is a clickhouse.Exception
// It also handles errors wrapped in HTTP response bodies
func containsClickHouseException(err error) bool {
if err == nil {
return false
}

// Check if the current error is directly a clickhouse.Exception
var wrappedException *clickhouse.Exception
if errors.As(err, &wrappedException) {
return true
}

errStr := err.Error()

// Look for common ClickHouse error patterns in response bodies
if strings.Contains(errStr, "DB::Exception") {
return true
}

// Check for multiple wrapped errors (e.g., from errors.Join)
type multiError interface {
Unwrap() []error
}

if u, ok := err.(multiError); ok {
for _, e := range u.Unwrap() {
if containsClickHouseException(e) {
return true
}
}
}

return false
}

func (h *Clickhouse) Settings(ctx context.Context, config backend.DataSourceInstanceSettings) sqlds.DriverSettings {
settings, err := LoadSettings(ctx, config)
timeout := 60
Expand Down
53 changes: 53 additions & 0 deletions pkg/plugin/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package plugin

import (
"encoding/json"
"errors"
"fmt"
"testing"

"github.com/ClickHouse/clickhouse-go/v2"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -257,3 +260,53 @@ func TestAssignFlattenedPath(t *testing.T) {
assert.Equal(t, expected, flatMap)
})
}

func TestContainsClickHouseException(t *testing.T) {
t.Run("nil error", func(t *testing.T) {
result := containsClickHouseException(nil)
assert.False(t, result)
})

t.Run("direct clickhouse exception", func(t *testing.T) {
chErr := &clickhouse.Exception{
Code: 60,
Message: "Unknown table",
}
result := containsClickHouseException(chErr)
assert.True(t, result)
})

t.Run("wrapped clickhouse exception", func(t *testing.T) {
chErr := &clickhouse.Exception{
Code: 62,
Message: "Syntax error",
}
wrappedErr := fmt.Errorf("query failed: %w", chErr)
result := containsClickHouseException(wrappedErr)
assert.True(t, result)
})

t.Run("HTTP response body with clickhouse error", func(t *testing.T) {
errMsg := `error querying the database: sendQuery: [HTTP 404] response body: \"Code: 60. DB::Exception: Unknown table expression identifier 'hello' in scope SELECT * FROM hello. (UNKNOWN_TABLE) (version 25.1.3.23 (official build))\n\"`
err := errors.New(errMsg)
result := containsClickHouseException(err)
assert.True(t, result)
})

t.Run("regular error without clickhouse patterns", func(t *testing.T) {
err := errors.New("connection timeout")
result := containsClickHouseException(err)
assert.False(t, result)
})

t.Run("multi-error with clickhouse exception", func(t *testing.T) {
chErr := &clickhouse.Exception{
Code: 60,
Message: "Unknown table",
}
regularErr := errors.New("regular error")
multiErr := errors.Join(regularErr, chErr)
result := containsClickHouseException(multiErr)
assert.True(t, result)
})
}
Loading