github.com/evdatsion/aphelion-dpos-bft@v0.32.1/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 "strconv" 15 "strings" 16 "time" 17 ) 18 19 // Query holds the query string and the query parser. 20 type Query struct { 21 str string 22 parser *QueryParser 23 } 24 25 // Condition represents a single condition within a query and consists of tag 26 // (e.g. "tx.gas"), operator (e.g. "=") and operand (e.g. "7"). 27 type Condition struct { 28 Tag string 29 Op Operator 30 Operand interface{} 31 } 32 33 // New parses the given string and returns a query or error if the string is 34 // invalid. 35 func New(s string) (*Query, error) { 36 p := &QueryParser{Buffer: fmt.Sprintf(`"%s"`, s)} 37 p.Init() 38 if err := p.Parse(); err != nil { 39 return nil, err 40 } 41 return &Query{str: s, parser: p}, nil 42 } 43 44 // MustParse turns the given string into a query or panics; for tests or others 45 // cases where you know the string is valid. 46 func MustParse(s string) *Query { 47 q, err := New(s) 48 if err != nil { 49 panic(fmt.Sprintf("failed to parse %s: %v", s, err)) 50 } 51 return q 52 } 53 54 // String returns the original string. 55 func (q *Query) String() string { 56 return q.str 57 } 58 59 // Operator is an operator that defines some kind of relation between tag and 60 // operand (equality, etc.). 61 type Operator uint8 62 63 const ( 64 // "<=" 65 OpLessEqual Operator = iota 66 // ">=" 67 OpGreaterEqual 68 // "<" 69 OpLess 70 // ">" 71 OpGreater 72 // "=" 73 OpEqual 74 // "CONTAINS"; used to check if a string contains a certain sub string. 75 OpContains 76 ) 77 78 const ( 79 // DateLayout defines a layout for all dates (`DATE date`) 80 DateLayout = "2006-01-02" 81 // TimeLayout defines a layout for all times (`TIME time`) 82 TimeLayout = time.RFC3339 83 ) 84 85 // Conditions returns a list of conditions. 86 func (q *Query) Conditions() []Condition { 87 conditions := make([]Condition, 0) 88 89 buffer, begin, end := q.parser.Buffer, 0, 0 90 91 var tag string 92 var op Operator 93 94 // tokens must be in the following order: tag ("tx.gas") -> operator ("=") -> operand ("7") 95 for _, token := range q.parser.Tokens() { 96 switch token.pegRule { 97 98 case rulePegText: 99 begin, end = int(token.begin), int(token.end) 100 case ruletag: 101 tag = buffer[begin:end] 102 case rulele: 103 op = OpLessEqual 104 case rulege: 105 op = OpGreaterEqual 106 case rulel: 107 op = OpLess 108 case ruleg: 109 op = OpGreater 110 case ruleequal: 111 op = OpEqual 112 case rulecontains: 113 op = OpContains 114 case rulevalue: 115 // strip single quotes from value (i.e. "'NewBlock'" -> "NewBlock") 116 valueWithoutSingleQuotes := buffer[begin+1 : end-1] 117 conditions = append(conditions, Condition{tag, op, valueWithoutSingleQuotes}) 118 case rulenumber: 119 number := buffer[begin:end] 120 if strings.ContainsAny(number, ".") { // if it looks like a floating-point number 121 value, err := strconv.ParseFloat(number, 64) 122 if err != nil { 123 panic(fmt.Sprintf("got %v while trying to parse %s as float64 (should never happen if the grammar is correct)", err, number)) 124 } 125 conditions = append(conditions, Condition{tag, op, value}) 126 } else { 127 value, err := strconv.ParseInt(number, 10, 64) 128 if err != nil { 129 panic(fmt.Sprintf("got %v while trying to parse %s as int64 (should never happen if the grammar is correct)", err, number)) 130 } 131 conditions = append(conditions, Condition{tag, op, value}) 132 } 133 case ruletime: 134 value, err := time.Parse(TimeLayout, buffer[begin:end]) 135 if err != nil { 136 panic(fmt.Sprintf("got %v while trying to parse %s as time.Time / RFC3339 (should never happen if the grammar is correct)", err, buffer[begin:end])) 137 } 138 conditions = append(conditions, Condition{tag, op, value}) 139 case ruledate: 140 value, err := time.Parse("2006-01-02", buffer[begin:end]) 141 if err != nil { 142 panic(fmt.Sprintf("got %v while trying to parse %s as time.Time / '2006-01-02' (should never happen if the grammar is correct)", err, buffer[begin:end])) 143 } 144 conditions = append(conditions, Condition{tag, op, value}) 145 } 146 } 147 148 return conditions 149 } 150 151 // Matches returns true if the query matches against any event in the given set 152 // of events, false otherwise. For each event, a match exists if the query is 153 // matched against *any* value in a slice of values. 154 // 155 // For example, query "name=John" matches events = {"name": ["John", "Eric"]}. 156 // More examples could be found in parser_test.go and query_test.go. 157 func (q *Query) Matches(events map[string][]string) bool { 158 if len(events) == 0 { 159 return false 160 } 161 162 buffer, begin, end := q.parser.Buffer, 0, 0 163 164 var tag string 165 var op Operator 166 167 // tokens must be in the following order: 168 // tag ("tx.gas") -> operator ("=") -> operand ("7") 169 for _, token := range q.parser.Tokens() { 170 switch token.pegRule { 171 172 case rulePegText: 173 begin, end = int(token.begin), int(token.end) 174 case ruletag: 175 tag = buffer[begin:end] 176 case rulele: 177 op = OpLessEqual 178 case rulege: 179 op = OpGreaterEqual 180 case rulel: 181 op = OpLess 182 case ruleg: 183 op = OpGreater 184 case ruleequal: 185 op = OpEqual 186 case rulecontains: 187 op = OpContains 188 case rulevalue: 189 // strip single quotes from value (i.e. "'NewBlock'" -> "NewBlock") 190 valueWithoutSingleQuotes := buffer[begin+1 : end-1] 191 192 // see if the triplet (tag, operator, operand) matches any tag 193 // "tx.gas", "=", "7", { "tx.gas": 7, "tx.ID": "4AE393495334" } 194 if !match(tag, op, reflect.ValueOf(valueWithoutSingleQuotes), events) { 195 return false 196 } 197 case rulenumber: 198 number := buffer[begin:end] 199 if strings.ContainsAny(number, ".") { // if it looks like a floating-point number 200 value, err := strconv.ParseFloat(number, 64) 201 if err != nil { 202 panic(fmt.Sprintf("got %v while trying to parse %s as float64 (should never happen if the grammar is correct)", err, number)) 203 } 204 if !match(tag, op, reflect.ValueOf(value), events) { 205 return false 206 } 207 } else { 208 value, err := strconv.ParseInt(number, 10, 64) 209 if err != nil { 210 panic(fmt.Sprintf("got %v while trying to parse %s as int64 (should never happen if the grammar is correct)", err, number)) 211 } 212 if !match(tag, op, reflect.ValueOf(value), events) { 213 return false 214 } 215 } 216 case ruletime: 217 value, err := time.Parse(TimeLayout, buffer[begin:end]) 218 if err != nil { 219 panic(fmt.Sprintf("got %v while trying to parse %s as time.Time / RFC3339 (should never happen if the grammar is correct)", err, buffer[begin:end])) 220 } 221 if !match(tag, op, reflect.ValueOf(value), events) { 222 return false 223 } 224 case ruledate: 225 value, err := time.Parse("2006-01-02", buffer[begin:end]) 226 if err != nil { 227 panic(fmt.Sprintf("got %v while trying to parse %s as time.Time / '2006-01-02' (should never happen if the grammar is correct)", err, buffer[begin:end])) 228 } 229 if !match(tag, op, reflect.ValueOf(value), events) { 230 return false 231 } 232 } 233 } 234 235 return true 236 } 237 238 // match returns true if the given triplet (tag, operator, operand) matches any 239 // value in an event for that key. 240 // 241 // First, it looks up the key in the events and if it finds one, tries to compare 242 // all the values from it to the operand using the operator. 243 // 244 // "tx.gas", "=", "7", {"tx": [{"gas": 7, "ID": "4AE393495334"}]} 245 func match(tag string, op Operator, operand reflect.Value, events map[string][]string) bool { 246 // look up the tag from the query in tags 247 values, ok := events[tag] 248 if !ok { 249 return false 250 } 251 252 for _, value := range values { 253 // return true if any value in the set of the event's values matches 254 if matchValue(value, op, operand) { 255 return true 256 } 257 } 258 259 return false 260 } 261 262 // matchValue will attempt to match a string value against an operation an 263 // operand. A boolean is returned representing the match result. It will panic 264 // if an error occurs or if the operand is invalid. 265 func matchValue(value string, op Operator, operand reflect.Value) bool { 266 switch operand.Kind() { 267 case reflect.Struct: // time 268 operandAsTime := operand.Interface().(time.Time) 269 270 // try our best to convert value from tags to time.Time 271 var ( 272 v time.Time 273 err error 274 ) 275 276 if strings.ContainsAny(value, "T") { 277 v, err = time.Parse(TimeLayout, value) 278 } else { 279 v, err = time.Parse(DateLayout, value) 280 } 281 if err != nil { 282 panic(fmt.Sprintf("failed to convert value %v from tag to time.Time: %v", value, err)) 283 } 284 285 switch op { 286 case OpLessEqual: 287 return v.Before(operandAsTime) || v.Equal(operandAsTime) 288 case OpGreaterEqual: 289 return v.Equal(operandAsTime) || v.After(operandAsTime) 290 case OpLess: 291 return v.Before(operandAsTime) 292 case OpGreater: 293 return v.After(operandAsTime) 294 case OpEqual: 295 return v.Equal(operandAsTime) 296 } 297 298 case reflect.Float64: 299 operandFloat64 := operand.Interface().(float64) 300 var v float64 301 302 // try our best to convert value from tags to float64 303 v, err := strconv.ParseFloat(value, 64) 304 if err != nil { 305 panic(fmt.Sprintf("failed to convert value %v from tag to float64: %v", value, err)) 306 } 307 308 switch op { 309 case OpLessEqual: 310 return v <= operandFloat64 311 case OpGreaterEqual: 312 return v >= operandFloat64 313 case OpLess: 314 return v < operandFloat64 315 case OpGreater: 316 return v > operandFloat64 317 case OpEqual: 318 return v == operandFloat64 319 } 320 321 case reflect.Int64: 322 operandInt := operand.Interface().(int64) 323 var v int64 324 // if value looks like float, we try to parse it as float 325 if strings.ContainsAny(value, ".") { 326 v1, err := strconv.ParseFloat(value, 64) 327 if err != nil { 328 panic(fmt.Sprintf("failed to convert value %v from tag to float64: %v", value, err)) 329 } 330 v = int64(v1) 331 } else { 332 var err error 333 // try our best to convert value from tags to int64 334 v, err = strconv.ParseInt(value, 10, 64) 335 if err != nil { 336 panic(fmt.Sprintf("failed to convert value %v from tag to int64: %v", value, err)) 337 } 338 } 339 switch op { 340 case OpLessEqual: 341 return v <= operandInt 342 case OpGreaterEqual: 343 return v >= operandInt 344 case OpLess: 345 return v < operandInt 346 case OpGreater: 347 return v > operandInt 348 case OpEqual: 349 return v == operandInt 350 } 351 352 case reflect.String: 353 switch op { 354 case OpEqual: 355 return value == operand.String() 356 case OpContains: 357 return strings.Contains(value, operand.String()) 358 } 359 360 default: 361 panic(fmt.Sprintf("unknown kind of operand %v", operand.Kind())) 362 } 363 364 return false 365 }