github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/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 "math/big" 14 "reflect" 15 "regexp" 16 "strconv" 17 "strings" 18 "time" 19 ) 20 21 var ( 22 numRegex = regexp.MustCompile(`([0-9\.]+)`) 23 ) 24 25 // Query holds the query string and the query parser. 26 type Query struct { 27 str string 28 parser *QueryParser 29 } 30 31 // Condition represents a single condition within a query and consists of composite key 32 // (e.g. "tx.gas"), operator (e.g. "=") and operand (e.g. "7"). 33 type Condition struct { 34 CompositeKey string 35 Op Operator 36 Operand interface{} 37 } 38 39 // New parses the given string and returns a query or error if the string is 40 // invalid. 41 func New(s string) (*Query, error) { 42 p := &QueryParser{Buffer: fmt.Sprintf(`"%s"`, s)} 43 if err := p.Init(); err != nil { 44 return nil, err 45 } 46 if err := p.Parse(); err != nil { 47 return nil, err 48 } 49 return &Query{str: s, parser: p}, nil 50 } 51 52 // MustParse turns the given string into a query or panics; for tests or others 53 // cases where you know the string is valid. 54 func MustParse(s string) *Query { 55 q, err := New(s) 56 if err != nil { 57 panic(fmt.Sprintf("failed to parse %s: %v", s, err)) 58 } 59 return q 60 } 61 62 // String returns the original string. 63 func (q *Query) String() string { 64 return q.str 65 } 66 67 // Operator is an operator that defines some kind of relation between composite key and 68 // operand (equality, etc.). 69 type Operator uint8 70 71 const ( 72 // "<=" 73 OpLessEqual Operator = iota 74 // ">=" 75 OpGreaterEqual 76 // "<" 77 OpLess 78 // ">" 79 OpGreater 80 // "=" 81 OpEqual 82 // "CONTAINS"; used to check if a string contains a certain sub string. 83 OpContains 84 // "EXISTS"; used to check if a certain event attribute is present. 85 OpExists 86 ) 87 88 const ( 89 // DateLayout defines a layout for all dates (`DATE date`) 90 DateLayout = "2006-01-02" 91 // TimeLayout defines a layout for all times (`TIME time`) 92 TimeLayout = time.RFC3339 93 ) 94 95 // Conditions returns a list of conditions. It returns an error if there is any 96 // error with the provided grammar in the Query. 97 func (q *Query) Conditions() ([]Condition, error) { 98 var ( 99 eventAttr string 100 op Operator 101 ) 102 103 conditions := make([]Condition, 0) 104 buffer, begin, end := q.parser.Buffer, 0, 0 105 106 // tokens must be in the following order: tag ("tx.gas") -> operator ("=") -> operand ("7") 107 for _, token := range q.parser.Tokens() { 108 switch token.pegRule { 109 case rulePegText: 110 begin, end = int(token.begin), int(token.end) 111 112 case ruletag: 113 eventAttr = buffer[begin:end] 114 115 case rulele: 116 op = OpLessEqual 117 118 case rulege: 119 op = OpGreaterEqual 120 121 case rulel: 122 op = OpLess 123 124 case ruleg: 125 op = OpGreater 126 127 case ruleequal: 128 op = OpEqual 129 130 case rulecontains: 131 op = OpContains 132 133 case ruleexists: 134 op = OpExists 135 conditions = append(conditions, Condition{eventAttr, op, nil}) 136 137 case rulevalue: 138 // strip single quotes from value (i.e. "'NewBlock'" -> "NewBlock") 139 valueWithoutSingleQuotes := buffer[begin+1 : end-1] 140 conditions = append(conditions, Condition{eventAttr, op, valueWithoutSingleQuotes}) 141 142 case rulenumber: 143 number := buffer[begin:end] 144 if strings.ContainsAny(number, ".") { // if it looks like a floating-point number 145 value, err := strconv.ParseFloat(number, 64) 146 if err != nil { 147 err = fmt.Errorf( 148 "got %v while trying to parse %s as float64 (should never happen if the grammar is correct)", 149 err, number, 150 ) 151 return nil, err 152 } 153 154 conditions = append(conditions, Condition{eventAttr, op, value}) 155 } else { 156 valueBig := new(big.Int) 157 158 _, ok := valueBig.SetString(number, 10) 159 if !ok { 160 err := fmt.Errorf( 161 "problem parsing %s as bigint (should never happen if the grammar is correct)", 162 number, 163 ) 164 return nil, err 165 } 166 conditions = append(conditions, Condition{eventAttr, op, valueBig}) 167 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 := new(big.Int) 306 _, ok := value.SetString(number, 10) 307 308 if !ok { 309 err := fmt.Errorf( 310 "problem parsing %s as bigInt (should never happen if the grammar is correct)", 311 number, 312 ) 313 return false, err 314 } 315 316 match, err := match(eventAttr, op, reflect.ValueOf(value), events) 317 if err != nil { 318 return false, err 319 } 320 321 if !match { 322 return false, nil 323 } 324 } 325 326 case ruletime: 327 value, err := time.Parse(TimeLayout, buffer[begin:end]) 328 if err != nil { 329 err = fmt.Errorf( 330 "got %v while trying to parse %s as time.Time / RFC3339 (should never happen if the grammar is correct)", 331 err, buffer[begin:end], 332 ) 333 return false, err 334 } 335 336 match, err := match(eventAttr, op, reflect.ValueOf(value), events) 337 if err != nil { 338 return false, err 339 } 340 341 if !match { 342 return false, nil 343 } 344 345 case ruledate: 346 value, err := time.Parse("2006-01-02", buffer[begin:end]) 347 if err != nil { 348 err = fmt.Errorf( 349 "got %v while trying to parse %s as time.Time / '2006-01-02' (should never happen if the grammar is correct)", 350 err, buffer[begin:end], 351 ) 352 return false, err 353 } 354 355 match, err := match(eventAttr, op, reflect.ValueOf(value), events) 356 if err != nil { 357 return false, err 358 } 359 360 if !match { 361 return false, nil 362 } 363 } 364 } 365 366 return true, nil 367 } 368 369 // match returns true if the given triplet (attribute, operator, operand) matches 370 // any value in an event for that attribute. If any match fails with an error, 371 // that error is returned. 372 // 373 // First, it looks up the key in the events and if it finds one, tries to compare 374 // all the values from it to the operand using the operator. 375 // 376 // "tx.gas", "=", "7", {"tx": [{"gas": 7, "ID": "4AE393495334"}]} 377 func match(attr string, op Operator, operand reflect.Value, events map[string][]string) (bool, error) { 378 // look up the tag from the query in tags 379 values, ok := events[attr] 380 if !ok { 381 return false, nil 382 } 383 384 for _, value := range values { 385 // return true if any value in the set of the event's values matches 386 match, err := matchValue(value, op, operand) 387 if err != nil { 388 return false, err 389 } 390 391 if match { 392 return true, nil 393 } 394 } 395 396 return false, nil 397 } 398 399 // matchValue will attempt to match a string value against an operator an 400 // operand. A boolean is returned representing the match result. It will return 401 // an error if the value cannot be parsed and matched against the operand type. 402 func matchValue(value string, op Operator, operand reflect.Value) (bool, error) { 403 switch operand.Kind() { 404 case reflect.Struct: // time 405 operandAsTime := operand.Interface().(time.Time) 406 407 // try our best to convert value from events to time.Time 408 var ( 409 v time.Time 410 err error 411 ) 412 413 if strings.ContainsAny(value, "T") { 414 v, err = time.Parse(TimeLayout, value) 415 } else { 416 v, err = time.Parse(DateLayout, value) 417 } 418 if err != nil { 419 return false, fmt.Errorf("failed to convert value %v from event attribute to time.Time: %w", value, err) 420 } 421 422 switch op { 423 case OpLessEqual: 424 return (v.Before(operandAsTime) || v.Equal(operandAsTime)), nil 425 case OpGreaterEqual: 426 return (v.Equal(operandAsTime) || v.After(operandAsTime)), nil 427 case OpLess: 428 return v.Before(operandAsTime), nil 429 case OpGreater: 430 return v.After(operandAsTime), nil 431 case OpEqual: 432 return v.Equal(operandAsTime), nil 433 } 434 435 case reflect.Float64: 436 var v float64 437 438 operandFloat64 := operand.Interface().(float64) 439 filteredValue := numRegex.FindString(value) 440 441 // try our best to convert value from tags to float64 442 v, err := strconv.ParseFloat(filteredValue, 64) 443 if err != nil { 444 return false, fmt.Errorf("failed to convert value %v from event attribute to float64: %w", filteredValue, err) 445 } 446 447 switch op { 448 case OpLessEqual: 449 return v <= operandFloat64, nil 450 case OpGreaterEqual: 451 return v >= operandFloat64, nil 452 case OpLess: 453 return v < operandFloat64, nil 454 case OpGreater: 455 return v > operandFloat64, nil 456 case OpEqual: 457 return v == operandFloat64, nil 458 } 459 460 case reflect.Pointer: 461 462 switch operand.Interface().(type) { 463 case *big.Int: 464 filteredValue := numRegex.FindString(value) 465 operandVal := operand.Interface().(*big.Int) 466 v := new(big.Int) 467 if strings.ContainsAny(filteredValue, ".") { 468 // We do this just to check whether the string can be parsed as a float 469 _, err := strconv.ParseFloat(filteredValue, 64) 470 if err != nil { 471 err = fmt.Errorf( 472 "got %v while trying to parse %s as float64 (should never happen if the grammar is correct)", 473 err, filteredValue, 474 ) 475 return false, err 476 } 477 478 // If yes, we get the int part of the string. 479 // We could simply cast the float to an int and use that to create a big int but 480 // if it is a number bigger than int64, it will not be parsed properly. 481 // If we use bigFloat and convert that to a string, the values will be rounded which 482 // is not what we want either. 483 // Here we are simulating the behavior that int64(floatValue). This was the default behavior 484 // before introducing BigInts and we do not want to break the logic in minor releases. 485 _, ok := v.SetString(strings.Split(filteredValue, ".")[0], 10) 486 if !ok { 487 return false, fmt.Errorf("failed to convert value %s from float to big int", filteredValue) 488 } 489 } else { 490 // try our best to convert value from tags to big int 491 _, ok := v.SetString(filteredValue, 10) 492 493 if !ok { 494 return false, fmt.Errorf("failed to convert value %v from event attribute to big int", filteredValue) 495 } 496 497 } 498 cmpRes := operandVal.Cmp(v) 499 switch op { 500 case OpLessEqual: 501 return cmpRes == 0 || cmpRes == 1, nil 502 case OpGreaterEqual: 503 return cmpRes == 0 || cmpRes == -1, nil 504 case OpLess: 505 return cmpRes == 1, nil 506 case OpGreater: 507 return cmpRes == -1, nil 508 case OpEqual: 509 return cmpRes == 0, nil 510 } 511 512 } 513 case reflect.String: 514 switch op { 515 case OpEqual: 516 return value == operand.String(), nil 517 case OpContains: 518 return strings.Contains(value, operand.String()), nil 519 } 520 521 default: 522 return false, fmt.Errorf("unknown kind of operand %v", operand.Kind()) 523 } 524 525 return false, nil 526 }