mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
217 lines
5.8 KiB
Go
217 lines
5.8 KiB
Go
package query
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/data/utils/jsoniter"
|
|
data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
|
|
"gonum.org/v1/gonum/graph/simple"
|
|
"gonum.org/v1/gonum/graph/topo"
|
|
|
|
query "github.com/grafana/grafana/pkg/apis/query/v0alpha1"
|
|
"github.com/grafana/grafana/pkg/expr"
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
|
"github.com/grafana/grafana/pkg/services/datasources/service"
|
|
)
|
|
|
|
type datasourceRequest struct {
|
|
// The type
|
|
PluginId string `json:"pluginId"`
|
|
|
|
// The UID
|
|
UID string `json:"uid"`
|
|
|
|
// Optionally show the additional query properties
|
|
Request *data.QueryDataRequest `json:"request"`
|
|
|
|
// Headers that should be forwarded to the next request
|
|
Headers map[string]string `json:"headers,omitempty"`
|
|
}
|
|
|
|
type parsedRequestInfo struct {
|
|
// Datasource queries, one for each datasource
|
|
Requests []datasourceRequest `json:"requests,omitempty"`
|
|
|
|
// Expressions in required execution order
|
|
Expressions []expr.ExpressionQuery `json:"expressions,omitempty"`
|
|
|
|
// Expressions include explicit hacks for influx+prometheus
|
|
RefIDTypes map[string]string `json:"types,omitempty"`
|
|
|
|
// Hidden queries used as dependencies
|
|
HideBeforeReturn []string `json:"hide,omitempty"`
|
|
}
|
|
|
|
type queryParser struct {
|
|
legacy service.LegacyDataSourceLookup
|
|
reader *expr.ExpressionQueryReader
|
|
tracer tracing.Tracer
|
|
}
|
|
|
|
func newQueryParser(reader *expr.ExpressionQueryReader, legacy service.LegacyDataSourceLookup, tracer tracing.Tracer) *queryParser {
|
|
return &queryParser{
|
|
reader: reader,
|
|
legacy: legacy,
|
|
tracer: tracer,
|
|
}
|
|
}
|
|
|
|
// Split the main query into multiple
|
|
func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRequest) (parsedRequestInfo, error) {
|
|
ctx, span := p.tracer.Start(ctx, "QueryService.parseRequest")
|
|
defer span.End()
|
|
|
|
queryRefIDs := make(map[string]*data.DataQuery, len(input.Queries))
|
|
expressions := make(map[string]*expr.ExpressionQuery)
|
|
index := make(map[string]int) // index lookup
|
|
rsp := parsedRequestInfo{
|
|
RefIDTypes: make(map[string]string, len(input.Queries)),
|
|
}
|
|
|
|
// Ensure a valid time range
|
|
if input.From == "" {
|
|
input.From = "now-6h"
|
|
}
|
|
if input.To == "" {
|
|
input.To = "now"
|
|
}
|
|
|
|
for _, q := range input.Queries {
|
|
_, found := queryRefIDs[q.RefID]
|
|
if found {
|
|
return rsp, MakePublicQueryError(q.RefID, "multiple queries with same refId")
|
|
}
|
|
_, found = expressions[q.RefID]
|
|
if found {
|
|
return rsp, MakePublicQueryError(q.RefID, "multiple queries with same refId")
|
|
}
|
|
|
|
ds, err := p.getValidDataSourceRef(ctx, q.Datasource, q.DatasourceID)
|
|
if err != nil {
|
|
return rsp, err
|
|
}
|
|
|
|
// Process each query
|
|
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
|
|
// could construct it from the untyped map[string]any additional properties
|
|
// but this approach lets us focus on well typed behavior first
|
|
raw, err := json.Marshal(q)
|
|
if err != nil {
|
|
return rsp, err
|
|
}
|
|
iter, err := jsoniter.ParseBytes(jsoniter.ConfigDefault, raw)
|
|
if err != nil {
|
|
return rsp, err
|
|
}
|
|
exp, err := p.reader.ReadQuery(q, iter)
|
|
if err != nil {
|
|
return rsp, err
|
|
}
|
|
exp.GraphID = int64(len(expressions) + 1)
|
|
expressions[q.RefID] = &exp
|
|
} else {
|
|
key := fmt.Sprintf("%s/%s", ds.Type, ds.UID)
|
|
idx, ok := index[key]
|
|
if !ok {
|
|
idx = len(index)
|
|
index[key] = idx
|
|
rsp.Requests = append(rsp.Requests, datasourceRequest{
|
|
PluginId: ds.Type,
|
|
UID: ds.UID,
|
|
Request: &data.QueryDataRequest{
|
|
TimeRange: input.TimeRange,
|
|
Debug: input.Debug,
|
|
// no queries
|
|
},
|
|
})
|
|
}
|
|
|
|
req := rsp.Requests[idx].Request
|
|
req.Queries = append(req.Queries, q)
|
|
queryRefIDs[q.RefID] = &req.Queries[len(req.Queries)-1]
|
|
}
|
|
|
|
// Mark all the queries that should be hidden ()
|
|
if q.Hide {
|
|
rsp.HideBeforeReturn = append(rsp.HideBeforeReturn, q.RefID)
|
|
}
|
|
}
|
|
|
|
// Make sure all referenced variables exist and the expression order is stable
|
|
if len(expressions) > 0 {
|
|
queryNode := &expr.ExpressionQuery{
|
|
GraphID: -1,
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
// Do not hide queries used in variables
|
|
if q != nil && q.Hide {
|
|
q.Hide = false
|
|
}
|
|
if target.ID() == exp.ID() {
|
|
return rsp, makeCyclicError(refId)
|
|
}
|
|
dg.SetEdge(dg.NewEdge(target, exp))
|
|
}
|
|
}
|
|
|
|
// Add the sorted expressions
|
|
sortedNodes, err := topo.SortStabilized(dg, nil)
|
|
if err != nil {
|
|
return rsp, makeCyclicError("")
|
|
}
|
|
for _, v := range sortedNodes {
|
|
if v.ID() > 0 {
|
|
rsp.Expressions = append(rsp.Expressions, *v.(*expr.ExpressionQuery))
|
|
}
|
|
}
|
|
}
|
|
|
|
return rsp, nil
|
|
}
|
|
|
|
func (p *queryParser) getValidDataSourceRef(ctx context.Context, ds *data.DataSourceRef, id int64) (*data.DataSourceRef, error) {
|
|
if ds == nil {
|
|
if id == 0 {
|
|
return nil, fmt.Errorf("missing datasource reference or id")
|
|
}
|
|
if p.legacy == nil {
|
|
return nil, fmt.Errorf("legacy datasource lookup unsupported (id:%d)", id)
|
|
}
|
|
return p.legacy.GetDataSourceFromDeprecatedFields(ctx, "", id)
|
|
}
|
|
if ds.Type == "" {
|
|
if ds.UID == "" {
|
|
return nil, fmt.Errorf("missing name/uid in data source reference")
|
|
}
|
|
if ds.UID == expr.DatasourceType {
|
|
return ds, nil
|
|
}
|
|
if p.legacy == nil {
|
|
return nil, fmt.Errorf("legacy datasource lookup unsupported (name:%s)", ds.UID)
|
|
}
|
|
return p.legacy.GetDataSourceFromDeprecatedFields(ctx, ds.UID, 0)
|
|
}
|
|
return ds, nil
|
|
}
|