github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/pkg/database/filter.go (about)

     1  // Copyright © 2021 Kaleido, Inc.
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  //
     5  // Licensed under the Apache License, Version 2.0 (the "License");
     6  // you may not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  package database
    18  
    19  import (
    20  	"context"
    21  	"database/sql/driver"
    22  	"fmt"
    23  	"strconv"
    24  	"strings"
    25  
    26  	"github.com/kaleido-io/firefly/internal/i18n"
    27  )
    28  
    29  // Filter is the output of the builder
    30  type Filter interface {
    31  	// Sort adds a set of sort conditions (all in a single sort order)
    32  	Sort(...string) Filter
    33  
    34  	// Ascending sort order
    35  	Ascending() Filter
    36  
    37  	// Descending sort order
    38  	Descending() Filter
    39  
    40  	// Skip for pagination
    41  	Skip(uint64) Filter
    42  
    43  	// Limit for pagination
    44  	Limit(uint64) Filter
    45  
    46  	// Finalize completes the filter, and for the plugin to validated output structure to convert
    47  	Finalize() (*FilterInfo, error)
    48  
    49  	// Builder returns the builder that made it
    50  	Builder() FilterBuilder
    51  }
    52  
    53  // MultiConditionFilter gives convenience methods to add conditions
    54  type MultiConditionFilter interface {
    55  	Filter
    56  	// Add adds filters to the condition
    57  	Condition(...Filter) MultiConditionFilter
    58  }
    59  
    60  type AndFilter interface{ MultiConditionFilter }
    61  
    62  type OrFilter interface{ MultiConditionFilter }
    63  
    64  // FilterOp enum of filter operations that must be implemented by plugins - the string value is
    65  // used in the core string formatting method (for logging etc.)
    66  type FilterOp string
    67  
    68  const (
    69  	// FilterOpAnd and
    70  	FilterOpAnd FilterOp = "&&"
    71  	// FilterOpOr or
    72  	FilterOpOr FilterOp = "||"
    73  	// FilterOpEq equal
    74  	FilterOpEq FilterOp = "=="
    75  	// FilterOpNe not equal
    76  	FilterOpNe FilterOp = "!="
    77  	// FilterOpIn in list of values
    78  	FilterOpIn FilterOp = "IN"
    79  	// FilterOpNotIn not in list of values
    80  	FilterOpNotIn FilterOp = "NI"
    81  	// FilterOpGt greater than
    82  	FilterOpGt FilterOp = ">"
    83  	// FilterOpLt less than
    84  	FilterOpLt FilterOp = "<"
    85  	// FilterOpGte greater than or equal
    86  	FilterOpGte FilterOp = ">="
    87  	// FilterOpLte less than or equal
    88  	FilterOpLte FilterOp = "<="
    89  	// FilterOpCont contains the specified text, case sensitive
    90  	FilterOpCont FilterOp = "%="
    91  	// FilterOpNotCont does not contain the specified text, case sensitive
    92  	FilterOpNotCont FilterOp = "%!"
    93  	// FilterOpICont contains the specified text, case insensitive
    94  	FilterOpICont FilterOp = "^="
    95  	// FilterOpNotICont does not contain the specified text, case insensitive
    96  	FilterOpNotICont FilterOp = "^!"
    97  )
    98  
    99  // FilterBuilder is the syntax used to build the filter, where And() and Or() can be nested
   100  type FilterBuilder interface {
   101  	// Fields is the list of available fields
   102  	Fields() []string
   103  	// And requires all sub-filters to match
   104  	And(and ...Filter) AndFilter
   105  	// Or requires any of the sub-filters to match
   106  	Or(and ...Filter) OrFilter
   107  	// Eq equal
   108  	Eq(name string, value driver.Value) Filter
   109  	// Neq not equal
   110  	Neq(name string, value driver.Value) Filter
   111  	// In one of an array of values
   112  	In(name string, value []driver.Value) Filter
   113  	// NotIn not one of an array of values
   114  	NotIn(name string, value []driver.Value) Filter
   115  	// Lt less than
   116  	Lt(name string, value driver.Value) Filter
   117  	// Gt greater than
   118  	Gt(name string, value driver.Value) Filter
   119  	// Gte greater than or equal
   120  	Gte(name string, value driver.Value) Filter
   121  	// Lte less than or equal
   122  	Lte(name string, value driver.Value) Filter
   123  	// Contains allows the string anywhere - case sensitive
   124  	Contains(name string, value driver.Value) Filter
   125  	// NotContains disallows the string anywhere - case sensitive
   126  	NotContains(name string, value driver.Value) Filter
   127  	// IContains allows the string anywhere - case sensitive
   128  	IContains(name string, value driver.Value) Filter
   129  	// INotContains disallows the string anywhere - case sensitive
   130  	NotIContains(name string, value driver.Value) Filter
   131  }
   132  
   133  // FilterInfo is the structure returned by Finalize to the plugin, to serialize this filter
   134  // into the underlying database mechanism's filter language
   135  type FilterInfo struct {
   136  	Sort       []string
   137  	Skip       uint64
   138  	Limit      uint64
   139  	Descending bool
   140  	Field      string
   141  	Op         FilterOp
   142  	Values     []FieldSerialization
   143  	Value      FieldSerialization
   144  	Children   []*FilterInfo
   145  }
   146  
   147  func valueString(f FieldSerialization) string {
   148  	v, _ := f.Value()
   149  	switch tv := v.(type) {
   150  	case nil:
   151  		return "null"
   152  	case []byte:
   153  		if tv == nil {
   154  			return "null"
   155  		}
   156  		return fmt.Sprintf("'%s'", tv)
   157  	case int64:
   158  		return strconv.FormatInt(tv, 10)
   159  	case bool:
   160  		return fmt.Sprintf("%t", tv)
   161  	default:
   162  		return fmt.Sprintf("'%s'", tv)
   163  	}
   164  }
   165  
   166  func (f *FilterInfo) filterString() string {
   167  	switch f.Op {
   168  	case FilterOpAnd, FilterOpOr:
   169  		cs := make([]string, len(f.Children))
   170  		for i, c := range f.Children {
   171  			cs[i] = fmt.Sprintf("( %s )", c.filterString())
   172  		}
   173  		return strings.Join(cs, fmt.Sprintf(" %s ", f.Op))
   174  	case FilterOpIn, FilterOpNotIn:
   175  		strValues := make([]string, len(f.Values))
   176  		for i, v := range f.Values {
   177  			strValues[i] = valueString(v)
   178  		}
   179  		return fmt.Sprintf("%s %s [%s]", f.Field, f.Op, strings.Join(strValues, ","))
   180  	default:
   181  		return fmt.Sprintf("%s %s %s", f.Field, f.Op, valueString(f.Value))
   182  	}
   183  }
   184  
   185  func (f *FilterInfo) String() string {
   186  
   187  	var val strings.Builder
   188  
   189  	val.WriteString(f.filterString())
   190  
   191  	if len(f.Sort) > 0 {
   192  		val.WriteString(fmt.Sprintf(" sort=%s", strings.Join(f.Sort, ",")))
   193  		if f.Descending {
   194  			val.WriteString(" descending")
   195  		}
   196  	}
   197  	if f.Skip > 0 {
   198  		val.WriteString(fmt.Sprintf(" skip=%d", f.Skip))
   199  	}
   200  	if f.Limit > 0 {
   201  		val.WriteString(fmt.Sprintf(" limit=%d", f.Limit))
   202  	}
   203  
   204  	return val.String()
   205  }
   206  
   207  func (fb *filterBuilder) Fields() []string {
   208  	keys := make([]string, len(fb.queryFields))
   209  	i := 0
   210  	for k := range fb.queryFields {
   211  		keys[i] = k
   212  		i++
   213  	}
   214  	return keys
   215  }
   216  
   217  type filterBuilder struct {
   218  	ctx         context.Context
   219  	queryFields queryFields
   220  	sort        []string
   221  	skip        uint64
   222  	limit       uint64
   223  	descending  bool
   224  }
   225  
   226  type baseFilter struct {
   227  	fb       *filterBuilder
   228  	children []Filter
   229  	op       FilterOp
   230  	field    string
   231  	value    interface{}
   232  }
   233  
   234  func (f *baseFilter) Builder() FilterBuilder {
   235  	return f.fb
   236  }
   237  
   238  func (f *baseFilter) Finalize() (fi *FilterInfo, err error) {
   239  	var children []*FilterInfo
   240  	var value FieldSerialization
   241  	var values []FieldSerialization
   242  
   243  	switch f.op {
   244  	case FilterOpAnd, FilterOpOr:
   245  		children = make([]*FilterInfo, len(f.children))
   246  		for i, c := range f.children {
   247  			if children[i], err = c.Finalize(); err != nil {
   248  				return nil, err
   249  			}
   250  		}
   251  	case FilterOpIn, FilterOpNotIn:
   252  		fValues := f.value.([]driver.Value)
   253  		values = make([]FieldSerialization, len(fValues))
   254  		name := strings.ToLower(f.field)
   255  		field, ok := f.fb.queryFields[name]
   256  		if !ok {
   257  			return nil, i18n.NewError(f.fb.ctx, i18n.MsgInvalidFilterField, name)
   258  		}
   259  		for i, fv := range fValues {
   260  			values[i] = field.getSerialization()
   261  			if err = values[i].Scan(fv); err != nil {
   262  				return nil, i18n.WrapError(f.fb.ctx, err, i18n.MsgInvalidValueForFilterField, name)
   263  			}
   264  		}
   265  	default:
   266  		name := strings.ToLower(f.field)
   267  		field, ok := f.fb.queryFields[name]
   268  		if !ok {
   269  			return nil, i18n.NewError(f.fb.ctx, i18n.MsgInvalidFilterField, name)
   270  		}
   271  		value = field.getSerialization()
   272  		if err = value.Scan(f.value); err != nil {
   273  			return nil, i18n.WrapError(f.fb.ctx, err, i18n.MsgInvalidValueForFilterField, name)
   274  		}
   275  	}
   276  
   277  	return &FilterInfo{
   278  		Children:   children,
   279  		Op:         f.op,
   280  		Field:      f.field,
   281  		Values:     values,
   282  		Value:      value,
   283  		Sort:       f.fb.sort,
   284  		Skip:       f.fb.skip,
   285  		Limit:      f.fb.limit,
   286  		Descending: f.fb.descending,
   287  	}, nil
   288  }
   289  
   290  func (f *baseFilter) Sort(fields ...string) Filter {
   291  	for _, field := range fields {
   292  		if _, ok := f.fb.queryFields[field]; ok {
   293  			f.fb.sort = append(f.fb.sort, field)
   294  		}
   295  	}
   296  	return f
   297  }
   298  
   299  func (f *baseFilter) Skip(skip uint64) Filter {
   300  	f.fb.skip = skip
   301  	return f
   302  }
   303  
   304  func (f *baseFilter) Limit(limit uint64) Filter {
   305  	f.fb.limit = limit
   306  	return f
   307  }
   308  
   309  func (f *baseFilter) Ascending() Filter {
   310  	f.fb.descending = false
   311  	return f
   312  }
   313  
   314  func (f *baseFilter) Descending() Filter {
   315  	f.fb.descending = true
   316  	return f
   317  }
   318  
   319  type andFilter struct {
   320  	baseFilter
   321  }
   322  
   323  func (fb *andFilter) Condition(children ...Filter) MultiConditionFilter {
   324  	fb.children = append(fb.children, children...)
   325  	return fb
   326  }
   327  
   328  func (fb *filterBuilder) And(and ...Filter) AndFilter {
   329  	return &andFilter{
   330  		baseFilter: baseFilter{
   331  			fb:       fb,
   332  			op:       FilterOpAnd,
   333  			children: and,
   334  		},
   335  	}
   336  }
   337  
   338  type orFilter struct {
   339  	baseFilter
   340  }
   341  
   342  func (fb *orFilter) Condition(children ...Filter) MultiConditionFilter {
   343  	fb.children = append(fb.children, children...)
   344  	return fb
   345  }
   346  
   347  func (fb *filterBuilder) Or(or ...Filter) OrFilter {
   348  	return &orFilter{
   349  		baseFilter: baseFilter{
   350  			fb:       fb,
   351  			op:       FilterOpOr,
   352  			children: or,
   353  		},
   354  	}
   355  }
   356  
   357  func (fb *filterBuilder) Eq(name string, value driver.Value) Filter {
   358  	return fb.fieldFilter(FilterOpEq, name, value)
   359  }
   360  
   361  func (fb *filterBuilder) Neq(name string, value driver.Value) Filter {
   362  	return fb.fieldFilter(FilterOpNe, name, value)
   363  }
   364  
   365  func (fb *filterBuilder) In(name string, values []driver.Value) Filter {
   366  	return fb.fieldFilter(FilterOpIn, name, values)
   367  }
   368  
   369  func (fb *filterBuilder) NotIn(name string, values []driver.Value) Filter {
   370  	return fb.fieldFilter(FilterOpNotIn, name, values)
   371  }
   372  
   373  func (fb *filterBuilder) Lt(name string, value driver.Value) Filter {
   374  	return fb.fieldFilter(FilterOpLt, name, value)
   375  }
   376  
   377  func (fb *filterBuilder) Gt(name string, value driver.Value) Filter {
   378  	return fb.fieldFilter(FilterOpGt, name, value)
   379  }
   380  
   381  func (fb *filterBuilder) Gte(name string, value driver.Value) Filter {
   382  	return fb.fieldFilter(FilterOpGte, name, value)
   383  }
   384  
   385  func (fb *filterBuilder) Lte(name string, value driver.Value) Filter {
   386  	return fb.fieldFilter(FilterOpLte, name, value)
   387  }
   388  
   389  func (fb *filterBuilder) Contains(name string, value driver.Value) Filter {
   390  	return fb.fieldFilter(FilterOpCont, name, value)
   391  }
   392  
   393  func (fb *filterBuilder) NotContains(name string, value driver.Value) Filter {
   394  	return fb.fieldFilter(FilterOpNotCont, name, value)
   395  }
   396  
   397  func (fb *filterBuilder) IContains(name string, value driver.Value) Filter {
   398  	return fb.fieldFilter(FilterOpICont, name, value)
   399  }
   400  
   401  func (fb *filterBuilder) NotIContains(name string, value driver.Value) Filter {
   402  	return fb.fieldFilter(FilterOpNotICont, name, value)
   403  }
   404  
   405  func (fb *filterBuilder) fieldFilter(op FilterOp, name string, value interface{}) Filter {
   406  	return &fieldFilter{
   407  		baseFilter: baseFilter{
   408  			fb:    fb,
   409  			op:    op,
   410  			field: name,
   411  			value: value,
   412  		},
   413  	}
   414  }
   415  
   416  type fieldFilter struct {
   417  	baseFilter
   418  }