2020-11-19 06:17:00 -06:00
|
|
|
package mathexp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"math"
|
|
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/expr/mathexp/parse"
|
|
|
|
)
|
|
|
|
|
|
|
|
var builtins = map[string]parse.Func{
|
|
|
|
"abs": {
|
|
|
|
Args: []parse.ReturnType{parse.TypeVariantSet},
|
|
|
|
VariantReturn: true,
|
|
|
|
F: abs,
|
|
|
|
},
|
|
|
|
"log": {
|
|
|
|
Args: []parse.ReturnType{parse.TypeVariantSet},
|
|
|
|
VariantReturn: true,
|
|
|
|
F: log,
|
|
|
|
},
|
|
|
|
"nan": {
|
|
|
|
Return: parse.TypeScalar,
|
|
|
|
F: nan,
|
|
|
|
},
|
2021-12-29 10:40:52 -06:00
|
|
|
"is_nan": {
|
|
|
|
Args: []parse.ReturnType{parse.TypeVariantSet},
|
|
|
|
VariantReturn: true,
|
|
|
|
F: isNaN,
|
|
|
|
},
|
2020-11-19 06:17:00 -06:00
|
|
|
"inf": {
|
|
|
|
Return: parse.TypeScalar,
|
|
|
|
F: inf,
|
|
|
|
},
|
2021-12-29 10:40:52 -06:00
|
|
|
"infn": {
|
|
|
|
Return: parse.TypeScalar,
|
|
|
|
F: infn,
|
|
|
|
},
|
|
|
|
"is_inf": {
|
|
|
|
Args: []parse.ReturnType{parse.TypeVariantSet},
|
|
|
|
VariantReturn: true,
|
|
|
|
F: isInf,
|
|
|
|
},
|
2020-11-19 06:17:00 -06:00
|
|
|
"null": {
|
|
|
|
Return: parse.TypeScalar,
|
|
|
|
F: null,
|
|
|
|
},
|
2021-12-29 10:40:52 -06:00
|
|
|
"is_null": {
|
|
|
|
Args: []parse.ReturnType{parse.TypeVariantSet},
|
|
|
|
VariantReturn: true,
|
|
|
|
F: isNull,
|
|
|
|
},
|
|
|
|
"is_number": {
|
|
|
|
Args: []parse.ReturnType{parse.TypeVariantSet},
|
|
|
|
VariantReturn: true,
|
|
|
|
F: isNumber,
|
|
|
|
},
|
2022-01-21 12:15:50 -06:00
|
|
|
"round": {
|
|
|
|
Args: []parse.ReturnType{parse.TypeVariantSet},
|
|
|
|
VariantReturn: true,
|
|
|
|
F: round,
|
|
|
|
},
|
|
|
|
"ceil": {
|
|
|
|
Args: []parse.ReturnType{parse.TypeVariantSet},
|
|
|
|
VariantReturn: true,
|
|
|
|
F: ceil,
|
|
|
|
},
|
|
|
|
"floor": {
|
|
|
|
Args: []parse.ReturnType{parse.TypeVariantSet},
|
|
|
|
VariantReturn: true,
|
|
|
|
F: floor,
|
|
|
|
},
|
2020-11-19 06:17:00 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// abs returns the absolute value for each result in NumberSet, SeriesSet, or Scalar
|
|
|
|
func abs(e *State, varSet Results) (Results, error) {
|
|
|
|
newRes := Results{}
|
|
|
|
for _, res := range varSet.Values {
|
2020-12-11 05:59:12 -06:00
|
|
|
newVal, err := perFloat(e, res, math.Abs)
|
2020-11-19 06:17:00 -06:00
|
|
|
if err != nil {
|
|
|
|
return newRes, err
|
|
|
|
}
|
|
|
|
newRes.Values = append(newRes.Values, newVal)
|
|
|
|
}
|
|
|
|
return newRes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// log returns the natural logarithm value for each result in NumberSet, SeriesSet, or Scalar
|
|
|
|
func log(e *State, varSet Results) (Results, error) {
|
|
|
|
newRes := Results{}
|
|
|
|
for _, res := range varSet.Values {
|
2020-12-11 05:59:12 -06:00
|
|
|
newVal, err := perFloat(e, res, math.Log)
|
2020-11-19 06:17:00 -06:00
|
|
|
if err != nil {
|
|
|
|
return newRes, err
|
|
|
|
}
|
|
|
|
newRes.Values = append(newRes.Values, newVal)
|
|
|
|
}
|
|
|
|
return newRes, nil
|
|
|
|
}
|
|
|
|
|
2021-12-29 10:40:52 -06:00
|
|
|
// isNaN returns 1 if the value for each result in NumberSet, SeriesSet, or Scalar is NaN, else 0.
|
|
|
|
func isNaN(e *State, varSet Results) (Results, error) {
|
|
|
|
newRes := Results{}
|
|
|
|
for _, res := range varSet.Values {
|
|
|
|
newVal, err := perFloat(e, res, func(f float64) float64 {
|
|
|
|
if math.IsNaN(f) {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return newRes, err
|
|
|
|
}
|
|
|
|
newRes.Values = append(newRes.Values, newVal)
|
|
|
|
}
|
|
|
|
return newRes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// isInf returns 1 if the value for each result in NumberSet, SeriesSet, or Scalar is a
|
|
|
|
// positive or negative Inf, else 0.
|
|
|
|
func isInf(e *State, varSet Results) (Results, error) {
|
|
|
|
newRes := Results{}
|
|
|
|
for _, res := range varSet.Values {
|
|
|
|
newVal, err := perFloat(e, res, func(f float64) float64 {
|
|
|
|
if math.IsInf(f, 0) {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return newRes, err
|
|
|
|
}
|
|
|
|
newRes.Values = append(newRes.Values, newVal)
|
|
|
|
}
|
|
|
|
return newRes, nil
|
|
|
|
}
|
|
|
|
|
2020-11-19 06:17:00 -06:00
|
|
|
// nan returns a scalar nan value
|
|
|
|
func nan(e *State) Results {
|
|
|
|
aNaN := math.NaN()
|
2020-12-11 05:59:12 -06:00
|
|
|
return NewScalarResults(e.RefID, &aNaN)
|
2020-11-19 06:17:00 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// inf returns a scalar positive infinity value
|
|
|
|
func inf(e *State) Results {
|
|
|
|
aInf := math.Inf(0)
|
2020-12-11 05:59:12 -06:00
|
|
|
return NewScalarResults(e.RefID, &aInf)
|
2020-11-19 06:17:00 -06:00
|
|
|
}
|
|
|
|
|
2021-12-29 10:40:52 -06:00
|
|
|
// infn returns a scalar negative infinity value
|
|
|
|
func infn(e *State) Results {
|
|
|
|
aInf := math.Inf(-1)
|
|
|
|
return NewScalarResults(e.RefID, &aInf)
|
|
|
|
}
|
|
|
|
|
2020-11-19 06:17:00 -06:00
|
|
|
// null returns a null scalar value
|
|
|
|
func null(e *State) Results {
|
2020-12-11 05:59:12 -06:00
|
|
|
return NewScalarResults(e.RefID, nil)
|
2020-11-19 06:17:00 -06:00
|
|
|
}
|
|
|
|
|
2021-12-29 10:40:52 -06:00
|
|
|
// isNull returns 1 if the value for each result in NumberSet, SeriesSet, or Scalar is null, else 0.
|
|
|
|
func isNull(e *State, varSet Results) (Results, error) {
|
|
|
|
newRes := Results{}
|
|
|
|
for _, res := range varSet.Values {
|
|
|
|
newVal, err := perNullableFloat(e, res, func(f *float64) *float64 {
|
|
|
|
nF := float64(0)
|
|
|
|
if f == nil {
|
|
|
|
nF = 1
|
|
|
|
}
|
|
|
|
return &nF
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return newRes, err
|
|
|
|
}
|
|
|
|
newRes.Values = append(newRes.Values, newVal)
|
|
|
|
}
|
|
|
|
return newRes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// isNumber returns 1 if the value for each result in NumberSet, SeriesSet, or Scalar is a real number, else 0.
|
|
|
|
// Therefore 0 is returned if the value Inf+, Inf-, NaN, or Null.
|
|
|
|
func isNumber(e *State, varSet Results) (Results, error) {
|
|
|
|
newRes := Results{}
|
|
|
|
for _, res := range varSet.Values {
|
|
|
|
newVal, err := perNullableFloat(e, res, func(f *float64) *float64 {
|
|
|
|
nF := float64(1)
|
|
|
|
if f == nil || math.IsInf(*f, 0) || math.IsNaN(*f) {
|
|
|
|
nF = 0
|
|
|
|
}
|
|
|
|
return &nF
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return newRes, err
|
|
|
|
}
|
|
|
|
newRes.Values = append(newRes.Values, newVal)
|
|
|
|
}
|
|
|
|
return newRes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// perFloat passes the non-null value of a Scalar/Number or each value point of a Series to floatF.
|
|
|
|
// The return Value type will be the same type provided to function, (e.g. a Series input returns a series).
|
|
|
|
// If input values are null the function is not called and NaN is returned for each value.
|
2020-12-11 05:59:12 -06:00
|
|
|
func perFloat(e *State, val Value, floatF func(x float64) float64) (Value, error) {
|
2020-11-19 06:17:00 -06:00
|
|
|
var newVal Value
|
|
|
|
switch val.Type() {
|
|
|
|
case parse.TypeNumberSet:
|
2020-12-11 05:59:12 -06:00
|
|
|
n := NewNumber(e.RefID, val.GetLabels())
|
2020-11-19 06:17:00 -06:00
|
|
|
f := val.(Number).GetFloat64Value()
|
|
|
|
nF := math.NaN()
|
|
|
|
if f != nil {
|
|
|
|
nF = floatF(*f)
|
|
|
|
}
|
|
|
|
n.SetValue(&nF)
|
|
|
|
newVal = n
|
|
|
|
case parse.TypeScalar:
|
|
|
|
f := val.(Scalar).GetFloat64Value()
|
|
|
|
nF := math.NaN()
|
|
|
|
if f != nil {
|
|
|
|
nF = floatF(*f)
|
|
|
|
}
|
2020-12-11 05:59:12 -06:00
|
|
|
newVal = NewScalar(e.RefID, &nF)
|
2020-11-19 06:17:00 -06:00
|
|
|
case parse.TypeSeriesSet:
|
|
|
|
resSeries := val.(Series)
|
2021-06-02 11:29:19 -05:00
|
|
|
newSeries := NewSeries(e.RefID, resSeries.GetLabels(), resSeries.Len())
|
2020-11-19 06:17:00 -06:00
|
|
|
for i := 0; i < resSeries.Len(); i++ {
|
|
|
|
t, f := resSeries.GetPoint(i)
|
|
|
|
nF := math.NaN()
|
|
|
|
if f != nil {
|
|
|
|
nF = floatF(*f)
|
|
|
|
}
|
2022-02-02 07:50:44 -06:00
|
|
|
newSeries.SetPoint(i, t, &nF)
|
2020-11-19 06:17:00 -06:00
|
|
|
}
|
|
|
|
newVal = newSeries
|
2023-01-20 08:53:03 -06:00
|
|
|
case parse.TypeNoData:
|
|
|
|
newVal = NewNoData()
|
2020-12-01 02:53:27 -06:00
|
|
|
default:
|
|
|
|
// TODO: Should we deal with TypeString, TypeVariantSet?
|
2020-11-19 06:17:00 -06:00
|
|
|
}
|
2020-12-01 02:53:27 -06:00
|
|
|
|
2020-11-19 06:17:00 -06:00
|
|
|
return newVal, nil
|
|
|
|
}
|
2021-12-29 10:40:52 -06:00
|
|
|
|
|
|
|
// perNullableFloat is like perFloat, but takes and returns float pointers instead of floats.
|
|
|
|
// This is for instead for functions that need specific null handling.
|
|
|
|
// The input float pointer should not be modified in the floatF func.
|
|
|
|
func perNullableFloat(e *State, val Value, floatF func(x *float64) *float64) (Value, error) {
|
|
|
|
var newVal Value
|
|
|
|
switch val.Type() {
|
|
|
|
case parse.TypeNumberSet:
|
|
|
|
n := NewNumber(e.RefID, val.GetLabels())
|
|
|
|
f := val.(Number).GetFloat64Value()
|
|
|
|
n.SetValue(floatF(f))
|
|
|
|
newVal = n
|
|
|
|
case parse.TypeScalar:
|
|
|
|
f := val.(Scalar).GetFloat64Value()
|
|
|
|
newVal = NewScalar(e.RefID, floatF(f))
|
|
|
|
case parse.TypeSeriesSet:
|
|
|
|
resSeries := val.(Series)
|
|
|
|
newSeries := NewSeries(e.RefID, resSeries.GetLabels(), resSeries.Len())
|
|
|
|
for i := 0; i < resSeries.Len(); i++ {
|
|
|
|
t, f := resSeries.GetPoint(i)
|
2022-02-02 07:50:44 -06:00
|
|
|
newSeries.SetPoint(i, t, floatF(f))
|
2021-12-29 10:40:52 -06:00
|
|
|
}
|
|
|
|
newVal = newSeries
|
2023-01-20 08:53:03 -06:00
|
|
|
case parse.TypeNoData:
|
|
|
|
newVal = NewNoData()
|
2021-12-29 10:40:52 -06:00
|
|
|
default:
|
|
|
|
// TODO: Should we deal with TypeString, TypeVariantSet?
|
|
|
|
}
|
|
|
|
|
|
|
|
return newVal, nil
|
|
|
|
}
|
2022-01-21 12:15:50 -06:00
|
|
|
|
|
|
|
// round returns the rounded value for each result in NumberSet, SeriesSet, or Scalar
|
|
|
|
func round(e *State, varSet Results) (Results, error) {
|
|
|
|
newRes := Results{}
|
|
|
|
for _, res := range varSet.Values {
|
|
|
|
newVal, err := perFloat(e, res, math.Round)
|
|
|
|
if err != nil {
|
|
|
|
return newRes, err
|
|
|
|
}
|
|
|
|
newRes.Values = append(newRes.Values, newVal)
|
|
|
|
}
|
|
|
|
return newRes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ceil returns the rounded up value for each result in NumberSet, SeriesSet, or Scalar
|
|
|
|
func ceil(e *State, varSet Results) (Results, error) {
|
|
|
|
newRes := Results{}
|
|
|
|
for _, res := range varSet.Values {
|
|
|
|
newVal, err := perFloat(e, res, math.Ceil)
|
|
|
|
if err != nil {
|
|
|
|
return newRes, err
|
|
|
|
}
|
|
|
|
newRes.Values = append(newRes.Values, newVal)
|
|
|
|
}
|
|
|
|
return newRes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// floor returns the rounded down value for each result in NumberSet, SeriesSet, or Scalar
|
|
|
|
func floor(e *State, varSet Results) (Results, error) {
|
|
|
|
newRes := Results{}
|
|
|
|
for _, res := range varSet.Values {
|
|
|
|
newVal, err := perFloat(e, res, math.Floor)
|
|
|
|
if err != nil {
|
|
|
|
return newRes, err
|
|
|
|
}
|
|
|
|
newRes.Values = append(newRes.Values, newVal)
|
|
|
|
}
|
|
|
|
return newRes, nil
|
|
|
|
}
|