Skip to content

Commit 2af52e2

Browse files
committed
Fully & correctly resolve complex filter strings
1 parent 1271c62 commit 2af52e2

File tree

4 files changed

+197
-95
lines changed

4 files changed

+197
-95
lines changed

internal/filter/parser.go

Lines changed: 91 additions & 56 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/filter/parser.y

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,60 @@ import "net/url"
1010
// Otherwise, it will pop the last pushed rule of that chain (second argument) and append it to the new *And chain.
1111
//
1212
// Example: `foo=bar|bar~foo&col!~val`
13-
// The second argument `rule` is supposed to be a filter.Any *Chain contains the first two conditions.
13+
// The second argument `left` is supposed to be a filter.Any Chain contains the first two conditions.
1414
// We then call this function when the parser is processing the logical `&` op and the Unlike condition,
1515
// and what this function will do is logically re-group the conditions into `foo=bar|(bar~foo&col!~val)`.
16-
func reduceFilter(op string, rule Filter, rules ...Filter) Filter {
17-
chain, ok := rule.(*Chain);
16+
func reduceFilter(op string, left Filter, right Filter) Filter {
17+
chain, ok := left.(*Chain)
1818
if ok && chain.op == Any && LogicalOp(op) == All {
19-
// Retrieve the last pushed condition and append it to the new "And" chain instead
20-
andChain, _ := NewChain(All, chain.pop())
21-
andChain.add(rules...)
19+
// Retrieve the last pushed filter Condition and append it to the new "And" chain instead
20+
back := chain.pop()
21+
// Chain#pop can return a filter Chain, and since we are only allowed to regroup two filter conditions,
22+
// we must traverse the last element of every single popped Chain till we reach a filter condition.
23+
for back != nil {
24+
if backChain, ok := back.(*Chain); !ok || backChain.grouped {
25+
// If the popped element is not of type filter Chain or the filter chain is parenthesized,
26+
// we don't need to continue here, so break out of the loop.
27+
break
28+
}
29+
30+
// Re-add the just popped item before stepping into it and popping its last item.
31+
chain.add(back)
32+
33+
chain = back.(*Chain)
34+
back = chain.pop()
35+
}
36+
37+
andChain, _ := NewChain(All, back)
38+
// We don't need to regroup an already grouped filter chain, since braces gain
39+
// a higher precedence than any logical operators.
40+
if anyChain, ok := right.(*Chain); ok && anyChain.op == Any && !chain.grouped && !anyChain.grouped {
41+
andChain.add(anyChain.top())
42+
// Prepend the newly created All chain
43+
anyChain.rules = append([]Filter{andChain}, anyChain.rules...)
2244

23-
chain.add(andChain)
45+
chain.add(anyChain)
46+
} else {
47+
andChain.add(right)
48+
chain.add(andChain)
49+
}
2450

25-
return chain
51+
return left
2652
}
2753

28-
// If the given operator is the same as the already existsing chains operator (*chain),
54+
// If the given operator is the same as the already existing chains operator (*chain),
2955
// we don't need to create another chain of the same operator type. Avoids something
3056
// like &Chain{op: All, &Chain{op: All, ...}}
3157
if chain == nil || chain.op != LogicalOp(op) {
32-
newChain, err := NewChain(LogicalOp(op), rule)
33-
if err != nil {
34-
// Just panic, filter.Parse will try to recover from this.
35-
panic(err)
36-
}
37-
38-
chain = newChain
58+
var err error
59+
chain, err = NewChain(LogicalOp(op), left)
60+
if err != nil {
61+
// Just panic, filter.Parse will try to recover from this.
62+
panic(err)
63+
}
3964
}
4065

41-
chain.add(rules...)
66+
chain.add(right)
4267

4368
return chain
4469
}
@@ -93,10 +118,15 @@ filter_rule: filter_chain logical_op filter_chain
93118
$$ = reduceFilter($2, $1, $3)
94119
yylex.(*Lexer).rule = $$
95120
}
96-
| filter_chain
121+
| filter_chain %prec PREFER_SHIFTING_LOGICAL_OP
97122
{
98123
yylex.(*Lexer).rule = $$
99124
}
125+
| filter_rule logical_op filter_chain
126+
{
127+
$$ = reduceFilter($2, $1, $3)
128+
yylex.(*Lexer).rule = $$
129+
}
100130
;
101131

102132
filter_chain: conditions_expr logical_op maybe_negated_condition_expr
@@ -128,6 +158,9 @@ maybe_negated_condition_expr: optional_negation condition_expr
128158
condition_expr: "(" filter_rule ")"
129159
{
130160
$$ = $2
161+
if chain, ok := $$.(*Chain); ok {
162+
chain.grouped = true
163+
}
131164
}
132165
| identifier comparison_op identifier
133166
{

0 commit comments

Comments
 (0)