add Conditions function

Refs https://github.com/tendermint/tendermint/pull/835
This commit is contained in:
Anton Kaliaev
2017-11-27 18:40:51 -06:00
parent 850fd24ee9
commit 3822727981
2 changed files with 135 additions and 32 deletions

View File

@ -22,6 +22,14 @@ type Query struct {
parser *QueryParser 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 // New parses the given string and returns a query or error if the string is
// invalid. // invalid.
func New(s string) (*Query, error) { func New(s string) (*Query, error) {
@ -48,17 +56,91 @@ func (q *Query) String() string {
return q.str 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 ( const (
opLessEqual operator = iota // "<="
opGreaterEqual OpLessEqual Operator = iota
opLess // ">="
opGreater OpGreaterEqual
opEqual // "<"
opContains 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. // Matches returns true if the query matches the given set of tags, false otherwise.
// //
// For example, query "name=John" matches tags = {"name": "John"}. More // 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 buffer, begin, end := q.parser.Buffer, 0, 0
var tag string var tag string
var op operator var op Operator
// tokens must be in the following order: tag ("tx.gas") -> operator ("=") -> operand ("7") // tokens must be in the following order: tag ("tx.gas") -> operator ("=") -> operand ("7")
for _, token := range q.parser.Tokens() { for _, token := range q.parser.Tokens() {
@ -82,17 +164,17 @@ func (q *Query) Matches(tags map[string]interface{}) bool {
case ruletag: case ruletag:
tag = buffer[begin:end] tag = buffer[begin:end]
case rulele: case rulele:
op = opLessEqual op = OpLessEqual
case rulege: case rulege:
op = opGreaterEqual op = OpGreaterEqual
case rulel: case rulel:
op = opLess op = OpLess
case ruleg: case ruleg:
op = opGreater op = OpGreater
case ruleequal: case ruleequal:
op = opEqual op = OpEqual
case rulecontains: case rulecontains:
op = opContains op = OpContains
case rulevalue: case rulevalue:
// strip single quotes from value (i.e. "'NewBlock'" -> "NewBlock") // strip single quotes from value (i.e. "'NewBlock'" -> "NewBlock")
valueWithoutSingleQuotes := buffer[begin+1 : end-1] 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. // value from it to the operand using the operator.
// //
// "tx.gas", "=", "7", { "tx.gas": 7, "tx.ID": "4AE393495334" } // "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 // look up the tag from the query in tags
value, ok := tags[tag] value, ok := tags[tag]
if !ok { if !ok {
@ -163,15 +245,15 @@ func match(tag string, op operator, operand reflect.Value, tags map[string]inter
return false return false
} }
switch op { switch op {
case opLessEqual: case OpLessEqual:
return v.Before(operandAsTime) || v.Equal(operandAsTime) return v.Before(operandAsTime) || v.Equal(operandAsTime)
case opGreaterEqual: case OpGreaterEqual:
return v.Equal(operandAsTime) || v.After(operandAsTime) return v.Equal(operandAsTime) || v.After(operandAsTime)
case opLess: case OpLess:
return v.Before(operandAsTime) return v.Before(operandAsTime)
case opGreater: case OpGreater:
return v.After(operandAsTime) return v.After(operandAsTime)
case opEqual: case OpEqual:
return v.Equal(operandAsTime) return v.Equal(operandAsTime)
} }
case reflect.Float64: 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)) panic(fmt.Sprintf("Incomparable types: %T (%v) vs float64 (%v)", value, value, operandFloat64))
} }
switch op { switch op {
case opLessEqual: case OpLessEqual:
return v <= operandFloat64 return v <= operandFloat64
case opGreaterEqual: case OpGreaterEqual:
return v >= operandFloat64 return v >= operandFloat64
case opLess: case OpLess:
return v < operandFloat64 return v < operandFloat64
case opGreater: case OpGreater:
return v > operandFloat64 return v > operandFloat64
case opEqual: case OpEqual:
return v == operandFloat64 return v == operandFloat64
} }
case reflect.Int64: 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)) panic(fmt.Sprintf("Incomparable types: %T (%v) vs int64 (%v)", value, value, operandInt))
} }
switch op { switch op {
case opLessEqual: case OpLessEqual:
return v <= operandInt return v <= operandInt
case opGreaterEqual: case OpGreaterEqual:
return v >= operandInt return v >= operandInt
case opLess: case OpLess:
return v < operandInt return v < operandInt
case opGreater: case OpGreater:
return v > operandInt return v > operandInt
case opEqual: case OpEqual:
return v == operandInt return v == operandInt
} }
case reflect.String: case reflect.String:
@ -248,9 +330,9 @@ func match(tag string, op operator, operand reflect.Value, tags map[string]inter
return false return false
} }
switch op { switch op {
case opEqual: case OpEqual:
return v == operand.String() return v == operand.String()
case opContains: case OpContains:
return strings.Contains(v, operand.String()) return strings.Contains(v, operand.String())
} }
default: default:

View File

@ -62,3 +62,24 @@ func TestMustParse(t *testing.T) {
assert.Panics(t, func() { query.MustParse("=") }) assert.Panics(t, func() { query.MustParse("=") })
assert.NotPanics(t, func() { query.MustParse("tm.events.type='NewBlock'") }) 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())
}
}