SSE: Add noData type (#51973)

When there is a single frame with no fields (e.g. splunk datasource) SSE errors when trying to figure out the data type. This frame needs to exist since this is where the executedQueryString metadata exists.

This adds a new return type to SSE to represent no data, so the original frame with its metadata can still be maintained.
This commit is contained in:
Kyle Brandt 2022-07-14 09:18:12 -04:00 committed by GitHub
parent 5d199a40b7
commit 05fd7eb047
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 88 additions and 2 deletions

View File

@ -117,6 +117,8 @@ func (e *State) walkUnary(node *parse.UnaryNode) (Results, error) {
newVal, err = e.unaryNumber(rt, node.OpStr)
case Series:
newVal, err = e.unarySeries(rt, node.OpStr)
case NoData:
newVal = NoData{}.New()
default:
return newResults, fmt.Errorf("can not perform a unary operation on type %v", rt.Type())
}
@ -192,9 +194,16 @@ type Union struct {
// number of tags.
func union(aResults, bResults Results) []*Union {
unions := []*Union{}
if len(aResults.Values) == 0 || len(bResults.Values) == 0 {
aValueLen := len(aResults.Values)
bValueLen := len(bResults.Values)
if aValueLen == 0 || bValueLen == 0 {
return unions
}
if aValueLen == 1 || bValueLen == 1 {
if aResults.Values[0].Type() == parse.TypeNoData || bResults.Values[0].Type() == parse.TypeNoData {
return unions
}
}
for _, a := range aResults.Values {
for _, b := range bResults.Values {
var labels data.Labels

View File

@ -401,6 +401,8 @@ const (
TypeSeriesSet
// TypeVariantSet is a collection of the same type Number, Series, or Scalar.
TypeVariantSet
// TypeNoData is a no data response without a known data type.
TypeNoData
)
// String returns a string representation of the ReturnType.
@ -416,6 +418,8 @@ func (f ReturnType) String() string {
return "scalar"
case TypeVariantSet:
return "variant"
case TypeNoData:
return "noData"
default:
return "unknown"
}

View File

@ -173,3 +173,44 @@ func (ff *Float64Field) Len() int {
df := data.Field(*ff)
return df.Len()
}
// NoData is an untyped no data response.
type NoData struct{ Frame *data.Frame }
// Type returns the Value type and allows it to fulfill the Value interface.
func (s NoData) Type() parse.ReturnType { return parse.TypeNoData }
// Value returns the actual value allows it to fulfill the Value interface.
func (s NoData) Value() interface{} { return s }
func (s NoData) GetLabels() data.Labels { return nil }
func (s NoData) SetLabels(ls data.Labels) {}
func (s NoData) GetMeta() interface{} {
return s.Frame.Meta.Custom
}
func (s NoData) SetMeta(v interface{}) {
m := s.Frame.Meta
if m == nil {
m = &data.FrameMeta{}
s.Frame.SetMeta(m)
}
m.Custom = v
}
func (s NoData) AddNotice(notice data.Notice) {
m := s.Frame.Meta
if m == nil {
m = &data.FrameMeta{}
s.Frame.SetMeta(m)
}
m.Notices = append(m.Notices, notice)
}
func (s NoData) AsDataFrame() *data.Frame { return s.Frame }
func (s NoData) New() NoData {
return NoData{data.NewFrame("no data")}
}

View File

@ -79,6 +79,32 @@ func Test_union(t *testing.T) {
unionsAre: assert.EqualValues,
unions: []*Union{},
},
{
name: "empty result and data result will result in no unions",
aResults: Results{
Values: Values{
makeSeries("a", data.Labels{"id": "1"}),
},
},
bResults: Results{},
unionsAre: assert.EqualValues,
unions: []*Union{},
},
{
name: "no data result and data result will result in no unions",
aResults: Results{
Values: Values{
makeSeries("a", data.Labels{"id": "1"}),
},
},
bResults: Results{
Values: Values{
NoData{}.New(),
},
},
unionsAre: assert.EqualValues,
unions: []*Union{},
},
{
name: "incompatible tags of different length with will result in no unions when len(A) != 1 && len(B) != 1",
aResults: Results{

View File

@ -237,7 +237,7 @@ func (dn *DSNode) Execute(ctx context.Context, vars mathexp.Vars, s *Service) (m
}
dataSource := dn.datasource.Type
if isAllFrameVectors(dataSource, qr.Frames) {
if isAllFrameVectors(dataSource, qr.Frames) { // Prometheus Specific Handling
vals, err = framesToNumbers(qr.Frames)
if err != nil {
return mathexp.Results{}, fmt.Errorf("failed to read frames as numbers: %w", err)
@ -247,6 +247,12 @@ func (dn *DSNode) Execute(ctx context.Context, vars mathexp.Vars, s *Service) (m
if len(qr.Frames) == 1 {
frame := qr.Frames[0]
// Handle Untyped NoData
if len(frame.Fields) == 0 {
return mathexp.Results{Values: mathexp.Values{mathexp.NoData{Frame: frame}}}, nil
}
// Handle Numeric Table
if frame.TimeSeriesSchema().Type == data.TimeSeriesTypeNot && isNumberTable(frame) {
logger.Debug("expression datasource query (numberSet)", "query", refID)
numberSet, err := extractNumberSet(frame)