github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/libs/pubsub/query/query.go (about)

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