From b0e147d5aedcd06bda81a80e2abf2453a621ef37 Mon Sep 17 00:00:00 2001 From: Rob Skillington Date: Wed, 17 Mar 2021 15:46:45 -0400 Subject: [PATCH] [query] Match graphite-web Graphite ** to allow for empty segment path selector to match elements (#3366) --- src/query/graphite/graphite/glob.go | 62 +++++++++++++++--------- src/query/graphite/graphite/glob_test.go | 33 +++++++++---- 2 files changed, 62 insertions(+), 33 deletions(-) diff --git a/src/query/graphite/graphite/glob.go b/src/query/graphite/graphite/glob.go index 99b5aaa0c5..6cbf723482 100644 --- a/src/query/graphite/graphite/glob.go +++ b/src/query/graphite/graphite/glob.go @@ -90,18 +90,24 @@ func (p *pattern) UnwriteLast() { func globToRegexPattern(glob string, opts GlobOptions) ([]byte, bool, error) { var ( - pattern pattern - escaping = false - regexed = false + p pattern + escaping bool + regexed bool + matchAll bool + prevMatchAll bool ) groupStartStack := []rune{rune(0)} // rune(0) indicates pattern is not in a group for i, r := range glob { - prevEval := pattern.LastEvaluate() - pattern.Evaluate(r) + // Evaluate if last was a matchAll statement and reset current matchAll. + prevMatchAll = matchAll + matchAll = false + + prevEval := p.LastEvaluate() + p.Evaluate(r) if escaping { - pattern.WriteRune(r) + p.WriteRune(r) escaping = false continue } @@ -109,28 +115,38 @@ func globToRegexPattern(glob string, opts GlobOptions) ([]byte, bool, error) { switch r { case '\\': escaping = true - pattern.WriteRune('\\') + p.WriteRune('\\') case '.': - // Match hierarchy separator - pattern.WriteString("\\.+") - regexed = true + // Check that previous rune was not the match all statement + // since we need to skip adding a trailing dot to pattern if so. + // It's meant to match zero or more segment separators. + // e.g. "foo.**.bar.baz" glob should match: + // - foo.term.bar.baz + // - foo.term1.term2.bar.baz + // - foo.bar.baz + if !prevMatchAll { + // Match hierarchy separator + p.WriteString("\\.+") + regexed = true + } case '?': // Match anything except the hierarchy separator - pattern.WriteString("[^\\.]") + p.WriteString("[^\\.]") regexed = true case '*': if opts.AllowMatchAll && prevEval == '*' { - pattern.UnwriteLast() - pattern.WriteString(".*") + p.UnwriteLast() + p.WriteString(".*") regexed = true + matchAll = true } else { // Match everything up to the next hierarchy separator - pattern.WriteString("[^\\.]*") + p.WriteString("[^\\.]*") regexed = true } case '{': // Begin non-capturing group - pattern.WriteString("(") + p.WriteString("(") groupStartStack = append(groupStartStack, r) regexed = true case '}': @@ -140,11 +156,11 @@ func globToRegexPattern(glob string, opts GlobOptions) ([]byte, bool, error) { return nil, false, errors.NewInvalidParamsError(fmt.Errorf("invalid '}' at %d, no prior for '{' in %s", i, glob)) } - pattern.WriteRune(')') + p.WriteRune(')') groupStartStack = groupStartStack[:len(groupStartStack)-1] case '[': // Begin character range - pattern.WriteRune('[') + p.WriteRune('[') groupStartStack = append(groupStartStack, r) regexed = true case ']': @@ -154,15 +170,15 @@ func globToRegexPattern(glob string, opts GlobOptions) ([]byte, bool, error) { return nil, false, errors.NewInvalidParamsError(fmt.Errorf("invalid ']' at %d, no prior for '[' in %s", i, glob)) } - pattern.WriteRune(']') + p.WriteRune(']') groupStartStack = groupStartStack[:len(groupStartStack)-1] case '<', '>', '\'', '$': - pattern.WriteRune('\\') - pattern.WriteRune(r) + p.WriteRune('\\') + p.WriteRune(r) case ',': // Commas are part of the pattern if they appear in a group if groupStartStack[len(groupStartStack)-1] == '{' { - pattern.WriteRune('|') + p.WriteRune('|') } else { return nil, false, errors.NewInvalidParamsError(fmt.Errorf("invalid ',' outside of matching group at pos %d in %s", i, glob)) } @@ -170,7 +186,7 @@ func globToRegexPattern(glob string, opts GlobOptions) ([]byte, bool, error) { if !strings.ContainsRune(ValidIdentifierRunes, r) { return nil, false, errors.NewInvalidParamsError(fmt.Errorf("invalid character %c at pos %d in %s", r, i, glob)) } - pattern.WriteRune(r) + p.WriteRune(r) } } @@ -178,5 +194,5 @@ func globToRegexPattern(glob string, opts GlobOptions) ([]byte, bool, error) { return nil, false, errors.NewInvalidParamsError(fmt.Errorf("unbalanced '%c' in %s", groupStartStack[len(groupStartStack)-1], glob)) } - return pattern.buff.Bytes(), regexed, nil + return p.buff.Bytes(), regexed, nil } diff --git a/src/query/graphite/graphite/glob_test.go b/src/query/graphite/graphite/glob_test.go index 3472c58891..87a614b246 100644 --- a/src/query/graphite/graphite/glob_test.go +++ b/src/query/graphite/graphite/glob_test.go @@ -33,50 +33,63 @@ func TestGlobToRegexPattern(t *testing.T) { tests := []struct { glob string isRegex bool - regex []byte + regex string + opts GlobOptions }{ { glob: "barbaz", isRegex: false, - regex: []byte("barbaz"), + regex: "barbaz", }, { glob: "barbaz:quxqaz", isRegex: false, - regex: []byte("barbaz:quxqaz"), + regex: "barbaz:quxqaz", }, { glob: "foo\\+bar.'baz<1001>'.qux", isRegex: true, - regex: []byte("foo\\+bar\\.+\\'baz\\<1001\\>\\'\\.+qux"), + regex: "foo\\+bar\\.+\\'baz\\<1001\\>\\'\\.+qux", }, { glob: "foo.host.me{1,2,3}.*", isRegex: true, - regex: []byte("foo\\.+host\\.+me(1|2|3)\\.+[^\\.]*"), + regex: "foo\\.+host\\.+me(1|2|3)\\.+[^\\.]*", }, { glob: "bar.zed.whatever[0-9].*.*.bar", isRegex: true, - regex: []byte("bar\\.+zed\\.+whatever[0-9]\\.+[^\\.]*\\.+[^\\.]*\\.+bar"), + regex: "bar\\.+zed\\.+whatever[0-9]\\.+[^\\.]*\\.+[^\\.]*\\.+bar", }, { glob: "foo{0[3-9],1[0-9],20}", isRegex: true, - regex: []byte("foo(0[3-9]|1[0-9]|20)"), + regex: "foo(0[3-9]|1[0-9]|20)", }, { glob: "foo{0[3-9],1[0-9],20}:bar", isRegex: true, - regex: []byte("foo(0[3-9]|1[0-9]|20):bar"), + regex: "foo(0[3-9]|1[0-9]|20):bar", + }, + { + glob: "foo.**.bar.baz", + isRegex: true, + regex: "foo\\.+.*bar\\.+baz", + opts: GlobOptions{AllowMatchAll: true}, + }, + { + glob: "foo**bar.baz", + isRegex: true, + regex: "foo.*bar\\.+baz", + opts: GlobOptions{AllowMatchAll: true}, }, } for _, test := range tests { - pattern, isRegex, err := GlobToRegexPattern(test.glob) + pattern, isRegex, err := ExtendedGlobToRegexPattern(test.glob, test.opts) require.NoError(t, err) assert.Equal(t, test.isRegex, isRegex) - assert.Equal(t, test.regex, pattern, "bad pattern for %s", test.glob) + assert.Equal(t, test.regex, string(pattern), "bad pattern for %s", test.glob) } }