github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/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  	"math/big"
    14  	"reflect"
    15  	"regexp"
    16  	"strconv"
    17  	"strings"
    18  	"time"
    19  )
    20  
    21  var (
    22  	numRegex = regexp.MustCompile(`([0-9\.]+)`)
    23  )
    24  
    25  // Query holds the query string and the query parser.
    26  type Query struct {
    27  	str    string
    28  	parser *QueryParser
    29  }
    30  
    31  // Condition represents a single condition within a query and consists of composite key
    32  // (e.g. "tx.gas"), operator (e.g. "=") and operand (e.g. "7").
    33  type Condition struct {
    34  	CompositeKey string
    35  	Op           Operator
    36  	Operand      interface{}
    37  }
    38  
    39  // New parses the given string and returns a query or error if the string is
    40  // invalid.
    41  func New(s string) (*Query, error) {
    42  	p := &QueryParser{Buffer: fmt.Sprintf(`"%s"`, s)}
    43  	if err := p.Init(); err != nil {
    44  		return nil, err
    45  	}
    46  	if err := p.Parse(); err != nil {
    47  		return nil, err
    48  	}
    49  	return &Query{str: s, parser: p}, nil
    50  }
    51  
    52  // MustParse turns the given string into a query or panics; for tests or others
    53  // cases where you know the string is valid.
    54  func MustParse(s string) *Query {
    55  	q, err := New(s)
    56  	if err != nil {
    57  		panic(fmt.Sprintf("failed to parse %s: %v", s, err))
    58  	}
    59  	return q
    60  }
    61  
    62  // String returns the original string.
    63  func (q *Query) String() string {
    64  	return q.str
    65  }
    66  
    67  // Operator is an operator that defines some kind of relation between composite key and
    68  // operand (equality, etc.).
    69  type Operator uint8
    70  
    71  const (
    72  	// "<="
    73  	OpLessEqual Operator = iota
    74  	// ">="
    75  	OpGreaterEqual
    76  	// "<"
    77  	OpLess
    78  	// ">"
    79  	OpGreater
    80  	// "="
    81  	OpEqual
    82  	// "CONTAINS"; used to check if a string contains a certain sub string.
    83  	OpContains
    84  	// "EXISTS"; used to check if a certain event attribute is present.
    85  	OpExists
    86  )
    87  
    88  const (
    89  	// DateLayout defines a layout for all dates (`DATE date`)
    90  	DateLayout = "2006-01-02"
    91  	// TimeLayout defines a layout for all times (`TIME time`)
    92  	TimeLayout = time.RFC3339
    93  )
    94  
    95  // Conditions returns a list of conditions. It returns an error if there is any
    96  // error with the provided grammar in the Query.
    97  func (q *Query) Conditions() ([]Condition, error) {
    98  	var (
    99  		eventAttr string
   100  		op        Operator
   101  	)
   102  
   103  	conditions := make([]Condition, 0)
   104  	buffer, begin, end := q.parser.Buffer, 0, 0
   105  
   106  	// tokens must be in the following order: tag ("tx.gas") -> operator ("=") -> operand ("7")
   107  	for _, token := range q.parser.Tokens() {
   108  		switch token.pegRule {
   109  		case rulePegText:
   110  			begin, end = int(token.begin), int(token.end)
   111  
   112  		case ruletag:
   113  			eventAttr = buffer[begin:end]
   114  
   115  		case rulele:
   116  			op = OpLessEqual
   117  
   118  		case rulege:
   119  			op = OpGreaterEqual
   120  
   121  		case rulel:
   122  			op = OpLess
   123  
   124  		case ruleg:
   125  			op = OpGreater
   126  
   127  		case ruleequal:
   128  			op = OpEqual
   129  
   130  		case rulecontains:
   131  			op = OpContains
   132  
   133  		case ruleexists:
   134  			op = OpExists
   135  			conditions = append(conditions, Condition{eventAttr, op, nil})
   136  
   137  		case rulevalue:
   138  			// strip single quotes from value (i.e. "'NewBlock'" -> "NewBlock")
   139  			valueWithoutSingleQuotes := buffer[begin+1 : end-1]
   140  			conditions = append(conditions, Condition{eventAttr, op, valueWithoutSingleQuotes})
   141  
   142  		case rulenumber:
   143  			number := buffer[begin:end]
   144  			if strings.ContainsAny(number, ".") { // if it looks like a floating-point number
   145  				value, err := strconv.ParseFloat(number, 64)
   146  				if err != nil {
   147  					err = fmt.Errorf(
   148  						"got %v while trying to parse %s as float64 (should never happen if the grammar is correct)",
   149  						err, number,
   150  					)
   151  					return nil, err
   152  				}
   153  
   154  				conditions = append(conditions, Condition{eventAttr, op, value})
   155  			} else {
   156  				valueBig := new(big.Int)
   157  
   158  				_, ok := valueBig.SetString(number, 10)
   159  				if !ok {
   160  					err := fmt.Errorf(
   161  						"problem parsing %s as bigint (should never happen if the grammar is correct)",
   162  						number,
   163  					)
   164  					return nil, err
   165  				}
   166  				conditions = append(conditions, Condition{eventAttr, op, valueBig})
   167  
   168  			}
   169  
   170  		case ruletime:
   171  			value, err := time.Parse(TimeLayout, buffer[begin:end])
   172  			if err != nil {
   173  				err = fmt.Errorf(
   174  					"got %v while trying to parse %s as time.Time / RFC3339 (should never happen if the grammar is correct)",
   175  					err, buffer[begin:end],
   176  				)
   177  				return nil, err
   178  			}
   179  
   180  			conditions = append(conditions, Condition{eventAttr, op, value})
   181  
   182  		case ruledate:
   183  			value, err := time.Parse("2006-01-02", buffer[begin:end])
   184  			if err != nil {
   185  				err = fmt.Errorf(
   186  					"got %v while trying to parse %s as time.Time / '2006-01-02' (should never happen if the grammar is correct)",
   187  					err, buffer[begin:end],
   188  				)
   189  				return nil, err
   190  			}
   191  
   192  			conditions = append(conditions, Condition{eventAttr, op, value})
   193  		}
   194  	}
   195  
   196  	return conditions, nil
   197  }
   198  
   199  // Matches returns true if the query matches against any event in the given set
   200  // of events, false otherwise. For each event, a match exists if the query is
   201  // matched against *any* value in a slice of values. An error is returned if
   202  // any attempted event match returns an error.
   203  //
   204  // For example, query "name=John" matches events = {"name": ["John", "Eric"]}.
   205  // More examples could be found in parser_test.go and query_test.go.
   206  func (q *Query) Matches(events map[string][]string) (bool, error) {
   207  	if len(events) == 0 {
   208  		return false, nil
   209  	}
   210  
   211  	var (
   212  		eventAttr string
   213  		op        Operator
   214  	)
   215  
   216  	buffer, begin, end := q.parser.Buffer, 0, 0
   217  
   218  	// tokens must be in the following order:
   219  
   220  	// tag ("tx.gas") -> operator ("=") -> operand ("7")
   221  	for _, token := range q.parser.Tokens() {
   222  		switch token.pegRule {
   223  		case rulePegText:
   224  			begin, end = int(token.begin), int(token.end)
   225  
   226  		case ruletag:
   227  			eventAttr = buffer[begin:end]
   228  
   229  		case rulele:
   230  			op = OpLessEqual
   231  
   232  		case rulege:
   233  			op = OpGreaterEqual
   234  
   235  		case rulel:
   236  			op = OpLess
   237  
   238  		case ruleg:
   239  			op = OpGreater
   240  
   241  		case ruleequal:
   242  			op = OpEqual
   243  
   244  		case rulecontains:
   245  			op = OpContains
   246  		case ruleexists:
   247  			op = OpExists
   248  			if strings.Contains(eventAttr, ".") {
   249  				// Searching for a full "type.attribute" event.
   250  				_, ok := events[eventAttr]
   251  				if !ok {
   252  					return false, nil
   253  				}
   254  			} else {
   255  				foundEvent := false
   256  
   257  			loop:
   258  				for compositeKey := range events {
   259  					if strings.Index(compositeKey, eventAttr) == 0 {
   260  						foundEvent = true
   261  						break loop
   262  					}
   263  				}
   264  				if !foundEvent {
   265  					return false, nil
   266  				}
   267  			}
   268  
   269  		case rulevalue:
   270  			// strip single quotes from value (i.e. "'NewBlock'" -> "NewBlock")
   271  			valueWithoutSingleQuotes := buffer[begin+1 : end-1]
   272  
   273  			// see if the triplet (event attribute, operator, operand) matches any event
   274  			// "tx.gas", "=", "7", { "tx.gas": 7, "tx.ID": "4AE393495334" }
   275  			match, err := match(eventAttr, op, reflect.ValueOf(valueWithoutSingleQuotes), events)
   276  			if err != nil {
   277  				return false, err
   278  			}
   279  
   280  			if !match {
   281  				return false, nil
   282  			}
   283  
   284  		case rulenumber:
   285  			number := buffer[begin:end]
   286  			if strings.ContainsAny(number, ".") { // if it looks like a floating-point number
   287  				value, err := strconv.ParseFloat(number, 64)
   288  				if err != nil {
   289  					err = fmt.Errorf(
   290  						"got %v while trying to parse %s as float64 (should never happen if the grammar is correct)",
   291  						err, number,
   292  					)
   293  					return false, err
   294  				}
   295  
   296  				match, err := match(eventAttr, op, reflect.ValueOf(value), events)
   297  				if err != nil {
   298  					return false, err
   299  				}
   300  
   301  				if !match {
   302  					return false, nil
   303  				}
   304  			} else {
   305  				value := new(big.Int)
   306  				_, ok := value.SetString(number, 10)
   307  
   308  				if !ok {
   309  					err := fmt.Errorf(
   310  						"problem parsing %s as bigInt (should never happen if the grammar is correct)",
   311  						number,
   312  					)
   313  					return false, err
   314  				}
   315  
   316  				match, err := match(eventAttr, op, reflect.ValueOf(value), events)
   317  				if err != nil {
   318  					return false, err
   319  				}
   320  
   321  				if !match {
   322  					return false, nil
   323  				}
   324  			}
   325  
   326  		case ruletime:
   327  			value, err := time.Parse(TimeLayout, buffer[begin:end])
   328  			if err != nil {
   329  				err = fmt.Errorf(
   330  					"got %v while trying to parse %s as time.Time / RFC3339 (should never happen if the grammar is correct)",
   331  					err, buffer[begin:end],
   332  				)
   333  				return false, err
   334  			}
   335  
   336  			match, err := match(eventAttr, op, reflect.ValueOf(value), events)
   337  			if err != nil {
   338  				return false, err
   339  			}
   340  
   341  			if !match {
   342  				return false, nil
   343  			}
   344  
   345  		case ruledate:
   346  			value, err := time.Parse("2006-01-02", buffer[begin:end])
   347  			if err != nil {
   348  				err = fmt.Errorf(
   349  					"got %v while trying to parse %s as time.Time / '2006-01-02' (should never happen if the grammar is correct)",
   350  					err, buffer[begin:end],
   351  				)
   352  				return false, err
   353  			}
   354  
   355  			match, err := match(eventAttr, op, reflect.ValueOf(value), events)
   356  			if err != nil {
   357  				return false, err
   358  			}
   359  
   360  			if !match {
   361  				return false, nil
   362  			}
   363  		}
   364  	}
   365  
   366  	return true, nil
   367  }
   368  
   369  // match returns true if the given triplet (attribute, operator, operand) matches
   370  // any value in an event for that attribute. If any match fails with an error,
   371  // that error is returned.
   372  //
   373  // First, it looks up the key in the events and if it finds one, tries to compare
   374  // all the values from it to the operand using the operator.
   375  //
   376  // "tx.gas", "=", "7", {"tx": [{"gas": 7, "ID": "4AE393495334"}]}
   377  func match(attr string, op Operator, operand reflect.Value, events map[string][]string) (bool, error) {
   378  	// look up the tag from the query in tags
   379  	values, ok := events[attr]
   380  	if !ok {
   381  		return false, nil
   382  	}
   383  
   384  	for _, value := range values {
   385  		// return true if any value in the set of the event's values matches
   386  		match, err := matchValue(value, op, operand)
   387  		if err != nil {
   388  			return false, err
   389  		}
   390  
   391  		if match {
   392  			return true, nil
   393  		}
   394  	}
   395  
   396  	return false, nil
   397  }
   398  
   399  // matchValue will attempt to match a string value against an operator an
   400  // operand. A boolean is returned representing the match result. It will return
   401  // an error if the value cannot be parsed and matched against the operand type.
   402  func matchValue(value string, op Operator, operand reflect.Value) (bool, error) {
   403  	switch operand.Kind() {
   404  	case reflect.Struct: // time
   405  		operandAsTime := operand.Interface().(time.Time)
   406  
   407  		// try our best to convert value from events to time.Time
   408  		var (
   409  			v   time.Time
   410  			err error
   411  		)
   412  
   413  		if strings.ContainsAny(value, "T") {
   414  			v, err = time.Parse(TimeLayout, value)
   415  		} else {
   416  			v, err = time.Parse(DateLayout, value)
   417  		}
   418  		if err != nil {
   419  			return false, fmt.Errorf("failed to convert value %v from event attribute to time.Time: %w", value, err)
   420  		}
   421  
   422  		switch op {
   423  		case OpLessEqual:
   424  			return (v.Before(operandAsTime) || v.Equal(operandAsTime)), nil
   425  		case OpGreaterEqual:
   426  			return (v.Equal(operandAsTime) || v.After(operandAsTime)), nil
   427  		case OpLess:
   428  			return v.Before(operandAsTime), nil
   429  		case OpGreater:
   430  			return v.After(operandAsTime), nil
   431  		case OpEqual:
   432  			return v.Equal(operandAsTime), nil
   433  		}
   434  
   435  	case reflect.Float64:
   436  		var v float64
   437  
   438  		operandFloat64 := operand.Interface().(float64)
   439  		filteredValue := numRegex.FindString(value)
   440  
   441  		// try our best to convert value from tags to float64
   442  		v, err := strconv.ParseFloat(filteredValue, 64)
   443  		if err != nil {
   444  			return false, fmt.Errorf("failed to convert value %v from event attribute to float64: %w", filteredValue, err)
   445  		}
   446  
   447  		switch op {
   448  		case OpLessEqual:
   449  			return v <= operandFloat64, nil
   450  		case OpGreaterEqual:
   451  			return v >= operandFloat64, nil
   452  		case OpLess:
   453  			return v < operandFloat64, nil
   454  		case OpGreater:
   455  			return v > operandFloat64, nil
   456  		case OpEqual:
   457  			return v == operandFloat64, nil
   458  		}
   459  
   460  	case reflect.Pointer:
   461  
   462  		switch operand.Interface().(type) {
   463  		case *big.Int:
   464  			filteredValue := numRegex.FindString(value)
   465  			operandVal := operand.Interface().(*big.Int)
   466  			v := new(big.Int)
   467  			if strings.ContainsAny(filteredValue, ".") {
   468  				// We do this just to check whether the string can be parsed as a float
   469  				_, err := strconv.ParseFloat(filteredValue, 64)
   470  				if err != nil {
   471  					err = fmt.Errorf(
   472  						"got %v while trying to parse %s as float64 (should never happen if the grammar is correct)",
   473  						err, filteredValue,
   474  					)
   475  					return false, err
   476  				}
   477  
   478  				// If yes, we get the int part of the  string.
   479  				// We could simply cast the float to an int and use that to create a big int but
   480  				// if it is a number bigger than int64, it will not be parsed properly.
   481  				// If we use bigFloat and convert that to a string, the values will be rounded which
   482  				// is not what we want either.
   483  				// Here we are simulating the behavior that int64(floatValue). This was the default behavior
   484  				// before introducing BigInts and we do not want to break the logic in minor releases.
   485  				_, ok := v.SetString(strings.Split(filteredValue, ".")[0], 10)
   486  				if !ok {
   487  					return false, fmt.Errorf("failed to convert value %s from float to big int", filteredValue)
   488  				}
   489  			} else {
   490  				// try our best to convert value from tags to big int
   491  				_, ok := v.SetString(filteredValue, 10)
   492  
   493  				if !ok {
   494  					return false, fmt.Errorf("failed to convert value %v from event attribute to big int", filteredValue)
   495  				}
   496  
   497  			}
   498  			cmpRes := operandVal.Cmp(v)
   499  			switch op {
   500  			case OpLessEqual:
   501  				return cmpRes == 0 || cmpRes == 1, nil
   502  			case OpGreaterEqual:
   503  				return cmpRes == 0 || cmpRes == -1, nil
   504  			case OpLess:
   505  				return cmpRes == 1, nil
   506  			case OpGreater:
   507  				return cmpRes == -1, nil
   508  			case OpEqual:
   509  				return cmpRes == 0, nil
   510  			}
   511  
   512  		}
   513  	case reflect.String:
   514  		switch op {
   515  		case OpEqual:
   516  			return value == operand.String(), nil
   517  		case OpContains:
   518  			return strings.Contains(value, operand.String()), nil
   519  		}
   520  
   521  	default:
   522  		return false, fmt.Errorf("unknown kind of operand %v", operand.Kind())
   523  	}
   524  
   525  	return false, nil
   526  }