SQL Expressions: Query Service Support (#101955)

---------

Co-authored-by: Adam Simpson <adam@adamsimpson.net>
Co-authored-by: Sarah Zinger <sarah.zinger@grafana.com>
This commit is contained in:
Kyle Brandt
2025-04-03 09:36:02 -04:00
committed by GitHub
co-authored by Adam Simpson Sarah Zinger
parent 370f4c2bcd
commit 4a0ec27e5d
8 changed files with 118 additions and 57 deletions
+28 -3
View File
@@ -43,6 +43,9 @@ type parsedRequestInfo struct {
// Hidden queries used as dependencies
HideBeforeReturn []string `json:"hide,omitempty"`
// SQL Inputs
SqlInputs map[string]struct{} `json:"sqlInputs,omitempty"`
}
type queryParser struct {
@@ -71,6 +74,7 @@ func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRe
index := make(map[string]int) // index lookup
rsp := parsedRequestInfo{
RefIDTypes: make(map[string]string, len(input.Queries)),
SqlInputs: make(map[string]struct{}),
}
for _, q := range input.Queries {
@@ -90,6 +94,7 @@ func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRe
}
// Process each query
// check if ds is expression
if expr.IsDataSource(ds.UID) {
// In order to process the query as a typed expression query, we
// are writing it back to JSON and parsing again. Alternatively we
@@ -149,20 +154,40 @@ func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRe
// Build the graph for a request
dg := simple.NewDirectedGraph()
dg.AddNode(queryNode)
for _, exp := range expressions {
dg.AddNode(exp)
}
for _, exp := range expressions {
vars := exp.Command.NeedsVars()
for _, refId := range vars {
target := queryNode
q, ok := queryRefIDs[refId]
if !ok {
target, ok = expressions[refId]
if !ok {
return rsp, makeDependencyError(exp.RefID, refId)
_, isSQLCMD := target.Command.(*expr.SQLCommand)
if isSQLCMD {
continue
} else {
target, ok = expressions[refId]
if !ok {
return rsp, makeDependencyError(exp.RefID, refId)
}
}
}
// If the input is SQL, conversion is handled differently
if _, isSqlExp := exp.Command.(*expr.SQLCommand); isSqlExp {
if _, ifDepIsAlsoExpression := expressions[refId]; ifDepIsAlsoExpression {
// Only allow data source nodes as SQL expression inputs for now
return rsp, fmt.Errorf("only data source queries may be inputs to a sql expression, %v is the input for %v", refId, exp.RefID)
} else {
rsp.SqlInputs[refId] = struct{}{}
}
}
// Do not hide queries used in variables
if q != nil && q.Hide {
q.Hide = false
+34
View File
@@ -166,6 +166,40 @@ func TestQuerySplitting(t *testing.T) {
})
}
func TestSqlInputs(t *testing.T) {
parser := newQueryParser(
expr.NewExpressionQueryReader(featuremgmt.WithFeatures(featuremgmt.FlagSqlExpressions)),
nil,
tracing.InitializeTracerForTest(),
log.NewNopLogger(),
)
parsedRequestInfo, err := parser.parseRequest(context.Background(), &query.QueryDataRequest{
QueryDataRequest: data.QueryDataRequest{
Queries: []data.DataQuery{
data.NewDataQuery(map[string]any{
"refId": "A",
"datasource": &data.DataSourceRef{
Type: "prometheus",
UID: "local-prom",
},
}),
data.NewDataQuery(map[string]any{
"refId": "B",
"datasource": &data.DataSourceRef{
Type: "__expr__",
UID: "__expr__",
},
"type": "sql",
"expression": "Select time, value + 10 from A",
}),
},
},
})
require.NoError(t, err)
require.Equal(t, parsedRequestInfo.SqlInputs["B"], struct{}{})
}
type legacyDataSourceRetriever struct{}
func (s *legacyDataSourceRetriever) GetDataSourceFromDeprecatedFields(ctx context.Context, name string, id int64) (*data.DataSourceRef, error) {
+5 -2
View File
@@ -381,11 +381,14 @@ func (b *QueryAPIBuilder) handleExpressions(ctx context.Context, req parsedReque
if !ok {
dr, ok := qdr.Responses[refId]
if ok {
_, res, err := b.converter.Convert(ctx, req.RefIDTypes[refId], dr.Frames)
_, isSqlInput := req.SqlInputs[refId]
_, res, err := b.converter.Convert(ctx, req.RefIDTypes[refId], dr.Frames, isSqlInput)
if err != nil {
expressionsLogger.Error("error converting frames for expressions", "error", err)
res.Error = err
}
vars[refId] = res
} else {
expressionsLogger.Error("missing variable in handle expressions", "refId", refId, "expressionRefId", expression.RefID)
@@ -427,7 +430,7 @@ func (b *QueryAPIBuilder) convertQueryWithoutExpression(ctx context.Context, req
return nil, fmt.Errorf("refID '%s' does not exist", refID)
}
frames := qdr.Responses[refID].Frames
_, results, err := b.converter.Convert(ctx, req.PluginId, frames)
_, results, err := b.converter.Convert(ctx, req.PluginId, frames, false)
if err != nil {
results.Error = err
}