diff --git a/app/vmselect/promql/binary_op.go b/app/vmselect/promql/binary_op.go index 7bde1cab5..609ce1020 100644 --- a/app/vmselect/promql/binary_op.go +++ b/app/vmselect/promql/binary_op.go @@ -416,10 +416,25 @@ func binaryOpIfnot(left, right float64) float64 { } func binaryOpEq(left, right float64) bool { + // Special handling for nan == nan. + // See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/150 . + if math.IsNaN(left) { + return math.IsNaN(right) + } + return left == right } func binaryOpNeq(left, right float64) bool { + // Special handling for comparison with nan. + // See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/150 . + if math.IsNaN(left) { + return !math.IsNaN(right) + } + if math.IsNaN(right) { + return true + } + return left != right } diff --git a/app/vmselect/promql/lexer.go b/app/vmselect/promql/lexer.go index 6be24bec5..8dfe23208 100644 --- a/app/vmselect/promql/lexer.go +++ b/app/vmselect/promql/lexer.go @@ -149,12 +149,6 @@ func scanString(s string) (string, error) { } func scanPositiveNumber(s string) (string, error) { - if strings.HasPrefix(s, "Inf") { - return "Inf", nil - } - if strings.HasPrefix(s, "NaN") { - return "NaN", nil - } // Scan integer part. It may be empty if fractional part exists. i := 0 for i < len(s) && isDecimalChar(s[i]) { @@ -333,6 +327,14 @@ func scanTagFilterOpPrefix(s string) int { return -1 } +func isInfOrNaN(s string) bool { + if len(s) != 3 { + return false + } + s = strings.ToLower(s) + return s == "inf" || s == "nan" +} + func isOffset(s string) bool { s = strings.ToLower(s) return s == "offset" @@ -361,7 +363,7 @@ func isPositiveNumberPrefix(s string) bool { // Check for .234 numbers if s[0] != '.' || len(s) < 2 { - return strings.HasPrefix(s, "Inf") || strings.HasPrefix(s, "NaN") + return false } return isDecimalChar(s[1]) } diff --git a/app/vmselect/promql/parser.go b/app/vmselect/promql/parser.go index 6c72690d0..b2511727e 100644 --- a/app/vmselect/promql/parser.go +++ b/app/vmselect/promql/parser.go @@ -373,7 +373,7 @@ func (p *parser) parseSingleExpr() (expr, error) { } func (p *parser) parseSingleExprWithoutRollupSuffix() (expr, error) { - if isPositiveNumberPrefix(p.lex.Token) { + if isPositiveNumberPrefix(p.lex.Token) || isInfOrNaN(p.lex.Token) { return p.parsePositiveNumberExpr() } if isStringPrefix(p.lex.Token) { @@ -417,7 +417,7 @@ func (p *parser) parseSingleExprWithoutRollupSuffix() (expr, error) { } func (p *parser) parsePositiveNumberExpr() (*numberExpr, error) { - if !isPositiveNumberPrefix(p.lex.Token) { + if !isPositiveNumberPrefix(p.lex.Token) && !isInfOrNaN(p.lex.Token) { return nil, fmt.Errorf(`positiveNumberExpr: unexpected token %q; want "number"`, p.lex.Token) } diff --git a/app/vmselect/promql/parser_test.go b/app/vmselect/promql/parser_test.go index cc90abf19..1ef8554cb 100644 --- a/app/vmselect/promql/parser_test.go +++ b/app/vmselect/promql/parser_test.go @@ -170,14 +170,34 @@ func TestParsePromQLSuccess(t *testing.T) { another(`-.2`, `-0.2`) another(`-.2E-2`, `-0.002`) same(`NaN`) + another(`nan`, `NaN`) + another(`NAN`, `NaN`) + another(`nAN`, `NaN`) another(`Inf`, `+Inf`) + another(`INF`, `+Inf`) + another(`inf`, `+Inf`) another(`+Inf`, `+Inf`) another(`-Inf`, `-Inf`) + another(`-inF`, `-Inf`) // binaryOpExpr - another(`NaN + 2 *3 * Inf`, `NaN`) - another(`Inf - Inf`, `NaN`) - another(`Inf + Inf`, `+Inf`) + another(`nan == nan`, `NaN`) + another(`nan ==bool nan`, `1`) + another(`nan !=bool nan`, `0`) + another(`nan !=bool 2`, `1`) + another(`2 !=bool nan`, `1`) + another(`nan >bool nan`, `0`) + another(`nan =bool 2`, `1`) + another(`-1 >bool -inf`, `1`) + another(`-1