Skip to content

Commit d08d4dd

Browse files
added optimized concept to tags
1 parent edb303d commit d08d4dd

6 files changed

Lines changed: 234 additions & 22 deletions

File tree

tags/expr.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const (
2525
type Expr interface {
2626
fmt.Stringer
2727
Eval(tags Tags) bool
28+
Optimize() Expr
2829
applyToBunQuery(qb bun.QueryBuilder, column string, mode bunMode, negate bool) bun.QueryBuilder
2930
}
3031

@@ -87,3 +88,59 @@ func (e NotExpr) Eval(tags Tags) bool {
8788
func (e BracesExpr) Eval(tags Tags) bool {
8889
return e.Expr.Eval(tags)
8990
}
91+
92+
// --- [Expr.Optimize] implementations ---
93+
94+
func (e ValueExpr) Optimize() Expr { return e }
95+
func (e AndExpr) Optimize() Expr {
96+
e.Exprs = slicest.Map(e.Exprs, func(expr Expr) Expr { return expr.Optimize() })
97+
98+
// resolve nested and expressions
99+
e.Exprs = slicest.Flatten(slicest.Map(e.Exprs, func(expr Expr) []Expr {
100+
if expr, ok := expr.(BracesExpr); ok {
101+
if subExpr, ok := expr.Expr.(AndExpr); ok {
102+
return subExpr.Exprs
103+
}
104+
}
105+
return []Expr{expr}
106+
}))
107+
108+
return e
109+
}
110+
func (e OrExpr) Optimize() Expr {
111+
e.Exprs = slicest.Map(e.Exprs, func(expr Expr) Expr { return expr.Optimize() })
112+
113+
// resolve nested or expressions
114+
e.Exprs = slicest.Flatten(slicest.Map(e.Exprs, func(expr Expr) []Expr {
115+
if expr, ok := expr.(BracesExpr); ok {
116+
if subExpr, ok := expr.Expr.(OrExpr); ok {
117+
return subExpr.Exprs
118+
}
119+
}
120+
return []Expr{expr}
121+
}))
122+
123+
return e
124+
}
125+
func (e NotExpr) Optimize() Expr {
126+
e.Expr = e.Expr.Optimize()
127+
128+
switch expr := e.Expr.(type) {
129+
case NotExpr:
130+
// Remove double negation
131+
return expr.Expr
132+
default:
133+
return e
134+
}
135+
}
136+
func (e BracesExpr) Optimize() Expr {
137+
e.Expr = e.Expr.Optimize()
138+
139+
switch expr := e.Expr.(type) {
140+
case BracesExpr, NotExpr, ValueExpr:
141+
// Braces not needed for single value expressions
142+
return expr
143+
default:
144+
return e
145+
}
146+
}

tags/expr_test.go

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,24 +90,66 @@ var exprTests = []exprTest{
9090
{tags.Tags{"a"}, true},
9191
{tags.Tags{""}, false},
9292
}},
93+
94+
// --- Optimizeable ---
95+
{"(aws|gcp)|legacy", "(aws | gcp) | legacy", []exprTestCase{
96+
{tags.Tags{"aws", "cloud"}, true},
97+
{tags.Tags{"gcp"}, true},
98+
{tags.Tags{"legacy"}, true},
99+
{tags.Tags{"azure"}, false},
100+
}},
101+
{"(aws&gcp)&legacy", "(aws & gcp) & legacy", []exprTestCase{
102+
{tags.Tags{"aws", "cloud"}, false},
103+
{tags.Tags{"gcp"}, false},
104+
{tags.Tags{"aws", "gcp", "legacy"}, true},
105+
}},
106+
{"auth&!(admin|super)", "auth & !(admin | super)", []exprTestCase{
107+
{tags.Tags{"auth", "sys-admin"}, true},
108+
{tags.Tags{"auth", "super-user"}, true},
109+
{tags.Tags{"auth", "user"}, true},
110+
{tags.Tags{"sys-admin"}, false},
111+
}},
112+
{"!(test&stage)&prod", "!(test & stage) & prod", []exprTestCase{
113+
{tags.Tags{"prod"}, true},
114+
{tags.Tags{"test", "prod"}, true},
115+
{tags.Tags{"stage", "test", "prod"}, false},
116+
{tags.Tags{"stage"}, false},
117+
}},
118+
{"!!prod", "!!prod", []exprTestCase{
119+
{tags.Tags{"prod"}, true},
120+
{tags.Tags{"test"}, false},
121+
}},
93122
}
94123

