Skip to content
This repository was archived by the owner on Feb 11, 2025. It is now read-only.

Commit 93955cb

Browse files
committed
Implement struct matching
1 parent 4663ece commit 93955cb

File tree

7 files changed

+1307
-8
lines changed

7 files changed

+1307
-8
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- name: unit-tests
2323
run: |
2424
go test ./... -coverprofile=coverage.out
25-
cat coverage.out | grep -v "query/parser.gen.go" > coverage_filtered.out
25+
cat coverage.out | grep -v "query/parser.gen.go" > coverage_filtered.out | grep -v "github.com/defer-panic/dumbql/query/ast.go:81"
2626
go tool cover -func=coverage_filtered.out
2727
2828
- name: golangci-lint

.golangci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ linters:
55
enable:
66
- cyclop
77
- decorder
8-
- dupl
98
- exhaustive
109
- fatcontext
1110
- funlen
@@ -29,6 +28,7 @@ linters:
2928
- reassign
3029
- recvcheck
3130
- revive
31+
- testifylint
3232
- testpackage
3333
- tparallel
3434
- unconvert

Taskfile.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ tasks:
3232
desc: "Run unit tests"
3333
cmds:
3434
- go test ./... -coverprofile=coverage.out
35-
- cat coverage.out | grep -v "query/parser.gen.go" > coverage_filtered.out
35+
- cat coverage.out | grep -v "query/parser.gen.go" | grep -v "github.com/defer-panic/dumbql/query/ast.go:81" > coverage_filtered.out
3636
- go tool cover -func=coverage_filtered.out
3737

3838
# Codegen

query/ast.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ type Expr interface {
1414
fmt.Stringer
1515
sq.Sqlizer
1616

17+
Match(target any, matcher Matcher) bool
1718
Validate(schema.Schema) (Expr, error)
1819
}
1920

2021
type Valuer interface {
2122
Value() any
23+
Match(target any, op FieldOperator) bool
2224
}
2325

2426
// BinaryExpr represents a binary operation (`and`, `or`, `AND`, `OR`) between two expressions.
@@ -57,8 +59,8 @@ type StringLiteral struct {
5759
StringValue string
5860
}
5961

60-
func (t *StringLiteral) String() string { return strconv.Quote(t.StringValue) }
61-
func (t *StringLiteral) Value() any { return t.StringValue }
62+
func (s *StringLiteral) String() string { return strconv.Quote(s.StringValue) }
63+
func (s *StringLiteral) Value() any { return s.StringValue }
6264

6365
type NumberLiteral struct {
6466
NumberValue float64

query/match.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package query
2+
3+
import (
4+
"reflect"
5+
"strings"
6+
)
7+
8+
type Matcher interface {
9+
MatchAnd(target any, left, right Expr) bool
10+
MatchOr(target any, left, right Expr) bool
11+
MatchNot(target any, expr Expr) bool
12+
MatchField(target any, field string, value Valuer, op FieldOperator) bool
13+
MatchValue(target any, value Valuer, op FieldOperator) bool
14+
}
15+
16+
type DefaultMatcher struct{}
17+
18+
func (m *DefaultMatcher) MatchAnd(target any, left, right Expr) bool {
19+
return left.Match(target, m) && right.Match(target, m)
20+
}
21+
22+
func (m *DefaultMatcher) MatchOr(target any, left, right Expr) bool {
23+
return left.Match(target, m) || right.Match(target, m)
24+
}
25+
26+
func (m *DefaultMatcher) MatchNot(target any, expr Expr) bool {
27+
return !expr.Match(target, m)
28+
}
29+
30+
func (m *DefaultMatcher) MatchField(target any, field string, value Valuer, op FieldOperator) bool {
31+
t := reflect.TypeOf(target)
32+
33+
if t.Kind() == reflect.Ptr {
34+
t = t.Elem()
35+
}
36+
37+
if t.Kind() != reflect.Struct {
38+
return false
39+
}
40+
41+
for i := 0; i < t.NumField(); i++ {
42+
f := t.Field(i)
43+
44+
fname := f.Name
45+
if f.Tag.Get("dumbql") != "" {
46+
fname = f.Tag.Get("dumbql")
47+
}
48+
49+
if fname == field {
50+
return m.MatchValue(reflect.ValueOf(target).Field(i).Interface(), value, op)
51+
}
52+
}
53+
54+
return false
55+
}
56+
57+
func (m *DefaultMatcher) MatchValue(target any, value Valuer, op FieldOperator) bool {
58+
return value.Match(target, op)
59+
}
60+
61+
func (b *BinaryExpr) Match(target any, matcher Matcher) bool {
62+
switch b.Op {
63+
case And:
64+
return matcher.MatchAnd(target, b.Left, b.Right)
65+
case Or:
66+
return matcher.MatchOr(target, b.Left, b.Right)
67+
default:
68+
return false
69+
}
70+
}
71+
72+
func (n *NotExpr) Match(target any, matcher Matcher) bool {
73+
return matcher.MatchNot(target, n.Expr)
74+
}
75+
76+
func (f *FieldExpr) Match(target any, matcher Matcher) bool {
77+
return matcher.MatchField(target, f.Field.String(), f.Value, f.Op)
78+
}
79+
80+
func (s *StringLiteral) Match(target any, op FieldOperator) bool {
81+
str, ok := target.(string)
82+
if !ok {
83+
return false
84+
}
85+
86+
return matchString(str, s.StringValue, op)
87+
}
88+
89+
func (i *IntegerLiteral) Match(target any, op FieldOperator) bool {
90+
intVal, ok := target.(int64)
91+
if !ok {
92+
return false
93+
}
94+
95+
return matchNum(intVal, i.IntegerValue, op)
96+
}
97+
98+
func (n *NumberLiteral) Match(target any, op FieldOperator) bool {
99+
floatVal, ok := target.(float64)
100+
if !ok {
101+
return false
102+
}
103+
104+
return matchNum(floatVal, n.NumberValue, op)
105+
}
106+
107+
func (i Identifier) Match(target any, op FieldOperator) bool {
108+
str, ok := target.(string)
109+
if !ok {
110+
return false
111+
}
112+
113+
return matchString(str, i.String(), op)
114+
}
115+
116+
func (o *OneOfExpr) Match(target any, op FieldOperator) bool {
117+
switch op { //nolint:exhaustive
118+
case Equal, Like:
119+
for _, v := range o.Values {
120+
if v.Match(target, op) {
121+
return true
122+
}
123+
}
124+
125+
return false
126+
127+
default:
128+
return false
129+
}
130+
}
131+
132+
func matchString(a, b string, op FieldOperator) bool {
133+
switch op { //nolint:exhaustive
134+
case Equal:
135+
return a == b
136+
case NotEqual:
137+
return a != b
138+
case Like:
139+
return strings.Contains(a, b)
140+
default:
141+
return false
142+
}
143+
}
144+
145+
func matchNum[T int64 | float64](a, b T, op FieldOperator) bool {
146+
switch op { //nolint:exhaustive
147+
case Equal:
148+
return a == b
149+
case NotEqual:
150+
return a != b
151+
case GreaterThan:
152+
return a > b
153+
case GreaterThanOrEqual:
154+
return a >= b
155+
case LessThan:
156+
return a < b
157+
case LessThanOrEqual:
158+
return a <= b
159+
default:
160+
return false
161+
}
162+
}

0 commit comments

Comments
 (0)