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

Commit

Permalink
Implement struct matching
Browse files Browse the repository at this point in the history
  • Loading branch information
tomakado committed Feb 11, 2025
1 parent 4663ece commit 93955cb
Show file tree
Hide file tree
Showing 7 changed files with 1,307 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: unit-tests
run: |
go test ./... -coverprofile=coverage.out
cat coverage.out | grep -v "query/parser.gen.go" > coverage_filtered.out
cat coverage.out | grep -v "query/parser.gen.go" > coverage_filtered.out | grep -v "github.com/defer-panic/dumbql/query/ast.go:81"
go tool cover -func=coverage_filtered.out
- name: golangci-lint
Expand Down
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ linters:
enable:
- cyclop
- decorder
- dupl
- exhaustive
- fatcontext
- funlen
Expand All @@ -29,6 +28,7 @@ linters:
- reassign
- recvcheck
- revive
- testifylint
- testpackage
- tparallel
- unconvert
Expand Down
2 changes: 1 addition & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ tasks:
desc: "Run unit tests"
cmds:
- go test ./... -coverprofile=coverage.out
- cat coverage.out | grep -v "query/parser.gen.go" > coverage_filtered.out
- cat coverage.out | grep -v "query/parser.gen.go" | grep -v "github.com/defer-panic/dumbql/query/ast.go:81" > coverage_filtered.out
- go tool cover -func=coverage_filtered.out

# Codegen
Expand Down
6 changes: 4 additions & 2 deletions query/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ type Expr interface {
fmt.Stringer
sq.Sqlizer

Match(target any, matcher Matcher) bool
Validate(schema.Schema) (Expr, error)
}

type Valuer interface {
Value() any
Match(target any, op FieldOperator) bool
}

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

func (t *StringLiteral) String() string { return strconv.Quote(t.StringValue) }
func (t *StringLiteral) Value() any { return t.StringValue }
func (s *StringLiteral) String() string { return strconv.Quote(s.StringValue) }
func (s *StringLiteral) Value() any { return s.StringValue }

type NumberLiteral struct {
NumberValue float64
Expand Down
162 changes: 162 additions & 0 deletions query/match.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package query

import (
"reflect"
"strings"
)

type Matcher interface {
MatchAnd(target any, left, right Expr) bool
MatchOr(target any, left, right Expr) bool
MatchNot(target any, expr Expr) bool
MatchField(target any, field string, value Valuer, op FieldOperator) bool
MatchValue(target any, value Valuer, op FieldOperator) bool
}

type DefaultMatcher struct{}

func (m *DefaultMatcher) MatchAnd(target any, left, right Expr) bool {
return left.Match(target, m) && right.Match(target, m)
}

func (m *DefaultMatcher) MatchOr(target any, left, right Expr) bool {
return left.Match(target, m) || right.Match(target, m)
}

func (m *DefaultMatcher) MatchNot(target any, expr Expr) bool {
return !expr.Match(target, m)
}

func (m *DefaultMatcher) MatchField(target any, field string, value Valuer, op FieldOperator) bool {
t := reflect.TypeOf(target)

if t.Kind() == reflect.Ptr {
t = t.Elem()
}

if t.Kind() != reflect.Struct {
return false
}

for i := 0; i < t.NumField(); i++ {
f := t.Field(i)

fname := f.Name
if f.Tag.Get("dumbql") != "" {
fname = f.Tag.Get("dumbql")
}

if fname == field {
return m.MatchValue(reflect.ValueOf(target).Field(i).Interface(), value, op)
}
}

return false
}

func (m *DefaultMatcher) MatchValue(target any, value Valuer, op FieldOperator) bool {
return value.Match(target, op)
}

func (b *BinaryExpr) Match(target any, matcher Matcher) bool {
switch b.Op {
case And:
return matcher.MatchAnd(target, b.Left, b.Right)
case Or:
return matcher.MatchOr(target, b.Left, b.Right)
default:
return false
}
}

func (n *NotExpr) Match(target any, matcher Matcher) bool {
return matcher.MatchNot(target, n.Expr)
}

func (f *FieldExpr) Match(target any, matcher Matcher) bool {
return matcher.MatchField(target, f.Field.String(), f.Value, f.Op)
}

func (s *StringLiteral) Match(target any, op FieldOperator) bool {
str, ok := target.(string)
if !ok {
return false
}

return matchString(str, s.StringValue, op)
}

func (i *IntegerLiteral) Match(target any, op FieldOperator) bool {
intVal, ok := target.(int64)
if !ok {
return false
}

return matchNum(intVal, i.IntegerValue, op)
}

func (n *NumberLiteral) Match(target any, op FieldOperator) bool {
floatVal, ok := target.(float64)
if !ok {
return false
}

return matchNum(floatVal, n.NumberValue, op)
}

func (i Identifier) Match(target any, op FieldOperator) bool {
str, ok := target.(string)
if !ok {
return false
}

return matchString(str, i.String(), op)
}

func (o *OneOfExpr) Match(target any, op FieldOperator) bool {
switch op { //nolint:exhaustive
case Equal, Like:
for _, v := range o.Values {
if v.Match(target, op) {
return true
}
}

return false

default:
return false
}
}

func matchString(a, b string, op FieldOperator) bool {
switch op { //nolint:exhaustive
case Equal:
return a == b
case NotEqual:
return a != b
case Like:
return strings.Contains(a, b)
default:
return false
}
}

func matchNum[T int64 | float64](a, b T, op FieldOperator) bool {
switch op { //nolint:exhaustive
case Equal:
return a == b
case NotEqual:
return a != b
case GreaterThan:
return a > b
case GreaterThanOrEqual:
return a >= b
case LessThan:
return a < b
case LessThanOrEqual:
return a <= b
default:
return false
}
}
Loading

0 comments on commit 93955cb

Please sign in to comment.