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
+43
View File
@@ -23,11 +23,17 @@ type ResultConverter struct {
func (c *ResultConverter) Convert(ctx context.Context,
datasourceType string,
frames data.Frames,
forSqlInput bool,
) (string, mathexp.Results, error) {
if len(frames) == 0 {
return "no-data", mathexp.Results{Values: mathexp.Values{mathexp.NewNoData()}}, nil
}
if forSqlInput {
results, err := handleSqlInput(frames)
return "sql input", results, err
}
var dt data.FrameType
dt, useDataplane, _ := shouldUseDataplane(frames, logger, c.Features.IsEnabled(ctx, featuremgmt.FlagDisableSSEDataplane))
if useDataplane {
@@ -120,6 +126,43 @@ func (c *ResultConverter) Convert(ctx context.Context,
}, nil
}
// copied from pkg/expr/nodes.go from within the Execute method
func handleSqlInput(dataFrames data.Frames) (mathexp.Results, error) {
var result mathexp.Results
var needsConversion bool
// Convert it if Multi:
if len(dataFrames) > 1 {
needsConversion = true
}
// Convert it if Wide (has labels):
if len(dataFrames) == 1 {
for _, field := range dataFrames[0].Fields {
if len(field.Labels) > 0 {
needsConversion = true
break
}
}
}
if needsConversion {
convertedFrames, err := ConvertToFullLong(dataFrames)
if err != nil {
return result, fmt.Errorf("failed to convert data frames to long format for sql: %w", err)
}
result.Values = mathexp.Values{
mathexp.TableData{Frame: convertedFrames[0]},
}
return result, nil
}
// Otherwise it is already Long format; return as is
result.Values = mathexp.Values{
mathexp.TableData{Frame: dataFrames[0]},
}
return result, nil
}
func getResponseFrame(logger *log.ConcreteLogger, resp *backend.QueryDataResponse, refID string) (data.Frames, error) {
response, ok := resp.Responses[refID]
if !ok {
+3 -3
View File
@@ -40,7 +40,7 @@ func TestConvertDataFramesToResults(t *testing.T) {
for _, dtype := range supported {
t.Run(dtype, func(t *testing.T) {
resultType, res, err := converter.Convert(context.Background(), dtype, frames)
resultType, res, err := converter.Convert(context.Background(), dtype, frames, false)
require.NoError(t, err)
assert.Equal(t, "single frame series", resultType)
require.Len(t, res.Values, 2)
@@ -68,7 +68,7 @@ func TestConvertDataFramesToResults(t *testing.T) {
for _, dtype := range supported {
t.Run(dtype, func(t *testing.T) {
resultType, res, err := converter.Convert(context.Background(), dtype, frames)
resultType, res, err := converter.Convert(context.Background(), dtype, frames, false)
require.NoError(t, err)
assert.Equal(t, "multi frame series", resultType)
require.Len(t, res.Values, 2)
@@ -101,7 +101,7 @@ func TestConvertDataFramesToResults(t *testing.T) {
for _, dtype := range supported {
t.Run(dtype, func(t *testing.T) {
resultType, res, err := converter.Convert(context.Background(), dtype, frames)
resultType, res, err := converter.Convert(context.Background(), dtype, frames, false)
require.NoError(t, err)
assert.Equal(t, "multi frame series", resultType)
require.Len(t, res.Values, 2)
+1 -1
View File
@@ -130,7 +130,7 @@ func (m *MLNode) Execute(ctx context.Context, now time.Time, _ mathexp.Vars, s *
}
// process the response the same way DSNode does. Use plugin ID as data source type. Semantically, they are the same.
responseType, result, err = s.converter.Convert(ctx, mlPluginID, dataFrames)
responseType, result, err = s.converter.Convert(ctx, mlPluginID, dataFrames, false)
return result, err
}
+2 -37
View File
@@ -341,7 +341,7 @@ func executeDSNodesGrouped(ctx context.Context, now time.Time, vars mathexp.Vars
}
var result mathexp.Results
responseType, result, err := s.converter.Convert(ctx, dn.datasource.Type, dataFrames)
responseType, result, err := s.converter.Convert(ctx, dn.datasource.Type, dataFrames, dn.isInputToSQLExpr)
if err != nil {
result.Error = makeConversionError(dn.RefID(), err)
}
@@ -409,44 +409,9 @@ func (dn *DSNode) Execute(ctx context.Context, now time.Time, _ mathexp.Vars, s
}
var result mathexp.Results
// If the datasource node is an input to a SQL expression,
// the data must be in the Long format
if dn.isInputToSQLExpr {
var needsConversion bool
// Convert it if Multi:
if len(dataFrames) > 1 {
needsConversion = true
}
// Convert it if Wide (has labels):
if len(dataFrames) == 1 {
for _, field := range dataFrames[0].Fields {
if len(field.Labels) > 0 {
needsConversion = true
break
}
}
}
responseType, result, err = s.converter.Convert(ctx, dn.datasource.Type, dataFrames, dn.isInputToSQLExpr)
if needsConversion {
convertedFrames, err := ConvertToFullLong(dataFrames)
if err != nil {
return result, fmt.Errorf("failed to convert data frames to long format for sql: %w", err)
}
result.Values = mathexp.Values{
mathexp.TableData{Frame: convertedFrames[0]},
}
return result, nil
}
// Otherwise it is already Long format; return as is
result.Values = mathexp.Values{
mathexp.TableData{Frame: dataFrames[0]},
}
return result, nil
}
responseType, result, err = s.converter.Convert(ctx, dn.datasource.Type, dataFrames)
if err != nil {
err = makeConversionError(dn.refID, err)
}
+2 -11
View File
@@ -126,9 +126,8 @@ func (h *ExpressionQueryReader) ReadQuery(
}
case QueryTypeSQL:
enabled := enableSqlExpressions(h)
if !enabled {
return eq, fmt.Errorf("sqlExpressions is not implemented")
if !h.features.IsEnabledGlobally(featuremgmt.FlagSqlExpressions) {
return eq, fmt.Errorf("sql expressions are disabled")
}
q := &SQLExpression{}
err = iter.ReadVal(q)
@@ -192,11 +191,3 @@ func getReferenceVar(exp string, refId string) (string, error) {
}
return exp, nil
}
func enableSqlExpressions(h *ExpressionQueryReader) bool {
enabled := !h.features.IsEnabledGlobally(featuremgmt.FlagSqlExpressions)
if enabled {
return false
}
return false
}