mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 12:32:22 +08:00
SQL Expressions: Re-implement feature using go-mysql-server (#99521)
* Under feature flag `sqlExpressions` and is experimental * Excluded from arm32 * Will not work with the Query Service yet * Does not have limits in place yet * Does not working with alerting yet * Currently requires "prepare time series" Transform for time series viz --------- Co-authored-by: Sam Jewell <sam.jewell@grafana.com>
This commit is contained in:
104
pkg/expr/service_sql_test.go
Normal file
104
pkg/expr/service_sql_test.go
Normal file
@ -0,0 +1,104 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSQLService(t *testing.T) {
|
||||
inputFrame := data.NewFrame("",
|
||||
data.NewField("time", nil, []time.Time{time.Unix(1, 0)}),
|
||||
data.NewField("value", nil, []*float64{fp(2)}),
|
||||
)
|
||||
|
||||
resp := map[string]backend.DataResponse{
|
||||
"A": {Frames: data.Frames{inputFrame}},
|
||||
}
|
||||
|
||||
newABSQLQueries := func(q string) []Query {
|
||||
q, err := jsonEscape(q)
|
||||
require.NoError(t, err)
|
||||
return []Query{
|
||||
{
|
||||
RefID: "A",
|
||||
DataSource: &datasources.DataSource{
|
||||
OrgID: 1,
|
||||
UID: "test",
|
||||
Type: "test",
|
||||
},
|
||||
JSON: json.RawMessage(`{ "datasource": { "uid": "1" }, "intervalMs": 1000, "maxDataPoints": 1000 }`),
|
||||
TimeRange: AbsoluteTimeRange{
|
||||
From: time.Time{},
|
||||
To: time.Time{},
|
||||
},
|
||||
},
|
||||
{
|
||||
RefID: "B",
|
||||
DataSource: dataSourceModel(),
|
||||
JSON: json.RawMessage(fmt.Sprintf(`{ "datasource": { "uid": "__expr__", "type": "__expr__"}, "type": "sql", "expression": "%s" }`, q)),
|
||||
TimeRange: AbsoluteTimeRange{
|
||||
From: time.Time{},
|
||||
To: time.Time{},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
t.Run("no feature flag no queries for you", func(t *testing.T) {
|
||||
s, req := newMockQueryService(resp, newABSQLQueries(""))
|
||||
|
||||
_, err := s.BuildPipeline(req)
|
||||
require.Error(t, err, "should not be able to build pipeline without feature flag")
|
||||
})
|
||||
|
||||
t.Run("with feature flag basic select works", func(t *testing.T) {
|
||||
s, req := newMockQueryService(resp, newABSQLQueries("SELECT * FROM A"))
|
||||
s.features = featuremgmt.WithFeatures(featuremgmt.FlagSqlExpressions)
|
||||
pl, err := s.BuildPipeline(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := s.ExecutePipeline(context.Background(), time.Now(), pl)
|
||||
require.NoError(t, err)
|
||||
|
||||
inputFrame.RefID = "B"
|
||||
inputFrame.Name = "B"
|
||||
if diff := cmp.Diff(res.Responses["B"].Frames[0], inputFrame, data.FrameTestCompareOptions()...); diff != "" {
|
||||
require.FailNowf(t, "Result mismatch (-want +got):%s\n", diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("load_file is blocked", func(t *testing.T) {
|
||||
s, req := newMockQueryService(resp,
|
||||
newABSQLQueries(`SELECT CAST(load_file('/etc/topSecretz') AS CHAR(10000) CHARACTER SET utf8)`),
|
||||
)
|
||||
|
||||
s.features = featuremgmt.WithFeatures(featuremgmt.FlagSqlExpressions)
|
||||
|
||||
pl, err := s.BuildPipeline(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
rsp, err := s.ExecutePipeline(context.Background(), time.Now(), pl)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Error(t, rsp.Responses["B"].Error, "should return invalid sql error")
|
||||
require.ErrorContains(t, rsp.Responses["B"].Error, "blocked function load_file")
|
||||
})
|
||||
}
|
||||
|
||||
func jsonEscape(input string) (string, error) {
|
||||
escaped, err := json.Marshal(input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// json.Marshal returns the escaped string with quotes, so we need to trim them
|
||||
return string(escaped[1 : len(escaped)-1]), nil
|
||||
}
|
Reference in New Issue
Block a user