Browse Source

add Conditions function

Refs https://github.com/tendermint/tendermint/pull/835
pull/1842/head
Anton Kaliaev 7 years ago
parent
commit
3822727981
No known key found for this signature in database GPG Key ID: 7B6881D965918214
2 changed files with 135 additions and 32 deletions
  1. +114
    -32
      pubsub/query/query.go
  2. +21
    -0
      pubsub/query/query_test.go

+ 114
- 32
pubsub/query/query.go View File

@ -22,6 +22,14 @@ type Query struct {
parser *QueryParser
}
// Condition represents a single condition within a query and consists of tag
// (e.g. "tx.gas"), operator (e.g. "=") and operand (e.g. "7").
type Condition struct {
Tag string
Op Operator
Operand interface{}
}
// New parses the given string and returns a query or error if the string is
// invalid.
func New(s string) (*Query, error) {
@ -48,17 +56,91 @@ func (q *Query) String() string {
return q.str
}
type operator uint8
// Operator is an operator that defines some kind of relation between tag and
// operand (equality, etc.).
type Operator uint8
const (
opLessEqual operator = iota
opGreaterEqual
opLess
opGreater
opEqual
opContains
// "<="
OpLessEqual Operator = iota
// ">="
OpGreaterEqual
// "<"
OpLess
// ">"
OpGreater
// "="
OpEqual
// "CONTAINS"; used to check if a string contains a certain sub string.
OpContains
)
// Conditions returns a list of conditions.
func (q *Query) Conditions() []Condition {
conditions := make([]Condition, 0)
buffer, begin, end := q.parser.Buffer, 0, 0
var tag string
var op Operator
// tokens must be in the following order: tag ("tx.gas") -> operator ("=") -> operand ("7")
for _, token := range q.parser.Tokens() {
switch token.pegRule {
case rulePegText:
begin, end = int(token.begin), int(token.end)
case ruletag:
tag = buffer[begin:end]
case rulele:
op = OpLessEqual
case rulege:
op = OpGreaterEqual
case rulel:
op = OpLess
case ruleg:
op = OpGreater
case ruleequal:
op = OpEqual
case rulecontains:
op = OpContains
case rulevalue:
// strip single quotes from value (i.e. "'NewBlock'" -> "NewBlock")
valueWithoutSingleQuotes := buffer[begin+1 : end-1]
conditions = append(conditions, Condition{tag, op, valueWithoutSingleQuotes})
case rulenumber:
number := buffer[begin:end]
if strings.Contains(number, ".") { // if it looks like a floating-point number
value, err := strconv.ParseFloat(number, 64)
if err != nil {
panic(fmt.Sprintf("got %v while trying to parse %s as float64 (should never happen if the grammar is correct)", err, number))
}
conditions = append(conditions, Condition{tag, op, value})
} else {
value, err := strconv.ParseInt(number, 10, 64)
if err != nil {
panic(fmt.Sprintf("got %v while trying to parse %s as int64 (should never happen if the grammar is correct)", err, number))
}
conditions = append(conditions, Condition{tag, op, value})
}
case ruletime:
value, err := time.Parse(time.RFC3339, buffer[begin:end])
if err != nil {
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]))
}
conditions = append(conditions, Condition{tag, op, value})
case ruledate:
value, err := time.Parse("2006-01-02", buffer[begin:end])
if err != nil {
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]))
}
conditions = append(conditions, Condition{tag, op, value})
}
}
return conditions
}
// Matches returns true if the query matches the given set of tags, false otherwise.
//
// For example, query "name=John" matches tags = {"name": "John"}. More
@ -71,7 +153,7 @@ func (q *Query) Matches(tags map[string]interface{}) bool {
buffer, begin, end := q.parser.Buffer, 0, 0
var tag string
var op operator
var op Operator
// tokens must be in the following order: tag ("tx.gas") -> operator ("=") -> operand ("7")
for _, token := range q.parser.Tokens() {
@ -82,17 +164,17 @@ func (q *Query) Matches(tags map[string]interface{}) bool {
case ruletag:
tag = buffer[begin:end]
case rulele:
op = opLessEqual
op = OpLessEqual
case rulege:
op = opGreaterEqual
op = OpGreaterEqual
case rulel:
op = opLess
op = OpLess
case ruleg:
op = opGreater
op = OpGreater
case ruleequal:
op = opEqual
op = OpEqual
case rulecontains:
op = opContains
op = OpContains
case rulevalue:
// strip single quotes from value (i.e. "'NewBlock'" -> "NewBlock")
valueWithoutSingleQuotes := buffer[begin+1 : end-1]
@ -149,7 +231,7 @@ func (q *Query) Matches(tags map[string]interface{}) bool {
// value from it to the operand using the operator.
//
// "tx.gas", "=", "7", { "tx.gas": 7, "tx.ID": "4AE393495334" }
func match(tag string, op operator, operand reflect.Value, tags map[string]interface{}) bool {
func match(tag string, op Operator, operand reflect.Value, tags map[string]interface{}) bool {
// look up the tag from the query in tags
value, ok := tags[tag]
if !ok {
@ -163,15 +245,15 @@ func match(tag string, op operator, operand reflect.Value, tags map[string]inter
return false
}
switch op {
case opLessEqual:
case OpLessEqual:
return v.Before(operandAsTime) || v.Equal(operandAsTime)
case opGreaterEqual:
case OpGreaterEqual:
return v.Equal(operandAsTime) || v.After(operandAsTime)
case opLess:
case OpLess:
return v.Before(operandAsTime)
case opGreater:
case OpGreater:
return v.After(operandAsTime)
case opEqual:
case OpEqual:
return v.Equal(operandAsTime)
}
case reflect.Float64:
@ -197,15 +279,15 @@ func match(tag string, op operator, operand reflect.Value, tags map[string]inter
panic(fmt.Sprintf("Incomparable types: %T (%v) vs float64 (%v)", value, value, operandFloat64))
}
switch op {
case opLessEqual:
case OpLessEqual:
return v <= operandFloat64
case opGreaterEqual:
case OpGreaterEqual:
return v >= operandFloat64
case opLess:
case OpLess:
return v < operandFloat64
case opGreater:
case OpGreater:
return v > operandFloat64
case opEqual:
case OpEqual:
return v == operandFloat64
}
case reflect.Int64:
@ -231,15 +313,15 @@ func match(tag string, op operator, operand reflect.Value, tags map[string]inter
panic(fmt.Sprintf("Incomparable types: %T (%v) vs int64 (%v)", value, value, operandInt))
}
switch op {
case opLessEqual:
case OpLessEqual:
return v <= operandInt
case opGreaterEqual:
case OpGreaterEqual:
return v >= operandInt
case opLess:
case OpLess:
return v < operandInt
case opGreater:
case OpGreater:
return v > operandInt
case opEqual:
case OpEqual:
return v == operandInt
}
case reflect.String:
@ -248,9 +330,9 @@ func match(tag string, op operator, operand reflect.Value, tags map[string]inter
return false
}
switch op {
case opEqual:
case OpEqual:
return v == operand.String()
case opContains:
case OpContains:
return strings.Contains(v, operand.String())
}
default:


+ 21
- 0
pubsub/query/query_test.go View File

@ -62,3 +62,24 @@ func TestMustParse(t *testing.T) {
assert.Panics(t, func() { query.MustParse("=") })
assert.NotPanics(t, func() { query.MustParse("tm.events.type='NewBlock'") })
}
func TestConditions(t *testing.T) {
txTime, err := time.Parse(time.RFC3339, "2013-05-03T14:45:00Z")
require.NoError(t, err)
testCases := []struct {
s string
conditions []query.Condition
}{
{"tm.events.type='NewBlock'", []query.Condition{query.Condition{"tm.events.type", query.OpEqual, "NewBlock"}}},
{"tx.gas > 7 AND tx.gas < 9", []query.Condition{query.Condition{"tx.gas", query.OpGreater, int64(7)}, query.Condition{"tx.gas", query.OpLess, int64(9)}}},
{"tx.time >= TIME 2013-05-03T14:45:00Z", []query.Condition{query.Condition{"tx.time", query.OpGreaterEqual, txTime}}},
}
for _, tc := range testCases {
query, err := query.New(tc.s)
require.Nil(t, err)
assert.Equal(t, tc.conditions, query.Conditions())
}
}

Loading…
Cancel
Save