SQL Expressions: Add setting to limit length of query (#110165)

sql_expression_query_length_limit

Set the maximum length of a SQL query that can be used in a SQL expression. Default is 10000 characters. A setting of 0 means no limit.
This commit is contained in:
Kyle Brandt
2025-08-27 12:08:25 -04:00
committed by GitHub
parent 74cfe7b803
commit dd4ffc9918
11 changed files with 88 additions and 18 deletions
+22
View File
@@ -9,6 +9,8 @@ import (
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"golang.org/x/exp/maps"
"gonum.org/v1/gonum/graph/simple"
"gonum.org/v1/gonum/graph/topo"
@@ -202,6 +204,26 @@ func (s *Service) buildPipeline(ctx context.Context, req *Request) (DataPipeline
req.Headers = map[string]string{}
}
instrumentSQLError := func(err error, span trace.Span) {
var sqlErr *sql.ErrorWithCategory
if errors.As(err, &sqlErr) {
// The SQL expression (and the entire pipeline) will not be executed, so we
// track the attempt to execute here.
s.metrics.SqlCommandCount.WithLabelValues("error", sqlErr.Category()).Inc()
}
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
}
}
_, span := s.tracer.Start(ctx, "SSE.BuildPipeline")
var err error
defer func() {
instrumentSQLError(err, span)
span.End()
}()
graph, err := s.buildDependencyGraph(ctx, req)
if err != nil {
return nil, err
+2 -1
View File
@@ -233,7 +233,8 @@ func TestServicebuildPipeLine(t *testing.T) {
},
}
s := Service{
cfg: setting.NewCfg(),
cfg: setting.NewCfg(),
tracer: &testTracer{},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+11
View File
@@ -199,4 +199,15 @@ func TestSQLServiceErrors(t *testing.T) {
_, err := s.BuildPipeline(t.Context(), req)
require.Error(t, err, "whole pipeline fails when selecting a dependency that does not exist")
})
t.Run("pipeline will fail if query is longer than the configured limit", func(t *testing.T) {
s, req := newMockQueryService(resp,
newABSQLQueries(`SELECT This is too long and does not need to be valid SQL`),
)
s.cfg.SQLExpressionQueryLengthLimit = 5
s.features = featuremgmt.WithFeatures(featuremgmt.FlagSqlExpressions)
_, err := s.BuildPipeline(t.Context(), req)
require.ErrorContains(t, err, "exceeded the configured limit of 5 characters")
})
}
+1
View File
@@ -195,6 +195,7 @@ func TestSQLExpressionCellLimitFromConfig(t *testing.T) {
converter: &ResultConverter{
Features: features,
},
tracer: &testTracer{},
}
req := &Request{Queries: queries, User: &user.SignedInUser{}}
+20
View File
@@ -389,3 +389,23 @@ func MakeColumnNotFoundError(refID string, err error) CategorizedError {
return &ErrorWithCategory{category: ErrCategoryColumnNotFound, err: ColumnNotFoundError.Build(data)}
}
const ErrCategoryQueryTooLong = "query_too_long"
var queryTooLongStr = `sql expression [{{.Public.refId}}] was not run because the SQL query exceeded the configured limit of {{ .Public.queryLengthLimit }} characters`
var QueryTooLongError = errutil.NewBase(
errutil.StatusBadRequest, sseErrBase+ErrCategoryQueryTooLong).MustTemplate(
queryTooLongStr,
errutil.WithPublic(queryTooLongStr))
func MakeQueryTooLongError(refID string, queryLengthLimit int64) CategorizedError {
data := errutil.TemplateData{
Public: map[string]interface{}{
"refId": refID,
"queryLengthLimit": queryLengthLimit,
},
}
return &ErrorWithCategory{category: ErrCategoryQueryTooLong, err: QueryTooLongError.Build(data)}
}
+4
View File
@@ -86,6 +86,10 @@ func UnmarshalSQLCommand(ctx context.Context, rn *rawNode, cfg *setting.Cfg) (*S
return nil, fmt.Errorf("expected sql expression to be type string, but got type %T", expressionRaw)
}
if cfg.SQLExpressionQueryLengthLimit > 0 && len(expression) > int(cfg.SQLExpressionQueryLengthLimit) {
return nil, sql.MakeQueryTooLongError(rn.RefID, cfg.SQLExpressionQueryLengthLimit)
}
formatRaw := rn.Query["format"]
format, _ := formatRaw.(string)