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  }