diff --git a/pkg/extpromql/parser.go b/pkg/extpromql/parser.go index 5284e5e9f3..1a43fd8a05 100644 --- a/pkg/extpromql/parser.go +++ b/pkg/extpromql/parser.go @@ -5,7 +5,9 @@ package extpromql import ( "fmt" + "strings" + "github.com/pkg/errors" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/promql/parser" @@ -22,23 +24,22 @@ func ParseExpr(input string) (parser.Expr, error) { // ParseMetricSelector parses the provided textual metric selector into a list of // label matchers. func ParseMetricSelector(input string) ([]*labels.Matcher, error) { - // Parse the input string as a PromQL expression. expr, err := ParseExpr(input) - if err != nil { + // because of the AST checking present in the ParseExpr function, + // we need to ignore the error if it is just the check for empty name matcher. + if err != nil && !isEmptyNameMatcherErr(err) { return nil, err } - // The type of the expression should be *parser.VectorSelector. vs, ok := expr.(*parser.VectorSelector) if !ok { return nil, fmt.Errorf("expected type *parser.VectorSelector, got %T", expr) } - // Convert the label matchers from the vector selector to the desired type. matchers := make([]*labels.Matcher, len(vs.LabelMatchers)) for i, lm := range vs.LabelMatchers { matchers[i] = &labels.Matcher{ - Type: labels.MatchType(lm.Type), + Type: lm.Type, Name: lm.Name, Value: lm.Value, } @@ -46,3 +47,13 @@ func ParseMetricSelector(input string) ([]*labels.Matcher, error) { return matchers, nil } + +func isEmptyNameMatcherErr(err error) bool { + var parseErrs parser.ParseErrors + if errors.As(err, &parseErrs) { + return len(parseErrs) == 1 && + strings.HasSuffix(parseErrs[0].Error(), "vector selector must contain at least one non-empty matcher") + } + + return false +} diff --git a/pkg/extpromql/parser_test.go b/pkg/extpromql/parser_test.go index 1c0cfebdcd..72ef7b26cb 100644 --- a/pkg/extpromql/parser_test.go +++ b/pkg/extpromql/parser_test.go @@ -39,6 +39,10 @@ func TestParseMetricSelector(t *testing.T) { name: "multiple selectors with regex", input: `http_requests_total{method="GET",code=~"2.*"}`, }, + { + name: "selector with negative regex", + input: `{code!~"2.*"}`, + }, } for _, tc := range testCases { diff --git a/pkg/store/storepb/custom_test.go b/pkg/store/storepb/custom_test.go index 91e2a1a669..4a1c6d83c2 100644 --- a/pkg/store/storepb/custom_test.go +++ b/pkg/store/storepb/custom_test.go @@ -15,6 +15,7 @@ import ( "github.com/prometheus/prometheus/tsdb/chunkenc" "github.com/efficientgo/core/testutil" + "github.com/thanos-io/thanos/pkg/extpromql" "github.com/thanos-io/thanos/pkg/store/labelpb" ) @@ -521,7 +522,7 @@ func TestMatchersToString_Translate(t *testing.T) { // Is this parsable? promMsParsed, err := extpromql.ParseMetricSelector(c.expected) - testutil.Ok(t, err) + testutil.Ok(t, err, "unexpected error parsing %q", c.expected) testutil.Assert(t, len(promMs) == len(promMsParsed)) for i := 0; i < len(promMs); i++ { testutil.Equals(t, promMs[i].String(), promMsParsed[i].String())