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