filter/filter.go
2017-11-12 15:37:16 +01:00

311 lines
5.9 KiB
Go

// Copyright (C) 2017 Marius Schellenberger
// Package filter implements simple struct field filtering
package filter
import (
"errors"
"reflect"
"sort"
"strings"
)
// Filter is the filter interface to be implemented for faster non generic filtering
// Example implementation:
// type f []struct
// func (v f) Len() int { return len(v) }
// func (v f) Index(i int) interface{} { return v[i] }
// func (v *f) Remove(i, j int) { *v = append((*v)[:i], (*v)[j:]...) }
type Filter interface {
Len() int
Index(int) interface{}
Remove(int, int)
}
type match struct {
fields []string
mf func(string) bool
bf func(bool) bool
}
type sorter struct {
enabled bool
only bool
desc bool
fields []string
}
const (
desc = "desc"
asc = "asc"
)
type logic int
const (
undefined logic = iota
and
or
not
)
func parseLogic(s string) (l logic, ok bool) {
ok = true
switch s {
case "AND":
l = and
case "OR":
l = or
case "NOT":
l = not
default:
ok = false
l = and
}
return
}
// Query is the filter query
type Query struct {
q string
m []match
l logic
s sorter
}
// NewQuery parses the filter/sort query and match logic
// If the query does not start with a filter logic (AND, OR, NOT), it defaults to AND.
// Examples:
// Explicit match: <struct field>:<struct value>
// Wildcard match (use ** to match the '*' character):
// <struct field>:*<part of struct value>
// <struct field>:<part of struct value>*
// <struct field>:*<middl of struct value>*
// Nested structs:
// <struct field>.<struct field>:<struct value>
// Multiple matches:
// Multiple statemenmts are seperated by a single whitespace character: ' '
// <struct field>:<struct value> <struct field>:<struct value>*
// Supported data types are: string, int, float, bool
// Bool does not support the wildcard operator
// Sorting:
// The sort statement consists of the literal string 'sort', the field name (nesting like above is supported) and the literal strings 'asc' or 'desc'.
// Examples:
// sort:<struct field>:asc
// sort:<struct field>:desc
// sort:<struct field>.<struct field>:desc
func NewQuery(s string) (q *Query, err error) {
q = &Query{q: s}
if s == "" {
err = errors.New("query is empty")
return
}
qm := make(map[string]struct{})
fields := clearFields(s)
if len(fields) == 0 {
err = errors.New("query is empty")
return
}
for _, v := range fields {
if q.l == undefined {
l, ok := parseLogic(v)
q.l = l
if ok {
continue
}
}
if !q.s.enabled {
s := strings.SplitN(v, ":", 3)
if len(s) == 3 {
if s[0] == "sort" {
switch s[2] {
case asc, desc:
q.s.fields = strings.Split(s[1], ".")
if err = checkFields(q.s.fields, v); err != nil {
return
}
if len(q.s.fields) > 0 {
if s[2] == desc {
q.s.desc = true
}
q.s.enabled = true
}
continue
default:
err = errors.New("invalid sort: " + v)
return
}
}
}
}
m := strings.SplitN(v, ":", 2)
if len(m) != 2 {
err = errors.New("invalid match: " + v)
return
}
if q.l == and {
if _, ok := qm[m[0]]; ok {
err = errors.New("field for AND matching used more than once: " + m[0])
return
}
qm[m[0]] = struct{}{}
}
f := strings.Split(m[0], ".")
if err = checkFields(f, m[0]); err != nil {
return
}
q.m = append(q.m, match{
f,
matchFunc(m[1]),
boolFunc(m[1]),
})
}
if len(q.m) == 0 && q.s.enabled {
q.s.only = true
} else if len(q.m) == 0 {
err = errors.New("query is empty")
}
if len(q.m) == 1 && q.l != not {
q.l = or
}
return
}
func (q *Query) String() string {
return q.q
}
func (q *Query) check(oks []bool) (b bool) {
b = true
switch q.l {
case and:
for _, ok := range oks {
if !ok {
b = false
return
}
}
case or:
b = false
case not:
for _, ok := range oks {
if ok {
b = false
return
}
}
}
return
}
func (q *Query) match(v reflect.Value) (ok bool) {
if v.Kind() != reflect.Struct {
return
}
ql := len(q.m)
if ql == 0 {
return
}
var (
oks []bool
match bool
)
if q.l != or {
oks = make([]bool, ql)
}
for i, qv := range q.m {
fl := len(qv.fields)
if fl == 0 {
continue
}
val := walkFields(v.FieldByName(qv.fields[0]), qv.fields[1:])
if val.Kind() == reflect.Invalid {
continue
}
s, b, err := getValue(val)
if err != nil {
continue
}
switch s {
case "":
match = qv.bf(b)
default:
match = qv.mf(s)
}
if q.l == or {
if match {
return true
}
continue
}
oks[i] = match
}
return q.check(oks)
}
// GenericFilter takes any slice of structs value and returns the same slice type containing only the filter matches.
// Returns nil if interface{} is not a slice.
// GenericFilter does not support sorting.
func (q *Query) GenericFilter(i interface{}) interface{} {
v := reflect.ValueOf(i)
if v.Kind() != reflect.Slice {
return nil
}
ret := reflect.MakeSlice(v.Type(), 0, 0)
for i := 0; i < v.Len(); i++ {
val := v.Index(i)
if q.match(val) {
ret = reflect.Append(ret, val)
}
}
return ret.Interface()
}
func (q Query) sort(f Filter) {
if f.Len() == 0 {
return
}
l := makeLess(f, q.s.fields)
if l == nil {
return
}
if q.s.desc {
l = reverse(l)
}
sort.SliceStable(reflect.Indirect(reflect.ValueOf(f)).Interface(), l)
}
// Filter takes a pointer to the Filter interface as an argument.
// The original slice is modified in the filtering/sorting process.
func (q Query) Filter(f Filter) error {
v := reflect.ValueOf(f)
if v.Kind() != reflect.Ptr {
return errors.New("filter: non-pointer " + v.Type().String())
}
if q.s.enabled && q.s.only {
q.sort(f)
return nil
}
count := 0
i := 0
for ; i < f.Len(); i++ {
if q.match(reflect.ValueOf(f.Index(i))) {
if count > 0 {
f.Remove(i-count, i)
i -= count
}
count = 0
} else {
count++
}
}
if count > 0 {
f.Remove(i-count, i)
}
if q.s.enabled {
q.sort(f)
}
return nil
}