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