github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/internal/pubsub/query/query.go (about) 1 // Package query implements the custom query format used to filter event 2 // subscriptions in Tendermint. 3 // 4 // Query expressions describe properties of events and their attributes, using 5 // strings like: 6 // 7 // abci.invoice.number = 22 AND abci.invoice.owner = 'Ivan' 8 // 9 // Query expressions can handle attribute values encoding numbers, strings, 10 // dates, and timestamps. The complete query grammar is described in the 11 // query/syntax package. 12 package query 13 14 import ( 15 "fmt" 16 "regexp" 17 "strconv" 18 "strings" 19 "time" 20 21 "github.com/ari-anchor/sei-tendermint/abci/types" 22 "github.com/ari-anchor/sei-tendermint/internal/pubsub/query/syntax" 23 ) 24 25 // All is a query that matches all events. 26 var All *Query 27 28 // A Query is the compiled form of a query. 29 type Query struct { 30 ast syntax.Query 31 conds []condition 32 } 33 34 // New parses and compiles the query expression into an executable query. 35 func New(query string) (*Query, error) { 36 ast, err := syntax.Parse(query) 37 if err != nil { 38 return nil, err 39 } 40 return Compile(ast) 41 } 42 43 // MustCompile compiles the query expression into an executable query. 44 // In case of error, MustCompile will panic. 45 // 46 // This is intended for use in program initialization; use query.New if you 47 // need to check errors. 48 func MustCompile(query string) *Query { 49 q, err := New(query) 50 if err != nil { 51 panic(err) 52 } 53 return q 54 } 55 56 // Compile compiles the given query AST so it can be used to match events. 57 func Compile(ast syntax.Query) (*Query, error) { 58 conds := make([]condition, len(ast)) 59 for i, q := range ast { 60 cond, err := compileCondition(q) 61 if err != nil { 62 return nil, fmt.Errorf("compile %s: %w", q, err) 63 } 64 conds[i] = cond 65 } 66 return &Query{ast: ast, conds: conds}, nil 67 } 68 69 // Matches reports whether q matches the given events. If q == nil, the query 70 // matches any non-empty collection of events. 71 func (q *Query) Matches(events []types.Event) bool { 72 if q == nil { 73 return true 74 } 75 for _, cond := range q.conds { 76 if !cond.matchesAny(events) { 77 return false 78 } 79 } 80 return len(events) != 0 81 } 82 83 // String matches part of the pubsub.Query interface. 84 func (q *Query) String() string { 85 if q == nil { 86 return "<empty>" 87 } 88 return q.ast.String() 89 } 90 91 // Syntax returns the syntax tree representation of q. 92 func (q *Query) Syntax() syntax.Query { 93 if q == nil { 94 return nil 95 } 96 return q.ast 97 } 98 99 // A condition is a compiled match condition. A condition matches an event if 100 // the event has the designated type, contains an attribute with the given 101 // name, and the match function returns true for the attribute value. 102 type condition struct { 103 tag string // e.g., "tx.hash" 104 match func(s string) bool 105 } 106 107 // findAttr returns a slice of attribute values from event matching the 108 // condition tag, and reports whether the event type strictly equals the 109 // condition tag. 110 func (c condition) findAttr(event types.Event) ([]string, bool) { 111 if !strings.HasPrefix(c.tag, event.Type) { 112 return nil, false // type does not match tag 113 } else if len(c.tag) == len(event.Type) { 114 return nil, true // type == tag 115 } 116 var vals []string 117 for _, attr := range event.Attributes { 118 fullName := event.Type + "." + string(attr.Key) 119 if fullName == c.tag { 120 vals = append(vals, string(attr.Value)) 121 } 122 } 123 return vals, false 124 } 125 126 // matchesAny reports whether c matches at least one of the given events. 127 func (c condition) matchesAny(events []types.Event) bool { 128 for _, event := range events { 129 if c.matchesEvent(event) { 130 return true 131 } 132 } 133 return false 134 } 135 136 // matchesEvent reports whether c matches the given event. 137 func (c condition) matchesEvent(event types.Event) bool { 138 vs, tagEqualsType := c.findAttr(event) 139 if len(vs) == 0 { 140 // As a special case, a condition tag that exactly matches the event type 141 // is matched against an empty string. This allows existence checks to 142 // work for type-only queries. 143 if tagEqualsType { 144 return c.match("") 145 } 146 return false 147 } 148 149 // At this point, we have candidate values. 150 for _, v := range vs { 151 if c.match(v) { 152 return true 153 } 154 } 155 return false 156 } 157 158 func compileCondition(cond syntax.Condition) (condition, error) { 159 out := condition{tag: cond.Tag} 160 161 // Handle existence checks separately to simplify the logic below for 162 // comparisons that take arguments. 163 if cond.Op == syntax.TExists { 164 out.match = func(string) bool { return true } 165 return out, nil 166 } 167 168 // All the other operators require an argument. 169 if cond.Arg == nil { 170 return condition{}, fmt.Errorf("missing argument for %v", cond.Op) 171 } 172 173 // Precompile the argument value matcher. 174 argType := cond.Arg.Type 175 var argValue interface{} 176 177 switch argType { 178 case syntax.TString: 179 argValue = cond.Arg.Value() 180 case syntax.TNumber: 181 argValue = cond.Arg.Number() 182 case syntax.TTime, syntax.TDate: 183 argValue = cond.Arg.Time() 184 default: 185 return condition{}, fmt.Errorf("unknown argument type %v", argType) 186 } 187 188 mcons := opTypeMap[cond.Op][argType] 189 if mcons == nil { 190 return condition{}, fmt.Errorf("invalid op/arg combination (%v, %v)", cond.Op, argType) 191 } 192 out.match = mcons(argValue) 193 return out, nil 194 } 195 196 // TODO(creachadair): The existing implementation allows anything number shaped 197 // to be treated as a number. This preserves the parts of that behavior we had 198 // tests for, but we should probably get rid of that. 199 var extractNum = regexp.MustCompile(`^\d+(\.\d+)?`) 200 201 func parseNumber(s string) (float64, error) { 202 return strconv.ParseFloat(extractNum.FindString(s), 64) 203 } 204 205 // A map of operator ⇒ argtype ⇒ match-constructor. 206 // An entry does not exist if the combination is not valid. 207 // 208 // Disable the dupl lint for this map. The result isn't even correct. 209 // 210 //nolint:dupl 211 var opTypeMap = map[syntax.Token]map[syntax.Token]func(interface{}) func(string) bool{ 212 syntax.TContains: { 213 syntax.TString: func(v interface{}) func(string) bool { 214 return func(s string) bool { 215 return strings.Contains(s, v.(string)) 216 } 217 }, 218 }, 219 syntax.TEq: { 220 syntax.TString: func(v interface{}) func(string) bool { 221 return func(s string) bool { return s == v.(string) } 222 }, 223 syntax.TNumber: func(v interface{}) func(string) bool { 224 return func(s string) bool { 225 w, err := parseNumber(s) 226 return err == nil && w == v.(float64) 227 } 228 }, 229 syntax.TDate: func(v interface{}) func(string) bool { 230 return func(s string) bool { 231 ts, err := syntax.ParseDate(s) 232 return err == nil && ts.Equal(v.(time.Time)) 233 } 234 }, 235 syntax.TTime: func(v interface{}) func(string) bool { 236 return func(s string) bool { 237 ts, err := syntax.ParseTime(s) 238 return err == nil && ts.Equal(v.(time.Time)) 239 } 240 }, 241 }, 242 syntax.TLt: { 243 syntax.TNumber: func(v interface{}) func(string) bool { 244 return func(s string) bool { 245 w, err := parseNumber(s) 246 return err == nil && w < v.(float64) 247 } 248 }, 249 syntax.TDate: func(v interface{}) func(string) bool { 250 return func(s string) bool { 251 ts, err := syntax.ParseDate(s) 252 return err == nil && ts.Before(v.(time.Time)) 253 } 254 }, 255 syntax.TTime: func(v interface{}) func(string) bool { 256 return func(s string) bool { 257 ts, err := syntax.ParseTime(s) 258 return err == nil && ts.Before(v.(time.Time)) 259 } 260 }, 261 }, 262 syntax.TLeq: { 263 syntax.TNumber: func(v interface{}) func(string) bool { 264 return func(s string) bool { 265 w, err := parseNumber(s) 266 return err == nil && w <= v.(float64) 267 } 268 }, 269 syntax.TDate: func(v interface{}) func(string) bool { 270 return func(s string) bool { 271 ts, err := syntax.ParseDate(s) 272 return err == nil && !ts.After(v.(time.Time)) 273 } 274 }, 275 syntax.TTime: func(v interface{}) func(string) bool { 276 return func(s string) bool { 277 ts, err := syntax.ParseTime(s) 278 return err == nil && !ts.After(v.(time.Time)) 279 } 280 }, 281 }, 282 syntax.TGt: { 283 syntax.TNumber: func(v interface{}) func(string) bool { 284 return func(s string) bool { 285 w, err := parseNumber(s) 286 return err == nil && w > v.(float64) 287 } 288 }, 289 syntax.TDate: func(v interface{}) func(string) bool { 290 return func(s string) bool { 291 ts, err := syntax.ParseDate(s) 292 return err == nil && ts.After(v.(time.Time)) 293 } 294 }, 295 syntax.TTime: func(v interface{}) func(string) bool { 296 return func(s string) bool { 297 ts, err := syntax.ParseTime(s) 298 return err == nil && ts.After(v.(time.Time)) 299 } 300 }, 301 }, 302 syntax.TGeq: { 303 syntax.TNumber: func(v interface{}) func(string) bool { 304 return func(s string) bool { 305 w, err := parseNumber(s) 306 return err == nil && w >= v.(float64) 307 } 308 }, 309 syntax.TDate: func(v interface{}) func(string) bool { 310 return func(s string) bool { 311 ts, err := syntax.ParseDate(s) 312 return err == nil && !ts.Before(v.(time.Time)) 313 } 314 }, 315 syntax.TTime: func(v interface{}) func(string) bool { 316 return func(s string) bool { 317 ts, err := syntax.ParseTime(s) 318 return err == nil && !ts.Before(v.(time.Time)) 319 } 320 }, 321 }, 322 }