95-
func TestExpr(t *testing.T) {
124+
func TestExprString(t *testing.T) {
96125
for _, et := range exprTests {
97-
t.Run(fmt.Sprintf("Matcher(%q)", et.matcher), func(t *testing.T) {
126+
t.Run(fmt.Sprintf("Matcher(%q).String()", et.matcher), func(t *testing.T) {
98127
expr, err := tags.ParseMatcher(et.matcher)
99128
if err != nil {
100129
t.Fatalf("failed to parse matcher %q: %v", et.matcher, err)
101130
}
102131

103-
t.Run("String()", func(t *testing.T) {
104-
got := expr.String()
105-
if got != et.rendered {
106-
t.Errorf("got = %q; expected = %q", got, et.rendered)
107-
}
132+
expr = expr.Optimize()
133+
134+
got := expr.String()
135+
if got != et.rendered {
136+
t.Errorf("got = %q; expected = %q", got, et.rendered)
137+
}
138+
139+
t.Log(got)
140+
})
141+
}
142+
}
143+
144+
func TestExprEval(t *testing.T) {
145+
for _, et := range exprTests {
146+
t.Run(fmt.Sprintf("Matcher(%q)", et.matcher), func(t *testing.T) {
147+
expr, err := tags.ParseMatcher(et.matcher)
148+
if err != nil {
149+
t.Fatalf("failed to parse matcher %q: %v", et.matcher, err)
150+
}
108151

109-
t.Log(got)
110-
})
152+
expr = expr.Optimize()
111153

112154
for _, tc := range et.cases {
113155
t.Run(fmt.Sprintf("Eval(%v)", tc.tags), func(t *testing.T) {

tags2/bun.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,27 @@ func applyBracesToBunQuery(e Expr, qb bun.QueryBuilder, column string, mode bunM
125125
return e.applyToBunQuery(qb, column, !bunMode(negate), negate)
126126
})
127127
}
128+
129+
func pushNegatesToValues(expr Expr) Expr {
130+
switch expr := expr.(type) {
131+
case NotExpr:
132+
switch subExpr := expr.Expr.(type) {
133+
case AndExpr:
134+
// flip negated and expressions
135+
return pushNegatesToValues(OrExpr{slicest.Map(subExpr.Exprs, func(expr Expr) Expr { return NotExpr{expr} })})
136+
case OrExpr:
137+
// flip negated or expressions
138+
return pushNegatesToValues(AndExpr{slicest.Map(subExpr.Exprs, func(expr Expr) Expr { return NotExpr{expr} })})
139+
default:
140+
return expr
141+
}
142+
case AndExpr:
143+
expr.Exprs = slicest.Map(expr.Exprs, func(expr Expr) Expr { return pushNegatesToValues(expr) })
144+
return expr
145+
case OrExpr:
146+
expr.Exprs = slicest.Map(expr.Exprs, func(expr Expr) Expr { return pushNegatesToValues(expr) })
147+
return expr
148+
default:
149+
return expr
150+
}
151+
}

tags2/expr.go

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const (
2525
type Expr interface {
2626
fmt.Stringer
2727
Eval(tags Tags) bool
28+
Optimize() Expr
2829
applyToBunQuery(qb bun.QueryBuilder, column string, mode bunMode, negate bool) bun.QueryBuilder
2930
}
3031

@@ -100,6 +101,53 @@ func (e NotExpr) Eval(tags Tags) bool {
100101
return !e.Expr.Eval(tags)
101102
}
102103

103-
// func (e BracesExpr) Eval(tags Tags) bool {
104-
// return e.Expr.Eval(tags)
105-
// }
104+
// --- [Expr.Optimize] implementations ---
105+
106+
func (e ValueExpr) Optimize() Expr { return e }
107+
func (e AndExpr) Optimize() Expr {
108+
e.Exprs = slicest.Map(e.Exprs, func(expr Expr) Expr { return expr.Optimize() })
109+
110+
// flatten nested and expressions
111+
e.Exprs = slicest.Flatten(slicest.Map(e.Exprs, func(expr Expr) []Expr {
112+
if expr, ok := expr.(AndExpr); ok {
113+
return expr.Exprs
114+
}
115+
return []Expr{expr}
116+
}))
117+
118+
return e
119+
}
120+
func (e OrExpr) Optimize() Expr {
121+
e.Exprs = slicest.Map(e.Exprs, func(expr Expr) Expr { return expr.Optimize() })
122+
123+
// flatten nested or expressions
124+
e.Exprs = slicest.Flatten(slicest.Map(e.Exprs, func(expr Expr) []Expr {
125+
if expr, ok := expr.(OrExpr); ok {
126+
return expr.Exprs
127+
}
128+
return []Expr{expr}
129+
}))
130+
131+
return e
132+
}
133+
func (e NotExpr) Optimize() Expr {
134+
// e.Expr = e.Expr.Optimize()
135+
136+
switch expr := e.Expr.(type) {
137+
case AndExpr:
138+
// flip negated and expressions and reoptimize them
139+
return OrExpr{slicest.Map(expr.Exprs, func(expr Expr) Expr {
140+
return NotExpr{expr}.Optimize()
141+
})}.Optimize()
142+
case OrExpr:
143+
// flip negated or expressions and reoptimize them
144+
return AndExpr{slicest.Map(expr.Exprs, func(expr Expr) Expr {
145+
return NotExpr{expr}.Optimize()
146+
})}.Optimize()
147+
case NotExpr:
148+
// Remove double negation
149+
return expr.Expr
150+
default:
151+
return e
152+
}
153+
}

tags2/expr_test.go

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,24 +90,66 @@ var exprTests = []exprTest{
9090
{tags.Tags{"a"}, true},
9191
{tags.Tags{""}, false},
9292
}},
93+
94+
// --- Optimizeable ---
95+
{"(aws|gcp)|legacy", "(aws | gcp) | legacy", []exprTestCase{
96+
{tags.Tags{"aws", "cloud"}, true},
97+
{tags.Tags{"gcp"}, true},
98+
{tags.Tags{"legacy"}, true},
99+
{tags.Tags{"azure"}, false},
100+
}},
101+
{"(aws&gcp)&legacy", "(aws & gcp) & legacy", []exprTestCase{
102+
{tags.Tags{"aws", "cloud"}, false},
103+
{tags.Tags{"gcp"}, false},
104+
{tags.Tags{"aws", "gcp", "legacy"}, true},
105+
}},
106+
{"auth&!(admin|super)", "auth & !(admin | super)", []exprTestCase{
107+
{tags.Tags{"auth", "sys-admin"}, true},
108+
{tags.Tags{"auth", "super-user"}, true},
109+
{tags.Tags{"auth", "user"}, true},
110+
{tags.Tags{"sys-admin"}, false},
111+
}},
112+
{"!(test&stage)&prod", "!(test & stage) & prod", []exprTestCase{
113+
{tags.Tags{"prod"}, true},
114+
{tags.Tags{"test", "prod"}, true},
115+
{tags.Tags{"stage", "test", "prod"}, false},
116+
{tags.Tags{"stage"}, false},
117+
}},
118+
{"!!prod", "!!prod", []exprTestCase{
119+
{tags.Tags{"prod"}, true},
120+
{tags.Tags{"test"}, false},
121+
}},
93122
}
94123

95-
func TestExpr(t *testing.T) {
124+
func TestExprString(t *testing.T) {
96125
for _, et := range exprTests {
97-
t.Run(fmt.Sprintf("Matcher(%q)", et.matcher), func(t *testing.T) {
126+
t.Run(fmt.Sprintf("Matcher(%q).String()", et.matcher), func(t *testing.T) {
98127
expr, err := tags.ParseMatcher(et.matcher)
99128
if err != nil {
100129
t.Fatalf("failed to parse matcher %q: %v", et.matcher, err)
101130
}
102131

103-
t.Run("String()", func(t *testing.T) {
104-
got := expr.String()
105-
if got != et.rendered {
106-
t.Errorf("got = %q; expected = %q", got, et.rendered)
107-
}
132+
expr = expr.Optimize()
133+
134+
got := expr.String()
135+
if got != et.rendered {
136+
t.Errorf("got = %q; expected = %q", got, et.rendered)
137+
}
138+
139+
t.Log(got)
140+
})
141+
}
142+
}
143+
144+
func TestExprEval(t *testing.T) {
145+
for _, et := range exprTests {
146+
t.Run(fmt.Sprintf("Matcher(%q)", et.matcher), func(t *testing.T) {
147+
expr, err := tags.ParseMatcher(et.matcher)
148+
if err != nil {
149+
t.Fatalf("failed to parse matcher %q: %v", et.matcher, err)
150+
}
108151

109-
t.Log(got)
110-
})
152+
expr = expr.Optimize()
111153

112154
for _, tc := range et.cases {
113155
t.Run(fmt.Sprintf("Eval(%v)", tc.tags), func(t *testing.T) {

tags2/parser.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ func parseMatcher(matcher string, originalMatcher string, pos int) (Expr, error)
5656
if strings.HasPrefix(matcher, string(exprBracesOpen)) && strings.HasSuffix(matcher, string(exprBracesClose)) {
5757
matcher = matcher[1 : len(matcher)-1]
5858
return parseMatcher(matcher, originalMatcher, pos+1)
59-
// return BracesExpr{expr}, err
6059
}
6160

6261
// raw value

0 commit comments

Comments
 (0)