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