Skip to content

Commit

Permalink
[query] Match graphite-web Graphite ** to allow for empty segment pat…
Browse files Browse the repository at this point in the history
…h selector to match elements (#3366)
  • Loading branch information
robskillington authored Mar 17, 2021
1 parent 541aba8 commit b0e147d
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 33 deletions.
62 changes: 39 additions & 23 deletions src/query/graphite/graphite/glob.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,47 +90,63 @@ 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
}

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 '}':
Expand All @@ -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 ']':
Expand All @@ -154,29 +170,29 @@ 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))
}
default:
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)
}
}

if len(groupStartStack) > 1 {
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
}
33 changes: 23 additions & 10 deletions src/query/graphite/graphite/glob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down

0 comments on commit b0e147d

Please sign in to comment.