diff --git a/internal/logql/logqlengine/logqlpattern/logqlpattern.go b/internal/logql/logqlengine/logqlpattern/logqlpattern.go index d5a2ddab..07ea6d3a 100644 --- a/internal/logql/logqlengine/logqlpattern/logqlpattern.go +++ b/internal/logql/logqlengine/logqlpattern/logqlpattern.go @@ -46,7 +46,16 @@ const ( LineFilterFlags = DisallowNamed ) -// Parse parses pattern. +// MustParse is like [Parse] but panics if the expression cannot be parsed. +func MustParse(input string, flags ParseFlags) Pattern { + p, err := Parse(input, flags) + if err != nil { + panic(err) + } + return p +} + +// Parse parses LogQL pattern. func Parse(input string, flags ParseFlags) (p Pattern, _ error) { if !utf8.ValidString(input) { return p, errors.New("pattern is invalid UTF-8") diff --git a/internal/logql/logqlengine/logqlpattern/match.go b/internal/logql/logqlengine/logqlpattern/match.go index bc05a3f5..32cf2e06 100644 --- a/internal/logql/logqlengine/logqlpattern/match.go +++ b/internal/logql/logqlengine/logqlpattern/match.go @@ -1,13 +1,9 @@ package logqlpattern -import ( - "strings" - - "github.com/go-faster/oteldb/internal/logql" -) +import "strings" // Match matches given pattern against input string. -func Match(p Pattern, input string, match func(label logql.Label, value string)) bool { +func Match[Label ~string](p Pattern, input string, match func(label Label, value string)) bool { parts := p.Parts if len(parts) == 0 && input == "" { return true @@ -24,7 +20,7 @@ func Match(p Pattern, input string, match func(label logql.Label, value string)) } case Capture: var ( - label = logql.Label(part.Value) + label = Label(part.Value) value string ) if i+1 < len(parts) { diff --git a/internal/logql/logqlengine/logqlpattern/match_test.go b/internal/logql/logqlengine/logqlpattern/match_test.go index a6d450f7..1aa85fab 100644 --- a/internal/logql/logqlengine/logqlpattern/match_test.go +++ b/internal/logql/logqlengine/logqlpattern/match_test.go @@ -5,8 +5,6 @@ import ( "testing" "github.com/stretchr/testify/require" - - "github.com/go-faster/oteldb/internal/logql" ) var matchTests = []struct { @@ -145,8 +143,8 @@ func TestMatch(t *testing.T) { require.NoError(t, err) matches := map[string]string{} - fullMatch := Match(compiled, tt.input, func(label logql.Label, value string) { - matches[string(label)] = value + fullMatch := Match(compiled, tt.input, func(label, value string) { + matches[label] = value }) require.Equal(t, tt.match, matches) require.Equal(t, tt.full, fullMatch) @@ -164,6 +162,6 @@ func FuzzMatch(f *testing.F) { t.Skipf("Invalid pattern %q: %+v", pattern, err) return } - Match(compiled, input, func(logql.Label, string) {}) + Match(compiled, input, func(string, string) {}) }) } diff --git a/internal/logql/logqlengine/pattern.go b/internal/logql/logqlengine/pattern.go index f0c4041b..9979fa83 100644 --- a/internal/logql/logqlengine/pattern.go +++ b/internal/logql/logqlengine/pattern.go @@ -1,7 +1,6 @@ package logqlengine import ( - "github.com/go-faster/errors" "go.opentelemetry.io/collector/pdata/pcommon" "github.com/go-faster/oteldb/internal/logql" @@ -16,14 +15,7 @@ type PatternExtractor struct { } func buildPatternExtractor(stage *logql.PatternLabelParser) (Processor, error) { - compiled, err := logqlpattern.Parse(stage.Pattern, logqlpattern.ExtractorFlags) - if err != nil { - return nil, errors.Wrapf(err, "parse pattern %q", stage.Pattern) - } - - return &PatternExtractor{ - pattern: compiled, - }, nil + return &PatternExtractor{pattern: stage.Pattern}, nil } // Process implements Processor. diff --git a/internal/logql/parser_pipeline.go b/internal/logql/parser_pipeline.go index a58fb22b..571b3327 100644 --- a/internal/logql/parser_pipeline.go +++ b/internal/logql/parser_pipeline.go @@ -6,6 +6,7 @@ import ( "github.com/go-faster/errors" "github.com/go-faster/oteldb/internal/logql/lexer" + "github.com/go-faster/oteldb/internal/logql/logqlengine/logqlpattern" ) func (p *parser) parsePipeline(allowUnwrap bool) (stages []PipelineStage, err error) { @@ -49,12 +50,18 @@ func (p *parser) parsePipeline(allowUnwrap bool) (stages []PipelineStage, err er } stages = append(stages, p) case lexer.Pattern: - pattern, err := p.parseString() + pattern, patternTok, err := p.consumeText(lexer.String) if err != nil { return stages, err } - // FIXME(tdakkota): parse pattern? - stages = append(stages, &PatternLabelParser{Pattern: pattern}) + compiled, err := logqlpattern.Parse(pattern, logqlpattern.ExtractorFlags) + if err != nil { + return nil, &ParseError{ + Pos: patternTok.Pos, + Err: errors.Wrap(err, "pattern"), + } + } + stages = append(stages, &PatternLabelParser{Pattern: compiled}) case lexer.Unpack: stages = append(stages, &UnpackLabelParser{}) case lexer.LineFormat: diff --git a/internal/logql/parser_test.go b/internal/logql/parser_test.go index e8aecdbf..1d772778 100644 --- a/internal/logql/parser_test.go +++ b/internal/logql/parser_test.go @@ -7,6 +7,8 @@ import ( "time" "github.com/stretchr/testify/require" + + "github.com/go-faster/oteldb/internal/logql/logqlengine/logqlpattern" ) func ptrTo[T any](v T) *T { @@ -226,7 +228,7 @@ var tests = []TestCase{ 1: "method", }, }, - &PatternLabelParser{Pattern: ""}, + &PatternLabelParser{Pattern: logqlpattern.MustParse("", logqlpattern.ExtractorFlags)}, &UnpackLabelParser{}, &LineFormat{Template: "{{ . }}"}, &DecolorizeExpr{}, @@ -1177,6 +1179,7 @@ var tests = []TestCase{ {`{foo = "bar"} | label_format status=foo,status=bar`, nil, true}, // Invalid regexp. + // {`{foo=~"\\"}`, nil, true}, {`{} |~ "\\"`, nil, true}, {`{} |~ ".+" or "\\"`, nil, true}, @@ -1187,6 +1190,15 @@ var tests = []TestCase{ {`{} | regexp "(?P\\w+)(?P\\w+)"`, nil, true}, // Invalid capture name. {`{} | regexp "(?P<0a>\\w+)"`, nil, true}, + + // Invalid pattern. + // + // No capture. + {`{} | pattern "a"`, nil, true}, + // Duplicate capture. + {`{} | pattern " foo "`, nil, true}, + // Consecutive capture. + {`{} | pattern ""`, nil, true}, } func TestParse(t *testing.T) { diff --git a/internal/logql/pipeline.go b/internal/logql/pipeline.go index a3745212..db658e83 100644 --- a/internal/logql/pipeline.go +++ b/internal/logql/pipeline.go @@ -8,6 +8,8 @@ import ( "time" "github.com/dustin/go-humanize" + + "github.com/go-faster/oteldb/internal/logql/logqlengine/logqlpattern" ) // PipelineStage is a LogQL pipeline stage. @@ -144,7 +146,7 @@ type RegexpLabelParser struct { // // See https://grafana.com/docs/loki/latest/logql/log_queries/#pattern. type PatternLabelParser struct { - Pattern string + Pattern logqlpattern.Pattern } // UnpackLabelParser unpacks data from promtail.