From 93631838413483c01e9249bddb7aa51d2a319a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wa=C5=9B?= Date: Sun, 14 Apr 2024 11:13:25 +0200 Subject: [PATCH] Export Trino errors with details --- trino/integration_test.go | 56 +++++++++++++++++++++++++++++++++++---- trino/trino.go | 51 ++++++++++++++++++++++++----------- trino/trino_test.go | 6 ++--- 3 files changed, 90 insertions(+), 23 deletions(-) diff --git a/trino/integration_test.go b/trino/integration_test.go index e0e43a3..a5f33cd 100644 --- a/trino/integration_test.go +++ b/trino/integration_test.go @@ -206,10 +206,56 @@ func TestIntegrationSelectFailedQuery(t *testing.T) { rows.Close() t.Fatal("query to invalid catalog succeeded") } - _, ok := err.(*ErrQueryFailed) + queryFailed, ok := err.(*ErrQueryFailed) if !ok { t.Fatal("unexpected error:", err) } + trinoErr, ok := errors.Unwrap(queryFailed).(*ErrTrino) + if !ok { + t.Fatal("unexpected error:", trinoErr) + } + expected := ErrTrino{ + Message: "line 1:15: Catalog 'catalog'", + SqlState: "", + ErrorCode: 44, + ErrorName: "CATALOG_NOT_FOUND", + ErrorType: "USER_ERROR", + ErrorLocation: ErrorLocation{ + LineNumber: 1, + ColumnNumber: 15, + }, + FailureInfo: FailureInfo{ + Type: "io.trino.spi.TrinoException", + Message: "line 1:15: Catalog 'catalog'", + }, + } + if !strings.HasPrefix(trinoErr.Message, expected.Message) { + t.Fatalf("expected ErrTrino.Message to start with `%s`, got: %s", expected.Message, trinoErr.Message) + } + if trinoErr.SqlState != expected.SqlState { + t.Fatalf("expected ErrTrino.SqlState to be `%s`, got: %s", expected.SqlState, trinoErr.SqlState) + } + if trinoErr.ErrorCode != expected.ErrorCode { + t.Fatalf("expected ErrTrino.ErrorCode to be `%d`, got: %d", expected.ErrorCode, trinoErr.ErrorCode) + } + if trinoErr.ErrorName != expected.ErrorName { + t.Fatalf("expected ErrTrino.ErrorName to be `%s`, got: %s", expected.ErrorName, trinoErr.ErrorName) + } + if trinoErr.ErrorType != expected.ErrorType { + t.Fatalf("expected ErrTrino.ErrorType to be `%s`, got: %s", expected.ErrorType, trinoErr.ErrorType) + } + if trinoErr.ErrorLocation.LineNumber != expected.ErrorLocation.LineNumber { + t.Fatalf("expected ErrTrino.ErrorLocation.LineNumber to be `%d`, got: %d", expected.ErrorLocation.LineNumber, trinoErr.ErrorLocation.LineNumber) + } + if trinoErr.ErrorLocation.ColumnNumber != expected.ErrorLocation.ColumnNumber { + t.Fatalf("expected ErrTrino.ErrorLocation.ColumnNumber to be `%d`, got: %d", expected.ErrorLocation.ColumnNumber, trinoErr.ErrorLocation.ColumnNumber) + } + if trinoErr.FailureInfo.Type != expected.FailureInfo.Type { + t.Fatalf("expected ErrTrino.FailureInfo.Type to be `%s`, got: %s", expected.FailureInfo.Type, trinoErr.FailureInfo.Type) + } + if !strings.HasPrefix(trinoErr.FailureInfo.Message, expected.FailureInfo.Message) { + t.Fatalf("expected ErrTrino.FailureInfo.Message to start with `%s`, got: %s", expected.FailureInfo.Message, trinoErr.FailureInfo.Message) + } } type tpchRow struct { @@ -486,13 +532,13 @@ func TestIntegrationQueryParametersSelect(t *testing.T) { name: "invalid string as bigint", query: "SELECT * FROM tpch.sf1.customer WHERE custkey=? LIMIT 2", args: []interface{}{"1"}, - expectedError: errors.New(`trino: query failed (200 OK): "io.trino.spi.TrinoException: line 1:46: Cannot apply operator: bigint = varchar(1)"`), + expectedError: errors.New(`trino: query failed (200 OK): "USER_ERROR: line 1:46: Cannot apply operator: bigint = varchar(1)"`), }, { name: "valid string as date", query: "SELECT * FROM tpch.sf1.lineitem WHERE shipdate=? LIMIT 2", args: []interface{}{"1995-01-27"}, - expectedError: errors.New(`trino: query failed (200 OK): "io.trino.spi.TrinoException: line 1:47: Cannot apply operator: date = varchar(10)"`), + expectedError: errors.New(`trino: query failed (200 OK): "USER_ERROR: line 1:47: Cannot apply operator: date = varchar(10)"`), }, } @@ -611,11 +657,11 @@ func TestIntegrationUnsupportedHeader(t *testing.T) { }{ { query: "SET ROLE dummy", - err: errors.New(`trino: query failed (200 OK): "io.trino.spi.TrinoException: line 1:1: Role 'dummy' does not exist"`), + err: errors.New(`trino: query failed (200 OK): "USER_ERROR: line 1:1: Role 'dummy' does not exist"`), }, { query: "SET PATH dummy", - err: errors.New(`trino: query failed (200 OK): "io.trino.spi.TrinoException: SET PATH not supported by client"`), + err: errors.New(`trino: query failed (200 OK): "USER_ERROR: SET PATH not supported by client"`), }, } for _, c := range cases { diff --git a/trino/trino.go b/trino/trino.go index fa99755..2383383 100644 --- a/trino/trino.go +++ b/trino/trino.go @@ -560,6 +560,11 @@ func (e *ErrQueryFailed) Error() string { e.StatusCode, http.StatusText(e.StatusCode), e.Reason) } +// Unwrap implements the unwrap interface. +func (e *ErrQueryFailed) Unwrap() error { + return e.Reason +} + func newErrQueryFailedFromResponse(resp *http.Response) *ErrQueryFailed { const maxBytes = 8 * 1024 defer resp.Body.Close() @@ -683,7 +688,7 @@ type stmtResponse struct { InfoURI string `json:"infoUri"` NextURI string `json:"nextUri"` Stats stmtStats `json:"stats"` - Error stmtError `json:"error"` + Error ErrTrino `json:"error"` UpdateType string `json:"updateType"` UpdateCount int64 `json:"updateCount"` } @@ -712,27 +717,43 @@ type stmtStats struct { RunningPercentage float32 `json:"runningPercentage"` } -type stmtError struct { - Message string `json:"message"` - ErrorName string `json:"errorName"` - ErrorCode int `json:"errorCode"` - ErrorLocation stmtErrorLocation `json:"errorLocation"` - FailureInfo stmtErrorFailureInfo `json:"failureInfo"` - // Other fields omitted +type ErrTrino struct { + Message string `json:"message"` + SqlState string `json:"sqlState"` + ErrorCode int `json:"errorCode"` + ErrorName string `json:"errorName"` + ErrorType string `json:"errorType"` + ErrorLocation ErrorLocation `json:"errorLocation"` + FailureInfo FailureInfo `json:"failureInfo"` +} + +func (i ErrTrino) Error() string { + return i.ErrorType + ": " + i.Message } -type stmtErrorLocation struct { +type ErrorLocation struct { LineNumber int `json:"lineNumber"` ColumnNumber int `json:"columnNumber"` } -type stmtErrorFailureInfo struct { +type FailureInfo struct { + Type string `json:"type"` + Message string `json:"message"` + Cause *FailureInfo `json:"cause"` + Suppressed []FailureInfo `json:"suppressed"` + Stack []string `json:"stack"` + ErrorInfo ErrorInfo `json:"errorInfo"` + ErrorLocation ErrorLocation `json:"errorLocation"` +} + +type ErrorInfo struct { + Code int `json:"code"` + Name string `json:"name"` Type string `json:"type"` - // Other fields omitted } -func (e stmtError) Error() string { - return e.FailureInfo.Type + ": " + e.Message +func (i ErrorInfo) Error() string { + return fmt.Sprintf("%s: %s (%d)", i.Type, i.Name, i.Code) } type stmtStage struct { @@ -1101,7 +1122,7 @@ type queryResponse struct { Columns []queryColumn `json:"columns"` Data []queryData `json:"data"` Stats stmtStats `json:"stats"` - Error stmtError `json:"error"` + Error ErrTrino `json:"error"` UpdateType string `json:"updateType"` UpdateCount int64 `json:"updateCount"` } @@ -1149,7 +1170,7 @@ type typeArgument struct { long int64 } -func handleResponseError(status int, respErr stmtError) error { +func handleResponseError(status int, respErr ErrTrino) error { switch respErr.ErrorName { case "": return nil diff --git a/trino/trino_test.go b/trino/trino_test.go index 318f0ef..c79b1d9 100644 --- a/trino/trino_test.go +++ b/trino/trino_test.go @@ -216,7 +216,7 @@ func TestRoundTripRetryQueryError(t *testing.T) { } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(&stmtResponse{ - Error: stmtError{ + Error: ErrTrino{ ErrorName: "TEST", }, }) @@ -920,7 +920,7 @@ func TestQueryCancellation(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(&stmtResponse{ - Error: stmtError{ + Error: ErrTrino{ ErrorName: "USER_CANCELLED", }, }) @@ -984,7 +984,7 @@ func TestFetchNoStackOverflow(t *testing.T) { } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(&stmtResponse{ - Error: stmtError{ + Error: ErrTrino{ ErrorName: "TEST", }, })