Skip to content

Commit 20ea556

Browse files
authored
fix(query-engine): Fix OR operations being silently dropped (#21204)
1 parent 8f2840b commit 20ea556

2 files changed

Lines changed: 105 additions & 14 deletions

File tree

‎pkg/engine/internal/planner/logical/planner.go‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,18 +530,29 @@ func convertMatcherType(t labels.MatchType) types.BinaryOp {
530530
}
531531

532532
func convertLineFilterExpr(expr *syntax.LineFilterExpr) Value {
533+
current := convertLineFilter(expr.LineFilter)
534+
535+
if expr.Or != nil {
536+
current = &BinOp{
537+
Left: current,
538+
Right: convertLineFilterExpr(expr.Or),
539+
Op: types.BinaryOpOr,
540+
}
541+
}
542+
533543
if expr.Left != nil {
534544
op := types.BinaryOpAnd
535545
if expr.IsOrChild {
536546
op = types.BinaryOpOr
537547
}
538548
return &BinOp{
539549
Left: convertLineFilterExpr(expr.Left),
540-
Right: convertLineFilter(expr.LineFilter),
550+
Right: current,
541551
Op: op,
542552
}
543553
}
544-
return convertLineFilter(expr.LineFilter)
554+
555+
return current
545556
}
546557

547558
func convertLineFilter(filter syntax.LineFilter) Value {

‎pkg/engine/internal/planner/logical/planner_test.go‎

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -102,19 +102,21 @@ func TestConvertAST_Success(t *testing.T) {
102102
%6 = OR %4 %5
103103
%7 = MATCH_STR builtin.message "metric.go"
104104
%8 = MATCH_STR builtin.message "foo"
105-
%9 = AND %7 %8
106-
%10 = NOT_MATCH_RE builtin.message "(a|b|c)"
107-
%11 = AND %9 %10
108-
%12 = MAKETABLE [selector=%3, predicates=[%6, %11], shard=0_of_1]
109-
%13 = GTE builtin.timestamp 1970-01-01T01:00:00Z
110-
%14 = SELECT %12 [predicate=%13]
111-
%15 = LT builtin.timestamp 1970-01-01T02:00:00Z
105+
%9 = MATCH_STR builtin.message "bar"
106+
%10 = OR %8 %9
107+
%11 = AND %7 %10
108+
%12 = NOT_MATCH_RE builtin.message "(a|b|c)"
109+
%13 = AND %11 %12
110+
%14 = MAKETABLE [selector=%3, predicates=[%6, %13], shard=0_of_1]
111+
%15 = GTE builtin.timestamp 1970-01-01T01:00:00Z
112112
%16 = SELECT %14 [predicate=%15]
113-
%17 = SELECT %16 [predicate=%6]
114-
%18 = SELECT %17 [predicate=%11]
115-
%19 = TOPK %18 [sort_by=builtin.timestamp, k=1000, asc=false, nulls_first=false]
116-
%20 = LOGQL_COMPAT %19
117-
RETURN %20
113+
%17 = LT builtin.timestamp 1970-01-01T02:00:00Z
114+
%18 = SELECT %16 [predicate=%17]
115+
%19 = SELECT %18 [predicate=%6]
116+
%20 = SELECT %19 [predicate=%13]
117+
%21 = TOPK %20 [sort_by=builtin.timestamp, k=1000, asc=false, nulls_first=false]
118+
%22 = LOGQL_COMPAT %21
119+
RETURN %22
118120
`
119121

120122
require.Equal(t, expected, logicalPlan.String())
@@ -125,6 +127,84 @@ RETURN %20
125127
t.Logf("\n%s\n", sb.String())
126128
}
127129

130+
func TestConvertAST_LineFilterOr(t *testing.T) {
131+
tests := []struct {
132+
name string
133+
query string
134+
contains []string
135+
}{
136+
{
137+
name: "simple or",
138+
query: `{app="loki"} |= "foo" or "bar"`,
139+
contains: []string{
140+
`MATCH_STR builtin.message "foo"`,
141+
`MATCH_STR builtin.message "bar"`,
142+
"OR %",
143+
},
144+
},
145+
{
146+
name: "or with regex",
147+
query: `{app="loki"} |~ "(?i)abc" or "(?i)def"`,
148+
contains: []string{
149+
`MATCH_RE builtin.message "(?i)abc"`,
150+
`MATCH_RE builtin.message "(?i)def"`,
151+
"OR %",
152+
},
153+
},
154+
{
155+
name: "three-way or",
156+
query: `{app="loki"} |= "a" or "b" or "c"`,
157+
contains: []string{
158+
`MATCH_STR builtin.message "a"`,
159+
`MATCH_STR builtin.message "b"`,
160+
`MATCH_STR builtin.message "c"`,
161+
"OR %",
162+
},
163+
},
164+
{
165+
name: "or preceded by and",
166+
query: `{app="loki"} |= "first" |= "foo" or "bar"`,
167+
contains: []string{
168+
`MATCH_STR builtin.message "first"`,
169+
`MATCH_STR builtin.message "foo"`,
170+
`MATCH_STR builtin.message "bar"`,
171+
"OR %",
172+
"AND %",
173+
},
174+
},
175+
{
176+
name: "negative filter or becomes and",
177+
query: `{app="loki"} != "foo" or "bar"`,
178+
contains: []string{
179+
`NOT_MATCH_STR builtin.message "foo"`,
180+
`NOT_MATCH_STR builtin.message "bar"`,
181+
"AND %",
182+
},
183+
},
184+
}
185+
186+
for _, tc := range tests {
187+
t.Run(tc.name, func(t *testing.T) {
188+
q := &query{
189+
statement: tc.query,
190+
start: 3600,
191+
end: 7200,
192+
direction: logproto.BACKWARD,
193+
limit: 1000,
194+
}
195+
plan, err := BuildPlan(context.Background(), q)
196+
require.NoError(t, err)
197+
198+
planStr := plan.String()
199+
t.Logf("\n%s\n", planStr)
200+
201+
for _, substr := range tc.contains {
202+
require.Contains(t, planStr, substr, "plan should contain %q", substr)
203+
}
204+
})
205+
}
206+
}
207+
128208
func TestConvertAST_MetricQuery_Success(t *testing.T) {
129209
t.Run("simple metric query", func(t *testing.T) {
130210
q := &query{

0 commit comments

Comments
 (0)