SQL Expressions: Rework backend errors and error instrumentation (#109633)

* Capture error_type label on metrics/traces
* Make error messages more helpful to user
* Use errutil, categorized errors, and tie them to error_type (category in code)
* Misc trace fixes
* Add metric to track SQL input conversion
This commit is contained in:
Kyle Brandt
2025-08-25 11:13:42 -04:00
committed by GitHub
parent 539b413584
commit 4f0cb47d3c
18 changed files with 858 additions and 299 deletions
+4 -40
View File
@@ -19,42 +19,6 @@ import (
// DB is a database that can execute SQL queries against a set of Frames.
type DB struct{}
// GoMySQLServerError represents an error from the underlying Go MySQL Server
type GoMySQLServerError struct {
Err error
}
// Error implements the error interface
func (e *GoMySQLServerError) Error() string {
return fmt.Sprintf("error in go-mysql-server: %v", e.Err)
}
// Unwrap provides the original error for errors.Is/As
func (e *GoMySQLServerError) Unwrap() error {
return e.Err
}
// WrapGoMySQLServerError wraps errors from Go MySQL Server with additional context
func WrapGoMySQLServerError(err error) error {
// Don't wrap nil errors
if err == nil {
return nil
}
// Check if it's a function not found error or other specific GMS errors
if isFunctionNotFoundError(err) {
return &GoMySQLServerError{Err: err}
}
// Return original error if it's not one we want to wrap
return err
}
// isFunctionNotFoundError checks if the error is related to a function not being found
func isFunctionNotFoundError(err error) bool {
return mysql.ErrFunctionNotFound.Is(err)
}
type QueryOption func(*QueryOptions)
type QueryOptions struct {
@@ -80,7 +44,7 @@ func WithMaxOutputCells(n int64) QueryOption {
// The name becomes the name and RefID of the returned frame.
func (db *DB) QueryFrames(ctx context.Context, tracer tracing.Tracer, name string, query string, frames []*data.Frame, opts ...QueryOption) (*data.Frame, error) {
// We are parsing twice due to TablesList, but don't care fow now. We can save the parsed query and reuse it later if we want.
if allow, err := AllowQuery(query); err != nil || !allow {
if allow, err := AllowQuery(name, query); err != nil || !allow {
if err != nil {
return nil, err
}
@@ -122,9 +86,9 @@ func (db *DB) QueryFrames(ctx context.Context, tracer tracing.Tracer, name strin
contextErr := func(err error) error {
switch {
case errors.Is(err, context.DeadlineExceeded):
return fmt.Errorf("SQL expression for refId %v did not complete within the timeout of %v: %w", name, QueryOptions.Timeout, err)
return MakeTimeOutError(err, name, QueryOptions.Timeout)
case errors.Is(err, context.Canceled):
return fmt.Errorf("SQL expression for refId %v was cancelled before it completed: %w", name, err)
return MakeCancelError(err, name)
default:
return fmt.Errorf("SQL expression for refId %v ended unexpectedly: %w", name, err)
}
@@ -136,7 +100,7 @@ func (db *DB) QueryFrames(ctx context.Context, tracer tracing.Tracer, name strin
if ctx.Err() != nil {
return nil, contextErr(ctx.Err())
}
return nil, WrapGoMySQLServerError(err)
return nil, MakeGMSError(name, err)
}
// Convert the iterator into a Grafana data.Frame