mirror of https://github.com/grafana/grafana.git
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:
parent
74cfe7b803
commit
dd4ffc9918
|
@ -2858,15 +2858,19 @@ Set this to `false` to disable expressions and hide them in the Grafana UI. Defa
|
||||||
|
|
||||||
#### `sql_expression_cell_limit`
|
#### `sql_expression_cell_limit`
|
||||||
|
|
||||||
Set the maximum number of cells that can be passed to a SQL expression. Default is `100000`.
|
Set the maximum number of cells that can be passed to a SQL expression. Default is `100000`. A setting of `0` means no limit.
|
||||||
|
|
||||||
#### `sql_expression_output_cell_limit`
|
#### `sql_expression_output_cell_limit`
|
||||||
|
|
||||||
Set the maximum number of cells that can be returned from a SQL expression. Default is `100000`.
|
Set the maximum number of cells that can be returned from a SQL expression. Default is `100000`. A setting of `0` means no limit.
|
||||||
|
|
||||||
|
### `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.
|
||||||
|
|
||||||
#### `sql_expression_timeout`
|
#### `sql_expression_timeout`
|
||||||
|
|
||||||
The duration a SQL expression will run before being cancelled. The default is `10s`.
|
The duration a SQL expression will run before being cancelled. The default is `10s`. A setting of `0s` means no limit.
|
||||||
|
|
||||||
### `[geomap]`
|
### `[geomap]`
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
"gonum.org/v1/gonum/graph/simple"
|
"gonum.org/v1/gonum/graph/simple"
|
||||||
"gonum.org/v1/gonum/graph/topo"
|
"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{}
|
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)
|
graph, err := s.buildDependencyGraph(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -233,7 +233,8 @@ func TestServicebuildPipeLine(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
s := Service{
|
s := Service{
|
||||||
cfg: setting.NewCfg(),
|
cfg: setting.NewCfg(),
|
||||||
|
tracer: &testTracer{},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
|
@ -199,4 +199,15 @@ func TestSQLServiceErrors(t *testing.T) {
|
||||||
_, err := s.BuildPipeline(t.Context(), req)
|
_, err := s.BuildPipeline(t.Context(), req)
|
||||||
require.Error(t, err, "whole pipeline fails when selecting a dependency that does not exist")
|
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")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,6 +195,7 @@ func TestSQLExpressionCellLimitFromConfig(t *testing.T) {
|
||||||
converter: &ResultConverter{
|
converter: &ResultConverter{
|
||||||
Features: features,
|
Features: features,
|
||||||
},
|
},
|
||||||
|
tracer: &testTracer{},
|
||||||
}
|
}
|
||||||
|
|
||||||
req := &Request{Queries: queries, User: &user.SignedInUser{}}
|
req := &Request{Queries: queries, User: &user.SignedInUser{}}
|
||||||
|
|
|
@ -389,3 +389,23 @@ func MakeColumnNotFoundError(refID string, err error) CategorizedError {
|
||||||
|
|
||||||
return &ErrorWithCategory{category: ErrCategoryColumnNotFound, err: ColumnNotFoundError.Build(data)}
|
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)}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
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"]
|
formatRaw := rn.Query["format"]
|
||||||
format, _ := formatRaw.(string)
|
format, _ := formatRaw.(string)
|
||||||
|
|
||||||
|
|
|
@ -47,11 +47,12 @@ func (s *singleTenantInstanceProvider) GetInstance(_ context.Context, _ map[stri
|
||||||
|
|
||||||
func (s *singleTenantInstance) GetSettings() clientapi.InstanceConfigurationSettings {
|
func (s *singleTenantInstance) GetSettings() clientapi.InstanceConfigurationSettings {
|
||||||
return clientapi.InstanceConfigurationSettings{
|
return clientapi.InstanceConfigurationSettings{
|
||||||
FeatureToggles: s.features,
|
FeatureToggles: s.features,
|
||||||
SQLExpressionCellLimit: s.cfg.SQLExpressionCellLimit,
|
SQLExpressionCellLimit: s.cfg.SQLExpressionCellLimit,
|
||||||
SQLExpressionOutputCellLimit: s.cfg.SQLExpressionOutputCellLimit,
|
SQLExpressionOutputCellLimit: s.cfg.SQLExpressionOutputCellLimit,
|
||||||
SQLExpressionTimeout: s.cfg.SQLExpressionTimeout,
|
SQLExpressionQueryLengthLimit: s.cfg.SQLExpressionQueryLengthLimit,
|
||||||
ExpressionsEnabled: s.cfg.ExpressionsEnabled,
|
SQLExpressionTimeout: s.cfg.SQLExpressionTimeout,
|
||||||
|
ExpressionsEnabled: s.cfg.ExpressionsEnabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,11 +21,12 @@ type QueryDataClient interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstanceConfigurationSettings struct {
|
type InstanceConfigurationSettings struct {
|
||||||
FeatureToggles featuremgmt.FeatureToggles
|
FeatureToggles featuremgmt.FeatureToggles
|
||||||
SQLExpressionCellLimit int64
|
SQLExpressionCellLimit int64
|
||||||
SQLExpressionOutputCellLimit int64
|
SQLExpressionOutputCellLimit int64
|
||||||
SQLExpressionTimeout time.Duration
|
SQLExpressionQueryLengthLimit int64
|
||||||
ExpressionsEnabled bool
|
SQLExpressionTimeout time.Duration
|
||||||
|
ExpressionsEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Instance interface {
|
type Instance interface {
|
||||||
|
|
|
@ -286,10 +286,11 @@ func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuil
|
||||||
|
|
||||||
exprService := expr.ProvideService(
|
exprService := expr.ProvideService(
|
||||||
&setting.Cfg{
|
&setting.Cfg{
|
||||||
ExpressionsEnabled: instanceConfig.ExpressionsEnabled,
|
ExpressionsEnabled: instanceConfig.ExpressionsEnabled,
|
||||||
SQLExpressionCellLimit: instanceConfig.SQLExpressionCellLimit,
|
SQLExpressionCellLimit: instanceConfig.SQLExpressionCellLimit,
|
||||||
SQLExpressionOutputCellLimit: instanceConfig.SQLExpressionOutputCellLimit,
|
SQLExpressionOutputCellLimit: instanceConfig.SQLExpressionOutputCellLimit,
|
||||||
SQLExpressionTimeout: instanceConfig.SQLExpressionTimeout,
|
SQLExpressionTimeout: instanceConfig.SQLExpressionTimeout,
|
||||||
|
SQLExpressionQueryLengthLimit: instanceConfig.SQLExpressionQueryLengthLimit,
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
|
|
@ -441,6 +441,9 @@ type Cfg struct {
|
||||||
// SQLExpressionOutputCellLimit is the maximum number of cells (rows × columns) that can be outputted by a SQL expression.
|
// SQLExpressionOutputCellLimit is the maximum number of cells (rows × columns) that can be outputted by a SQL expression.
|
||||||
SQLExpressionOutputCellLimit int64
|
SQLExpressionOutputCellLimit int64
|
||||||
|
|
||||||
|
// SQLExpressionQueryLengthLimit is the maximum length of a SQL query that can be used in a SQL expression.
|
||||||
|
SQLExpressionQueryLengthLimit int64
|
||||||
|
|
||||||
// SQLExpressionTimeoutSeconds is the duration a SQL expression will run before timing out
|
// SQLExpressionTimeoutSeconds is the duration a SQL expression will run before timing out
|
||||||
SQLExpressionTimeout time.Duration
|
SQLExpressionTimeout time.Duration
|
||||||
|
|
||||||
|
@ -830,6 +833,7 @@ func (cfg *Cfg) readExpressionsSettings() {
|
||||||
cfg.SQLExpressionCellLimit = expressions.Key("sql_expression_cell_limit").MustInt64(100000)
|
cfg.SQLExpressionCellLimit = expressions.Key("sql_expression_cell_limit").MustInt64(100000)
|
||||||
cfg.SQLExpressionOutputCellLimit = expressions.Key("sql_expression_output_cell_limit").MustInt64(100000)
|
cfg.SQLExpressionOutputCellLimit = expressions.Key("sql_expression_output_cell_limit").MustInt64(100000)
|
||||||
cfg.SQLExpressionTimeout = expressions.Key("sql_expression_timeout").MustDuration(time.Second * 10)
|
cfg.SQLExpressionTimeout = expressions.Key("sql_expression_timeout").MustDuration(time.Second * 10)
|
||||||
|
cfg.SQLExpressionQueryLengthLimit = expressions.Key("sql_expression_query_length_limit").MustInt64(10000)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AnnotationCleanupSettings struct {
|
type AnnotationCleanupSettings struct {
|
||||||
|
|
Loading…
Reference in New Issue