prometheus/promql/parser/parse_test.go
Lukasz Mierzwa 7e22d2e5c0 Refactor TestParseExpressions to be more explicit about errors
Right now TestParseExpressions tests if a query returns an error but it only does a fuzzy check on returned errors.
The error returned by the parser is ParseErrors, which is a slice of ParseErr structs.
The Error() method on ParseErrors will return an error string based on the first error in that slice. This hides other returned errors so we can end up with bogus errors being returned but won't ever find this via this test.
This change makes the test compare returned error (which is always ParseErrors type) with expected ParseErrors slice.
The extra benefit of this is that current tests mostly ignore error positional range and only test for correct error message. Now errors must return expected positional information.
There are a few cases uncovered where the positional informatio of errors seems wrong, added FIXME for these lines.

Signed-off-by: Lukasz Mierzwa <l.mierzwa@gmail.com>
2025-08-14 10:01:44 +01:00

5893 lines
156 KiB
Go

// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package parser
import (
"errors"
"fmt"
"math"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql/parser/posrange"
"github.com/prometheus/prometheus/util/testutil"
)
func repeatError(query string, err error, start, startStep, end, endStep, count int) (errs ParseErrors) {
for i := 0; i < count; i++ {
errs = append(errs, ParseErr{
PositionRange: posrange.PositionRange{
Start: posrange.Pos(start + (i * startStep)),
End: posrange.Pos(end + (i * endStep)),
},
Err: err,
Query: query,
})
}
return errs
}
var testExpr = []struct {
input string // The input to be parsed.
expected Expr // The expected expression AST.
fail bool // Whether parsing is supposed to fail.
errors ParseErrors // The errors that should be returned.
}{
// Scalars and scalar-to-scalar operations.
{
input: "1",
expected: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 0, End: 1},
},
},
{
input: "+Inf",
expected: &NumberLiteral{
Val: math.Inf(1),
PosRange: posrange.PositionRange{Start: 0, End: 4},
},
},
{
input: "-Inf",
expected: &NumberLiteral{
Val: math.Inf(-1),
PosRange: posrange.PositionRange{Start: 0, End: 4},
},
},
{
input: ".5",
expected: &NumberLiteral{
Val: 0.5,
PosRange: posrange.PositionRange{Start: 0, End: 2},
},
},
{
input: "5.",
expected: &NumberLiteral{
Val: 5,
PosRange: posrange.PositionRange{Start: 0, End: 2},
},
},
{
input: "123.4567",
expected: &NumberLiteral{
Val: 123.4567,
PosRange: posrange.PositionRange{Start: 0, End: 8},
},
},
{
input: "5e-3",
expected: &NumberLiteral{
Val: 0.005,
PosRange: posrange.PositionRange{Start: 0, End: 4},
},
},
{
input: "5e3",
expected: &NumberLiteral{
Val: 5000,
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
},
{
input: "0xc",
expected: &NumberLiteral{
Val: 12,
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
},
{
input: "0755",
expected: &NumberLiteral{
Val: 493,
PosRange: posrange.PositionRange{Start: 0, End: 4},
},
},
{
input: "+5.5e-3",
expected: &NumberLiteral{
Val: 0.0055,
PosRange: posrange.PositionRange{Start: 0, End: 7},
},
},
{
input: "-0755",
expected: &NumberLiteral{
Val: -493,
PosRange: posrange.PositionRange{Start: 0, End: 5},
},
},
{
input: "1 + 1",
expected: &BinaryExpr{
Op: ADD,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 4, End: 5},
},
},
},
{
input: "1 - 1",
expected: &BinaryExpr{
Op: SUB,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 4, End: 5},
},
},
},
{
input: "1 * 1",
expected: &BinaryExpr{
Op: MUL,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 4, End: 5},
},
},
},
{
input: "1 % 1",
expected: &BinaryExpr{
Op: MOD,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 4, End: 5},
},
},
},
{
input: "1 / 1",
expected: &BinaryExpr{
Op: DIV,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 4, End: 5},
},
},
},
{
input: "1 == bool 1",
expected: &BinaryExpr{
Op: EQLC,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 10, End: 11},
},
ReturnBool: true,
},
},
{
input: "1 != bool 1",
expected: &BinaryExpr{
Op: NEQ,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 10, End: 11},
},
ReturnBool: true,
},
},
{
input: "1 > bool 1",
expected: &BinaryExpr{
Op: GTR,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 9, End: 10},
},
ReturnBool: true,
},
},
{
input: "1 >= bool 1",
expected: &BinaryExpr{
Op: GTE,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 10, End: 11},
},
ReturnBool: true,
},
},
{
input: "1 < bool 1",
expected: &BinaryExpr{
Op: LSS,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 9, End: 10},
},
ReturnBool: true,
},
},
{
input: "1 <= bool 1",
expected: &BinaryExpr{
Op: LTE,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 10, End: 11},
},
ReturnBool: true,
},
},
{
input: "-1^2",
expected: &UnaryExpr{
Op: SUB,
Expr: &BinaryExpr{
Op: POW,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 1, End: 2},
},
RHS: &NumberLiteral{
Val: 2,
PosRange: posrange.PositionRange{Start: 3, End: 4},
},
},
},
},
{
input: "-1*2",
expected: &BinaryExpr{
Op: MUL,
LHS: &NumberLiteral{
Val: -1,
PosRange: posrange.PositionRange{Start: 0, End: 2},
},
RHS: &NumberLiteral{
Val: 2,
PosRange: posrange.PositionRange{Start: 3, End: 4},
},
},
},
{
input: "-1+2",
expected: &BinaryExpr{
Op: ADD,
LHS: &NumberLiteral{
Val: -1,
PosRange: posrange.PositionRange{Start: 0, End: 2},
},
RHS: &NumberLiteral{
Val: 2,
PosRange: posrange.PositionRange{Start: 3, End: 4},
},
},
},
{
input: "-1^-2",
expected: &UnaryExpr{
Op: SUB,
Expr: &BinaryExpr{
Op: POW,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 1, End: 2},
},
RHS: &NumberLiteral{
Val: -2,
PosRange: posrange.PositionRange{Start: 3, End: 5},
},
},
},
},
{
input: "+1 + -2 * 1",
expected: &BinaryExpr{
Op: ADD,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 0, End: 2},
},
RHS: &BinaryExpr{
Op: MUL,
LHS: &NumberLiteral{
Val: -2,
PosRange: posrange.PositionRange{Start: 5, End: 7},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 10, End: 11},
},
},
},
},
{
input: "1 + 2/(3*1)",
expected: &BinaryExpr{
Op: ADD,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 0, End: 1},
},
RHS: &BinaryExpr{
Op: DIV,
LHS: &NumberLiteral{
Val: 2,
PosRange: posrange.PositionRange{Start: 4, End: 5},
},
RHS: &ParenExpr{
Expr: &BinaryExpr{
Op: MUL,
LHS: &NumberLiteral{
Val: 3,
PosRange: posrange.PositionRange{Start: 7, End: 8},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 9, End: 10},
},
},
PosRange: posrange.PositionRange{Start: 6, End: 11},
},
},
},
},
{
input: "1 < bool 2 - 1 * 2",
expected: &BinaryExpr{
Op: LSS,
ReturnBool: true,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 0, End: 1},
},
RHS: &BinaryExpr{
Op: SUB,
LHS: &NumberLiteral{
Val: 2,
PosRange: posrange.PositionRange{Start: 9, End: 10},
},
RHS: &BinaryExpr{
Op: MUL,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 13, End: 14},
},
RHS: &NumberLiteral{
Val: 2,
PosRange: posrange.PositionRange{Start: 17, End: 18},
},
},
},
},
},
{
input: "-some_metric",
expected: &UnaryExpr{
Op: SUB,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 1, End: 12},
},
},
},
{
input: "+some_metric",
expected: &UnaryExpr{
Op: ADD,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 1, End: 12},
},
},
},
{
input: " +some_metric",
expected: &UnaryExpr{
Op: ADD,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 2, End: 13},
},
StartPos: 1,
},
},
{
input: ` +{"some_metric"}`,
expected: &UnaryExpr{
Op: ADD,
Expr: &VectorSelector{
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 2, End: 17},
},
StartPos: 1,
},
},
{
input: "",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 0},
Err: errors.New("no expression found in input"),
Query: "",
},
},
},
{
input: "# just a comment\n\n",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 0},
Err: errors.New("no expression found in input"),
Query: "# just a comment\n\n",
},
},
},
{
input: "1+",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 2, End: 2},
Err: errors.New("unexpected end of input"),
Query: "1+",
},
},
},
{
input: ".",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 1},
Err: errors.New("unexpected character: '.'"),
Query: ".",
},
},
},
{
input: "2.5.",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 4},
Err: errors.New(`bad number or duration syntax: "2.5."`),
Query: "2.5.",
},
},
},
{
input: "100..4",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 6},
Err: errors.New(`bad number or duration syntax: "100.."`),
Query: "100..4",
},
},
},
{
input: "0deadbeef",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 9},
Err: errors.New("bad number or duration syntax: \"0de\""),
Query: "0deadbeef",
},
},
},
{
input: "1 /",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 3, End: 3},
Err: errors.New("unexpected end of input"),
Query: "1 /",
},
},
},
{
input: "*1",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 1},
Err: errors.New("unexpected <op:*>"),
Query: "*1",
},
},
},
{
input: "(1))",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 4},
Err: errors.New("unexpected right parenthesis ')'"),
Query: "(1))",
},
},
},
{
input: "((1)",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 4},
Err: errors.New("unclosed left parenthesis"),
Query: "((1)",
},
},
},
{
input: "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 309},
Err: errors.New(`error parsing number: strconv.ParseFloat: parsing "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999": value out of range`),
Query: "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999",
},
},
},
{
input: "(",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 1, End: 1},
Err: errors.New("unclosed left parenthesis"),
Query: "(",
},
},
},
{
input: "1 and 1",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 7},
Err: errors.New("set operator \"and\" not allowed in binary scalar expression"),
Query: "1 and 1",
},
},
},
{
input: "1 == 1",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 2, End: 3},
Err: errors.New("comparisons between scalars must use BOOL modifier"),
Query: "1 == 1",
},
},
},
{
input: "1 or 1",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 6},
Err: errors.New("set operator \"or\" not allowed in binary scalar expression"),
Query: "1 or 1",
},
},
},
{
input: "1 unless 1",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 10},
Err: errors.New("set operator \"unless\" not allowed in binary scalar expression"),
Query: "1 unless 1",
},
},
},
{
input: "1 !~ 1",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 2, End: 6},
Err: errors.New("unexpected character after '!': '~'"),
Query: "1 !~ 1",
},
},
},
{
input: "1 =~ 1",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 2, End: 6},
Err: errors.New("unexpected character after '=': '~'"),
Query: "1 =~ 1",
},
},
},
{
input: `-"string"`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 9},
Err: errors.New(`unary expression only allowed on expressions of type scalar or instant vector, got "string"`),
Query: `-"string"`,
},
},
},
{
input: `-test[5m]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 9},
Err: errors.New(`unary expression only allowed on expressions of type scalar or instant vector, got "range vector"`),
Query: `-test[5m]`,
},
},
},
{
input: `*test`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 1},
Err: errors.New("unexpected <op:*>"),
Query: `*test`,
},
},
},
{
input: "1 offset 1d",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 1},
Err: errors.New("offset modifier must be preceded by an instant vector selector or range vector selector or a subquery"),
Query: "1 offset 1d",
},
},
},
{
input: "foo offset 1s offset 2s",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 13},
Err: errors.New("offset may not be set multiple times"),
Query: "foo offset 1s offset 2s",
},
},
},
{
input: "a - on(b) ignoring(c) d",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 10, End: 18},
Err: errors.New("unexpected <ignoring>"),
Query: "a - on(b) ignoring(c) d",
},
},
},
// Vector selectors.
{
input: `offset{step="1s"}[5m]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "offset",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "step", "1s"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "offset"),
},
PosRange: posrange.PositionRange{Start: 0, End: 17},
},
Range: 5 * time.Minute,
EndPos: 21,
},
},
{
input: `step{offset="1s"}[5m]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "step",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "offset", "1s"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "step"),
},
PosRange: posrange.PositionRange{Start: 0, End: 17},
},
Range: 5 * time.Minute,
EndPos: 21,
},
},
// Vector binary operations.
{
input: "foo * bar",
expected: &BinaryExpr{
Op: MUL,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 6, End: 9},
},
VectorMatching: &VectorMatching{Card: CardOneToOne},
},
},
{
input: "foo * sum",
expected: &BinaryExpr{
Op: MUL,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "sum",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "sum"),
},
PosRange: posrange.PositionRange{Start: 6, End: 9},
},
VectorMatching: &VectorMatching{Card: CardOneToOne},
},
},
{
input: "foo == 1",
expected: &BinaryExpr{
Op: EQLC,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 7, End: 8},
},
},
},
{
input: "foo == bool 1",
expected: &BinaryExpr{
Op: EQLC,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 12, End: 13},
},
ReturnBool: true,
},
},
{
input: "2.5 / bar",
expected: &BinaryExpr{
Op: DIV,
LHS: &NumberLiteral{
Val: 2.5,
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 6, End: 9},
},
},
},
{
input: "foo and bar",
expected: &BinaryExpr{
Op: LAND,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 8, End: 11},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
},
{
input: "foo or bar",
expected: &BinaryExpr{
Op: LOR,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 7, End: 10},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
},
{
input: "foo unless bar",
expected: &BinaryExpr{
Op: LUNLESS,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 11, End: 14},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
},
{
// Test and/or precedence and reassigning of operands.
input: "foo + bar or bla and blub",
expected: &BinaryExpr{
Op: LOR,
LHS: &BinaryExpr{
Op: ADD,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 6, End: 9},
},
VectorMatching: &VectorMatching{Card: CardOneToOne},
},
RHS: &BinaryExpr{
Op: LAND,
LHS: &VectorSelector{
Name: "bla",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bla"),
},
PosRange: posrange.PositionRange{Start: 13, End: 16},
},
RHS: &VectorSelector{
Name: "blub",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "blub"),
},
PosRange: posrange.PositionRange{Start: 21, End: 25},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
},
{
// Test and/or/unless precedence.
input: "foo and bar unless baz or qux",
expected: &BinaryExpr{
Op: LOR,
LHS: &BinaryExpr{
Op: LUNLESS,
LHS: &BinaryExpr{
Op: LAND,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 8, End: 11},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
RHS: &VectorSelector{
Name: "baz",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "baz"),
},
PosRange: posrange.PositionRange{Start: 19, End: 22},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
RHS: &VectorSelector{
Name: "qux",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "qux"),
},
PosRange: posrange.PositionRange{Start: 26, End: 29},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
},
{
// Test precedence and reassigning of operands.
input: "bar + on(foo) bla / on(baz, buz) group_right(test) blub",
expected: &BinaryExpr{
Op: ADD,
LHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &BinaryExpr{
Op: DIV,
LHS: &VectorSelector{
Name: "bla",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bla"),
},
PosRange: posrange.PositionRange{Start: 14, End: 17},
},
RHS: &VectorSelector{
Name: "blub",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "blub"),
},
PosRange: posrange.PositionRange{Start: 51, End: 55},
},
VectorMatching: &VectorMatching{
Card: CardOneToMany,
MatchingLabels: []string{"baz", "buz"},
On: true,
Include: []string{"test"},
},
},
VectorMatching: &VectorMatching{
Card: CardOneToOne,
MatchingLabels: []string{"foo"},
On: true,
},
},
},
{
input: "foo * on(test,blub) bar",
expected: &BinaryExpr{
Op: MUL,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 20, End: 23},
},
VectorMatching: &VectorMatching{
Card: CardOneToOne,
MatchingLabels: []string{"test", "blub"},
On: true,
},
},
},
{
input: "foo * on(test,blub) group_left bar",
expected: &BinaryExpr{
Op: MUL,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 31, End: 34},
},
VectorMatching: &VectorMatching{
Card: CardManyToOne,
MatchingLabels: []string{"test", "blub"},
On: true,
},
},
},
{
input: "foo and on(test,blub) bar",
expected: &BinaryExpr{
Op: LAND,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 22, End: 25},
},
VectorMatching: &VectorMatching{
Card: CardManyToMany,
MatchingLabels: []string{"test", "blub"},
On: true,
},
},
},
{
input: "foo and on() bar",
expected: &BinaryExpr{
Op: LAND,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 13, End: 16},
},
VectorMatching: &VectorMatching{
Card: CardManyToMany,
MatchingLabels: []string{},
On: true,
},
},
},
{
input: "foo and ignoring(test,blub) bar",
expected: &BinaryExpr{
Op: LAND,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 28, End: 31},
},
VectorMatching: &VectorMatching{
Card: CardManyToMany,
MatchingLabels: []string{"test", "blub"},
},
},
},
{
input: "foo and ignoring() bar",
expected: &BinaryExpr{
Op: LAND,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 19, End: 22},
},
VectorMatching: &VectorMatching{
Card: CardManyToMany,
MatchingLabels: []string{},
},
},
},
{
input: "foo unless on(bar) baz",
expected: &BinaryExpr{
Op: LUNLESS,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "baz",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "baz"),
},
PosRange: posrange.PositionRange{Start: 19, End: 22},
},
VectorMatching: &VectorMatching{
Card: CardManyToMany,
MatchingLabels: []string{"bar"},
On: true,
},
},
},
{
input: "foo / on(test,blub) group_left(bar) bar",
expected: &BinaryExpr{
Op: DIV,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 36, End: 39},
},
VectorMatching: &VectorMatching{
Card: CardManyToOne,
MatchingLabels: []string{"test", "blub"},
On: true,
Include: []string{"bar"},
},
},
},
{
input: "foo / ignoring(test,blub) group_left(blub) bar",
expected: &BinaryExpr{
Op: DIV,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 43, End: 46},
},
VectorMatching: &VectorMatching{
Card: CardManyToOne,
MatchingLabels: []string{"test", "blub"},
Include: []string{"blub"},
},
},
},
{
input: "foo / ignoring(test,blub) group_left(bar) bar",
expected: &BinaryExpr{
Op: DIV,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 42, End: 45},
},
VectorMatching: &VectorMatching{
Card: CardManyToOne,
MatchingLabels: []string{"test", "blub"},
Include: []string{"bar"},
},
},
},
{
input: "foo - on(test,blub) group_right(bar,foo) bar",
expected: &BinaryExpr{
Op: SUB,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 41, End: 44},
},
VectorMatching: &VectorMatching{
Card: CardOneToMany,
MatchingLabels: []string{"test", "blub"},
Include: []string{"bar", "foo"},
On: true,
},
},
},
{
input: "foo - ignoring(test,blub) group_right(bar,foo) bar",
expected: &BinaryExpr{
Op: SUB,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 47, End: 50},
},
VectorMatching: &VectorMatching{
Card: CardOneToMany,
MatchingLabels: []string{"test", "blub"},
Include: []string{"bar", "foo"},
},
},
},
{
input: "foo and 1",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 9},
Err: errors.New("set operator \"and\" not allowed in binary scalar expression"),
Query: "foo and 1",
},
},
},
{
input: "1 and foo",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 9},
Err: errors.New("set operator \"and\" not allowed in binary scalar expression"),
Query: "1 and foo",
},
},
},
{
input: "foo or 1",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 8},
Err: errors.New("set operator \"or\" not allowed in binary scalar expression"),
Query: "foo or 1",
},
},
},
{
input: "1 or foo",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 8},
Err: errors.New("set operator \"or\" not allowed in binary scalar expression"),
Query: "1 or foo",
},
},
},
{
input: "foo unless 1",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 12},
Err: errors.New("set operator \"unless\" not allowed in binary scalar expression"),
Query: "foo unless 1",
},
},
},
{
input: "1 unless foo",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 12},
Err: errors.New("set operator \"unless\" not allowed in binary scalar expression"),
Query: "1 unless foo",
},
},
},
{
input: "1 or on(bar) foo",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 16},
Err: errors.New("vector matching only allowed between instant vectors"),
Query: "1 or on(bar) foo",
},
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 16},
Err: errors.New(`set operator "or" not allowed in binary scalar expression`),
Query: "1 or on(bar) foo",
},
},
},
{
input: "foo == on(bar) 10",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 17},
Err: errors.New("vector matching only allowed between instant vectors"),
Query: "foo == on(bar) 10",
},
},
},
{
input: "foo + group_left(baz) bar",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 6, End: 16},
Err: errors.New("unexpected <group_left>"),
Query: "foo + group_left(baz) bar",
},
},
},
{
input: "foo and on(bar) group_left(baz) bar",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 35},
Err: errors.New("no grouping allowed for \"and\" operation"),
Query: "foo and on(bar) group_left(baz) bar",
},
{
PositionRange: posrange.PositionRange{Start: 0, End: 35},
Err: errors.New("set operations must always be many-to-many"),
Query: "foo and on(bar) group_left(baz) bar",
},
},
},
{
input: "foo and on(bar) group_right(baz) bar",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 36},
Err: errors.New("no grouping allowed for \"and\" operation"),
Query: "foo and on(bar) group_right(baz) bar",
},
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 36},
Err: errors.New("set operations must always be many-to-many"),
Query: "foo and on(bar) group_right(baz) bar",
},
},
},
{
input: "foo or on(bar) group_left(baz) bar",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 34},
Err: errors.New("no grouping allowed for \"or\" operation"),
Query: "foo or on(bar) group_left(baz) bar",
},
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 34},
Err: errors.New("set operations must always be many-to-many"),
Query: "foo or on(bar) group_left(baz) bar",
},
},
},
{
input: "foo or on(bar) group_right(baz) bar",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 35},
Err: errors.New("no grouping allowed for \"or\" operation"),
Query: "foo or on(bar) group_right(baz) bar",
},
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 35},
Err: errors.New("set operations must always be many-to-many"),
Query: "foo or on(bar) group_right(baz) bar",
},
},
},
{
input: "foo unless on(bar) group_left(baz) bar",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 38},
Err: errors.New("no grouping allowed for \"unless\" operation"),
Query: "foo unless on(bar) group_left(baz) bar",
},
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 38},
Err: errors.New("set operations must always be many-to-many"),
Query: "foo unless on(bar) group_left(baz) bar",
},
},
},
{
input: "foo unless on(bar) group_right(baz) bar",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 39},
Err: errors.New("no grouping allowed for \"unless\" operation"),
Query: "foo unless on(bar) group_right(baz) bar",
},
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 39},
Err: errors.New("set operations must always be many-to-many"),
Query: "foo unless on(bar) group_right(baz) bar",
},
},
},
{
input: `http_requests{group="production"} + on(instance) group_left(job,instance) cpu_count{type="smp"}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 34, End: 72},
Err: errors.New("label \"instance\" must not occur in ON and GROUP clause at once"),
Query: `http_requests{group="production"} + on(instance) group_left(job,instance) cpu_count{type="smp"}`,
},
},
},
{
input: "foo + bool bar",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 9},
Err: errors.New("bool modifier can only be used on comparison operators"),
Query: "foo + bool bar",
},
},
},
{
input: "foo + bool 10",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 9},
Err: errors.New("bool modifier can only be used on comparison operators"),
Query: "foo + bool 10",
},
},
},
{
input: "foo and bool 10",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 11},
Err: errors.New("bool modifier can only be used on comparison operators"),
Query: "foo and bool 10",
},
ParseErr{
PositionRange: posrange.PositionRange{End: 15},
Err: errors.New(`set operator "and" not allowed in binary scalar expression`),
Query: "foo and bool 10",
},
},
},
// Test Vector selector.
{
input: "foo",
expected: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
},
{
input: "min",
expected: &VectorSelector{
Name: "min",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "min"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
},
{
input: "foo offset 5m",
expected: &VectorSelector{
Name: "foo",
OriginalOffset: 5 * time.Minute,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 13},
},
},
{
input: "foo offset -7m",
expected: &VectorSelector{
Name: "foo",
OriginalOffset: -7 * time.Minute,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 14},
},
},
{
input: `http_requests{group="production"} + on(instance) group_left(job,instance) cpu_count{type="smp"}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 34, End: 72},
Err: errors.New("label \"instance\" must not occur in ON and GROUP clause at once"),
Query: `http_requests{group="production"} + on(instance) group_left(job,instance) cpu_count{type="smp"}`,
},
},
},
{
input: `foo OFFSET 1h30m`,
expected: &VectorSelector{
Name: "foo",
OriginalOffset: 90 * time.Minute,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 16},
},
},
{
input: `foo OFFSET 1m30ms`,
expected: &VectorSelector{
Name: "foo",
OriginalOffset: time.Minute + 30*time.Millisecond,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 17},
},
},
{
input: `foo @ 1603774568`,
expected: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(1603774568000),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 16},
},
},
{
input: `foo @ -100`,
expected: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(-100000),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 10},
},
},
{
input: `foo @ .3`,
expected: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(300),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 8},
},
},
{
input: `foo @ 3.`,
expected: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(3000),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 8},
},
},
{
input: `foo @ 3.33`,
expected: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(3330),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 10},
},
},
{ // Rounding off.
input: `foo @ 3.3333`,
expected: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(3333),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 12},
},
},
{ // Rounding off.
input: `foo @ 3.3335`,
expected: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(3334),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 12},
},
},
{
input: `foo @ 3e2`,
expected: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(300000),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 9},
},
},
{
input: `foo @ 3e-1`,
expected: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(300),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 10},
},
},
{
input: `foo @ 0xA`,
expected: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(10000),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 9},
},
},
{
input: `foo @ -3.3e1`,
expected: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(-33000),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 12},
},
},
{
input: `foo @ +Inf`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 3},
Err: errors.New("timestamp out of bounds for @ modifier: +Inf"),
Query: `foo @ +Inf`,
},
},
},
{
input: `foo @ -Inf`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 3},
Err: errors.New("timestamp out of bounds for @ modifier: -Inf"),
Query: `foo @ -Inf`,
},
},
},
{
input: `foo @ NaN`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 3},
Err: errors.New("timestamp out of bounds for @ modifier: NaN"),
Query: `foo @ NaN`,
},
},
},
{
input: fmt.Sprintf(`foo @ %f`, float64(math.MaxInt64)+1),
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 3},
Err: fmt.Errorf("timestamp out of bounds for @ modifier: %f", float64(math.MaxInt64)+1),
Query: fmt.Sprintf(`foo @ %f`, float64(math.MaxInt64)+1),
},
},
},
{
input: fmt.Sprintf(`foo @ %f`, float64(math.MinInt64)-1),
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 3},
Err: fmt.Errorf("timestamp out of bounds for @ modifier: %f", float64(math.MinInt64)-1),
Query: fmt.Sprintf(`foo @ %f`, float64(math.MinInt64)-1),
},
},
},
{
input: `foo:bar{a="bc"}`,
expected: &VectorSelector{
Name: "foo:bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "a", "bc"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo:bar"),
},
PosRange: posrange.PositionRange{Start: 0, End: 15},
},
},
{
input: `{"foo"}`,
expected: &VectorSelector{
// When a metric is named inside the braces, the Name field is not set.
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 7},
},
},
{
input: `{'foo\'bar', 'a\\dos\\path'='boo\\urns'}`,
expected: &VectorSelector{
// When a metric is named inside the braces, the Name field is not set.
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, `foo'bar`),
MustLabelMatcher(labels.MatchEqual, `a\dos\path`, `boo\urns`),
},
PosRange: posrange.PositionRange{Start: 0, End: 40},
},
},
{
input: `{'foo\'bar', ` + "`" + `a\dos\path` + "`" + `="boo"}`,
expected: &VectorSelector{
// When a metric is named inside the braces, the Name field is not set.
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, `foo'bar`),
MustLabelMatcher(labels.MatchEqual, `a\dos\path`, "boo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 32},
},
},
{
input: `{"foo", a="bc"}`,
expected: &VectorSelector{
// When a metric is named inside the braces, the Name field is not set.
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
MustLabelMatcher(labels.MatchEqual, "a", "bc"),
},
PosRange: posrange.PositionRange{Start: 0, End: 15},
},
},
{
input: `foo{NaN='bc'}`,
expected: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "NaN", "bc"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 13},
},
},
{
input: `foo{bar='}'}`,
expected: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "bar", "}"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 12},
},
},
{
input: `foo{a="b", foo!="bar", test=~"test", bar!~"baz"}`,
expected: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "a", "b"),
MustLabelMatcher(labels.MatchNotEqual, "foo", "bar"),
MustLabelMatcher(labels.MatchRegexp, "test", "test"),
MustLabelMatcher(labels.MatchNotRegexp, "bar", "baz"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 48},
},
},
{
// Metric name in the middle of selector list is fine.
input: `{a="b", foo!="bar", "foo", test=~"test", bar!~"baz"}`,
expected: &VectorSelector{
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "a", "b"),
MustLabelMatcher(labels.MatchNotEqual, "foo", "bar"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
MustLabelMatcher(labels.MatchRegexp, "test", "test"),
MustLabelMatcher(labels.MatchNotRegexp, "bar", "baz"),
},
PosRange: posrange.PositionRange{Start: 0, End: 52},
},
},
{
input: `foo{a="b", foo!="bar", test=~"test", bar!~"baz",}`,
expected: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "a", "b"),
MustLabelMatcher(labels.MatchNotEqual, "foo", "bar"),
MustLabelMatcher(labels.MatchRegexp, "test", "test"),
MustLabelMatcher(labels.MatchNotRegexp, "bar", "baz"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 49},
},
},
{
// Specifying __name__ twice inside the braces is ok.
input: `{__name__=~"bar", __name__!~"baz"}`,
expected: &VectorSelector{
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchRegexp, model.MetricNameLabel, "bar"),
MustLabelMatcher(labels.MatchNotRegexp, model.MetricNameLabel, "baz"),
},
PosRange: posrange.PositionRange{Start: 0, End: 34},
},
},
{
// Specifying __name__ with equality twice inside the braces is even allowed.
input: `{__name__="bar", __name__="baz"}`,
expected: &VectorSelector{
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "baz"),
},
PosRange: posrange.PositionRange{Start: 0, End: 32},
},
},
{
// Because the above are allowed, this is also allowed.
input: `{"bar", __name__="baz"}`,
expected: &VectorSelector{
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "baz"),
},
PosRange: posrange.PositionRange{Start: 0, End: 23},
},
},
{
input: `{`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 1, End: 1},
Err: errors.New("unexpected end of input inside braces"),
Query: `{`,
},
},
},
{
input: `}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 1},
Err: errors.New("unexpected character: '}'"),
Query: `}`,
},
},
},
{
input: `some{`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 5, End: 5},
Err: errors.New("unexpected end of input inside braces"),
Query: `some{`,
},
},
},
{
input: `some}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 5},
Err: errors.New("unexpected character: '}'"),
Query: `some}`,
},
},
},
{
input: `some_metric{a=b}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 14, End: 15},
Err: errors.New("unexpected identifier \"b\" in label matching, expected string"),
Query: `some_metric{a=b}`,
},
},
},
{
input: `some_metric{a:b="b"}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 13, End: 20},
Err: errors.New("unexpected character inside braces: ':'"),
Query: `some_metric{a:b="b"}`,
},
},
},
{
input: `foo{a*"b"}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 5, End: 10},
Err: errors.New("unexpected character inside braces: '*'"),
Query: `foo{a*"b"}`,
},
},
},
{
input: `foo{a>="b"}`,
fail: true,
// TODO(fabxc): willingly lexing wrong tokens allows for more precise error
// messages from the parser - consider if this is an option.
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 5, End: 11},
Err: errors.New("unexpected character inside braces: '>'"),
Query: `foo{a>="b"}`,
},
},
},
{
input: "some_metric{a=\"\xff\"}",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 14, End: 18},
Err: errors.New("invalid UTF-8 rune"),
Query: "some_metric{a=\"\xff\"}",
},
},
},
{
input: `foo{gibberish}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 13, End: 14},
Err: errors.New(`unexpected "}" in label matching, expected label matching operator`),
Query: `foo{gibberish}`,
},
},
},
{
input: `foo{1}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 6},
Err: errors.New("unexpected character inside braces: '1'"),
Query: `foo{1}`,
},
},
},
{
input: `{}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 2},
Err: errors.New("vector selector must contain at least one non-empty matcher"),
Query: `{}`,
},
},
},
{
input: `{x=""}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 6},
Err: errors.New("vector selector must contain at least one non-empty matcher"),
Query: `{x=""}`,
},
},
},
{
input: `{x=~".*"}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 9},
Err: errors.New("vector selector must contain at least one non-empty matcher"),
Query: `{x=~".*"}`,
},
},
},
{
input: `{x!~".+"}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 9},
Err: errors.New("vector selector must contain at least one non-empty matcher"),
Query: `{x!~".+"}`,
},
},
},
{
input: `{x!="a"}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 8},
Err: errors.New("vector selector must contain at least one non-empty matcher"),
Query: `{x!="a"}`,
},
},
},
// Although {"bar", __name__="baz"} is allowed (see above), specifying a
// metric name inside and outside the braces is not.
{
input: `foo{__name__="bar"}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 19},
Err: errors.New(`metric name must not be set twice: "foo" or "bar"`),
Query: `foo{__name__="bar"}`,
},
},
},
{
input: `foo{__name__= =}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 14, End: 15},
Err: errors.New("unexpected \"=\" in label matching, expected string"),
Query: "foo{__name__= =}",
},
},
},
{
input: `foo{,}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 5},
Err: errors.New("unexpected \",\" in label matching, expected identifier or \"}\""),
Query: "foo{,}",
},
},
},
{
input: `foo{__name__ == "bar"}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 14, End: 15},
Err: errors.New("unexpected \"=\" in label matching, expected string"),
Query: "foo{__name__ == \"bar\"}",
},
},
},
{
input: `foo{__name__="bar" lol}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 19, End: 22},
Err: errors.New(`unexpected identifier "lol" in label matching, expected "," or "}"`),
Query: `foo{__name__="bar" lol}`,
},
},
},
{
input: `foo{"a"=}`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 8, End: 9},
Err: errors.New(`unexpected "}" in label matching, expected string`),
Query: `foo{"a"=}`,
},
},
},
// Test matrix selector.
{
input: "test[1000ms]",
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
},
PosRange: posrange.PositionRange{Start: 0, End: 4},
},
Range: 1000 * time.Millisecond,
EndPos: 12,
},
},
{
input: "test[1001ms]",
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
},
PosRange: posrange.PositionRange{Start: 0, End: 4},
},
Range: 1001 * time.Millisecond,
EndPos: 12,
},
},
{
input: "test[1002ms]",
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
},
PosRange: posrange.PositionRange{Start: 0, End: 4},
},
Range: 1002 * time.Millisecond,
EndPos: 12,
},
},
{
input: "test[5s]",
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
},
PosRange: posrange.PositionRange{Start: 0, End: 4},
},
Range: 5 * time.Second,
EndPos: 8,
},
},
{
input: "test[5m]",
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
},
PosRange: posrange.PositionRange{Start: 0, End: 4},
},
Range: 5 * time.Minute,
EndPos: 8,
},
},
{
input: `foo[5m30s]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
Range: 5*time.Minute + 30*time.Second,
EndPos: 10,
},
},
{
input: "test[5h] OFFSET 5m",
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
OriginalOffset: 5 * time.Minute,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
},
PosRange: posrange.PositionRange{Start: 0, End: 4},
},
Range: 5 * time.Hour,
EndPos: 18,
},
},
{
input: "test[5d] OFFSET 10s",
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
OriginalOffset: 10 * time.Second,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
},
PosRange: posrange.PositionRange{Start: 0, End: 4},
},
Range: 5 * 24 * time.Hour,
EndPos: 19,
},
},
{
input: "test[5w] offset 2w",
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
OriginalOffset: 14 * 24 * time.Hour,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
},
PosRange: posrange.PositionRange{Start: 0, End: 4},
},
Range: 5 * 7 * 24 * time.Hour,
EndPos: 18,
},
},
{
input: `test{a="b"}[5y] OFFSET 3d`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
OriginalOffset: 3 * 24 * time.Hour,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "a", "b"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
},
PosRange: posrange.PositionRange{Start: 0, End: 11},
},
Range: 5 * 365 * 24 * time.Hour,
EndPos: 25,
},
},
{
input: `test{a="b"}[5m] OFFSET 3600`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
OriginalOffset: 1 * time.Hour,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "a", "b"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
},
PosRange: posrange.PositionRange{Start: 0, End: 11},
},
Range: 5 * time.Minute,
EndPos: 27,
},
},
{
input: `foo[3ms] @ 2.345`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(2345),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
Range: 3 * time.Millisecond,
EndPos: 16,
},
},
{
input: `foo[4s180ms] @ 2.345`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(2345),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
Range: 4*time.Second + 180*time.Millisecond,
EndPos: 20,
},
},
{
input: `foo[4.18] @ 2.345`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(2345),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
Range: 4*time.Second + 180*time.Millisecond,
EndPos: 17,
},
},
{
input: `foo[4s18ms] @ 2.345`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(2345),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
Range: 4*time.Second + 18*time.Millisecond,
EndPos: 19,
},
},
{
input: `foo[4.018] @ 2.345`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
Timestamp: makeInt64Pointer(2345),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
Range: 4*time.Second + 18*time.Millisecond,
EndPos: 18,
},
},
{
input: `test{a="b"}[5y] @ 1603774699`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
Timestamp: makeInt64Pointer(1603774699000),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "a", "b"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
},
PosRange: posrange.PositionRange{Start: 0, End: 11},
},
Range: 5 * 365 * 24 * time.Hour,
EndPos: 28,
},
},
{
input: "test[5]",
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
},
PosRange: posrange.PositionRange{Start: 0, End: 4},
},
Range: 5 * time.Second,
EndPos: 7,
},
},
{
input: `some_metric[5m] @ 1m`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "some_metric",
Timestamp: makeInt64Pointer(60000),
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 0, End: 11},
},
Range: 5 * time.Minute,
EndPos: 20,
},
},
{
input: `foo[5mm]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 8},
Err: errors.New("bad number or duration syntax: \"5mm\""),
Query: `foo[5mm]`,
},
},
},
{
input: `foo[5m1]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 8},
Err: errors.New("bad number or duration syntax: \"5m1\""),
Query: `foo[5m1]`,
},
},
},
{
input: `foo[5m:1m1]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 7, End: 11},
Err: errors.New("bad number or duration syntax: \"1m1\""),
Query: `foo[5m:1m1]`,
},
},
},
{
input: `foo[5y1hs]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 9},
Err: errors.New("unknown unit \"hs\" in duration \"5y1hs\""),
Query: `foo[5y1hs]`,
},
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 9},
Err: errors.New("duration must be greater than 0"),
Query: "foo[5y1hs]",
},
},
},
{
input: `foo[5m1h]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 8},
Err: errors.New("not a valid duration string: \"5m1h\""),
Query: `foo[5m1h]`,
},
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 8},
Err: errors.New("duration must be greater than 0"),
Query: "foo[5m1h]",
},
},
},
{
input: `foo[5m1m]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 8},
Err: errors.New("not a valid duration string: \"5m1m\""),
Query: `foo[5m1m]`,
},
{
PositionRange: posrange.PositionRange{Start: 4, End: 8},
Err: errors.New(`duration must be greater than 0`),
Query: `foo[5m1m]`,
},
},
},
{
input: `foo[0m]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 6},
Err: errors.New("duration must be greater than 0"),
Query: `foo[0m]`,
},
},
},
{
input: `foo["5m"]`,
fail: true,
errors: ParseErrors{
{
PositionRange: posrange.PositionRange{Start: 4, End: 9},
Err: errors.New(`unexpected character in duration expression: '"'`),
Query: `foo["5m"]`,
},
},
},
{
input: `foo[]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 5},
Err: errors.New("unexpected \"]\" in subquery or range selector, expected number, duration, or step()"),
Query: `foo[]`,
},
},
},
{
input: `foo[-1]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 6},
Err: errors.New("duration must be greater than 0"),
Query: `foo[-1]`,
},
},
},
{
input: `some_metric[5m] OFFSET 1mm`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 23, End: 26},
Err: errors.New("bad number or duration syntax: \"1mm\""),
Query: `some_metric[5m] OFFSET 1mm`,
},
},
},
{
input: `some_metric[5m] OFFSET`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 22, End: 22},
Err: errors.New("unexpected end of input in offset, expected number, duration, or step()"),
Query: `some_metric[5m] OFFSET`,
},
},
},
{
input: `some_metric OFFSET 1m[5m]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 21, End: 25},
Err: errors.New("no offset modifiers allowed before range"),
Query: `some_metric OFFSET 1m[5m]`,
},
},
},
{
input: `some_metric[5m] @`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 17, End: 17},
Err: errors.New("unexpected end of input in @, expected timestamp"),
Query: `some_metric[5m] @`,
},
},
},
{
input: `some_metric @ 1234 [5m]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 19, End: 23},
Err: errors.New("no @ modifiers allowed before range"),
Query: `some_metric @ 1234 [5m]`,
},
},
},
{
input: `(foo + bar)[5m]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 11, End: 15},
Err: errors.New("ranges only allowed for vector selectors"),
Query: `(foo + bar)[5m]`,
},
},
},
// Test aggregation.
{
input: "sum by (foo)(some_metric)",
expected: &AggregateExpr{
Op: SUM,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 13, End: 24},
},
Grouping: []string{"foo"},
PosRange: posrange.PositionRange{Start: 0, End: 25},
},
},
{
input: `sum by ("foo bar")({"some.metric"})`,
expected: &AggregateExpr{
Op: SUM,
Expr: &VectorSelector{
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some.metric"),
},
PosRange: posrange.PositionRange{Start: 19, End: 34},
},
Grouping: []string{"foo bar"},
PosRange: posrange.PositionRange{Start: 0, End: 35},
},
},
{
input: `sum by ("foo)(some_metric{})`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 8, End: 28},
Err: errors.New("unterminated quoted string"),
Query: `sum by ("foo)(some_metric{})`,
},
},
},
{
input: `sum by ("foo", bar, 'baz')({"some.metric"})`,
expected: &AggregateExpr{
Op: SUM,
Expr: &VectorSelector{
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some.metric"),
},
PosRange: posrange.PositionRange{Start: 27, End: 42},
},
Grouping: []string{"foo", "bar", "baz"},
PosRange: posrange.PositionRange{Start: 0, End: 43},
},
},
{
input: "avg by (foo)(some_metric)",
expected: &AggregateExpr{
Op: AVG,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 13, End: 24},
},
Grouping: []string{"foo"},
PosRange: posrange.PositionRange{Start: 0, End: 25},
},
},
{
input: "max by (foo)(some_metric)",
expected: &AggregateExpr{
Op: MAX,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 13, End: 24},
},
Grouping: []string{"foo"},
PosRange: posrange.PositionRange{Start: 0, End: 25},
},
},
{
input: "sum without (foo) (some_metric)",
expected: &AggregateExpr{
Op: SUM,
Without: true,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 19, End: 30},
},
Grouping: []string{"foo"},
PosRange: posrange.PositionRange{Start: 0, End: 31},
},
},
{
input: "sum (some_metric) without (foo)",
expected: &AggregateExpr{
Op: SUM,
Without: true,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 5, End: 16},
},
Grouping: []string{"foo"},
PosRange: posrange.PositionRange{Start: 0, End: 31},
},
},
{
input: "stddev(some_metric)",
expected: &AggregateExpr{
Op: STDDEV,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 7, End: 18},
},
PosRange: posrange.PositionRange{Start: 0, End: 19},
},
},
{
input: "stdvar by (foo)(some_metric)",
expected: &AggregateExpr{
Op: STDVAR,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 16, End: 27},
},
Grouping: []string{"foo"},
PosRange: posrange.PositionRange{Start: 0, End: 28},
},
},
{
input: "sum by ()(some_metric)",
expected: &AggregateExpr{
Op: SUM,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 10, End: 21},
},
Grouping: []string{},
PosRange: posrange.PositionRange{Start: 0, End: 22},
},
},
{
input: "sum by (foo,bar,)(some_metric)",
expected: &AggregateExpr{
Op: SUM,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 18, End: 29},
},
Grouping: []string{"foo", "bar"},
PosRange: posrange.PositionRange{Start: 0, End: 30},
},
},
{
input: "sum by (foo,)(some_metric)",
expected: &AggregateExpr{
Op: SUM,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 14, End: 25},
},
Grouping: []string{"foo"},
PosRange: posrange.PositionRange{Start: 0, End: 26},
},
},
{
input: "topk(5, some_metric)",
expected: &AggregateExpr{
Op: TOPK,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 8, End: 19},
},
Param: &NumberLiteral{
Val: 5,
PosRange: posrange.PositionRange{Start: 5, End: 6},
},
PosRange: posrange.PositionRange{Start: 0, End: 20},
},
},
{
input: `count_values("value", some_metric)`,
expected: &AggregateExpr{
Op: COUNT_VALUES,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 22, End: 33},
},
Param: &StringLiteral{
Val: "value",
PosRange: posrange.PositionRange{Start: 13, End: 20},
},
PosRange: posrange.PositionRange{Start: 0, End: 34},
},
},
{
// Test usage of keywords as label names.
input: "sum without(and, by, avg, count, alert, annotations)(some_metric)",
expected: &AggregateExpr{
Op: SUM,
Without: true,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 53, End: 64},
},
Grouping: []string{"and", "by", "avg", "count", "alert", "annotations"},
PosRange: posrange.PositionRange{Start: 0, End: 65},
},
},
{
input: "sum without(==)(some_metric)",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 12, End: 14},
Err: errors.New("unexpected <op:==> in grouping opts, expected label"),
Query: "sum without(==)(some_metric)",
},
},
},
{
input: "sum without(,)(some_metric)",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 12, End: 13},
Err: errors.New(`unexpected "," in grouping opts, expected label`),
Query: "sum without(,)(some_metric)",
},
},
},
{
input: "sum without(foo,,)(some_metric)",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 16, End: 17},
Err: errors.New(`unexpected "," in grouping opts, expected label`),
Query: "sum without(foo,,)(some_metric)",
},
},
},
{
input: `sum some_metric by (test)`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 15},
Err: errors.New(`unexpected identifier "some_metric"`),
Query: `sum some_metric by (test)`,
},
},
},
{
input: `sum (some_metric) by test`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 21, End: 25},
Err: errors.New(`unexpected identifier "test" in grouping opts, expected "("`),
Query: `sum (some_metric) by test`,
},
},
},
{
input: `sum () by (test)`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 16},
Err: errors.New("no arguments for aggregate expression provided"),
Query: `sum () by (test)`,
},
},
},
{
input: "MIN keep_common (some_metric)",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 15},
Err: errors.New("unexpected identifier \"keep_common\""),
Query: "MIN keep_common (some_metric)",
},
},
},
{
input: "MIN (some_metric) keep_common",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 18, End: 29},
Err: errors.New(`unexpected identifier "keep_common"`),
Query: "MIN (some_metric) keep_common",
},
},
},
{
input: `sum (some_metric) without (test) by (test)`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 33, End: 35},
Err: errors.New("unexpected <by>"),
Query: `sum (some_metric) without (test) by (test)`,
},
},
},
{
input: `sum without (test) (some_metric) by (test)`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 33, End: 35},
Err: errors.New("unexpected <by>"),
Query: `sum without (test) (some_metric) by (test)`,
},
},
},
{
input: `topk(some_metric)`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 17},
Err: errors.New("wrong number of arguments for aggregate expression provided, expected 2, got 1"),
Query: `topk(some_metric)`,
},
},
},
{
input: `topk(some_metric,)`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 16, End: 17},
Err: errors.New("trailing commas not allowed in function call args"),
Query: `topk(some_metric,)`,
},
{
PositionRange: posrange.PositionRange{Start: 0, End: 18},
Err: errors.New("wrong number of arguments for aggregate expression provided, expected 2, got 1"),
Query: "topk(some_metric,)",
},
},
},
{
input: `topk(some_metric, other_metric)`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 5, End: 16},
Err: errors.New("expected type scalar in aggregation parameter, got instant vector"),
Query: `topk(some_metric, other_metric)`,
},
},
},
{
input: `count_values(5, other_metric)`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 13, End: 14},
Err: errors.New("expected type string in aggregation parameter, got scalar"),
Query: `count_values(5, other_metric)`,
},
},
},
{
input: `rate(some_metric[5m]) @ 1234`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 21},
Err: errors.New("@ modifier must be preceded by an instant vector selector or range vector selector or a subquery"),
Query: `rate(some_metric[5m]) @ 1234`,
},
},
},
// Test function calls.
{
input: "time()",
expected: &Call{
Func: MustGetFunction("time"),
Args: Expressions{},
PosRange: posrange.PositionRange{Start: 0, End: 6},
},
},
{
input: `floor(some_metric{foo!="bar"})`,
expected: &Call{
Func: MustGetFunction("floor"),
Args: Expressions{
&VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchNotEqual, "foo", "bar"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 6, End: 29},
},
},
PosRange: posrange.PositionRange{Start: 0, End: 30},
},
},
{
input: "rate(some_metric[5m])",
expected: &Call{
Func: MustGetFunction("rate"),
Args: Expressions{
&MatrixSelector{
VectorSelector: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 5, End: 16},
},
Range: 5 * time.Minute,
EndPos: 20,
},
},
PosRange: posrange.PositionRange{Start: 0, End: 21},
},
},
{
input: "round(some_metric)",
expected: &Call{
Func: MustGetFunction("round"),
Args: Expressions{
&VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 6, End: 17},
},
},
PosRange: posrange.PositionRange{Start: 0, End: 18},
},
},
{
input: "round(some_metric, 5)",
expected: &Call{
Func: MustGetFunction("round"),
Args: Expressions{
&VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 6, End: 17},
},
&NumberLiteral{
Val: 5,
PosRange: posrange.PositionRange{Start: 19, End: 20},
},
},
PosRange: posrange.PositionRange{Start: 0, End: 21},
},
},
{
input: "floor()",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 7},
Err: errors.New("expected 1 argument(s) in call to \"floor\", got 0"),
Query: "floor()",
},
},
},
{
input: "floor(some_metric, other_metric)",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 32},
Err: errors.New("expected 1 argument(s) in call to \"floor\", got 2"),
Query: "floor(some_metric, other_metric)",
},
},
},
{
input: "floor(some_metric, 1)",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 21},
Err: errors.New("expected 1 argument(s) in call to \"floor\", got 2"),
Query: "floor(some_metric, 1)",
},
},
},
{
input: "floor(1)",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 6, End: 7},
Err: errors.New("expected type instant vector in call to function \"floor\", got scalar"),
Query: "floor(1)",
},
},
},
{
input: "hour(some_metric, some_metric, some_metric)",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 43},
Err: errors.New("expected at most 1 argument(s) in call to \"hour\", got 3"),
Query: "hour(some_metric, some_metric, some_metric)",
},
},
},
{
input: "time(some_metric)",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 17},
Err: errors.New("expected 0 argument(s) in call to \"time\", got 1"),
Query: "time(some_metric)",
},
},
},
{
input: "non_existent_function_far_bar()",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 29},
Err: errors.New("unknown function with name \"non_existent_function_far_bar\""),
Query: "non_existent_function_far_bar()",
},
},
},
{
input: "rate(some_metric)",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 5, End: 16},
Err: errors.New("expected type range vector in call to function \"rate\", got instant vector"),
Query: "rate(some_metric)",
},
},
},
{
input: "label_replace(a, `b`, `c\xff`, `d`, `.*`)",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 22, End: 38},
Err: errors.New("invalid UTF-8 rune"),
Query: "label_replace(a, `b`, `c\xff`, `d`, `.*`)",
},
{
PositionRange: posrange.PositionRange{Start: 20, End: 21},
Err: errors.New("trailing commas not allowed in function call args"),
Query: "label_replace(a, `b`, `c\xff`, `d`, `.*`)",
},
},
},
// Fuzzing regression tests.
{
input: "*1",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 1},
Err: errors.New("unexpected <op:*>"),
Query: "*1",
},
},
},
{
input: "-=",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 1, End: 2},
Err: errors.New(`unexpected "="`),
Query: "-=",
},
},
},
{
input: "++-++-+-+-<",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 10, End: 11},
Err: errors.New(`unexpected <op:<>`),
Query: "++-++-+-+-<",
},
},
},
{
input: "e-+=/(0)",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 3, End: 4},
Err: errors.New(`unexpected "="`),
Query: "e-+=/(0)",
},
},
},
{
input: "a>b()",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 2, End: 3},
Err: errors.New(`unknown function with name "b"`),
Query: "a>b()",
},
},
},
{
input: "rate(avg)",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 5, End: 8},
Err: errors.New(`expected type range vector in call to function "rate", got instant vector`),
Query: "rate(avg)",
},
},
},
{
// This is testing that we are not re-rendering the expression string for each error, which would timeout.
input: "(" + strings.Repeat("-{}-1", 10000) + ")" + strings.Repeat("[1m:]", 1000),
fail: true,
// This test generates a lot of errors, so we need a helper function to generate it for us.
errors: append(
repeatError(
"("+strings.Repeat("-{}-1", 10000)+")"+strings.Repeat("[1m:]", 1000),
errors.New("vector selector must contain at least one non-empty matcher"),
2, 5, // begin with start=2, increment by 5 each time
4, 5, // begin with end=2, increment by 5 each time
10000, // number of errors to generate
),
repeatError(
"("+strings.Repeat("-{}-1", 10000)+")"+strings.Repeat("[1m:]", 1000),
errors.New("subquery is only allowed on instant vector, got matrix instead"),
0, 0, // begin with start=0, don't increment, it's always start=0
50012, 5, // begin with end=50012, increment by 5 each time
999, // number of errors to generate
)...,
),
},
{
input: "sum(sum)",
expected: &AggregateExpr{
Op: SUM,
Expr: &VectorSelector{
Name: "sum",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "sum"),
},
PosRange: posrange.PositionRange{Start: 4, End: 7},
},
PosRange: posrange.PositionRange{Start: 0, End: 8},
},
},
{
input: "a + sum",
expected: &BinaryExpr{
Op: ADD,
LHS: &VectorSelector{
Name: "a",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "a"),
},
PosRange: posrange.PositionRange{Start: 0, End: 1},
},
RHS: &VectorSelector{
Name: "sum",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "sum"),
},
PosRange: posrange.PositionRange{Start: 4, End: 7},
},
VectorMatching: &VectorMatching{},
},
},
// String quoting and escape sequence interpretation tests.
{
input: `"double-quoted string \" with escaped quote"`,
expected: &StringLiteral{
Val: "double-quoted string \" with escaped quote",
PosRange: posrange.PositionRange{Start: 0, End: 44},
},
},
{
input: `'single-quoted string \' with escaped quote'`,
expected: &StringLiteral{
Val: "single-quoted string ' with escaped quote",
PosRange: posrange.PositionRange{Start: 0, End: 44},
},
},
{
input: "`backtick-quoted string`",
expected: &StringLiteral{
Val: "backtick-quoted string",
PosRange: posrange.PositionRange{Start: 0, End: 24},
},
},
{
input: `"\a\b\f\n\r\t\v\\\" - \xFF\377\u1234\U00010111\U0001011111☺"`,
expected: &StringLiteral{
Val: "\a\b\f\n\r\t\v\\\" - \xFF\377\u1234\U00010111\U0001011111☺",
PosRange: posrange.PositionRange{Start: 0, End: 62},
},
},
{
input: `'\a\b\f\n\r\t\v\\\' - \xFF\377\u1234\U00010111\U0001011111☺'`,
expected: &StringLiteral{
Val: "\a\b\f\n\r\t\v\\' - \xFF\377\u1234\U00010111\U0001011111☺",
PosRange: posrange.PositionRange{Start: 0, End: 62},
},
},
{
input: "`" + `\a\b\f\n\r\t\v\\\"\' - \xFF\377\u1234\U00010111\U0001011111☺` + "`",
expected: &StringLiteral{
Val: `\a\b\f\n\r\t\v\\\"\' - \xFF\377\u1234\U00010111\U0001011111☺`,
PosRange: posrange.PositionRange{Start: 0, End: 64},
},
},
{
input: "`\\``",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 3, End: 4},
Err: errors.New("unterminated raw string"),
Query: "`\\``",
},
},
},
{
input: `"\`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 2},
Err: errors.New("escape sequence not terminated"),
Query: `"\`,
},
},
},
{
input: `"\c"`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 4},
Err: errors.New("unknown escape sequence U+0063 'c'"),
Query: `"\c"`,
},
},
},
{
input: `"\x."`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 5},
Err: errors.New("illegal character U+002E '.' in escape sequence"),
Query: `"\x."`,
},
},
},
// Subquery.
{
input: `foo{bar="baz"}[`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 15, End: 15},
Err: errors.New(`unexpected end of input in duration expression`),
Query: `foo{bar="baz"}[`,
},
},
},
{
input: `foo{bar="baz"}[10m:6s]`,
expected: &SubqueryExpr{
Expr: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "bar", "baz"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 14},
},
Range: 10 * time.Minute,
Step: 6 * time.Second,
EndPos: 22,
},
},
{
input: `foo{bar="baz"}[10m5s:1h6ms]`,
expected: &SubqueryExpr{
Expr: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "bar", "baz"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 14},
},
Range: 10*time.Minute + 5*time.Second,
Step: time.Hour + 6*time.Millisecond,
EndPos: 27,
},
},
{
input: `foo[10m:]`,
expected: &SubqueryExpr{
Expr: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
Range: 10 * time.Minute,
EndPos: 9,
},
},
{
input: `min_over_time(rate(foo{bar="baz"}[2s])[5m:5s])`,
expected: &Call{
Func: MustGetFunction("min_over_time"),
Args: Expressions{
&SubqueryExpr{
Expr: &Call{
Func: MustGetFunction("rate"),
Args: Expressions{
&MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "bar", "baz"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 19, End: 33},
},
Range: 2 * time.Second,
EndPos: 37,
},
},
PosRange: posrange.PositionRange{Start: 14, End: 38},
},
Range: 5 * time.Minute,
Step: 5 * time.Second,
EndPos: 45,
},
},
PosRange: posrange.PositionRange{Start: 0, End: 46},
},
},
{
input: `min_over_time(rate(foo{bar="baz"}[2s])[5m:])[4m:3s]`,
expected: &SubqueryExpr{
Expr: &Call{
Func: MustGetFunction("min_over_time"),
Args: Expressions{
&SubqueryExpr{
Expr: &Call{
Func: MustGetFunction("rate"),
Args: Expressions{
&MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "bar", "baz"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 19, End: 33},
},
Range: 2 * time.Second,
EndPos: 37,
},
},
PosRange: posrange.PositionRange{Start: 14, End: 38},
},
Range: 5 * time.Minute,
EndPos: 43,
},
},
PosRange: posrange.PositionRange{Start: 0, End: 44},
},
Range: 4 * time.Minute,
Step: 3 * time.Second,
EndPos: 51,
},
},
{
input: `min_over_time(rate(foo{bar="baz"}[2s])[5m:] offset 4m)[4m:3s]`,
expected: &SubqueryExpr{
Expr: &Call{
Func: MustGetFunction("min_over_time"),
Args: Expressions{
&SubqueryExpr{
Expr: &Call{
Func: MustGetFunction("rate"),
Args: Expressions{
&MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "bar", "baz"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 19, End: 33},
},
Range: 2 * time.Second,
EndPos: 37,
},
},
PosRange: posrange.PositionRange{Start: 14, End: 38},
},
Range: 5 * time.Minute,
OriginalOffset: 4 * time.Minute,
EndPos: 53,
},
},
PosRange: posrange.PositionRange{Start: 0, End: 54},
},
Range: 4 * time.Minute,
Step: 3 * time.Second,
EndPos: 61,
},
},
{
input: `min_over_time(rate(foo{bar="baz"}[2s])[5m:] @ 1603775091)[4m:3s]`,
expected: &SubqueryExpr{
Expr: &Call{
Func: MustGetFunction("min_over_time"),
Args: Expressions{
&SubqueryExpr{
Expr: &Call{
Func: MustGetFunction("rate"),
Args: Expressions{
&MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "bar", "baz"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 19, End: 33},
},
Range: 2 * time.Second,
EndPos: 37,
},
},
PosRange: posrange.PositionRange{Start: 14, End: 38},
},
Range: 5 * time.Minute,
Timestamp: makeInt64Pointer(1603775091000),
EndPos: 56,
},
},
PosRange: posrange.PositionRange{Start: 0, End: 57},
},
Range: 4 * time.Minute,
Step: 3 * time.Second,
EndPos: 64,
},
},
{
input: `min_over_time(rate(foo{bar="baz"}[2s])[5m:] @ -160377509)[4m:3s]`,
expected: &SubqueryExpr{
Expr: &Call{
Func: MustGetFunction("min_over_time"),
Args: Expressions{
&SubqueryExpr{
Expr: &Call{
Func: MustGetFunction("rate"),
Args: Expressions{
&MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "bar", "baz"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 19, End: 33},
},
Range: 2 * time.Second,
EndPos: 37,
},
},
PosRange: posrange.PositionRange{Start: 14, End: 38},
},
Range: 5 * time.Minute,
Timestamp: makeInt64Pointer(-160377509000),
EndPos: 56,
},
},
PosRange: posrange.PositionRange{Start: 0, End: 57},
},
Range: 4 * time.Minute,
Step: 3 * time.Second,
EndPos: 64,
},
},
{
input: "sum without(and, by, avg, count, alert, annotations)(some_metric) [30m:10s]",
expected: &SubqueryExpr{
Expr: &AggregateExpr{
Op: SUM,
Without: true,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 53, End: 64},
},
Grouping: []string{"and", "by", "avg", "count", "alert", "annotations"},
PosRange: posrange.PositionRange{Start: 0, End: 65},
},
Range: 30 * time.Minute,
Step: 10 * time.Second,
EndPos: 75,
},
},
{
input: `some_metric OFFSET 1m [10m:5s]`,
expected: &SubqueryExpr{
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 0, End: 21},
OriginalOffset: 1 * time.Minute,
},
Range: 10 * time.Minute,
Step: 5 * time.Second,
EndPos: 30,
},
},
{
input: `some_metric @ 123 [10m:5s]`,
expected: &SubqueryExpr{
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 0, End: 17},
Timestamp: makeInt64Pointer(123000),
},
Range: 10 * time.Minute,
Step: 5 * time.Second,
EndPos: 26,
},
},
{
input: `some_metric @ 123 offset 1m [10m:5s]`,
expected: &SubqueryExpr{
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 0, End: 27},
Timestamp: makeInt64Pointer(123000),
OriginalOffset: 1 * time.Minute,
},
Range: 10 * time.Minute,
Step: 5 * time.Second,
EndPos: 36,
},
},
{
input: `some_metric offset 1m @ 123 [10m:5s]`,
expected: &SubqueryExpr{
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 0, End: 27},
Timestamp: makeInt64Pointer(123000),
OriginalOffset: 1 * time.Minute,
},
Range: 10 * time.Minute,
Step: 5 * time.Second,
EndPos: 36,
},
},
{
input: `some_metric[10m:5s] offset 1m @ 123`,
expected: &SubqueryExpr{
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "some_metric"),
},
PosRange: posrange.PositionRange{Start: 0, End: 11},
},
Timestamp: makeInt64Pointer(123000),
OriginalOffset: 1 * time.Minute,
Range: 10 * time.Minute,
Step: 5 * time.Second,
EndPos: 35,
},
},
{
input: `(foo + bar{nm="val"})[5m:]`,
expected: &SubqueryExpr{
Expr: &ParenExpr{
Expr: &BinaryExpr{
Op: ADD,
VectorMatching: &VectorMatching{
Card: CardOneToOne,
},
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 1, End: 4},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "nm", "val"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 7, End: 20},
},
},
PosRange: posrange.PositionRange{Start: 0, End: 21},
},
Range: 5 * time.Minute,
EndPos: 26,
},
},
{
input: `(foo + bar{nm="val"})[5m:] offset 10m`,
expected: &SubqueryExpr{
Expr: &ParenExpr{
Expr: &BinaryExpr{
Op: ADD,
VectorMatching: &VectorMatching{
Card: CardOneToOne,
},
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 1, End: 4},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "nm", "val"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 7, End: 20},
},
},
PosRange: posrange.PositionRange{Start: 0, End: 21},
},
Range: 5 * time.Minute,
OriginalOffset: 10 * time.Minute,
EndPos: 37,
},
},
{
input: `(foo + bar{nm="val"} @ 1234)[5m:] @ 1603775019`,
expected: &SubqueryExpr{
Expr: &ParenExpr{
Expr: &BinaryExpr{
Op: ADD,
VectorMatching: &VectorMatching{
Card: CardOneToOne,
},
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 1, End: 4},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "nm", "val"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
Timestamp: makeInt64Pointer(1234000),
PosRange: posrange.PositionRange{Start: 7, End: 27},
},
},
PosRange: posrange.PositionRange{Start: 0, End: 28},
},
Range: 5 * time.Minute,
Timestamp: makeInt64Pointer(1603775019000),
EndPos: 46,
},
},
{
input: "test[5d] OFFSET 10s [10m:5s]",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 28},
Err: errors.New("subquery is only allowed on instant vector, got matrix instead"),
Query: "test[5d] OFFSET 10s [10m:5s]",
},
},
},
{
input: `(foo + bar{nm="val"})[5m:][10m:5s]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 34},
Err: errors.New(`subquery is only allowed on instant vector, got matrix instead`),
Query: `(foo + bar{nm="val"})[5m:][10m:5s]`,
},
},
},
{
input: "rate(food[1m])[1h] offset 1h",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 14, End: 18},
Err: errors.New(`ranges only allowed for vector selectors`),
Query: "rate(food[1m])[1h] offset 1h",
},
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 18},
Err: errors.New(`ranges only allowed for vector selectors`),
Query: "rate(food[1m])[1h] offset 1h",
},
},
},
{
input: "rate(food[1m])[1h] @ 100",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 14, End: 18},
Err: errors.New(`ranges only allowed for vector selectors`),
Query: "rate(food[1m])[1h] @ 100",
},
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 18},
Err: errors.New(`ranges only allowed for vector selectors`),
Query: "rate(food[1m])[1h] @ 100",
},
},
},
// Preprocessors.
{
input: `foo @ start()`,
expected: &VectorSelector{
Name: "foo",
StartOrEnd: START,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 13},
},
},
{
input: `foo @ end()`,
expected: &VectorSelector{
Name: "foo",
StartOrEnd: END,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 11},
},
},
{
input: `test[5y] @ start()`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
StartOrEnd: START,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
},
PosRange: posrange.PositionRange{Start: 0, End: 4},
},
Range: 5 * 365 * 24 * time.Hour,
EndPos: 18,
},
},
{
input: `test[5y] @ end()`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
StartOrEnd: END,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
},
PosRange: posrange.PositionRange{Start: 0, End: 4},
},
Range: 5 * 365 * 24 * time.Hour,
EndPos: 16,
},
},
{
input: `foo[10m:6s] @ start()`,
expected: &SubqueryExpr{
Expr: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
Range: 10 * time.Minute,
Step: 6 * time.Second,
StartOrEnd: START,
EndPos: 21,
},
},
{
input: `foo[10m:6s] @ end()`,
expected: &SubqueryExpr{
Expr: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
Range: 10 * time.Minute,
Step: 6 * time.Second,
StartOrEnd: END,
EndPos: 19,
},
},
{
input: `start()`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 5, End: 6},
Err: errors.New(`unexpected "("`),
Query: `start()`,
},
},
},
{
input: `end()`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 3, End: 4},
Err: errors.New(`unexpected "("`),
Query: `end()`,
},
},
},
// Check that start and end functions do not mask metrics.
{
input: `start`,
expected: &VectorSelector{
Name: "start",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "start"),
},
PosRange: posrange.PositionRange{Start: 0, End: 5},
},
},
{
input: `end`,
expected: &VectorSelector{
Name: "end",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "end"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
},
{
input: `start{end="foo"}`,
expected: &VectorSelector{
Name: "start",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "end", "foo"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "start"),
},
PosRange: posrange.PositionRange{Start: 0, End: 16},
},
},
{
input: `end{start="foo"}`,
expected: &VectorSelector{
Name: "end",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "start", "foo"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "end"),
},
PosRange: posrange.PositionRange{Start: 0, End: 16},
},
},
{
input: `foo unless on(start) bar`,
expected: &BinaryExpr{
Op: LUNLESS,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 21, End: 24},
},
VectorMatching: &VectorMatching{
Card: CardManyToMany,
MatchingLabels: []string{"start"},
On: true,
},
},
},
{
input: `foo unless on(end) bar`,
expected: &BinaryExpr{
Op: LUNLESS,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "bar"),
},
PosRange: posrange.PositionRange{Start: 19, End: 22},
},
VectorMatching: &VectorMatching{
Card: CardManyToMany,
MatchingLabels: []string{"end"},
On: true,
},
},
},
{
input: `info(rate(http_request_counter_total{}[5m]))`,
expected: &Call{
Func: MustGetFunction("info"),
Args: Expressions{
&Call{
Func: MustGetFunction("rate"),
PosRange: posrange.PositionRange{Start: 5, End: 43},
Args: Expressions{
&MatrixSelector{
VectorSelector: &VectorSelector{
Name: "http_request_counter_total",
OriginalOffset: 0,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "http_request_counter_total"),
},
PosRange: posrange.PositionRange{Start: 10, End: 38},
},
EndPos: 42,
Range: 5 * time.Minute,
},
},
},
},
PosRange: posrange.PositionRange{Start: 0, End: 44},
},
},
{
input: `info(rate(http_request_counter_total{}[5m]), target_info{foo="bar"})`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 45, End: 67},
Err: errors.New(`expected label selectors only, got vector selector instead`),
Query: `info(rate(http_request_counter_total{}[5m]), target_info{foo="bar"})`,
},
},
},
{
input: `info(http_request_counter_total{namespace="zzz"}, {foo="bar", bar="baz"})`,
expected: &Call{
Func: MustGetFunction("info"),
Args: Expressions{
&VectorSelector{
Name: "http_request_counter_total",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "namespace", "zzz"),
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "http_request_counter_total"),
},
PosRange: posrange.PositionRange{Start: 5, End: 48},
},
&VectorSelector{
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, "foo", "bar"),
MustLabelMatcher(labels.MatchEqual, "bar", "baz"),
},
PosRange: posrange.PositionRange{Start: 50, End: 72},
BypassEmptyMatcherCheck: true,
},
},
PosRange: posrange.PositionRange{Start: 0, End: 73},
},
},
// Test that nested parentheses result in the correct position range.
{
input: `foo[11s+10s-5*2^2]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RangeExpr: &DurationExpr{
Op: SUB,
LHS: &DurationExpr{
Op: ADD,
LHS: &NumberLiteral{
Val: 11,
PosRange: posrange.PositionRange{Start: 4, End: 7},
Duration: true,
},
RHS: &NumberLiteral{
Val: 10,
PosRange: posrange.PositionRange{Start: 8, End: 11},
Duration: true,
},
},
RHS: &DurationExpr{
Op: MUL,
LHS: &NumberLiteral{Val: 5, PosRange: posrange.PositionRange{Start: 12, End: 13}},
RHS: &DurationExpr{
Op: POW,
LHS: &NumberLiteral{Val: 2, PosRange: posrange.PositionRange{Start: 14, End: 15}},
RHS: &NumberLiteral{Val: 2, PosRange: posrange.PositionRange{Start: 16, End: 17}},
},
},
},
EndPos: 18,
},
},
{
input: `foo[-(10s-5s)+20s]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RangeExpr: &DurationExpr{
Op: ADD,
LHS: &DurationExpr{
Op: SUB,
StartPos: 4,
RHS: &DurationExpr{
Op: SUB,
LHS: &NumberLiteral{
Val: 10,
PosRange: posrange.PositionRange{Start: 6, End: 9},
Duration: true,
},
RHS: &NumberLiteral{
Val: 5,
PosRange: posrange.PositionRange{Start: 10, End: 12},
Duration: true,
},
Wrapped: true,
},
},
RHS: &NumberLiteral{
Val: 20,
PosRange: posrange.PositionRange{Start: 14, End: 17},
Duration: true,
},
},
EndPos: 18,
},
},
{
input: `foo[-10s+15s]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RangeExpr: &DurationExpr{
Op: ADD,
LHS: &NumberLiteral{
Val: -10,
PosRange: posrange.PositionRange{Start: 4, End: 8},
Duration: true,
},
RHS: &NumberLiteral{
Val: 15,
PosRange: posrange.PositionRange{Start: 9, End: 12},
Duration: true,
},
},
EndPos: 13,
},
},
{
input: `foo[step()]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RangeExpr: &DurationExpr{
Op: STEP,
StartPos: 4,
EndPos: 10,
},
EndPos: 11,
},
},
{
input: `foo[ - step ( ) ]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RangeExpr: &DurationExpr{
Op: SUB,
StartPos: 6,
RHS: &DurationExpr{
Op: STEP,
StartPos: 9,
EndPos: 19,
},
},
EndPos: 22,
},
},
{
input: `foo[ step ( ) ]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RangeExpr: &DurationExpr{
Op: STEP,
StartPos: 7,
EndPos: 17,
},
EndPos: 20,
},
},
{
input: `foo[-step()]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RangeExpr: &DurationExpr{
Op: SUB,
StartPos: 4,
RHS: &DurationExpr{Op: STEP, StartPos: 5, EndPos: 11},
},
EndPos: 12,
},
},
{
input: `foo offset step()`,
expected: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 17},
OriginalOffsetExpr: &DurationExpr{
Op: STEP,
StartPos: 11,
EndPos: 17,
},
},
},
{
input: `foo offset -step()`,
expected: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 18},
OriginalOffsetExpr: &DurationExpr{
Op: SUB,
StartPos: 11,
RHS: &DurationExpr{Op: STEP, StartPos: 12, EndPos: 18},
},
},
},
{
input: `foo[max(step(),5s)]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RangeExpr: &DurationExpr{
Op: MAX,
LHS: &DurationExpr{
Op: STEP,
StartPos: 8,
EndPos: 14,
},
RHS: &NumberLiteral{
Val: 5,
Duration: true,
PosRange: posrange.PositionRange{Start: 15, End: 17},
},
StartPos: 4,
EndPos: 18,
},
EndPos: 19,
},
},
{
input: `foo offset max(step(),5s)`,
expected: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 25},
OriginalOffsetExpr: &DurationExpr{
Op: MAX,
LHS: &DurationExpr{
Op: STEP,
StartPos: 15,
EndPos: 21,
},
RHS: &NumberLiteral{
Val: 5,
Duration: true,
PosRange: posrange.PositionRange{Start: 22, End: 24},
},
StartPos: 11,
EndPos: 25,
},
},
},
{
input: `foo offset -min(5s,step()+8s)`,
expected: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 29},
OriginalOffsetExpr: &DurationExpr{
Op: SUB,
RHS: &DurationExpr{
Op: MIN,
LHS: &NumberLiteral{
Val: 5,
Duration: true,
PosRange: posrange.PositionRange{Start: 16, End: 18},
},
RHS: &DurationExpr{
Op: ADD,
LHS: &DurationExpr{
Op: STEP,
StartPos: 19,
EndPos: 25,
},
RHS: &NumberLiteral{
Val: 8,
Duration: true,
PosRange: posrange.PositionRange{Start: 26, End: 28},
},
},
StartPos: 12,
EndPos: 28,
},
StartPos: 11,
EndPos: 28,
},
},
},
{
input: `foo[4s+4s:1s*2] offset (5s-8)`,
expected: &SubqueryExpr{
Expr: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RangeExpr: &DurationExpr{
Op: ADD,
LHS: &NumberLiteral{
Val: 4,
PosRange: posrange.PositionRange{Start: 4, End: 6},
Duration: true,
},
RHS: &NumberLiteral{
Val: 4,
PosRange: posrange.PositionRange{Start: 7, End: 9},
Duration: true,
},
},
StepExpr: &DurationExpr{
Op: MUL,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 10, End: 12},
Duration: true,
},
RHS: &NumberLiteral{
Val: 2,
PosRange: posrange.PositionRange{Start: 13, End: 14},
},
},
OriginalOffsetExpr: &DurationExpr{
Op: SUB,
LHS: &NumberLiteral{
Val: 5,
PosRange: posrange.PositionRange{Start: 24, End: 26},
Duration: true,
},
RHS: &NumberLiteral{
Val: 8,
PosRange: posrange.PositionRange{Start: 27, End: 28},
},
Wrapped: true,
},
EndPos: 29,
},
},
{
input: `foo offset 5s-8`,
expected: &BinaryExpr{
Op: SUB,
LHS: &VectorSelector{
Name: "foo",
OriginalOffset: 5 * time.Second,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 13},
},
RHS: &NumberLiteral{
Val: 8,
PosRange: posrange.PositionRange{Start: 14, End: 15},
},
},
},
{
input: `rate(foo[2m+2m])`,
expected: &Call{
Func: MustGetFunction("rate"),
Args: Expressions{
&MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 5, End: 8},
},
RangeExpr: &DurationExpr{
Op: ADD,
LHS: &NumberLiteral{
Val: 120,
PosRange: posrange.PositionRange{Start: 9, End: 11},
Duration: true,
},
RHS: &NumberLiteral{
Val: 120,
PosRange: posrange.PositionRange{Start: 12, End: 14},
Duration: true,
},
},
EndPos: 15,
},
},
PosRange: posrange.PositionRange{Start: 0, End: 16},
},
},
{
input: `foo offset -1^1`,
expected: &BinaryExpr{
Op: POW,
LHS: &VectorSelector{
Name: "foo",
OriginalOffset: -time.Second,
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 13},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 14, End: 15},
},
},
},
{
input: `foo offset -(1^2)`,
expected: &VectorSelector{
Name: "foo",
OriginalOffset: 0,
OriginalOffsetExpr: &DurationExpr{
Op: SUB,
RHS: &DurationExpr{
Op: POW,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 13, End: 14},
},
RHS: &NumberLiteral{
Val: 2,
PosRange: posrange.PositionRange{Start: 15, End: 16},
},
Wrapped: true,
},
StartPos: 11,
},
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 17},
},
},
{
input: `foo offset (-1^2)`,
expected: &VectorSelector{
Name: "foo",
OriginalOffset: 0,
OriginalOffsetExpr: &DurationExpr{
Op: SUB,
RHS: &DurationExpr{
Op: POW,
LHS: &NumberLiteral{
Val: 1,
PosRange: posrange.PositionRange{Start: 13, End: 14},
},
RHS: &NumberLiteral{
Val: 2,
PosRange: posrange.PositionRange{Start: 15, End: 16},
},
},
Wrapped: true,
StartPos: 12,
},
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 17},
},
},
{
input: `foo[-2^2]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RangeExpr: &DurationExpr{
Op: SUB,
LHS: nil,
RHS: &DurationExpr{
Op: POW,
LHS: &NumberLiteral{
Val: 2,
PosRange: posrange.PositionRange{Start: 5, End: 6},
},
RHS: &NumberLiteral{
Val: 2,
PosRange: posrange.PositionRange{Start: 7, End: 8},
},
},
StartPos: 4,
},
EndPos: 9,
},
},
{
input: `foo[0+-2^2]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RangeExpr: &DurationExpr{
Op: ADD,
LHS: &NumberLiteral{
Val: 0,
PosRange: posrange.PositionRange{Start: 4, End: 5},
},
RHS: &DurationExpr{
Op: SUB,
LHS: nil,
RHS: &DurationExpr{
Op: POW,
LHS: &NumberLiteral{
Val: 2,
PosRange: posrange.PositionRange{Start: 7, End: 8},
},
RHS: &NumberLiteral{
Val: 2,
PosRange: posrange.PositionRange{Start: 9, End: 10},
},
},
StartPos: 6,
},
},
EndPos: 11,
},
},
{
input: `foo[step]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 8, End: 9},
Err: errors.New(`unexpected "]" in subquery or range selector, expected number, duration, or step()`),
Query: `foo[step]`,
},
},
},
{
input: `foo[step()/0d]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 10, End: 11},
Err: errors.New(`division by zero`),
Query: `foo[step()/0d]`,
},
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 0}, // FIXME: this position looks wrong.
Err: errors.New(`duration must be greater than 0`),
Query: `foo[step()/0d]`,
},
},
},
{
input: `foo[5s/0d]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 6, End: 7},
Err: errors.New(`division by zero`),
Query: `foo[5s/0d]`,
},
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 0}, // FIXME: this position looks wrong.
Err: errors.New(`duration must be greater than 0`),
Query: `foo[5s/0d]`,
},
},
},
{
input: `foo offset (4d/0)`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 14, End: 15},
Err: errors.New(`division by zero`),
Query: `foo offset (4d/0)`,
},
},
},
{
input: `foo[5s%0d]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 6, End: 7},
Err: errors.New(`modulo by zero`),
Query: `foo[5s%0d]`,
},
ParseErr{
Err: errors.New(`duration must be greater than 0`),
Query: `foo[5s%0d]`,
},
},
},
{
input: `foo offset 9.5e10`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 11, End: 17},
Err: errors.New(`duration out of range`),
Query: `foo offset 9.5e10`,
},
},
},
{
input: `foo[9.5e10]`,
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 10},
Err: errors.New(`duration out of range`),
Query: `foo[9.5e10]`,
},
ParseErr{
Err: errors.New(`duration must be greater than 0`),
Query: `foo[9.5e10]`,
},
},
},
{
input: "(sum(foo))",
expected: &ParenExpr{
Expr: &AggregateExpr{
Op: SUM,
Expr: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 5, End: 8},
},
PosRange: posrange.PositionRange{Start: 1, End: 9},
},
PosRange: posrange.PositionRange{Start: 0, End: 10},
},
},
{
input: "(sum(foo) )",
expected: &ParenExpr{
Expr: &AggregateExpr{
Op: SUM,
Expr: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{
Start: 5,
End: 8,
},
},
PosRange: posrange.PositionRange{Start: 1, End: 9},
},
PosRange: posrange.PositionRange{Start: 0, End: 11},
},
},
{
input: "(sum(foo) by (bar))",
expected: &ParenExpr{
Expr: &AggregateExpr{
Op: SUM,
Grouping: []string{"bar"},
Expr: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 5, End: 8},
},
PosRange: posrange.PositionRange{Start: 1, End: 18},
},
PosRange: posrange.PositionRange{Start: 0, End: 19},
},
},
{
input: "(sum by (bar) (foo))",
expected: &ParenExpr{
Expr: &AggregateExpr{
Op: SUM,
Grouping: []string{"bar"},
Expr: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 15, End: 18},
},
PosRange: posrange.PositionRange{Start: 1, End: 19},
},
PosRange: posrange.PositionRange{Start: 0, End: 20},
},
},
{
input: "sum by (job)(rate(http_requests_total[30m]))",
expected: &AggregateExpr{
Op: SUM,
Grouping: []string{"job"},
Expr: &Call{
Func: MustGetFunction("rate"),
Args: Expressions{
&MatrixSelector{
VectorSelector: &VectorSelector{
Name: "http_requests_total",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "http_requests_total"),
},
PosRange: posrange.PositionRange{
Start: 18,
End: 37,
},
},
Range: 30 * time.Minute,
EndPos: 42,
},
},
PosRange: posrange.PositionRange{Start: 13, End: 43},
},
PosRange: posrange.PositionRange{Start: 0, End: 44},
},
},
{
input: "sum(",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 4, End: 4},
Err: errors.New("unclosed left parenthesis"),
Query: "sum(",
},
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 0}, // FIXME: this position looks wrong.
Err: errors.New("no arguments for aggregate expression provided"),
Query: "sum(",
},
},
},
{
input: "sum(rate(",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 9, End: 9},
Err: errors.New("unclosed left parenthesis"),
Query: "sum(rate(",
},
ParseErr{
PositionRange: posrange.PositionRange{Start: 0, End: 0}, // FIXME: this position looks wrong.
Err: errors.New("no arguments for aggregate expression provided"),
Query: "sum(rate(",
},
},
},
{
input: "foo[5s x 5s]",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 7, End: 12},
Err: errors.New("unexpected character: 'x', expected ':'"),
Query: "foo[5s x 5s]",
},
},
},
{
input: "foo[5s s 5s]",
fail: true,
errors: ParseErrors{
ParseErr{
PositionRange: posrange.PositionRange{Start: 7, End: 12},
Err: errors.New("unexpected character: 's', expected ':'"),
Query: "foo[5s s 5s]",
},
},
},
}
func makeInt64Pointer(val int64) *int64 {
valp := new(int64)
*valp = val
return valp
}
func readable(s string) string {
const maxReadableStringLen = 40
if len(s) < maxReadableStringLen {
return s
}
return s[:maxReadableStringLen] + "..."
}
func TestParseExpressions(t *testing.T) {
// Enable experimental functions testing.
EnableExperimentalFunctions = true
// Enable experimental duration expression parsing.
ExperimentalDurationExpr = true
t.Cleanup(func() {
EnableExperimentalFunctions = false
ExperimentalDurationExpr = false
})
for _, test := range testExpr {
t.Run(readable(test.input), func(t *testing.T) {
expr, err := ParseExpr(test.input)
// Unexpected errors are always caused by a bug.
require.NotEqual(t, err, errUnexpected, "unexpected error occurred")
if !test.fail {
require.NoError(t, err)
expected := test.expected
// The FastRegexMatcher is not comparable with a deep equal, so only compare its String() version.
if actualVector, ok := expr.(*VectorSelector); ok {
require.IsType(t, test.expected, actualVector, "error on input '%s'", test.input)
expectedVector := test.expected.(*VectorSelector)
require.Len(t, actualVector.LabelMatchers, len(expectedVector.LabelMatchers), "error on input '%s'", test.input)
for i := 0; i < len(actualVector.LabelMatchers); i++ {
expectedMatcher := expectedVector.LabelMatchers[i].String()
actualMatcher := actualVector.LabelMatchers[i].String()
require.Equal(t, expectedMatcher, actualMatcher, "unexpected label matcher '%s' on input '%s'", actualMatcher, test.input)
}
// Make a shallow copy of the expected expr (because the test cases are defined in a global variable)
// and then reset the LabelMatcher to not compared them with the following deep equal.
expectedCopy := *expectedVector
expectedCopy.LabelMatchers = nil
expected = &expectedCopy
actualVector.LabelMatchers = nil
}
require.Equal(t, expected, expr, "error on input '%s'", test.input)
} else {
require.Error(t, err)
var errorList ParseErrors
ok := errors.As(err, &errorList)
require.True(t, ok, "unexpected error type")
if diff := cmp.Diff(test.errors, errorList, equalParseErr()); diff != "" {
t.Errorf("mismatch (-want +got):\n%s\nErrors: %+v", diff, errorList)
}
for _, e := range errorList {
require.LessOrEqual(t, 0, e.PositionRange.Start, "parse error has negative position\nExpression '%s'\nError: %v", test.input, e)
require.LessOrEqual(t, e.PositionRange.Start, e.PositionRange.End, "parse error has negative length\nExpression '%s'\nError: %v", test.input, e)
require.LessOrEqual(t, e.PositionRange.End, posrange.Pos(len(test.input)), "parse error is not contained in input\nExpression '%s'\nError: %v", test.input, e)
}
}
})
}
}
// Two errors are identical if they return the exact same string from Error() call.
func equalParseErr() cmp.Option {
return cmp.Comparer(func(a, b ParseErr) bool {
if a.Query != b.Query {
return false
}
if a.LineOffset != b.LineOffset {
return false
}
if !cmp.Equal(a.PositionRange, b.PositionRange) {
return false
}
return a.Err.Error() == b.Err.Error()
})
}
func TestParseSeriesDesc(t *testing.T) {
tests := []struct {
name string
input string
expectedLabels labels.Labels
expectedValues []SequenceValue
expectError string
}{
{
name: "empty string",
expectedLabels: labels.EmptyLabels(),
expectedValues: []SequenceValue{},
},
{
name: "simple line",
input: `http_requests{job="api-server", instance="0", group="production"}`,
expectedLabels: labels.FromStrings(
"__name__", "http_requests",
"group", "production",
"instance", "0",
"job", "api-server",
),
expectedValues: []SequenceValue{},
},
{
name: "label name characters that require quoting",
input: `{"http.requests", "service.name"="api-server", instance="0", group="canary"} 0+50x2`,
expectedLabels: labels.FromStrings(
"__name__", "http.requests",
"group", "canary",
"instance", "0",
"service.name", "api-server",
),
expectedValues: []SequenceValue{
{Value: 0, Omitted: false, Histogram: (*histogram.FloatHistogram)(nil)},
{Value: 50, Omitted: false, Histogram: (*histogram.FloatHistogram)(nil)},
{Value: 100, Omitted: false, Histogram: (*histogram.FloatHistogram)(nil)},
},
},
{
name: "confirm failure on junk after identifier",
input: `{"http.requests"xx} 0+50x2`,
expectError: `parse error: unexpected identifier "xx" in label set, expected "," or "}"`,
},
{
name: "confirm failure on bare operator after identifier",
input: `{"http.requests"=, x="y"} 0+50x2`,
expectError: `parse error: unexpected "," in label set, expected string`,
},
{
name: "confirm failure on unterminated string identifier",
input: `{"http.requests} 0+50x2`,
expectError: `parse error: unterminated quoted string`,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
l, v, err := ParseSeriesDesc(tc.input)
if tc.expectError != "" {
require.Contains(t, err.Error(), tc.expectError)
} else {
require.NoError(t, err)
require.True(t, labels.Equal(tc.expectedLabels, l))
require.Equal(t, tc.expectedValues, v)
}
})
}
}
// NaN has no equality. Thus, we need a separate test for it.
func TestNaNExpression(t *testing.T) {
expr, err := ParseExpr("NaN")
require.NoError(t, err)
nl, ok := expr.(*NumberLiteral)
require.True(t, ok, "expected number literal but got %T", expr)
require.True(t, math.IsNaN(nl.Val), "expected 'NaN' in number literal but got %v", nl.Val)
}
var testSeries = []struct {
input string
expectedMetric labels.Labels
expectedValues []SequenceValue
fail bool
}{
{
input: `{} 1 2 3`,
expectedMetric: labels.EmptyLabels(),
expectedValues: newSeq(1, 2, 3),
}, {
input: `{a="b"} -1 2 3`,
expectedMetric: labels.FromStrings("a", "b"),
expectedValues: newSeq(-1, 2, 3),
}, {
input: `my_metric 1 2 3`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric"),
expectedValues: newSeq(1, 2, 3),
}, {
input: `my_metric{} 1 2 3`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric"),
expectedValues: newSeq(1, 2, 3),
}, {
input: `my_metric{a="b"} 1 2 3`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, 2, 3),
}, {
input: `my_metric{a="b"} 1 2 3-10x4`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, 2, 3, -7, -17, -27, -37),
}, {
input: `my_metric{a="b"} 1 2 3-0x4`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, 2, 3, 3, 3, 3, 3),
}, {
input: `{} 1+1`,
fail: true,
}, {
input: `{} 1x0`,
expectedMetric: labels.EmptyLabels(),
expectedValues: newSeq(1),
}, {
input: `{} 1+1x0`,
expectedMetric: labels.EmptyLabels(),
expectedValues: newSeq(1),
}, {
input: `my_metric{a="b"} 1 3 _ 5 _x4`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, 3, none, 5, none, none, none, none),
}, {
input: `my_metric{a="b"} 1 3 _ 5 _a4`,
fail: true,
}, {
input: `my_metric{a="b"} 1 -1`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, -1),
}, {
input: `my_metric{a="b"} 1 +1`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, 1),
}, {
input: `my_metric{a="b"} 1 -1 -3-10x4 7 9 +5`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, -1, -3, -13, -23, -33, -43, 7, 9, 5),
}, {
input: `my_metric{a="b"} 1 +1 +4 -6 -2 8`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, 1, 4, -6, -2, 8),
}, {
// Trailing spaces should be correctly handles.
input: `my_metric{a="b"} 1 2 3 `,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, 2, 3),
}, {
// Handle escaped unicode characters as whole label values.
input: `my_metric{a="\u70ac"} 1 2 3`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", ``),
expectedValues: newSeq(1, 2, 3),
}, {
// Handle escaped unicode characters as partial label values.
input: `my_metric{a="\u70ac = torch"} 1 2 3`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", `炬 = torch`),
expectedValues: newSeq(1, 2, 3),
}, {
input: `my_metric{a="b"} -3-3 -3`,
fail: true,
}, {
input: `my_metric{a="b"} -3 -3-3`,
fail: true,
}, {
input: `my_metric{a="b"} -3 _-2`,
fail: true,
}, {
input: `my_metric{a="b"} -3 3+3x4-4`,
fail: true,
},
}
// For these tests only, we use the smallest float64 to signal an omitted value.
const none = math.SmallestNonzeroFloat64
func newSeq(vals ...float64) (res []SequenceValue) {
for _, v := range vals {
if v == none {
res = append(res, SequenceValue{Omitted: true})
} else {
res = append(res, SequenceValue{Value: v})
}
}
return res
}
func TestParseHistogramSeries(t *testing.T) {
for _, test := range []struct {
name string
input string
expected []histogram.FloatHistogram
expectedError string
}{
{
name: "empty histogram",
input: "{} {{}}",
expected: []histogram.FloatHistogram{{}},
},
{
name: "empty histogram with space",
input: "{} {{ }}",
expected: []histogram.FloatHistogram{{}},
},
{
name: "all properties used",
input: `{} {{schema:1 sum:0.3 count:3.1 z_bucket:7.1 z_bucket_w:0.05 buckets:[5.1 10 7] offset:3 n_buckets:[4.1 5] n_offset:5 counter_reset_hint:gauge}}`,
expected: []histogram.FloatHistogram{{
Schema: 1,
Sum: 0.3,
Count: 3.1,
ZeroCount: 7.1,
ZeroThreshold: 0.05,
PositiveBuckets: []float64{5.1, 10, 7},
PositiveSpans: []histogram.Span{{Offset: 3, Length: 3}},
NegativeBuckets: []float64{4.1, 5},
NegativeSpans: []histogram.Span{{Offset: 5, Length: 2}},
CounterResetHint: histogram.GaugeType,
}},
},
{
name: "all properties used - with spaces",
input: `{} {{schema:1 sum:0.3 count:3 z_bucket:7 z_bucket_w:5 buckets:[5 10 7 ] offset:-3 n_buckets:[4 5] n_offset:5 counter_reset_hint:gauge }}`,
expected: []histogram.FloatHistogram{{
Schema: 1,
Sum: 0.3,
Count: 3,
ZeroCount: 7,
ZeroThreshold: 5,
PositiveBuckets: []float64{5, 10, 7},
PositiveSpans: []histogram.Span{{Offset: -3, Length: 3}},
NegativeBuckets: []float64{4, 5},
NegativeSpans: []histogram.Span{{Offset: 5, Length: 2}},
CounterResetHint: histogram.GaugeType,
}},
},
{
name: "all properties used, with negative values where supported",
input: `{} {{schema:1 sum:-0.3 count:-3.1 z_bucket:-7.1 z_bucket_w:0.05 buckets:[-5.1 -10 -7] offset:-3 n_buckets:[-4.1 -5] n_offset:-5 counter_reset_hint:gauge}}`,
expected: []histogram.FloatHistogram{{
Schema: 1,
Sum: -0.3,
Count: -3.1,
ZeroCount: -7.1,
ZeroThreshold: 0.05,
PositiveBuckets: []float64{-5.1, -10, -7},
PositiveSpans: []histogram.Span{{Offset: -3, Length: 3}},
NegativeBuckets: []float64{-4.1, -5},
NegativeSpans: []histogram.Span{{Offset: -5, Length: 2}},
CounterResetHint: histogram.GaugeType,
}},
},
{
name: "static series",
input: `{} {{buckets:[5 10 7] schema:1}}x2`,
expected: []histogram.FloatHistogram{
{
Schema: 1,
PositiveBuckets: []float64{5, 10, 7},
PositiveSpans: []histogram.Span{{
Offset: 0,
Length: 3,
}},
},
{
Schema: 1,
PositiveBuckets: []float64{5, 10, 7},
PositiveSpans: []histogram.Span{{
Offset: 0,
Length: 3,
}},
},
{
Schema: 1,
PositiveBuckets: []float64{5, 10, 7},
PositiveSpans: []histogram.Span{{
Offset: 0,
Length: 3,
}},
},
},
},
{
name: "static series - x0",
input: `{} {{buckets:[5 10 7] schema:1}}x0`,
expected: []histogram.FloatHistogram{
{
Schema: 1,
PositiveBuckets: []float64{5, 10, 7},
PositiveSpans: []histogram.Span{{
Offset: 0,
Length: 3,
}},
},
},
},
{
name: "2 histograms stated explicitly",
input: `{} {{buckets:[5 10 7] schema:1}} {{buckets:[1 2 3] schema:1}}`,
expected: []histogram.FloatHistogram{
{
Schema: 1,
PositiveBuckets: []float64{5, 10, 7},
PositiveSpans: []histogram.Span{{
Offset: 0,
Length: 3,
}},
},
{
Schema: 1,
PositiveBuckets: []float64{1, 2, 3},
PositiveSpans: []histogram.Span{{
Offset: 0,
Length: 3,
}},
},
},
},
{
name: "series with increment - with different schemas",
input: `{} {{buckets:[5] schema:0}}+{{buckets:[1 2] schema:1}}x2`,
expected: []histogram.FloatHistogram{
{
PositiveBuckets: []float64{5},
PositiveSpans: []histogram.Span{{
Offset: 0,
Length: 1,
}},
},
{
PositiveBuckets: []float64{6, 2},
PositiveSpans: []histogram.Span{{
Offset: 0,
Length: 2,
}},
},
{
PositiveBuckets: []float64{7, 4},
PositiveSpans: []histogram.Span{{
Offset: 0,
Length: 2,
}},
},
},
},
{
name: "series with two different increments",
input: `{} {{sum:1}}+{{sum:1}}x2 {{sum:2}}+{{sum:2}}x2`,
expected: []histogram.FloatHistogram{
{
Sum: 1,
},
{
Sum: 2,
},
{
Sum: 3,
},
{
Sum: 2,
},
{
Sum: 4,
},
{
Sum: 6,
},
},
},
{
name: "series with decrement",
input: `{} {{buckets:[5 10 7] schema:1}}-{{buckets:[1 2 3] schema:1}}x2`,
expected: []histogram.FloatHistogram{
{
Schema: 1,
PositiveBuckets: []float64{5, 10, 7},
PositiveSpans: []histogram.Span{{
Offset: 0,
Length: 3,
}},
},
{
Schema: 1,
PositiveBuckets: []float64{4, 8, 4},
PositiveSpans: []histogram.Span{{
Offset: 0,
Length: 3,
}},
CounterResetHint: histogram.GaugeType,
},
{
Schema: 1,
PositiveBuckets: []float64{3, 6, 1},
PositiveSpans: []histogram.Span{{
Offset: 0,
Length: 3,
}},
CounterResetHint: histogram.GaugeType,
},
},
},
{
name: "series with increment - 0x",
input: `{} {{buckets:[5 10 7] schema:1}}+{{buckets:[1 2 3] schema:1}}x0`,
expected: []histogram.FloatHistogram{
{
Schema: 1,
PositiveBuckets: []float64{5, 10, 7},
PositiveSpans: []histogram.Span{{
Offset: 0,
Length: 3,
}},
},
},
},
{
name: "series with different schemas - second one is smaller",
input: `{} {{buckets:[5 10 7] schema:1}}+{{buckets:[1 2 3] schema:0}}x2`,
expectedError: `1:63: parse error: error combining histograms: cannot merge from schema 0 to 1`,
},
{
name: "different order",
input: `{} {{buckets:[5 10 7] schema:1}}`,
expected: []histogram.FloatHistogram{{
Schema: 1,
PositiveBuckets: []float64{5, 10, 7},
PositiveSpans: []histogram.Span{{
Offset: 0,
Length: 3,
}},
}},
},
{
name: "double property",
input: `{} {{schema:1 schema:1}}`,
expectedError: `1:1: parse error: duplicate key "schema" in histogram`,
},
{
name: "unknown property",
input: `{} {{foo:1}}`,
expectedError: `1:6: parse error: bad histogram descriptor found: "foo"`,
},
{
name: "space before :",
input: `{} {{schema :1}}`,
expectedError: "1:6: parse error: missing `:` for histogram descriptor",
},
{
name: "space after :",
input: `{} {{schema: 1}}`,
expectedError: `1:13: parse error: unexpected " " in series values`,
},
{
name: "space after [",
input: `{} {{buckets:[ 1]}}`,
expectedError: `1:15: parse error: unexpected " " in series values`,
},
{
name: "space after {{",
input: `{} {{ schema:1}}`,
expectedError: `1:7: parse error: unexpected "<Item 57372>" "schema" in series values`,
},
{
name: "invalid counter reset hint value",
input: `{} {{counter_reset_hint:foo}}`,
expectedError: `1:25: parse error: bad histogram descriptor found: "foo"`,
},
{
name: "'unknown' counter reset hint value",
input: `{} {{counter_reset_hint:unknown}}`,
expected: []histogram.FloatHistogram{{
CounterResetHint: histogram.UnknownCounterReset,
}},
},
{
name: "'reset' counter reset hint value",
input: `{} {{counter_reset_hint:reset}}`,
expected: []histogram.FloatHistogram{{
CounterResetHint: histogram.CounterReset,
}},
},
{
name: "'not_reset' counter reset hint value",
input: `{} {{counter_reset_hint:not_reset}}`,
expected: []histogram.FloatHistogram{{
CounterResetHint: histogram.NotCounterReset,
}},
},
{
name: "'gauge' counter reset hint value",
input: `{} {{counter_reset_hint:gauge}}`,
expected: []histogram.FloatHistogram{{
CounterResetHint: histogram.GaugeType,
}},
},
} {
t.Run(test.name, func(t *testing.T) {
_, vals, err := ParseSeriesDesc(test.input)
if test.expectedError != "" {
require.EqualError(t, err, test.expectedError)
return
}
require.NoError(t, err)
var got []histogram.FloatHistogram
for _, v := range vals {
got = append(got, *v.Histogram)
}
require.Equal(t, test.expected, got)
})
}
}
func TestHistogramTestExpression(t *testing.T) {
for _, test := range []struct {
name string
input histogram.FloatHistogram
expected string
}{
{
name: "single positive and negative span",
input: histogram.FloatHistogram{
Schema: 1,
Sum: -0.3,
Count: 3.1,
ZeroCount: 7.1,
ZeroThreshold: 0.05,
PositiveBuckets: []float64{5.1, 10, 7},
PositiveSpans: []histogram.Span{{Offset: -3, Length: 3}},
NegativeBuckets: []float64{4.1, 5},
NegativeSpans: []histogram.Span{{Offset: -5, Length: 2}},
},
expected: `{{schema:1 count:3.1 sum:-0.3 z_bucket:7.1 z_bucket_w:0.05 offset:-3 buckets:[5.1 10 7] n_offset:-5 n_buckets:[4.1 5]}}`,
},
{
name: "multiple positive and negative spans",
input: histogram.FloatHistogram{
PositiveBuckets: []float64{5.1, 10, 7},
PositiveSpans: []histogram.Span{
{Offset: -3, Length: 1},
{Offset: 4, Length: 2},
},
NegativeBuckets: []float64{4.1, 5, 7, 8, 9},
NegativeSpans: []histogram.Span{
{Offset: -1, Length: 2},
{Offset: 2, Length: 3},
},
},
expected: `{{offset:-3 buckets:[5.1 0 0 0 0 10 7] n_offset:-1 n_buckets:[4.1 5 0 0 7 8 9]}}`,
},
{
name: "known counter reset hint",
input: histogram.FloatHistogram{
Schema: 1,
Sum: -0.3,
Count: 3.1,
ZeroCount: 7.1,
ZeroThreshold: 0.05,
PositiveBuckets: []float64{5.1, 10, 7},
PositiveSpans: []histogram.Span{{Offset: -3, Length: 3}},
NegativeBuckets: []float64{4.1, 5},
NegativeSpans: []histogram.Span{{Offset: -5, Length: 2}},
CounterResetHint: histogram.CounterReset,
},
expected: `{{schema:1 count:3.1 sum:-0.3 z_bucket:7.1 z_bucket_w:0.05 counter_reset_hint:reset offset:-3 buckets:[5.1 10 7] n_offset:-5 n_buckets:[4.1 5]}}`,
},
} {
t.Run(test.name, func(t *testing.T) {
expression := test.input.TestExpression()
require.Equal(t, test.expected, expression)
_, vals, err := ParseSeriesDesc("{} " + expression)
require.NoError(t, err)
require.Len(t, vals, 1)
canonical := vals[0].Histogram
require.NotNil(t, canonical)
require.Equal(t, test.expected, canonical.TestExpression())
})
}
}
func TestParseSeries(t *testing.T) {
for _, test := range testSeries {
metric, vals, err := ParseSeriesDesc(test.input)
// Unexpected errors are always caused by a bug.
require.NotEqual(t, err, errUnexpected, "unexpected error occurred")
if !test.fail {
require.NoError(t, err)
testutil.RequireEqual(t, test.expectedMetric, metric, "error on input '%s'", test.input)
require.Equal(t, test.expectedValues, vals, "error in input '%s'", test.input)
} else {
require.Error(t, err)
}
}
}
func TestRecoverParserRuntime(t *testing.T) {
p := NewParser("foo bar")
var err error
defer func() {
require.Equal(t, errUnexpected, err)
}()
defer p.recover(&err)
// Cause a runtime panic.
var a []int
a[123] = 1 //nolint:govet // This is intended to cause a runtime panic.
}
func TestRecoverParserError(t *testing.T) {
p := NewParser("foo bar")
var err error
e := errors.New("custom error")
defer func() {
require.EqualError(t, err, e.Error())
}()
defer p.recover(&err)
panic(e)
}
func TestExtractSelectors(t *testing.T) {
for _, tc := range [...]struct {
input string
expected []string
}{
{
"foo",
[]string{`{__name__="foo"}`},
}, {
`foo{bar="baz"}`,
[]string{`{bar="baz", __name__="foo"}`},
}, {
`foo{bar="baz"} / flip{flop="flap"}`,
[]string{`{bar="baz", __name__="foo"}`, `{flop="flap", __name__="flip"}`},
}, {
`rate(foo[5m])`,
[]string{`{__name__="foo"}`},
}, {
`vector(1)`,
[]string{},
},
} {
expr, err := ParseExpr(tc.input)
require.NoError(t, err)
var expected [][]*labels.Matcher
for _, s := range tc.expected {
selector, err := ParseMetricSelector(s)
require.NoError(t, err)
expected = append(expected, selector)
}
require.Equal(t, expected, ExtractSelectors(expr))
}
}
func TestParseCustomFunctions(t *testing.T) {
funcs := Functions
funcs["custom_func"] = &Function{
Name: "custom_func",
ArgTypes: []ValueType{ValueTypeMatrix},
ReturnType: ValueTypeVector,
}
input := "custom_func(metric[1m])"
p := NewParser(input, WithFunctions(funcs))
expr, err := p.ParseExpr()
require.NoError(t, err)
call, ok := expr.(*Call)
require.True(t, ok)
require.Equal(t, "custom_func", call.Func.Name)
}