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
+57 -22
View File
@@ -241,25 +241,40 @@ func (r *queryREST) Connect(connectCtx context.Context, name string, _ runtime.O
}), nil
}
func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuilder, httpreq *http.Request, responder responderWrapper, connectLogger log.Logger) (*backend.QueryDataResponse, error) {
var jsonQueries = make([]*simplejson.Json, 0, len(raw.Queries))
for _, query := range raw.Queries {
dsRef, err := getValidDataSourceRef(ctx, query.Datasource, query.DatasourceID, b.legacyDatasourceLookup)
if err != nil {
connectLogger.Error("error getting valid datasource ref", err)
}
if dsRef != nil {
query.Datasource = dsRef
type preparedQuery struct {
mReq dtos.MetricRequest
cache datasources.CacheService
headers map[string]string
logger log.Logger
builder dsquerierclient.QSDatasourceClientBuilder
exprSvc *expr.Service
reportMetrics func()
}
func prepareQuery(
ctx context.Context,
raw query.QueryDataRequest,
b QueryAPIBuilder,
httpreq *http.Request,
connectLogger log.Logger,
) (*preparedQuery, error) {
// Normalize DS refs and build []*simplejson.Json
jsonQueries := make([]*simplejson.Json, 0, len(raw.Queries))
for _, q := range raw.Queries {
if dsRef, derr := getValidDataSourceRef(ctx, q.Datasource, q.DatasourceID, b.legacyDatasourceLookup); derr != nil {
connectLogger.Error("error getting valid datasource ref", "err", derr)
} else if dsRef != nil {
q.Datasource = dsRef
}
jsonBytes, err := json.Marshal(query)
jsonBytes, err := json.Marshal(q)
if err != nil {
connectLogger.Error("error marshalling", err)
connectLogger.Error("error marshalling query", "err", err)
}
sjQuery, _ := simplejson.NewJson(jsonBytes)
sjQuery, err := simplejson.NewJson(jsonBytes)
if err != nil {
connectLogger.Error("error unmarshalling", err)
connectLogger.Error("error creating simplejson for query", "err", err)
}
jsonQueries = append(jsonQueries, sjQuery)
@@ -274,13 +289,11 @@ func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuil
cache := &MyCacheService{
legacy: b.legacyDatasourceLookup,
}
headers := ExtractKnownHeaders(httpreq.Header)
instance, err := b.instanceProvider.GetInstance(ctx, connectLogger, headers)
if err != nil {
connectLogger.Error("failed to get instance configuration settings", "err", err)
responder.Error(err)
return nil, err
}
@@ -288,12 +301,14 @@ func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuil
dsQuerierLoggerWithSlug := instance.GetLogger()
// Datasource client qsDsClientBuilder
qsDsClientBuilder := dsquerierclient.NewQsDatasourceClientBuilderWithInstance(
instance,
ctx,
dsQuerierLoggerWithSlug,
)
// Expressions service
exprService := expr.ProvideService(
&setting.Cfg{
ExpressionsEnabled: instanceConfig.ExpressionsEnabled,
@@ -310,17 +325,37 @@ func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuil
qsDsClientBuilder,
)
qdr, err := service.QueryData(ctx, dsQuerierLoggerWithSlug, cache, exprService, mReq, qsDsClientBuilder, headers)
return &preparedQuery{
mReq: mReq,
cache: cache,
headers: headers,
logger: dsQuerierLoggerWithSlug,
builder: qsDsClientBuilder,
exprSvc: exprService,
reportMetrics: func() { instance.ReportMetrics() },
}, nil
}
// tell the `instance` structure that it can now report
// metrics that are only reported once during a request
instance.ReportMetrics()
func handlePreparedQuery(ctx context.Context, pq *preparedQuery) (*backend.QueryDataResponse, error) {
resp, err := service.QueryData(ctx, pq.logger, pq.cache, pq.exprSvc, pq.mReq, pq.builder, pq.headers)
pq.reportMetrics()
return resp, err
}
func handleQuery(
ctx context.Context,
raw query.QueryDataRequest,
b QueryAPIBuilder,
httpreq *http.Request,
responder responderWrapper,
connectLogger log.Logger,
) (*backend.QueryDataResponse, error) {
pq, err := prepareQuery(ctx, raw, b, httpreq, connectLogger)
if err != nil {
return qdr, err
responder.Error(err)
return nil, err
}
return qdr, nil
return handlePreparedQuery(ctx, pq)
}
type responderWrapper struct {