SQL Expressions: Add endpoint to get Schemas (#108864)

Return the SQL schema for all DS queries in request (to provide information to AI / Autocomplete for SQL expressions).

All DS queries are treated as if they were inputs to SQL expressions in terms of conversion, regardless if they are selected in a query or not.

Requires feature toggle queryService = true

Endpoint is apis/query.grafana.app/v0alpha1/namespaces/default/sqlschemas

---------

Co-authored-by: Todd Treece <360020+toddtreece@users.noreply.github.com>
This commit is contained in:
Kyle Brandt
2025-10-30 10:05:12 -04:00
committed by GitHub
co-authored by Todd Treece
parent c487952279
commit c3d7dbc258
15 changed files with 630 additions and 24 deletions
+75
View File
@@ -0,0 +1,75 @@
package query
import (
"context"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/dsquerierclient"
"github.com/grafana/grafana/pkg/services/validations"
)
func (s *ServiceImpl) GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error) {
//TODO DEdupe code
parsedReq, err := s.parseMetricRequest(ctx, user, false, reqDTO, false)
if err != nil {
return expr.SQLSchemas{}, err
}
exprReq := expr.Request{
Queries: []expr.Query{},
}
if user != nil { // for passthrough authentication, SSE does not authenticate
exprReq.User = user
exprReq.OrgId = user.GetOrgID()
}
for _, pq := range parsedReq.getFlattenedQueries() {
if pq.datasource == nil {
return nil, ErrMissingDataSourceInfo.Build(errutil.TemplateData{
Public: map[string]any{
"RefId": pq.query.RefID,
},
})
}
exprReq.Queries = append(exprReq.Queries, expr.Query{
JSON: pq.query.JSON,
Interval: pq.query.Interval,
RefID: pq.query.RefID,
MaxDataPoints: pq.query.MaxDataPoints,
QueryType: pq.query.QueryType,
DataSource: pq.datasource,
TimeRange: expr.AbsoluteTimeRange{
From: pq.query.TimeRange.From,
To: pq.query.TimeRange.To,
},
})
}
return s.expressionService.GetSQLSchemas(ctx, exprReq)
}
func GetSQLSchemas(ctx context.Context, log log.Logger, dscache datasources.CacheService, exprService *expr.Service, reqDTO dtos.MetricRequest, qsDatasourceClientBuilder dsquerierclient.QSDatasourceClientBuilder, headers map[string]string) (expr.SQLSchemas, error) {
s := &ServiceImpl{
log: log,
dataSourceCache: dscache,
expressionService: exprService,
dataSourceRequestValidator: validations.ProvideValidator(),
qsDatasourceClientBuilder: qsDatasourceClientBuilder,
headers: headers,
concurrentQueryLimit: 16, // TODO: make it configurable
}
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}
return s.GetSQLSchemas(ctx, user, reqDTO)
}
+2
View File
@@ -73,6 +73,8 @@ type Service interface {
// this is more "forward compatible", for example supports per-query time ranges
QueryDataNew(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error)
GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error)
}
// Gives us compile time error if the service does not adhere to the contract of the interface
+6
View File
@@ -4,10 +4,12 @@ package query
import (
context "context"
"fmt"
backend "github.com/grafana/grafana-plugin-sdk-go/backend"
dtos "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/expr"
identity "github.com/grafana/grafana/pkg/apimachinery/identity"
@@ -97,6 +99,10 @@ func (_m *FakeQueryService) Run(ctx context.Context) error {
return r0
}
func (_m *FakeQueryService) GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error) {
return nil, fmt.Errorf("sql schema endpoint not supported with public dashboards")
}
// NewFakeQueryService creates a new instance of FakeQueryService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewFakeQueryService(t interface {