PromQL: Make parser completely generated (#6548)

Signed-off-by: Tobias Guggenmos <tguggenm@redhat.com>
This commit is contained in:
Tobias Guggenmos 2020-01-08 12:04:47 +01:00 committed by Brian Brazil
parent b7376f3bff
commit 3d6cf1c289
6 changed files with 1473 additions and 1146 deletions

View file

@ -18,6 +18,7 @@ import (
"math"
"sort"
"strconv"
"time"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/pkg/value"
@ -35,202 +36,264 @@ import (
series []sequenceValue
uint uint64
float float64
string string
duration time.Duration
}
%token <item> ERROR
%token <item> EOF
%token <item> COMMENT
%token <item> IDENTIFIER
%token <item> METRIC_IDENTIFIER
%token <item> LEFT_PAREN
%token <item> RIGHT_PAREN
%token <item> LEFT_BRACE
%token <item> RIGHT_BRACE
%token <item> LEFT_BRACKET
%token <item> RIGHT_BRACKET
%token <item> COMMA
%token <item> ASSIGN
%token <item> COLON
%token <item> SEMICOLON
%token <item> STRING
%token <item> NUMBER
%token <item> DURATION
%token <item> BLANK
%token <item> TIMES
%token <item> SPACE
%token <item>
ASSIGN
BLANK
COLON
COMMA
COMMENT
DURATION
EOF
ERROR
IDENTIFIER
LEFT_BRACE
LEFT_BRACKET
LEFT_PAREN
METRIC_IDENTIFIER
NUMBER
RIGHT_BRACE
RIGHT_BRACKET
RIGHT_PAREN
SEMICOLON
SPACE
STRING
TIMES
%token operatorsStart
// Operators.
%token <item> SUB
%token <item> ADD
%token <item> MUL
%token <item> MOD
%token <item> DIV
%token <item> LAND
%token <item> LOR
%token <item> LUNLESS
%token <item> EQL
%token <item> NEQ
%token <item> LTE
%token <item> LSS
%token <item> GTE
%token <item> GTR
%token <item> EQL_REGEX
%token <item> NEQ_REGEX
%token <item> POW
%token operatorsStart
%token <item>
ADD
DIV
EQL
EQL_REGEX
GTE
GTR
LAND
LOR
LSS
LTE
LUNLESS
MOD
MUL
NEQ
NEQ_REGEX
POW
SUB
%token operatorsEnd
%token aggregatorsStart
// Aggregators.
%token <item> AVG
%token <item> COUNT
%token <item> SUM
%token <item> MIN
%token <item> MAX
%token <item> STDDEV
%token <item> STDVAR
%token <item> TOPK
%token <item> BOTTOMK
%token <item> COUNT_VALUES
%token <item> QUANTILE
%token aggregatorsStart
%token <item>
AVG
BOTTOMK
COUNT
COUNT_VALUES
MAX
MIN
QUANTILE
STDDEV
STDVAR
SUM
TOPK
%token aggregatorsEnd
%token keywordsStart
// Keywords.
%token <item> OFFSET
%token <item> BY
%token <item> WITHOUT
%token <item> ON
%token <item> IGNORING
%token <item> GROUP_LEFT
%token <item> GROUP_RIGHT
%token <item> BOOL
%token keywordsStart
%token <item>
BOOL
BY
GROUP_LEFT
GROUP_RIGHT
IGNORING
OFFSET
ON
WITHOUT
%token keywordsEnd
%token startSymbolsStart
// Start symbols for the generated parser.
%token START_LABELS
%token START_METRIC
%token START_GROUPING_LABELS
%token START_SERIES_DESCRIPTION
%token startSymbolsStart
%token
START_METRIC
START_SERIES_DESCRIPTION
START_EXPRESSION
START_METRIC_SELECTOR
%token startSymbolsEnd
%type <matchers> label_matchers label_match_list
// Type definitions for grammar rules.
%type <matchers> label_match_list label_matchers
%type <matcher> label_matcher
%type <item> match_op metric_identifier grouping_label maybe_label
%type <item> aggregate_op grouping_label match_op maybe_label metric_identifier unary_op
%type <labels> label_set_list label_set metric
%type <label> label_set_item
%type <strings> grouping_labels grouping_label_list
%type <series> series_values series_item
%type <labels> label_set label_set_list metric
%type <label> label_set_item
%type <strings> grouping_label_list grouping_labels maybe_grouping_labels
%type <series> series_item series_values
%type <uint> uint
%type <float> series_value signed_number number
%type <float> number series_value signed_number
%type <node> aggregate_expr aggregate_modifier bin_modifier binary_expr bool_modifier expr function_call function_call_args function_call_body group_modifiers matrix_selector number_literal offset_expr on_or_ignoring paren_expr string_literal subquery_expr unary_expr vector_selector
%type <string> string
%type <duration> duration maybe_duration
%start start
// Operators are listed with increasing precedence.
%left LOR
%left LAND LUNLESS
%left EQL GTE GTR LSS LTE NEQ
%left ADD SUB
%left MUL DIV MOD
%right POW
// Fake token for giving unary expressions maximal precendence
%token PREC_MAX
%nonassoc PREC_MAX
// Offset modifiers do not have associativity.
%nonassoc OFFSET
// This ensures that it is always attempted to parse range or subquery selectors when a left
// bracket is encountered.
%right LEFT_BRACKET
%%
start : START_LABELS label_matchers
{yylex.(*parser).generatedParserResult.(*VectorSelector).LabelMatchers = $2}
| START_METRIC metric
{ yylex.(*parser).generatedParserResult = $2 }
| START_GROUPING_LABELS grouping_labels
{ yylex.(*parser).generatedParserResult = $2 }
start :
START_METRIC metric
{ yylex.(*parser).generatedParserResult = $2 }
| START_SERIES_DESCRIPTION series_description
| START_EXPRESSION /* empty */ EOF
{ yylex.(*parser).errorf("no expression found in input")}
| START_EXPRESSION expr
{ yylex.(*parser).generatedParserResult = $2 }
| START_METRIC_SELECTOR vector_selector
{ yylex.(*parser).generatedParserResult = $2 }
| start EOF
| error /* If none of the more detailed error messages are triggered, we fall back to this. */
{ yylex.(*parser).unexpected("","") }
;
label_matchers :
LEFT_BRACE label_match_list RIGHT_BRACE
{ $$ = $2 }
| LEFT_BRACE label_match_list COMMA RIGHT_BRACE
{ $$ = $2 }
| LEFT_BRACE RIGHT_BRACE
{ $$ = []*labels.Matcher{} }
expr :
aggregate_expr
| binary_expr
| function_call
| matrix_selector
| number_literal
| offset_expr
| paren_expr
| string_literal
| subquery_expr
| unary_expr
| vector_selector
;
label_match_list:
label_match_list COMMA label_matcher
{ $$ = append($1, $3)}
| label_matcher
{ $$ = []*labels.Matcher{$1}}
| label_match_list error
{ yylex.(*parser).unexpected("label matching", "\",\" or \"}\"") }
/*
* Aggregations.
*/
aggregate_expr : aggregate_op aggregate_modifier function_call_body
{ $$ = yylex.(*parser).newAggregateExpr($1, $2, $3) }
| aggregate_op function_call_body aggregate_modifier
{ $$ = yylex.(*parser).newAggregateExpr($1, $3, $2) }
| aggregate_op function_call_body
{ $$ = yylex.(*parser).newAggregateExpr($1, &AggregateExpr{}, $2) }
| aggregate_op error
{ yylex.(*parser).unexpected("aggregation","") }
;
label_matcher :
IDENTIFIER match_op STRING
{ $$ = yylex.(*parser).newLabelMatcher($1, $2, $3) }
| IDENTIFIER match_op error
{ yylex.(*parser).unexpected("label matching", "string")}
| IDENTIFIER error
{ yylex.(*parser).unexpected("label matching", "label matching operator") }
| error
{ yylex.(*parser).unexpected("label matching", "identifier or \"}\"")}
aggregate_modifier:
BY grouping_labels
{
$$ = &AggregateExpr{
Grouping: $2,
}
}
| WITHOUT grouping_labels
{
$$ = &AggregateExpr{
Grouping: $2,
Without: true,
}
}
;
match_op :
EQL {$$ =$1}
| NEQ {$$=$1}
| EQL_REGEX {$$=$1}
| NEQ_REGEX {$$=$1}
/*
* Binary expressions.
*/
// Operator precedence only works if each of those is listed separately.
binary_expr : expr ADD bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
| expr DIV bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
| expr EQL bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
| expr GTE bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
| expr GTR bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
| expr LAND bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
| expr LOR bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
| expr LSS bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
| expr LTE bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
| expr LUNLESS bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
| expr MOD bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
| expr MUL bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
| expr NEQ bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
| expr POW bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
| expr SUB bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
;
// Using left recursion for the modifier rules, helps to keep the parser stack small and
// reduces allocations
bin_modifier : group_modifiers;
bool_modifier : /* empty */
{ $$ = &BinaryExpr{
VectorMatching: &VectorMatching{Card: CardOneToOne},
}
}
| BOOL
{ $$ = &BinaryExpr{
VectorMatching: &VectorMatching{Card: CardOneToOne},
ReturnBool: true,
}
}
;
on_or_ignoring : bool_modifier IGNORING grouping_labels
{
$$ = $1
$$.(*BinaryExpr).VectorMatching.MatchingLabels = $3
}
| bool_modifier ON grouping_labels
{
$$ = $1
$$.(*BinaryExpr).VectorMatching.MatchingLabels = $3
$$.(*BinaryExpr).VectorMatching.On = true
}
;
group_modifiers: bool_modifier /* empty */
| on_or_ignoring /* empty */
| on_or_ignoring GROUP_LEFT maybe_grouping_labels
{
$$ = $1
$$.(*BinaryExpr).VectorMatching.Card = CardManyToOne
$$.(*BinaryExpr).VectorMatching.Include = $3
}
| on_or_ignoring GROUP_RIGHT maybe_grouping_labels
{
$$ = $1
$$.(*BinaryExpr).VectorMatching.Card = CardOneToMany
$$.(*BinaryExpr).VectorMatching.Include = $3
}
;
metric :
metric_identifier label_set
{ $$ = append($2, labels.Label{Name: labels.MetricName, Value: $1.Val}); sort.Sort($$) }
| label_set
{$$ = $1}
;
metric_identifier
:
METRIC_IDENTIFIER {$$=$1}
| IDENTIFIER {$$=$1}
label_set :
LEFT_BRACE label_set_list RIGHT_BRACE
{ $$ = labels.New($2...) }
| LEFT_BRACE label_set_list COMMA RIGHT_BRACE
{ $$ = labels.New($2...) }
| LEFT_BRACE RIGHT_BRACE
{ $$ = labels.New() }
| /* empty */
{ $$ = labels.New() }
;
label_set_list :
label_set_list COMMA label_set_item
{ $$ = append($1, $3) }
| label_set_item
{ $$ = []labels.Label{$1} }
| label_set_list error
{ yylex.(*parser).unexpected("label set", "\",\" or \"}\"", ) }
;
label_set_item :
IDENTIFIER EQL STRING
{ $$ = labels.Label{Name: $1.Val, Value: yylex.(*parser).unquoteString($3.Val) } }
| IDENTIFIER EQL error
{ yylex.(*parser).unexpected("label set", "string")}
| IDENTIFIER error
{ yylex.(*parser).unexpected("label set", "\"=\"")}
| error
{ yylex.(*parser).unexpected("label set", "identifier or \"}\"") }
;
grouping_labels :
LEFT_PAREN grouping_label_list RIGHT_PAREN
grouping_labels : LEFT_PAREN grouping_label_list RIGHT_PAREN
{ $$ = $2 }
| LEFT_PAREN grouping_label_list COMMA RIGHT_PAREN
{ $$ = $2 }
@ -247,11 +310,10 @@ grouping_label_list:
| grouping_label
{ $$ = []string{$1.Val} }
| grouping_label_list error
{ yylex.(*parser).unexpected("grouping opts", "\",\" or \"}\"") }
{ yylex.(*parser).unexpected("grouping opts", "\",\" or \")\"") }
;
grouping_label :
maybe_label
grouping_label : maybe_label
{
if !isLabel($1.Val) {
yylex.(*parser).unexpected("grouping opts", "label")
@ -262,37 +324,201 @@ grouping_label :
{ yylex.(*parser).unexpected("grouping opts", "label") }
;
/* inside of grouping options label names can be recognized as keywords by the lexer */
maybe_label :
IDENTIFIER
| METRIC_IDENTIFIER
| LAND
| LOR
| LUNLESS
| AVG
| COUNT
| SUM
| MIN
| MAX
| STDDEV
| STDVAR
| TOPK
| BOTTOMK
| COUNT_VALUES
| QUANTILE
| OFFSET
| BY
| ON
| IGNORING
| GROUP_LEFT
| GROUP_RIGHT
| BOOL
/*
* Function calls.
*/
function_call : IDENTIFIER function_call_body
{
fn, exist := getFunction($1.Val)
if !exist{
yylex.(*parser).errorf("unknown function with name %q", $1.Val)
}
$$ = &Call{
Func: fn,
Args: $2.(Expressions),
}
}
;
// The series description grammar is only used inside unit tests.
series_description:
metric series_values
function_call_body: LEFT_PAREN function_call_args RIGHT_PAREN
{ $$ = $2 }
| LEFT_PAREN RIGHT_PAREN
{$$ = Expressions{}}
;
function_call_args: function_call_args COMMA expr
{ $$ = append($1.(Expressions), $3.(Expr)) }
| expr
{ $$ = Expressions{$1.(Expr)} }
;
/*
* Expressions inside parentheses.
*/
paren_expr : LEFT_PAREN expr RIGHT_PAREN
{ $$ = &ParenExpr{Expr: $2.(Expr)} }
;
/*
* Offset modifiers.
*/
offset_expr: expr OFFSET duration
{
yylex.(*parser).addOffset($1, $3)
$$ = $1
}
| expr OFFSET error
{ yylex.(*parser).unexpected("offset", "duration") }
;
/*
* Subquery and range selectors.
*/
matrix_selector : expr LEFT_BRACKET duration RIGHT_BRACKET
{
vs, ok := $1.(*VectorSelector)
if !ok{
yylex.(*parser).errorf("ranges only allowed for vector selectors")
}
if vs.Offset != 0{
yylex.(*parser).errorf("no offset modifiers allowed before range")
}
$$ = &MatrixSelector{
Name: vs.Name,
Offset: vs.Offset,
LabelMatchers: vs.LabelMatchers,
Range: $3,
}
}
;
subquery_expr : expr LEFT_BRACKET duration COLON maybe_duration RIGHT_BRACKET
{
$$ = &SubqueryExpr{
Expr: $1.(Expr),
Range: $3,
Step: $5,
}
}
| expr LEFT_BRACKET duration COLON duration error
{ yylex.(*parser).unexpected("subquery selector", "\"]\"") }
| expr LEFT_BRACKET duration COLON error
{ yylex.(*parser).unexpected("subquery selector", "duration or \"]\"") }
| expr LEFT_BRACKET duration error
{ yylex.(*parser).unexpected("subquery or range", "\":\" or \"]\"") }
| expr LEFT_BRACKET error
{ yylex.(*parser).unexpected("subquery selector", "duration") }
;
/*
* Unary expressions.
*/
unary_expr :
/* gives the rule the same prec as POW. This has the effect that unary opts are always evaluated with highest precedence */
unary_op expr %prec PREC_MAX
{
if nl, ok := $2.(*NumberLiteral); ok {
if $1.Typ == SUB {
nl.Val *= -1
}
$$ = nl
} else {
$$ = &UnaryExpr{Op: $1.Typ, Expr: $2.(Expr)}
}
}
;
/*
* Vector selectors.
*/
vector_selector: metric_identifier label_matchers
{ $$ = yylex.(*parser).newVectorSelector($1.Val, $2) }
| metric_identifier
{ $$ = yylex.(*parser).newVectorSelector($1.Val, nil) }
| label_matchers
{ $$ = yylex.(*parser).newVectorSelector("", $1) }
;
label_matchers : LEFT_BRACE label_match_list RIGHT_BRACE
{ $$ = $2 }
| LEFT_BRACE label_match_list COMMA RIGHT_BRACE
{ $$ = $2 }
| LEFT_BRACE RIGHT_BRACE
{ $$ = []*labels.Matcher{} }
;
label_match_list: label_match_list COMMA label_matcher
{ $$ = append($1, $3)}
| label_matcher
{ $$ = []*labels.Matcher{$1}}
| label_match_list error
{ yylex.(*parser).unexpected("label matching", "\",\" or \"}\"") }
;
label_matcher : IDENTIFIER match_op STRING
{ $$ = yylex.(*parser).newLabelMatcher($1, $2, $3) }
| IDENTIFIER match_op error
{ yylex.(*parser).unexpected("label matching", "string")}
| IDENTIFIER error
{ yylex.(*parser).unexpected("label matching", "label matching operator") }
| error
{ yylex.(*parser).unexpected("label matching", "identifier or \"}\"")}
;
/*
* Metric descriptions.
*/
metric : metric_identifier label_set
{ $$ = append($2, labels.Label{Name: labels.MetricName, Value: $1.Val}); sort.Sort($$) }
| label_set
{$$ = $1}
;
metric_identifier: METRIC_IDENTIFIER | IDENTIFIER;
label_set : LEFT_BRACE label_set_list RIGHT_BRACE
{ $$ = labels.New($2...) }
| LEFT_BRACE label_set_list COMMA RIGHT_BRACE
{ $$ = labels.New($2...) }
| LEFT_BRACE RIGHT_BRACE
{ $$ = labels.New() }
| /* empty */
{ $$ = labels.New() }
;
label_set_list : label_set_list COMMA label_set_item
{ $$ = append($1, $3) }
| label_set_item
{ $$ = []labels.Label{$1} }
| label_set_list error
{ yylex.(*parser).unexpected("label set", "\",\" or \"}\"", ) }
;
label_set_item : IDENTIFIER EQL STRING
{ $$ = labels.Label{Name: $1.Val, Value: yylex.(*parser).unquoteString($3.Val) } }
| IDENTIFIER EQL error
{ yylex.(*parser).unexpected("label set", "string")}
| IDENTIFIER error
{ yylex.(*parser).unexpected("label set", "\"=\"")}
| error
{ yylex.(*parser).unexpected("label set", "identifier or \"}\"") }
;
/*
* Series descriptions (only used by unit tests).
*/
series_description: metric series_values
{
yylex.(*parser).generatedParserResult = &seriesDescription{
labels: $1,
@ -301,8 +527,7 @@ series_description:
}
;
series_values :
/*empty*/
series_values : /*empty*/
{ $$ = []sequenceValue{} }
| series_values SPACE series_item
{ $$ = append($1, $3...) }
@ -312,8 +537,7 @@ series_values :
{ yylex.(*parser).unexpected("series values", "") }
;
series_item :
BLANK
series_item : BLANK
{ $$ = []sequenceValue{{omitted: true}}}
| BLANK TIMES uint
{
@ -339,26 +563,9 @@ series_item :
$1 += $2
}
}
uint :
NUMBER
{
var err error
$$, err = strconv.ParseUint($1.Val, 10, 64)
if err != nil {
yylex.(*parser).errorf("invalid repitition in series values: %s", err)
}
}
;
signed_number :
ADD number
{ $$ = $2 }
| SUB number
{ $$ = -$2 }
;
series_value :
IDENTIFIER
series_value : IDENTIFIER
{
if $1.Val != "stale" {
yylex.(*parser).unexpected("series values", "number or \"stale\"")
@ -366,16 +573,73 @@ series_value :
$$ = math.Float64frombits(value.StaleNaN)
}
| number
{ $$ = $1 }
| signed_number
{ $$ = $1 }
;
number :
NUMBER
{$$ = yylex.(*parser).number($1.Val) }
/*
* Keyword lists.
*/
aggregate_op : AVG | BOTTOMK | COUNT | COUNT_VALUES | MAX | MIN | QUANTILE | STDDEV | STDVAR | SUM | TOPK ;
// inside of grouping options label names can be recognized as keywords by the lexer. This is a list of keywords that could also be a label name.
maybe_label : AVG | BOOL | BOTTOMK | BY | COUNT | COUNT_VALUES | GROUP_LEFT | GROUP_RIGHT | IDENTIFIER | IGNORING | LAND | LOR | LUNLESS | MAX | METRIC_IDENTIFIER | MIN | OFFSET | ON | QUANTILE | STDDEV | STDVAR | SUM | TOPK;
unary_op : ADD | SUB;
match_op : EQL | NEQ | EQL_REGEX | NEQ_REGEX ;
/*
* Literals.
*/
number_literal : number { $$ = &NumberLiteral{$1}} ;
number : NUMBER { $$ = yylex.(*parser).number($1.Val) } ;
signed_number : ADD number { $$ = $2 }
| SUB number { $$ = -$2 }
;
uint : NUMBER
{
var err error
$$, err = strconv.ParseUint($1.Val, 10, 64)
if err != nil {
yylex.(*parser).errorf("invalid repetition in series values: %s", err)
}
}
;
duration : DURATION
{
var err error
$$, err = parseDuration($1.Val)
if err != nil {
yylex.(*parser).error(err)
}
}
;
string_literal : string { $$ = &StringLiteral{$1}} ;
string : STRING { $$ = yylex.(*parser).unquoteString($1.Val) } ;
/*
* Wrappers for optional arguments.
*/
maybe_duration : /* empty */
{$$ = 0}
| duration
;
maybe_grouping_labels: /* empty */ { $$ = nil }
| grouping_labels
;
%%

File diff suppressed because it is too large Load diff

View file

@ -89,38 +89,6 @@ func (i ItemType) isSetOperator() bool {
// LowestPrec is a constant for operator precedence in expressions.
const LowestPrec = 0 // Non-operators.
// Precedence returns the operator precedence of the binary
// operator op. If op is not a binary operator, the result
// is LowestPrec.
func (i ItemType) precedence() int {
switch i {
case LOR:
return 1
case LAND, LUNLESS:
return 2
case EQL, NEQ, LTE, LSS, GTE, GTR:
return 3
case ADD, SUB:
return 4
case MUL, DIV, MOD:
return 5
case POW:
return 6
default:
return LowestPrec
}
}
func (i ItemType) isRightAssociative() bool {
switch i {
case POW:
return true
default:
return false
}
}
type ItemType int
// This is a list of all keywords in PromQL.

View file

@ -54,14 +54,14 @@ func (e *ParseErr) Error() string {
}
// ParseExpr returns the expression parsed from the input.
func ParseExpr(input string) (Expr, error) {
func ParseExpr(input string) (expr Expr, err error) {
p := newParser(input)
expr, err := p.parseExpr()
if err != nil {
return nil, err
}
defer p.recover(&err)
expr = p.parseGenerated(START_EXPRESSION, []ItemType{EOF}).(Expr)
err = p.typecheck(expr)
return expr, err
}
@ -70,7 +70,7 @@ func ParseMetric(input string) (m labels.Labels, err error) {
p := newParser(input)
defer p.recover(&err)
return p.parseGenerated(START_METRIC, []ItemType{RIGHT_BRACE, EOF}).(labels.Labels), nil
return p.parseGenerated(START_METRIC, []ItemType{EOF}).(labels.Labels), nil
}
// ParseMetricSelector parses the provided textual metric selector into a list of
@ -79,15 +79,7 @@ func ParseMetricSelector(input string) (m []*labels.Matcher, err error) {
p := newParser(input)
defer p.recover(&err)
name := ""
if t := p.peek().Typ; t == METRIC_IDENTIFIER || t == IDENTIFIER {
name = p.next().Val
}
vs := p.VectorSelector(name)
if p.peek().Typ != EOF {
p.errorf("could not parse remaining input %.15q...", p.lex.input[p.lex.lastPos:])
}
return vs.LabelMatchers, nil
return p.parseGenerated(START_METRIC_SELECTOR, []ItemType{EOF}).(*VectorSelector).LabelMatchers, nil
}
// newParser returns a new parser.
@ -98,23 +90,6 @@ func newParser(input string) *parser {
return p
}
// parseExpr parses a single expression from the input.
func (p *parser) parseExpr() (expr Expr, err error) {
defer p.recover(&err)
for p.peek().Typ != EOF {
if expr != nil {
p.errorf("could not parse remaining input %.15q...", p.lex.input[p.lex.lastPos:])
}
expr = p.expr()
}
if expr == nil {
p.errorf("no expression found in input")
}
return
}
// sequenceValue is an omittable value in a sequence of time series values.
type sequenceValue struct {
value float64
@ -176,27 +151,6 @@ func (p *parser) next() Item {
return p.token
}
// peek returns but does not consume the next token.
func (p *parser) peek() Item {
if p.peeking {
return p.token
}
p.peeking = true
t := p.lex.NextItem()
// Skip comments.
for t.Typ == COMMENT {
t = p.lex.NextItem()
}
p.token = t
return p.token
}
// backup backs the input stream up one token.
func (p *parser) backup() {
p.peeking = true
}
// errorf formats the error and terminates processing.
func (p *parser) errorf(format string, args ...interface{}) {
p.error(errors.Errorf(format, args...))
@ -237,25 +191,6 @@ func (p *parser) unexpected(context string, expected string) {
p.error(errors.New(errMsg.String()))
}
// expect consumes the next token and guarantees it has the required type.
func (p *parser) expect(exp ItemType, context string) Item {
token := p.next()
if token.Typ != exp {
p.unexpected(context, exp.desc())
}
return token
}
// expectOneOf consumes the next token and guarantees it has one of the required types.
func (p *parser) expectOneOf(exp1, exp2 ItemType, context string) Item {
token := p.next()
if token.Typ != exp1 && token.Typ != exp2 {
expected := exp1.desc() + " or " + exp2.desc()
p.unexpected(context, expected)
}
return token
}
var errUnexpected = errors.New("unexpected error")
// recover is the handler that turns panics into returns from the top level of Parse.
@ -329,436 +264,55 @@ func (p *parser) InjectItem(typ ItemType) {
p.inject = Item{Typ: typ}
p.injecting = true
}
func (p *parser) newBinaryExpression(lhs Node, op Item, modifiers Node, rhs Node) *BinaryExpr {
ret := modifiers.(*BinaryExpr)
// expr parses any expression.
func (p *parser) expr() Expr {
// Parse the starting expression.
expr := p.unaryExpr()
ret.LHS = lhs.(Expr)
ret.RHS = rhs.(Expr)
ret.Op = op.Typ
// Loop through the operations and construct a binary operation tree based
// on the operators' precedence.
for {
// If the next token is not an operator the expression is done.
op := p.peek().Typ
if !op.isOperator() {
// Check for subquery.
if op == LEFT_BRACKET {
expr = p.subqueryOrRangeSelector(expr, false)
if s, ok := expr.(*SubqueryExpr); ok {
// Parse optional offset.
if p.peek().Typ == OFFSET {
offset := p.offset()
s.Offset = offset
}
}
}
return expr
}
p.next() // Consume operator.
// Parse optional operator matching options. Its validity
// is checked in the type-checking stage.
vecMatching := &VectorMatching{
Card: CardOneToOne,
}
if op.isSetOperator() {
vecMatching.Card = CardManyToMany
}
returnBool := false
// Parse bool modifier.
if p.peek().Typ == BOOL {
if !op.isComparisonOperator() {
p.errorf("bool modifier can only be used on comparison operators")
}
p.next()
returnBool = true
}
// Parse ON/IGNORING clause.
if p.peek().Typ == ON || p.peek().Typ == IGNORING {
if p.peek().Typ == ON {
vecMatching.On = true
}
p.next()
vecMatching.MatchingLabels = p.labels()
// Parse grouping.
if t := p.peek().Typ; t == GROUP_LEFT || t == GROUP_RIGHT {
p.next()
if t == GROUP_LEFT {
vecMatching.Card = CardManyToOne
} else {
vecMatching.Card = CardOneToMany
}
if p.peek().Typ == LEFT_PAREN {
vecMatching.Include = p.labels()
}
}
}
for _, ln := range vecMatching.MatchingLabels {
for _, ln2 := range vecMatching.Include {
if ln == ln2 && vecMatching.On {
p.errorf("label %q must not occur in ON and GROUP clause at once", ln)
}
}
}
// Parse the next operand.
rhs := p.unaryExpr()
// Assign the new root based on the precedence of the LHS and RHS operators.
expr = p.balance(expr, op, rhs, vecMatching, returnBool)
if ret.ReturnBool && !op.Typ.isComparisonOperator() {
p.errorf("bool modifier can only be used on comparison operators")
}
}
func (p *parser) balance(lhs Expr, op ItemType, rhs Expr, vecMatching *VectorMatching, returnBool bool) *BinaryExpr {
if lhsBE, ok := lhs.(*BinaryExpr); ok {
precd := lhsBE.Op.precedence() - op.precedence()
if (precd < 0) || (precd == 0 && op.isRightAssociative()) {
balanced := p.balance(lhsBE.RHS, op, rhs, vecMatching, returnBool)
if lhsBE.Op.isComparisonOperator() && !lhsBE.ReturnBool && balanced.Type() == ValueTypeScalar && lhsBE.LHS.Type() == ValueTypeScalar {
p.errorf("comparisons between scalars must use BOOL modifier")
}
return &BinaryExpr{
Op: lhsBE.Op,
LHS: lhsBE.LHS,
RHS: balanced,
VectorMatching: lhsBE.VectorMatching,
ReturnBool: lhsBE.ReturnBool,
}
}
}
if op.isComparisonOperator() && !returnBool && rhs.Type() == ValueTypeScalar && lhs.Type() == ValueTypeScalar {
if op.Typ.isComparisonOperator() && !ret.ReturnBool && ret.RHS.Type() == ValueTypeScalar && ret.LHS.Type() == ValueTypeScalar {
p.errorf("comparisons between scalars must use BOOL modifier")
}
return &BinaryExpr{
Op: op,
LHS: lhs,
RHS: rhs,
VectorMatching: vecMatching,
ReturnBool: returnBool,
}
}
// unaryExpr parses a unary expression.
//
// <Vector_selector> | <Matrix_selector> | (+|-) <number_literal> | '(' <expr> ')'
//
func (p *parser) unaryExpr() Expr {
switch t := p.peek(); t.Typ {
case ADD, SUB:
p.next()
e := p.unaryExpr()
// Simplify unary expressions for number literals.
if nl, ok := e.(*NumberLiteral); ok {
if t.Typ == SUB {
nl.Val *= -1
}
return nl
}
return &UnaryExpr{Op: t.Typ, Expr: e}
case LEFT_PAREN:
p.next()
e := p.expr()
p.expect(RIGHT_PAREN, "paren expression")
return &ParenExpr{Expr: e}
}
e := p.primaryExpr()
// Expression might be followed by a range selector.
if p.peek().Typ == LEFT_BRACKET {
e = p.subqueryOrRangeSelector(e, true)
if op.Typ.isSetOperator() && ret.VectorMatching.Card == CardOneToOne {
ret.VectorMatching.Card = CardManyToMany
}
// Parse optional offset.
if p.peek().Typ == OFFSET {
offset := p.offset()
switch s := e.(type) {
case *VectorSelector:
s.Offset = offset
case *MatrixSelector:
s.Offset = offset
case *SubqueryExpr:
s.Offset = offset
default:
p.errorf("offset modifier must be preceded by an instant or range selector, but follows a %T instead", e)
}
}
return e
}
// subqueryOrRangeSelector parses a Subquery based on given Expr (or)
// a Matrix (a.k.a. range) selector based on a given Vector selector.
//
// <Vector_selector> '[' <duration> ']' | <Vector_selector> '[' <duration> ':' [<duration>] ']'
//
func (p *parser) subqueryOrRangeSelector(expr Expr, checkRange bool) Expr {
ctx := "subquery selector"
if checkRange {
ctx = "range/subquery selector"
}
p.next()
var erange time.Duration
var err error
erangeStr := p.expect(DURATION, ctx).Val
erange, err = parseDuration(erangeStr)
if err != nil {
p.error(err)
}
var itm Item
if checkRange {
itm = p.expectOneOf(RIGHT_BRACKET, COLON, ctx)
if itm.Typ == RIGHT_BRACKET {
// Range selector.
vs, ok := expr.(*VectorSelector)
if !ok {
p.errorf("range specification must be preceded by a metric selector, but follows a %T instead", expr)
}
return &MatrixSelector{
Name: vs.Name,
LabelMatchers: vs.LabelMatchers,
Range: erange,
for _, l1 := range ret.VectorMatching.MatchingLabels {
for _, l2 := range ret.VectorMatching.Include {
if l1 == l2 && ret.VectorMatching.On {
p.errorf("label %q must not occur in ON and GROUP clause at once", l1)
}
}
} else {
itm = p.expect(COLON, ctx)
}
// Subquery.
var estep time.Duration
itm = p.expectOneOf(RIGHT_BRACKET, DURATION, ctx)
if itm.Typ == DURATION {
estepStr := itm.Val
estep, err = parseDuration(estepStr)
if err != nil {
p.error(err)
}
p.expect(RIGHT_BRACKET, ctx)
}
return &SubqueryExpr{
Expr: expr,
Range: erange,
Step: estep,
}
return ret
}
// number parses a number.
func (p *parser) number(val string) float64 {
n, err := strconv.ParseInt(val, 0, 64)
f := float64(n)
if err != nil {
f, err = strconv.ParseFloat(val, 64)
}
if err != nil {
p.errorf("error parsing number: %s", err)
}
return f
}
func (p *parser) newVectorSelector(name string, labelMatchers []*labels.Matcher) *VectorSelector {
ret := &VectorSelector{LabelMatchers: labelMatchers}
// primaryExpr parses a primary expression.
//
// <metric_name> | <function_call> | <Vector_aggregation> | <literal>
//
func (p *parser) primaryExpr() Expr {
switch t := p.next(); {
case t.Typ == NUMBER:
f := p.number(t.Val)
return &NumberLiteral{f}
case t.Typ == STRING:
return &StringLiteral{p.unquoteString(t.Val)}
case t.Typ == LEFT_BRACE:
// Metric selector without metric name.
p.backup()
return p.VectorSelector("")
case t.Typ == IDENTIFIER:
// Check for function call.
if p.peek().Typ == LEFT_PAREN {
return p.call(t.Val)
}
fallthrough // Else metric selector.
case t.Typ == METRIC_IDENTIFIER:
return p.VectorSelector(t.Val)
case t.Typ.isAggregator():
p.backup()
return p.aggrExpr()
default:
p.errorf("no valid expression found")
}
return nil
}
// labels parses a list of labelnames.
//
// '(' <label_name>, ... ')'
//
func (p *parser) labels() []string {
return p.parseGenerated(START_GROUPING_LABELS, []ItemType{RIGHT_PAREN, EOF}).([]string)
}
// aggrExpr parses an aggregation expression.
//
// <aggr_op> (<Vector_expr>) [by|without <labels>]
// <aggr_op> [by|without <labels>] (<Vector_expr>)
//
func (p *parser) aggrExpr() *AggregateExpr {
const ctx = "aggregation"
agop := p.next()
if !agop.Typ.isAggregator() {
p.errorf("expected aggregation operator but got %s", agop)
}
var grouping []string
var without bool
modifiersFirst := false
if t := p.peek().Typ; t == BY || t == WITHOUT {
if t == WITHOUT {
without = true
}
p.next()
grouping = p.labels()
modifiersFirst = true
}
p.expect(LEFT_PAREN, ctx)
var param Expr
if agop.Typ.isAggregatorWithParam() {
param = p.expr()
p.expect(COMMA, ctx)
}
e := p.expr()
p.expect(RIGHT_PAREN, ctx)
if !modifiersFirst {
if t := p.peek().Typ; t == BY || t == WITHOUT {
if len(grouping) > 0 {
p.errorf("aggregation must only contain one grouping clause")
}
if t == WITHOUT {
without = true
}
p.next()
grouping = p.labels()
}
}
return &AggregateExpr{
Op: agop.Typ,
Expr: e,
Param: param,
Grouping: grouping,
Without: without,
}
}
// call parses a function call.
//
// <func_name> '(' [ <arg_expr>, ...] ')'
//
func (p *parser) call(name string) *Call {
const ctx = "function call"
fn, exist := getFunction(name)
if !exist {
p.errorf("unknown function with name %q", name)
}
p.expect(LEFT_PAREN, ctx)
// Might be call without args.
if p.peek().Typ == RIGHT_PAREN {
p.next() // Consume.
return &Call{fn, nil}
}
var args []Expr
for {
e := p.expr()
args = append(args, e)
// Terminate if no more arguments.
if p.peek().Typ != COMMA {
break
}
p.next()
}
// Call must be closed.
p.expect(RIGHT_PAREN, ctx)
return &Call{Func: fn, Args: args}
}
// offset parses an offset modifier.
//
// offset <duration>
//
func (p *parser) offset() time.Duration {
const ctx = "offset"
p.next()
offi := p.expect(DURATION, ctx)
offset, err := parseDuration(offi.Val)
if err != nil {
p.error(err)
}
return offset
}
// VectorSelector parses a new (instant) vector selector.
//
// <metric_identifier> [<label_matchers>]
// [<metric_identifier>] <label_matchers>
//
func (p *parser) VectorSelector(name string) *VectorSelector {
ret := &VectorSelector{
Name: name,
}
// Parse label matching if any.
if t := p.peek(); t.Typ == LEFT_BRACE {
p.generatedParserResult = ret
p.parseGenerated(START_LABELS, []ItemType{RIGHT_BRACE, EOF})
}
// Metric name must not be set in the label matchers and before at the same time.
if name != "" {
ret.Name = name
for _, m := range ret.LabelMatchers {
if m.Name == labels.MetricName {
p.errorf("metric name must not be set twice: %q or %q", name, m.Value)
}
}
// Set name label matching.
m, err := labels.NewMatcher(labels.MatchEqual, labels.MetricName, name)
nameMatcher, err := labels.NewMatcher(labels.MatchEqual, labels.MetricName, name)
if err != nil {
panic(err) // Must not happen with metric.Equal.
panic(err) // Must not happen with labels.MatchEqual
}
ret.LabelMatchers = append(ret.LabelMatchers, m)
ret.LabelMatchers = append(ret.LabelMatchers, nameMatcher)
}
if len(ret.LabelMatchers) == 0 {
p.errorf("vector selector must contain label matchers or metric name")
}
// A Vector selector must contain at least one non-empty matcher to prevent
// implicit selection of all metrics (e.g. by a typo).
notEmpty := false
@ -775,6 +329,53 @@ func (p *parser) VectorSelector(name string) *VectorSelector {
return ret
}
func (p *parser) newAggregateExpr(op Item, modifier Node, args Node) (ret *AggregateExpr) {
ret = modifier.(*AggregateExpr)
arguments := args.(Expressions)
ret.Op = op.Typ
if len(arguments) == 0 {
p.errorf("no arguments for aggregate expression provided")
// Currently p.errorf() panics, so this return is not needed
// at the moment.
// However, this behaviour is likely to be changed in the
// future. In case of having non-panicking errors this
// return prevents invalid array accesses
return
}
desiredArgs := 1
if ret.Op.isAggregatorWithParam() {
desiredArgs = 2
ret.Param = arguments[0]
}
if len(arguments) != desiredArgs {
p.errorf("wrong number of arguments for aggregate expression provided, expected %d, got %d", desiredArgs, len(arguments))
return
}
ret.Expr = arguments[desiredArgs-1]
return ret
}
// number parses a number.
func (p *parser) number(val string) float64 {
n, err := strconv.ParseInt(val, 0, 64)
f := float64(n)
if err != nil {
f, err = strconv.ParseFloat(val, 64)
}
if err != nil {
p.errorf("error parsing number: %s", err)
}
return f
}
// expectType checks the type of the node and raises an error if it
// is not of the expected type.
func (p *parser) expectType(node Node, want ValueType, context string) {
@ -974,3 +575,27 @@ func (p *parser) newLabelMatcher(label Item, operator Item, value Item) *labels.
return m
}
func (p *parser) addOffset(e Node, offset time.Duration) {
var offsetp *time.Duration
switch s := e.(type) {
case *VectorSelector:
offsetp = &s.Offset
case *MatrixSelector:
offsetp = &s.Offset
case *SubqueryExpr:
offsetp = &s.Offset
default:
p.errorf("offset modifier must be preceded by an instant or range selector, but follows a %T instead", e)
return
}
// it is already ensured by parseDuration func that there never will be a zero offset modifier
if *offsetp != 0 {
p.errorf("offset may not be set multiple times")
} else {
*offsetp = offset
}
}

View file

@ -102,6 +102,9 @@ var testExpr = []struct {
}, {
input: "1 <= bool 1",
expected: &BinaryExpr{LTE, &NumberLiteral{1}, &NumberLiteral{1}, nil, true},
}, {
input: "-1^2",
expected: &BinaryExpr{POW, &NumberLiteral{-1}, &NumberLiteral{2}, nil, false},
}, {
input: "+1 + -2 * 1",
expected: &BinaryExpr{
@ -171,7 +174,7 @@ var testExpr = []struct {
}, {
input: "1+",
fail: true,
errMsg: "no valid expression found",
errMsg: "unexpected end of input",
}, {
input: ".",
fail: true,
@ -179,11 +182,11 @@ var testExpr = []struct {
}, {
input: "2.5.",
fail: true,
errMsg: "could not parse remaining input \".\"...",
errMsg: "unexpected character: '.'",
}, {
input: "100..4",
fail: true,
errMsg: "could not parse remaining input \".4\"...",
errMsg: `unexpected number ".4"`,
}, {
input: "0deadbeef",
fail: true,
@ -191,15 +194,15 @@ var testExpr = []struct {
}, {
input: "1 /",
fail: true,
errMsg: "no valid expression found",
errMsg: "unexpected end of input",
}, {
input: "*1",
fail: true,
errMsg: "no valid expression found",
errMsg: "unexpected <op:*>",
}, {
input: "(1))",
fail: true,
errMsg: "could not parse remaining input \")\"...",
errMsg: "unexpected \")\"",
}, {
input: "((1)",
fail: true,
@ -231,11 +234,11 @@ var testExpr = []struct {
}, {
input: "1 !~ 1",
fail: true,
errMsg: "could not parse remaining input \"!~ 1\"...",
errMsg: `unexpected character after '!': '~'`,
}, {
input: "1 =~ 1",
fail: true,
errMsg: "could not parse remaining input \"=~ 1\"...",
errMsg: `unexpected character after '=': '~'`,
}, {
input: `-"string"`,
fail: true,
@ -247,15 +250,19 @@ var testExpr = []struct {
}, {
input: `*test`,
fail: true,
errMsg: "no valid expression found",
errMsg: "unexpected <op:*>",
}, {
input: "1 offset 1d",
fail: true,
errMsg: "offset modifier must be preceded by an instant or range selector",
}, {
input: "foo offset 1s offset 2s",
fail: true,
errMsg: "offset may not be set multiple times",
}, {
input: "a - on(b) ignoring(c) d",
fail: true,
errMsg: "1:11: parse error: no valid expression found",
errMsg: "1:11: parse error: unexpected <ignoring>",
},
// Vector binary operations.
{
@ -779,6 +786,10 @@ var testExpr = []struct {
input: "foo == on(bar) 10",
fail: true,
errMsg: "vector matching only allowed between instant vectors",
}, {
input: "foo + group_left(baz) bar",
fail: true,
errMsg: "unexpected <group_left>",
}, {
input: "foo and on(bar) group_left(baz) bar",
fail: true,
@ -910,7 +921,7 @@ var testExpr = []struct {
}, {
input: `some}`,
fail: true,
errMsg: "could not parse remaining input \"}\"...",
errMsg: "unexpected character: '}'",
}, {
input: `some_metric{a=b}`,
fail: true,
@ -944,7 +955,7 @@ var testExpr = []struct {
}, {
input: `{}`,
fail: true,
errMsg: "vector selector must contain label matchers or metric name",
errMsg: "vector selector must contain at least one non-empty matcher",
}, {
input: `{x=""}`,
fail: true,
@ -1086,11 +1097,11 @@ var testExpr = []struct {
}, {
input: `some_metric OFFSET 1m[5m]`,
fail: true,
errMsg: "1:25: parse error: unexpected \"]\" in subquery selector, expected \":\"",
errMsg: "1:25: parse error: no offset modifiers allowed before range",
}, {
input: `(foo + bar)[5m]`,
fail: true,
errMsg: "1:15: parse error: unexpected \"]\" in subquery selector, expected \":\"",
errMsg: "1:15: parse error: ranges only allowed for vector selectors",
},
// Test aggregation.
{
@ -1267,39 +1278,39 @@ var testExpr = []struct {
}, {
input: `sum some_metric by (test)`,
fail: true,
errMsg: "unexpected identifier \"some_metric\" in aggregation, expected \"(\"",
errMsg: "unexpected identifier \"some_metric\" in aggregation",
}, {
input: `sum (some_metric) by test`,
fail: true,
errMsg: "unexpected identifier \"test\" in grouping opts, expected \"(\"",
errMsg: "unexpected identifier \"test\" in grouping opts",
}, {
input: `sum (some_metric) by test`,
fail: true,
errMsg: "unexpected identifier \"test\" in grouping opts, expected \"(\"",
errMsg: "unexpected identifier \"test\" in grouping opts",
}, {
input: `sum () by (test)`,
fail: true,
errMsg: "no valid expression found",
errMsg: "no arguments for aggregate expression provided",
}, {
input: "MIN keep_common (some_metric)",
fail: true,
errMsg: "1:5: parse error: unexpected identifier \"keep_common\" in aggregation, expected \"(\"",
errMsg: "1:5: parse error: unexpected identifier \"keep_common\" in aggregation",
}, {
input: "MIN (some_metric) keep_common",
fail: true,
errMsg: "could not parse remaining input \"keep_common\"...",
errMsg: `unexpected identifier "keep_common"`,
}, {
input: `sum (some_metric) without (test) by (test)`,
fail: true,
errMsg: "could not parse remaining input \"by (test)\"...",
errMsg: "unexpected <by>",
}, {
input: `sum without (test) (some_metric) by (test)`,
fail: true,
errMsg: "could not parse remaining input \"by (test)\"...",
errMsg: "unexpected <by>",
}, {
input: `topk(some_metric)`,
fail: true,
errMsg: "1:17: parse error: unexpected \")\" in aggregation, expected \",\"",
errMsg: "wrong number of arguments for aggregate expression provided, expected 2, got 1",
}, {
input: `topk(some_metric, other_metric)`,
fail: true,
@ -1314,6 +1325,7 @@ var testExpr = []struct {
input: "time()",
expected: &Call{
Func: mustGetFunction("time"),
Args: Expressions{},
},
}, {
input: `floor(some_metric{foo!="bar"})`,
@ -1399,15 +1411,15 @@ var testExpr = []struct {
{
input: "-=",
fail: true,
errMsg: `no valid expression found`,
errMsg: `unexpected "="`,
}, {
input: "++-++-+-+-<",
fail: true,
errMsg: `no valid expression found`,
errMsg: `unexpected <op:<>`,
}, {
input: "e-+=/(0)",
fail: true,
errMsg: `no valid expression found`,
errMsg: `unexpected "="`,
},
// String quoting and escape sequence interpretation tests.
{
@ -1443,7 +1455,7 @@ var testExpr = []struct {
}, {
input: "`\\``",
fail: true,
errMsg: "could not parse remaining input",
errMsg: "unterminated raw string",
}, {
input: `"\`,
fail: true,
@ -1651,7 +1663,7 @@ var testExpr = []struct {
}, {
input: `(foo + bar{nm="val"})[5m:][10m:5s]`,
fail: true,
errMsg: "1:27: parse error: could not parse remaining input \"[10m:5s]\"...",
errMsg: `1:35: parse error: subquery is only allowed on instant vector, got matrix in "(foo + bar{nm=\"val\"})[5m:][10m:5s]" instead`,
},
}

View file

@ -45,13 +45,13 @@ var scenarios = map[string]struct {
"invalid params from the beginning": {
params: "match[]=-not-a-valid-metric-name",
code: 400,
body: `1:1: parse error: vector selector must contain label matchers or metric name
body: `1:1: parse error: unexpected <op:->
`,
},
"invalid params somewhere in the middle": {
params: "match[]=not-a-valid-metric-name",
code: 400,
body: `1:4: parse error: could not parse remaining input "-a-valid-metric"...
body: `1:4: parse error: unexpected <op:->
`,
},
"test_metric1": {