SQL Expressions: Add sql expression specific timeout and output limit (#104834)

Adds settings for SQL expressions:
 sql_expression_cell_output_limit

Set the maximum number of cells that can be returned from a SQL expression. Default is 100000.

sql_expression_timeout

The duration a SQL expression will run before being cancelled. The default is 10s.
This commit is contained in:
Kyle Brandt
2025-05-13 15:22:20 -04:00
committed by GitHub
parent 02d977e1af
commit 5e056c2a3f
12 changed files with 183 additions and 22 deletions

View File

@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/expr/metrics"
"github.com/grafana/grafana/pkg/expr/sql"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/setting"
)
var (
@ -30,12 +31,16 @@ type SQLCommand struct {
query string
varsToQuery []string
refID string
limit int64
format string
format string
inputLimit int64
outputLimit int64
timeout time.Duration
}
// NewSQLCommand creates a new SQLCommand.
func NewSQLCommand(refID, format, rawSQL string, limit int64) (*SQLCommand, error) {
func NewSQLCommand(refID, format, rawSQL string, intputLimit, outputLimit int64, timeout time.Duration) (*SQLCommand, error) {
if rawSQL == "" {
return nil, ErrMissingSQLQuery
}
@ -63,13 +68,15 @@ func NewSQLCommand(refID, format, rawSQL string, limit int64) (*SQLCommand, erro
query: rawSQL,
varsToQuery: tables,
refID: refID,
limit: limit,
inputLimit: intputLimit,
outputLimit: outputLimit,
timeout: timeout,
format: format,
}, nil
}
// UnmarshalSQLCommand creates a SQLCommand from Grafana's frontend query.
func UnmarshalSQLCommand(rn *rawNode, limit int64) (*SQLCommand, error) {
func UnmarshalSQLCommand(rn *rawNode, cfg *setting.Cfg) (*SQLCommand, error) {
if rn.TimeRange == nil {
logger.Error("time range must be specified for refID", "refID", rn.RefID)
return nil, fmt.Errorf("time range must be specified for refID %s", rn.RefID)
@ -89,7 +96,7 @@ func UnmarshalSQLCommand(rn *rawNode, limit int64) (*SQLCommand, error) {
formatRaw := rn.Query["format"]
format, _ := formatRaw.(string)
return NewSQLCommand(rn.RefID, format, expression, limit)
return NewSQLCommand(rn.RefID, format, expression, cfg.SQLExpressionCellLimit, cfg.SQLExpressionOutputCellLimit, cfg.SQLExpressionTimeout)
}
// NeedsVars returns the variable names (refIds) that are dependencies
@ -131,11 +138,11 @@ func (gr *SQLCommand) Execute(ctx context.Context, now time.Time, vars mathexp.V
tc = totalCells(allFrames)
// limit of 0 or less means no limit (following convention)
if gr.limit > 0 && tc > gr.limit {
if gr.inputLimit > 0 && tc > gr.inputLimit {
return mathexp.Results{},
fmt.Errorf(
"SQL expression: total cell count across all input tables exceeds limit of %d. Total cells: %d",
gr.limit,
gr.inputLimit,
tc,
)
}
@ -143,7 +150,7 @@ func (gr *SQLCommand) Execute(ctx context.Context, now time.Time, vars mathexp.V
logger.Debug("Executing query", "query", gr.query, "frames", len(allFrames))
db := sql.DB{}
frame, err := db.QueryFrames(ctx, tracer, gr.refID, gr.query, allFrames)
frame, err := db.QueryFrames(ctx, tracer, gr.refID, gr.query, allFrames, sql.WithMaxOutputCells(gr.outputLimit), sql.WithTimeout(gr.timeout))
rsp := mathexp.Results{}
if err != nil {