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