Skip to content

Commit 3e221a8

Browse files
mayor improvements to tests and optimize
1 parent bf24522 commit 3e221a8

4 files changed

Lines changed: 411 additions & 93 deletions

File tree

tags3/bun_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ var bunTests = []bunTest{
3838
// --- Complex Nesting ---
3939
{"(aws|gcp)&!legacy", "SELECT * WHERE ((tags LIKE '%|aws|%' ESCAPE '!') OR (tags LIKE '%|gcp|%' ESCAPE '!')) AND (tags NOT LIKE '%|legacy|%' ESCAPE '!')"},
4040
{"auth&(**-admin|super-**)", "SELECT * WHERE (tags LIKE '%|auth|%' ESCAPE '!') AND ((tags LIKE '%|%!-admin|%' ESCAPE '!') OR (tags LIKE '%|super!-%|%' ESCAPE '!'))"},
41-
{"!(test|stage)&prod", "SELECT * WHERE ((tags NOT LIKE '%|test|%' ESCAPE '!') AND (tags NOT LIKE '%|stage|%' ESCAPE '!')) AND (tags LIKE '%|prod|%' ESCAPE '!')"},
41+
{"!(test|stage)&prod", "SELECT * WHERE (tags NOT LIKE '%|test|%' ESCAPE '!') AND (tags NOT LIKE '%|stage|%' ESCAPE '!') AND (tags LIKE '%|prod|%' ESCAPE '!')"},
4242

4343
// --- Edge Cases ---
4444
{"**", "SELECT * WHERE (tags LIKE '%|%|%' ESCAPE '!')"},

tags3/expr.go

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package tags3
66
import (
77
"fmt"
88
"regexp"
9+
"slices"
910
"strings"
1011

1112
"github.com/toeirei/keymaster/util/slicest"
@@ -25,6 +26,7 @@ const (
2526
type Expr interface {
2627
fmt.Stringer
2728
Eval(tags Tags) bool
29+
hash() string
2830
Optimize() Expr
2931
applyToBunQuery(qb bun.QueryBuilder, column string, mode bunMode) bun.QueryBuilder
3032
}
@@ -44,8 +46,6 @@ var _ Expr = AndExpr{}
4446
var _ Expr = OrExpr{}
4547
var _ Expr = NotExpr{}
4648

47-
// var _ Expr = BracesExpr{}
48-
4949
// --- [fmt.Stringer] implementations ---
5050

5151
func (e ValueExpr) String() string {
@@ -54,7 +54,7 @@ func (e ValueExpr) String() string {
5454
func (e AndExpr) String() string {
5555
return strings.Join(slicest.Map(e.Exprs, func(e Expr) string {
5656
switch e.(type) {
57-
case OrExpr:
57+
case AndExpr, OrExpr:
5858
return string(exprBracesOpen) + e.String() + string(exprBracesClose)
5959
default:
6060
return e.String()
@@ -101,6 +101,44 @@ func (e NotExpr) Eval(tags Tags) bool {
101101
return !e.Expr.Eval(tags)
102102
}
103103

104+
// --- [Expr.hash] implementations ---
105+
106+
func (e ValueExpr) hash() string {
107+
return e.Value
108+
}
109+
func (e AndExpr) hash() string {
110+
strs := slicest.Map(e.Exprs, func(e Expr) string {
111+
switch e.(type) {
112+
case AndExpr, OrExpr:
113+
return string(exprBracesOpen) + e.hash() + string(exprBracesClose)
114+
default:
115+
return e.hash()
116+
}
117+
})
118+
slices.Sort(strs)
119+
return strings.Join(strs, string(exprAnd))
120+
}
121+
func (e OrExpr) hash() string {
122+
strs := slicest.Map(e.Exprs, func(e Expr) string {
123+
switch e.(type) {
124+
case AndExpr, OrExpr:
125+
return string(exprBracesOpen) + e.String() + string(exprBracesClose)
126+
default:
127+
return e.String()
128+
}
129+
})
130+
slices.Sort(strs)
131+
return strings.Join(strs, string(exprOr))
132+
}
133+
func (e NotExpr) hash() string {
134+
switch e.Expr.(type) {
135+
case AndExpr, OrExpr:
136+
return exprNot + string(exprBracesOpen) + e.Expr.String() + string(exprBracesClose)
137+
default:
138+
return exprNot + e.Expr.String()
139+
}
140+
}
141+
104142
// --- [Expr.Optimize] implementations ---
105143

106144
func (e ValueExpr) Optimize() Expr { return e }
@@ -115,6 +153,28 @@ func (e AndExpr) Optimize() Expr {
115153
return []Expr{expr}
116154
}))
117155

156+
// deduplicate nested expressions
157+
e.Exprs = sliceDeduplicateFunc(e.Exprs, func(expr Expr) string { return expr.hash() })
158+
159+
// remove redundant nested or expressions
160+
e.Exprs = slicest.Filter(e.Exprs, func(expr Expr) bool {
161+
if orExpr, ok := expr.(OrExpr); ok {
162+
// does or expression not contain any expression...
163+
return !slices.ContainsFunc(orExpr.Exprs, func(orSubExpr Expr) bool {
164+
// ... wich is contained in the and expression
165+
return slices.ContainsFunc(e.Exprs, func(andSubExpr Expr) bool {
166+
return orSubExpr.hash() == andSubExpr.hash()
167+
})
168+
})
169+
}
170+
return true
171+
})
172+
173+
// return remaining expression when its the only one
174+
if len(e.Exprs) == 1 {
175+
return e.Exprs[0]
176+
}
177+
118178
return e
119179
}
120180
func (e OrExpr) Optimize() Expr {
@@ -128,6 +188,28 @@ func (e OrExpr) Optimize() Expr {
128188
return []Expr{expr}
129189
}))
130190

191+
// deduplicate nested expressions
192+
e.Exprs = sliceDeduplicateFunc(e.Exprs, func(expr Expr) string { return expr.hash() })
193+
194+
// remove redundant nested and expressions
195+
e.Exprs = slicest.Filter(e.Exprs, func(expr Expr) bool {
196+
if andExpr, ok := expr.(AndExpr); ok {
197+
// does and expression contain any expression...
198+
return !slices.ContainsFunc(andExpr.Exprs, func(andSubExpr Expr) bool {
199+
// ... wich is contained in the or expression
200+
return slices.ContainsFunc(e.Exprs, func(orSubExpr Expr) bool {
201+
return andSubExpr.hash() == orSubExpr.hash()
202+
})
203+
})
204+
}
205+
return true
206+
})
207+
208+
// return remaining expression when its the only one
209+
if len(e.Exprs) == 1 {
210+
return e.Exprs[0]
211+
}
212+
131213
return e
132214
}
133215
func (e NotExpr) Optimize() Expr {

0 commit comments

Comments
 (0)