Merge pull request #84 from tendermint/85-indexing
query#Conditions for indexing
This commit is contained in:
commit
21fb781989
|
@ -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:
|
||||
|
|
|
@ -45,15 +45,15 @@ func TestMatches(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
query, err := query.New(tc.s)
|
||||
q, err := query.New(tc.s)
|
||||
if !tc.err {
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
if tc.matches {
|
||||
assert.True(t, query.Matches(tc.tags), "Query '%s' should match %v", tc.s, tc.tags)
|
||||
assert.True(t, q.Matches(tc.tags), "Query '%s' should match %v", tc.s, tc.tags)
|
||||
} else {
|
||||
assert.False(t, query.Matches(tc.tags), "Query '%s' should not match %v", tc.s, tc.tags)
|
||||
assert.False(t, q.Matches(tc.tags), "Query '%s' should not match %v", tc.s, tc.tags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
q, err := query.New(tc.s)
|
||||
require.Nil(t, err)
|
||||
|
||||
assert.Equal(t, tc.conditions, q.Conditions())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue