// 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 := range count { 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 "), 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 "), 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 "), 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 "), 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 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 "), 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 "), 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 "), 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 `), 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 "" "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) }