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 }