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 }