github.com/Finschia/ostracon@v1.1.5/libs/pubsub/query/query.go (about) 1 // Package query provides a parser for a custom query format: 2 // 3 // abci.invoice.number=22 AND abci.invoice.owner=Ivan 4 // 5 // See query.peg for the grammar, which is a https://en.wikipedia.org/wiki/Parsing_expression_grammar. 6 // More: https://github.com/PhilippeSigaud/Pegged/wiki/PEG-Basics 7 // 8 // It has a support for numbers (integer and floating point), dates and times. 9 package query 10 11 import ( 12 "fmt" 13 "reflect" 14 "regexp" 15 "strconv" 16 "strings" 17 "time" 18 ) 19 20 var ( 21 numRegex = regexp.MustCompile(`([0-9\.]+)`) 22 ) 23 24 // Query holds the query string and the query parser. 25 type Query struct { 26 str string 27 parser *QueryParser 28 } 29 30 // Condition represents a single condition within a query and consists of composite key 31 // (e.g. "tx.gas"), operator (e.g. "=") and operand (e.g. "7"). 32 type Condition struct { 33 CompositeKey string 34 Op Operator 35 Operand interface{} 36 } 37 38 // New parses the given string and returns a query or error if the string is 39 // invalid. 40 func New(s string) (*Query, error) { 41 p := &QueryParser{Buffer: fmt.Sprintf(`"%s"`, s)} 42 err := p.Init() 43 if err != nil { 44 return nil, err 45 } 46 47 if err := p.Parse(); err != nil { 48 return nil, err 49 } 50 51 return &Query{str: s, parser: p}, nil 52 } 53 54 // MustParse turns the given string into a query or panics; for tests or others 55 // cases where you know the string is valid. 56 func MustParse(s string) *Query { 57 q, err := New(s) 58 if err != nil { 59 panic(fmt.Sprintf("failed to parse %s: %v", s, err)) 60 } 61 return q 62 } 63 64 // String returns the original string. 65 func (q *Query) String() string { 66 return q.str 67 } 68 69 // Operator is an operator that defines some kind of relation between composite key and 70 // operand (equality, etc.). 71 type Operator uint8 72 73 const ( 74 // "<=" 75 OpLessEqual Operator = iota 76 // ">=" 77 OpGreaterEqual 78 // "<" 79 OpLess 80 // ">" 81 OpGreater 82 // "=" 83 OpEqual 84 // "CONTAINS"; used to check if a string contains a certain sub string. 85 OpContains 86 // "EXISTS"; used to check if a certain event attribute is present. 87 OpExists 88 ) 89 90 const ( 91 // DateLayout defines a layout for all dates (`DATE date`) 92 DateLayout = "2006-01-02" 93 // TimeLayout defines a layout for all times (`TIME time`) 94 TimeLayout = time.RFC3339 95 ) 96 97 // Conditions returns a list of conditions. It returns an error if there is any 98 // error with the provided grammar in the Query. 99 func (q *Query) Conditions() ([]Condition, error) { 100 var ( 101 eventAttr string 102 op Operator 103 ) 104 105 conditions := make([]Condition, 0) 106 buffer, begin, end := q.parser.Buffer, 0, 0 107 108 // tokens must be in the following order: tag ("tx.gas") -> operator ("=") -> operand ("7") 109 for _, token := range q.parser.Tokens() { 110 switch token.pegRule { 111 case rulePegText: 112 begin, end = int(token.begin), int(token.end) 113 114 case ruletag: 115 eventAttr = buffer[begin:end] 116 117 case rulele: 118 op = OpLessEqual 119 120 case rulege: 121 op = OpGreaterEqual 122 123 case rulel: 124 op = OpLess 125 126 case ruleg: 127 op = OpGreater 128 129 case ruleequal: 130 op = OpEqual 131 132 case rulecontains: 133 op = OpContains 134 135 case ruleexists: 136 op = OpExists 137 conditions = append(conditions, Condition{eventAttr, op, nil}) 138 139 case rulevalue: 140 // strip single quotes from value (i.e. "'NewBlock'" -> "NewBlock") 141 valueWithoutSingleQuotes := buffer[begin+1 : end-1] 142 conditions = append(conditions, Condition{eventAttr, op, valueWithoutSingleQuotes}) 143 144 case rulenumber: 145 number := buffer[begin:end] 146 if strings.ContainsAny(number, ".") { // if it looks like a floating-point number 147 value, err := strconv.ParseFloat(number, 64) 148 if err != nil { 149 err = fmt.Errorf( 150 "got %v while trying to parse %s as float64 (should never happen if the grammar is correct)", 151 err, number, 152 ) 153 return nil, err 154 } 155 156 conditions = append(conditions, Condition{eventAttr, op, value}) 157 } else { 158 value, err := strconv.ParseInt(number, 10, 64) 159 if err != nil { 160 err = fmt.Errorf( 161 "got %v while trying to parse %s as int64 (should never happen if the grammar is correct)", 162 err, number, 163 ) 164 return nil, err 165 } 166 167 conditions = append(conditions, Condition{eventAttr, op, value}) 168 } 169 170 case ruletime: 171 value, err := time.Parse(TimeLayout, buffer[begin:end]) 172 if err != nil { 173 err = fmt.Errorf( 174 "got %v while trying to parse %s as time.Time / RFC3339 (should never happen if the grammar is correct)", 175 err, buffer[begin:end], 176 ) 177 return nil, err 178 } 179 180 conditions = append(conditions, Condition{eventAttr, op, value}) 181 182 case ruledate: 183 value, err := time.Parse("2006-01-02", buffer[begin:end]) 184 if err != nil { 185 err = fmt.Errorf( 186 "got %v while trying to parse %s as time.Time / '2006-01-02' (should never happen if the grammar is correct)", 187 err, buffer[begin:end], 188 ) 189 return nil, err 190 } 191 192 conditions = append(conditions, Condition{eventAttr, op, value}) 193 } 194 } 195 196 return conditions, nil 197 } 198 199 // Matches returns true if the query matches against any event in the given set 200 // of events, false otherwise. For each event, a match exists if the query is 201 // matched against *any* value in a slice of values. An error is returned if 202 // any attempted event match returns an error. 203 // 204 // For example, query "name=John" matches events = {"name": ["John", "Eric"]}. 205 // More examples could be found in parser_test.go and query_test.go. 206 func (q *Query) Matches(events map[string][]string) (bool, error) { 207 if len(events) == 0 { 208 return false, nil 209 } 210 211 var ( 212 eventAttr string 213 op Operator 214 ) 215 216 buffer, begin, end := q.parser.Buffer, 0, 0 217 218 // tokens must be in the following order: 219 220 // tag ("tx.gas") -> operator ("=") -> operand ("7") 221 for _, token := range q.parser.Tokens() { 222 switch token.pegRule { 223 case rulePegText: 224 begin, end = int(token.begin), int(token.end) 225 226 case ruletag: 227 eventAttr = buffer[begin:end] 228 229 case rulele: 230 op = OpLessEqual 231 232 case rulege: 233 op = OpGreaterEqual 234 235 case rulel: 236 op = OpLess 237 238 case ruleg: 239 op = OpGreater 240 241 case ruleequal: 242 op = OpEqual 243 244 case rulecontains: 245 op = OpContains 246 case ruleexists: 247 op = OpExists 248 if strings.Contains(eventAttr, ".") { 249 // Searching for a full "type.attribute" event. 250 _, ok := events[eventAttr] 251 if !ok { 252 return false, nil 253 } 254 } else { 255 foundEvent := false 256 257 loop: 258 for compositeKey := range events { 259 if strings.Index(compositeKey, eventAttr) == 0 { 260 foundEvent = true 261 break loop 262 } 263 } 264 if !foundEvent { 265 return false, nil 266 } 267 } 268 269 case rulevalue: 270 // strip single quotes from value (i.e. "'NewBlock'" -> "NewBlock") 271 valueWithoutSingleQuotes := buffer[begin+1 : end-1] 272 273 // see if the triplet (event attribute, operator, operand) matches any event 274 // "tx.gas", "=", "7", { "tx.gas": 7, "tx.ID": "4AE393495334" } 275 match, err := match(eventAttr, op, reflect.ValueOf(valueWithoutSingleQuotes), events) 276 if err != nil { 277 return false, err 278 } 279 280 if !match { 281 return false, nil 282 } 283 284 case rulenumber: 285 number := buffer[begin:end] 286 if strings.ContainsAny(number, ".") { // if it looks like a floating-point number 287 value, err := strconv.ParseFloat(number, 64) 288 if err != nil { 289 err = fmt.Errorf( 290 "got %v while trying to parse %s as float64 (should never happen if the grammar is correct)", 291 err, number, 292 ) 293 return false, err 294 } 295 296 match, err := match(eventAttr, op, reflect.ValueOf(value), events) 297 if err != nil { 298 return false, err 299 } 300 301 if !match { 302 return false, nil 303 } 304 } else { 305 value, err := strconv.ParseInt(number, 10, 64) 306 if err != nil { 307 err = fmt.Errorf( 308 "got %v while trying to parse %s as int64 (should never happen if the grammar is correct)", 309 err, number, 310 ) 311 return false, err 312 } 313 314 match, err := match(eventAttr, op, reflect.ValueOf(value), events) 315 if err != nil { 316 return false, err 317 } 318 319 if !match { 320 return false, nil 321 } 322 } 323 324 case ruletime: 325 value, err := time.Parse(TimeLayout, buffer[begin:end]) 326 if err != nil { 327 err = fmt.Errorf( 328 "got %v while trying to parse %s as time.Time / RFC3339 (should never happen if the grammar is correct)", 329 err, buffer[begin:end], 330 ) 331 return false, err 332 } 333 334 match, err := match(eventAttr, op, reflect.ValueOf(value), events) 335 if err != nil { 336 return false, err 337 } 338 339 if !match { 340 return false, nil 341 } 342 343 case ruledate: 344 value, err := time.Parse("2006-01-02", buffer[begin:end]) 345 if err != nil { 346 err = fmt.Errorf( 347 "got %v while trying to parse %s as time.Time / '2006-01-02' (should never happen if the grammar is correct)", 348 err, buffer[begin:end], 349 ) 350 return false, err 351 } 352 353 match, err := match(eventAttr, op, reflect.ValueOf(value), events) 354 if err != nil { 355 return false, err 356 } 357 358 if !match { 359 return false, nil 360 } 361 } 362 } 363 364 return true, nil 365 } 366 367 // match returns true if the given triplet (attribute, operator, operand) matches 368 // any value in an event for that attribute. If any match fails with an error, 369 // that error is returned. 370 // 371 // First, it looks up the key in the events and if it finds one, tries to compare 372 // all the values from it to the operand using the operator. 373 // 374 // "tx.gas", "=", "7", {"tx": [{"gas": 7, "ID": "4AE393495334"}]} 375 func match(attr string, op Operator, operand reflect.Value, events map[string][]string) (bool, error) { 376 // look up the tag from the query in tags 377 values, ok := events[attr] 378 if !ok { 379 return false, nil 380 } 381 382 for _, value := range values { 383 // return true if any value in the set of the event's values matches 384 match, err := matchValue(value, op, operand) 385 if err != nil { 386 return false, err 387 } 388 389 if match { 390 return true, nil 391 } 392 } 393 394 return false, nil 395 } 396 397 // matchValue will attempt to match a string value against an operator an 398 // operand. A boolean is returned representing the match result. It will return 399 // an error if the value cannot be parsed and matched against the operand type. 400 func matchValue(value string, op Operator, operand reflect.Value) (bool, error) { 401 switch operand.Kind() { 402 case reflect.Struct: // time 403 operandAsTime := operand.Interface().(time.Time) 404 405 // try our best to convert value from events to time.Time 406 var ( 407 v time.Time 408 err error 409 ) 410 411 if strings.ContainsAny(value, "T") { 412 v, err = time.Parse(TimeLayout, value) 413 } else { 414 v, err = time.Parse(DateLayout, value) 415 } 416 if err != nil { 417 return false, fmt.Errorf("failed to convert value %v from event attribute to time.Time: %w", value, err) 418 } 419 420 switch op { 421 case OpLessEqual: 422 return (v.Before(operandAsTime) || v.Equal(operandAsTime)), nil 423 case OpGreaterEqual: 424 return (v.Equal(operandAsTime) || v.After(operandAsTime)), nil 425 case OpLess: 426 return v.Before(operandAsTime), nil 427 case OpGreater: 428 return v.After(operandAsTime), nil 429 case OpEqual: 430 return v.Equal(operandAsTime), nil 431 } 432 433 case reflect.Float64: 434 var v float64 435 436 operandFloat64 := operand.Interface().(float64) 437 filteredValue := numRegex.FindString(value) 438 439 // try our best to convert value from tags to float64 440 v, err := strconv.ParseFloat(filteredValue, 64) 441 if err != nil { 442 return false, fmt.Errorf("failed to convert value %v from event attribute to float64: %w", filteredValue, err) 443 } 444 445 switch op { 446 case OpLessEqual: 447 return v <= operandFloat64, nil 448 case OpGreaterEqual: 449 return v >= operandFloat64, nil 450 case OpLess: 451 return v < operandFloat64, nil 452 case OpGreater: 453 return v > operandFloat64, nil 454 case OpEqual: 455 return v == operandFloat64, nil 456 } 457 458 case reflect.Int64: 459 var v int64 460 461 operandInt := operand.Interface().(int64) 462 filteredValue := numRegex.FindString(value) 463 464 // if value looks like float, we try to parse it as float 465 if strings.ContainsAny(filteredValue, ".") { 466 v1, err := strconv.ParseFloat(filteredValue, 64) 467 if err != nil { 468 return false, fmt.Errorf("failed to convert value %v from event attribute to float64: %w", filteredValue, err) 469 } 470 471 v = int64(v1) 472 } else { 473 var err error 474 // try our best to convert value from tags to int64 475 v, err = strconv.ParseInt(filteredValue, 10, 64) 476 if err != nil { 477 return false, fmt.Errorf("failed to convert value %v from event attribute to int64: %w", filteredValue, err) 478 } 479 } 480 481 switch op { 482 case OpLessEqual: 483 return v <= operandInt, nil 484 case OpGreaterEqual: 485 return v >= operandInt, nil 486 case OpLess: 487 return v < operandInt, nil 488 case OpGreater: 489 return v > operandInt, nil 490 case OpEqual: 491 return v == operandInt, nil 492 } 493 494 case reflect.String: 495 switch op { 496 case OpEqual: 497 return value == operand.String(), nil 498 case OpContains: 499 return strings.Contains(value, operand.String()), nil 500 } 501 502 default: 503 return false, fmt.Errorf("unknown kind of operand %v", operand.Kind()) 504 } 505 506 return false, nil 507 }