diff --git a/lib/logstorage/parser.go b/lib/logstorage/parser.go index 29a05dff5..05e183c9e 100644 --- a/lib/logstorage/parser.go +++ b/lib/logstorage/parser.go @@ -714,13 +714,6 @@ func ParseQuery(s string) (*Query, error) { func ParseQueryAtTimestamp(s string, timestamp int64) (*Query, error) { lex := newLexerAtTimestamp(s, timestamp) - // Verify the first token doesn't match pipe names. - firstToken := strings.ToLower(lex.rawToken) - if _, ok := pipeNames[firstToken]; ok { - return nil, fmt.Errorf("the query [%s] cannot start with pipe - it must start with mandatory filter; see https://docs.victoriametrics.com/victorialogs/logsql/#query-syntax; "+ - "if the filter isn't missing, then please put the first word of the filter into quotes: %q", s, firstToken) - } - q, err := parseQuery(lex) if err != nil { return nil, err @@ -759,9 +752,17 @@ func parseQuery(lex *lexer) (*Query, error) { } func parseFilter(lex *lexer) (filter, error) { - if lex.isKeyword("|", "") { + if lex.isKeyword("|", ")", "") { return nil, fmt.Errorf("missing query") } + + // Verify the first token in the filter doesn't match pipe names. + firstToken := strings.ToLower(lex.rawToken) + if _, ok := pipeNames[firstToken]; ok { + return nil, fmt.Errorf("query filter cannot start with pipe keyword %q; see https://docs.victoriametrics.com/victorialogs/logsql/#query-syntax; "+ + "please put the first word of the filter into quotes", firstToken) + } + fo, err := parseFilterOr(lex, "") if err != nil { return nil, err diff --git a/lib/logstorage/parser_test.go b/lib/logstorage/parser_test.go index 77e46ca46..7b22fa44b 100644 --- a/lib/logstorage/parser_test.go +++ b/lib/logstorage/parser_test.go @@ -1196,6 +1196,11 @@ func TestParseQuerySuccess(t *testing.T) { // filter pipe f(`* | filter error ip:12.3.4.5 or warn`, `* | filter error ip:12.3.4.5 or warn`) f(`foo | stats by (host) count() logs | filter logs:>50 | sort by (logs desc) | limit 10`, `foo | stats by (host) count(*) as logs | filter logs:>50 | sort by (logs desc) | limit 10`) + f(`* | error`, `* | filter error`) + f(`* | "by"`, `* | filter "by"`) + f(`* | "stats"`, `* | filter "stats"`) + f(`* | "count"`, `* | filter "count"`) + f(`* | foo:bar AND baz:<10`, `* | filter foo:bar baz:<10`) // extract pipe f(`* | extract "foobaz"`, `* | extract "foobaz"`) @@ -1237,7 +1242,7 @@ func TestParseQueryFailure(t *testing.T) { t.Helper() q, err := ParseQuery(s) if q != nil { - t.Fatalf("expecting nil result; got %s", q) + t.Fatalf("expecting nil result; got [%s]", q) } if err == nil { t.Fatalf("expecting non-nil error") @@ -1635,6 +1640,9 @@ func TestParseQueryFailure(t *testing.T) { // stats result names identical to by fields f(`foo | stats by (x) count() x`) + // missing stats function + f(`foo | by (bar)`) + // invalid sort pipe f(`foo | sort bar`) f(`foo | sort by`) @@ -1669,6 +1677,14 @@ func TestParseQueryFailure(t *testing.T) { f(`foo | filter (`) f(`foo | filter )`) + f(`foo | filter stats`) + f(`foo | filter fields`) + f(`foo | filter by`) + f(`foo | count`) + f(`foo | filter count`) + f(`foo | (`) + f(`foo | )`) + // invalid extract pipe f(`foo | extract`) f(`foo | extract bar`)