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