github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/event/query/expression.go (about)

     1  package query
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/hyperledger/burrow/logging/errors"
    10  )
    11  
    12  const (
    13  	// DateLayout defines a layout for all dates (`DATE date`)
    14  	DateLayout = "2006-01-02"
    15  	// TimeLayout defines a layout for all times (`TIME time`)
    16  	TimeLayout = time.RFC3339
    17  )
    18  
    19  // Operator is an operator that defines some kind of relation between tag and
    20  // operand (equality, etc.).
    21  type Operator uint8
    22  
    23  const (
    24  	OpTerminal Operator = iota
    25  	OpAnd
    26  	OpOr
    27  	OpLessEqual
    28  	OpGreaterEqual
    29  	OpLess
    30  	OpGreater
    31  	OpEqual
    32  	OpContains
    33  	OpNotEqual
    34  	OpNot
    35  )
    36  
    37  var opNames = map[Operator]string{
    38  	OpAnd:          "AND",
    39  	OpOr:           "OR",
    40  	OpLessEqual:    "<=",
    41  	OpGreaterEqual: ">=",
    42  	OpLess:         "<",
    43  	OpGreater:      ">",
    44  	OpEqual:        "=",
    45  	OpContains:     "CONTAINS",
    46  	OpNotEqual:     "!=",
    47  	OpNot:          "Not",
    48  }
    49  
    50  func (op Operator) String() string {
    51  	return opNames[op]
    52  }
    53  
    54  func (op Operator) Arity() int {
    55  	if op == OpNot {
    56  		return 1
    57  	}
    58  	return 2
    59  }
    60  
    61  // Instruction is a container suitable for the code tape and the stack to hold values an operations
    62  type instruction struct {
    63  	op     Operator
    64  	tag    *string
    65  	string *string
    66  	time   *time.Time
    67  	number *big.Float
    68  	match  bool
    69  }
    70  
    71  func (in *instruction) String() string {
    72  	switch {
    73  	case in.op != OpTerminal:
    74  		return in.op.String()
    75  	case in.tag != nil:
    76  		return *in.tag
    77  	case in.string != nil:
    78  		return "'" + *in.string + "'"
    79  	case in.time != nil:
    80  		return in.time.String()
    81  	case in.number != nil:
    82  		return in.number.String()
    83  	default:
    84  		if in.match {
    85  			return "true"
    86  		}
    87  		return "false"
    88  	}
    89  }
    90  
    91  // A Boolean expression for the query grammar
    92  type Expression struct {
    93  	// This is our 'bytecode'
    94  	code      []*instruction
    95  	errors    errors.MultipleErrors
    96  	explainer func(format string, args ...interface{})
    97  }
    98  
    99  // Evaluate expects an Execute() to have filled the code of the Expression so it can be run in the little stack machine
   100  // below
   101  func (e *Expression) Evaluate(getTagValue func(tag string) (interface{}, bool)) (bool, error) {
   102  	if len(e.errors) > 0 {
   103  		return false, e.errors
   104  	}
   105  	var left, right *instruction
   106  	stack := make([]*instruction, 0, len(e.code))
   107  	var err error
   108  	for _, in := range e.code {
   109  		if in.op == OpTerminal {
   110  			// just push terminals on to the stack
   111  			stack = append(stack, in)
   112  			continue
   113  		}
   114  
   115  		stack, left, right, err = pop(stack, in.op)
   116  		if err != nil {
   117  			return false, fmt.Errorf("cannot process instruction %v in expression [%v]: %w", in, e, err)
   118  		}
   119  		ins := &instruction{}
   120  		switch in.op {
   121  		case OpNot:
   122  			ins.match = !right.match
   123  		case OpAnd:
   124  			ins.match = left.match && right.match
   125  		case OpOr:
   126  			ins.match = left.match || right.match
   127  		default:
   128  			// We have a a non-terminal, non-connective operation
   129  			tagValue, ok := getTagValue(*left.tag)
   130  			// No match if we can't get tag value
   131  			if ok {
   132  				switch {
   133  				case right.string != nil:
   134  					ins.match = compareString(in.op, tagValue, *right.string)
   135  				case right.number != nil:
   136  					ins.match = compareNumber(in.op, tagValue, right.number)
   137  				case right.time != nil:
   138  					ins.match = compareTime(in.op, tagValue, *right.time)
   139  				}
   140  			}
   141  			// Uncomment this for a little bit of debug:
   142  			//e.explainf("%v := %v\n", left, tagValue)
   143  		}
   144  		// Uncomment this for a little bit of debug:
   145  		//e.explainf("%v %v %v => %v\n", left, in.op, right, ins.match)
   146  
   147  		// Push whether this was a match back on to stack
   148  		stack = append(stack, ins)
   149  	}
   150  	if len(stack) != 1 {
   151  		return false, fmt.Errorf("stack for query expression [%v] should have exactly one element after "+
   152  			"evaulation but has %d", e, len(stack))
   153  	}
   154  	return stack[0].match, nil
   155  }
   156  
   157  func (e *Expression) explainf(fmt string, args ...interface{}) {
   158  	if e.explainer != nil {
   159  		e.explainer(fmt, args...)
   160  	}
   161  }
   162  
   163  func pop(stack []*instruction, op Operator) ([]*instruction, *instruction, *instruction, error) {
   164  	arity := op.Arity()
   165  	if len(stack) < arity {
   166  		return stack, nil, nil, fmt.Errorf("cannot pop arguments for arity %d operator %v from stack "+
   167  			"because stack has fewer than %d elements", arity, op, arity)
   168  	}
   169  	if arity == 1 {
   170  		return stack[:len(stack)-1], nil, stack[len(stack)-1], nil
   171  	}
   172  	return stack[:len(stack)-2], stack[len(stack)-2], stack[len(stack)-1], nil
   173  }
   174  
   175  func compareString(op Operator, tagValue interface{}, value string) bool {
   176  	tagString := StringFromValue(tagValue)
   177  	switch op {
   178  	case OpContains:
   179  		return strings.Contains(tagString, value)
   180  	case OpEqual:
   181  		return tagString == value
   182  	case OpNotEqual:
   183  		return tagString != value
   184  	}
   185  	return false
   186  }
   187  
   188  func compareNumber(op Operator, tagValue interface{}, value *big.Float) bool {
   189  	tagNumber := new(big.Float)
   190  	switch n := tagValue.(type) {
   191  	case string:
   192  		f, _, err := big.ParseFloat(n, 10, 64, big.ToNearestEven)
   193  		if err != nil {
   194  			return false
   195  		}
   196  		tagNumber.Set(f)
   197  	case *big.Float:
   198  		tagNumber.Set(n)
   199  	case *big.Int:
   200  		tagNumber.SetInt(n)
   201  	case float32:
   202  		tagNumber.SetFloat64(float64(n))
   203  	case float64:
   204  		tagNumber.SetFloat64(n)
   205  	case int:
   206  		tagNumber.SetInt64(int64(n))
   207  	case int32:
   208  		tagNumber.SetInt64(int64(n))
   209  	case int64:
   210  		tagNumber.SetInt64(n)
   211  	case uint:
   212  		tagNumber.SetUint64(uint64(n))
   213  	case uint32:
   214  		tagNumber.SetUint64(uint64(n))
   215  	case uint64:
   216  		tagNumber.SetUint64(n)
   217  	default:
   218  		return false
   219  	}
   220  	cmp := tagNumber.Cmp(value)
   221  	switch op {
   222  	case OpLessEqual:
   223  		return cmp < 1
   224  	case OpGreaterEqual:
   225  		return cmp > -1
   226  	case OpLess:
   227  		return cmp == -1
   228  	case OpGreater:
   229  		return cmp == 1
   230  	case OpEqual:
   231  		return cmp == 0
   232  	case OpNotEqual:
   233  		return cmp != 0
   234  	}
   235  	return false
   236  }
   237  
   238  func compareTime(op Operator, tagValue interface{}, value time.Time) bool {
   239  	var tagTime time.Time
   240  	var err error
   241  	switch t := tagValue.(type) {
   242  	case time.Time:
   243  		tagTime = t
   244  	case int64:
   245  		// Hmmm, should we?
   246  		tagTime = time.Unix(t, 0)
   247  	case string:
   248  		tagTime, err = time.Parse(TimeLayout, t)
   249  		if err != nil {
   250  			tagTime, err = time.Parse(DateLayout, t)
   251  			if err != nil {
   252  				return false
   253  			}
   254  		}
   255  	default:
   256  		return false
   257  	}
   258  	switch op {
   259  	case OpLessEqual:
   260  		return tagTime.Before(value) || tagTime.Equal(value)
   261  	case OpGreaterEqual:
   262  		return tagTime.Equal(value) || tagTime.After(value)
   263  	case OpLess:
   264  		return tagTime.Before(value)
   265  	case OpGreater:
   266  		return tagTime.After(value)
   267  	case OpEqual:
   268  		return tagTime.Equal(value)
   269  	case OpNotEqual:
   270  		return !tagTime.Equal(value)
   271  	}
   272  	return false
   273  }
   274  
   275  // These methods implement the various visitors that are called in the PEG grammar with statements like
   276  // { p.Operator(OpEqual) }
   277  
   278  func (e *Expression) String() string {
   279  	strs := make([]string, len(e.code))
   280  	for i, in := range e.code {
   281  		strs[i] = in.String()
   282  	}
   283  	return strings.Join(strs, ", ")
   284  }
   285  
   286  func (e *Expression) Operator(operator Operator) {
   287  	e.code = append(e.code, &instruction{
   288  		op: operator,
   289  	})
   290  }
   291  
   292  // Terminals...
   293  
   294  func (e *Expression) Tag(value string) {
   295  	e.code = append(e.code, &instruction{
   296  		tag: &value,
   297  	})
   298  }
   299  
   300  func (e *Expression) Time(value string) {
   301  	t, err := time.Parse(TimeLayout, value)
   302  	e.pushErr(err)
   303  	e.code = append(e.code, &instruction{
   304  		time: &t,
   305  	})
   306  
   307  }
   308  func (e *Expression) Date(value string) {
   309  	date, err := time.Parse(DateLayout, value)
   310  	e.pushErr(err)
   311  	e.code = append(e.code, &instruction{
   312  		time: &date,
   313  	})
   314  }
   315  
   316  func (e *Expression) Number(value string) {
   317  	number, _, err := big.ParseFloat(value, 10, 64, big.ToNearestEven)
   318  	e.pushErr(err)
   319  	e.code = append(e.code, &instruction{
   320  		number: number,
   321  	})
   322  }
   323  
   324  func (e *Expression) Value(value string) {
   325  	e.code = append(e.code, &instruction{
   326  		string: &value,
   327  	})
   328  }
   329  
   330  func (e *Expression) pushErr(err error) {
   331  	if err != nil {
   332  		e.errors = append(e.errors, err)
   333  	}
   334  }