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