@ -11,9 +11,16 @@ package query
import (
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
)
var (
numRegex = regexp . MustCompile ( ` ([0-9\.]+) ` )
)
// Query holds the query string and the query parser.
@ -82,209 +89,283 @@ const (
TimeLayout = time . RFC3339
)
// Conditions returns a list of conditions.
func ( q * Query ) Conditions ( ) [ ] Condition {
conditions := make ( [ ] Condition , 0 )
// Conditions returns a list of conditions. It returns an error if there is any
// error with the provided grammar in the Query.
func ( q * Query ) Conditions ( ) ( [ ] Condition , error ) {
var (
eventAttr string
op Operator
)
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")
// tokens must be in the following order: event attribute ("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 ]
eventAttr = 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 } )
conditions = append ( conditions , Condition { eventAttr , op , valueWithoutSingleQuotes } )
case rulenumber :
number := buffer [ begin : end ]
if strings . ContainsAny ( 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 ) )
err = fmt . Errorf (
"got %v while trying to parse %s as float64 (should never happen if the grammar is correct)" ,
err , number ,
)
return nil , err
}
conditions = append ( conditions , Condition { tag , op , value } )
conditions = append ( conditions , Condition { eventAttr , 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 ) )
err = fmt . Errorf (
"got %v while trying to parse %s as int64 (should never happen if the grammar is correct)" ,
err , number ,
)
return nil , err
}
conditions = append ( conditions , Condition { tag , op , value } )
conditions = append ( conditions , Condition { eventAttr , op , value } )
}
case ruletime :
value , err := time . Parse ( TimeLayout , buffer [ begin : end ] )
if err != nil {
panic ( fmt . Sprint f(
err = fmt . Error f(
"got %v while trying to parse %s as time.Time / RFC3339 (should never happen if the grammar is correct)" ,
err ,
buffer [ begin : end ] ) )
err , buffer [ begin : end ] ,
)
return nil , err
}
conditions = append ( conditions , Condition { tag , op , value } )
conditions = append ( conditions , Condition { eventAttr , op , value } )
case ruledate :
value , err := time . Parse ( "2006-01-02" , buffer [ begin : end ] )
if err != nil {
panic ( fmt . Sprint f(
err = fmt . Error f(
"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 ] ) )
err , buffer [ begin : end ] ,
)
return nil , err
}
conditions = append ( conditions , Condition { tag , op , value } )
conditions = append ( conditions , Condition { eventAttr , op , value } )
}
}
return conditions
return conditions , nil
}
// Matches returns true if the query matches against any event in the given set
// of events, false otherwise. For each event, a match exists if the query is
// matched against *any* value in a slice of values.
// matched against *any* value in a slice of values. An error is returned if
// any attempted event match returns an error.
//
// For example, query "name=John" matches events = {"name": ["John", "Eric"]}.
// More examples could be found in parser_test.go and query_test.go.
func ( q * Query ) Matches ( events map [ string ] [ ] string ) bool {
func ( q * Query ) Matches ( events map [ string ] [ ] string ) ( bool , error ) {
if len ( events ) == 0 {
return false
return false , nil
}
buffer , begin , end := q . parser . Buffer , 0 , 0
var (
eventAttr string
op Operator
)
var tag string
var op Operator
buffer , begin , end := q . parser . Buffer , 0 , 0
// tokens must be in the following order:
// tag ("tx.gas") -> operator ("=") -> operand ("7")
// event attribute ("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 ]
eventAttr = 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 ]
// see if the triplet (tag, operator, operand) matches any tag
// see if the triplet (event attribute, operator, operand) matches any event
// "tx.gas", "=", "7", { "tx.gas": 7, "tx.ID": "4AE393495334" }
if ! match ( tag , op , reflect . ValueOf ( valueWithoutSingleQuotes ) , events ) {
return false
match , err := match ( eventAttr , op , reflect . ValueOf ( valueWithoutSingleQuotes ) , events )
if err != nil {
return false , err
}
if ! match {
return false , nil
}
case rulenumber :
number := buffer [ begin : end ]
if strings . ContainsAny ( number , "." ) { // if it looks like a floating-point number
value , err := strconv . ParseFloat ( number , 64 )
if err != nil {
panic ( fmt . Sprint f(
err = fmt . Error f(
"got %v while trying to parse %s as float64 (should never happen if the grammar is correct)" ,
err ,
number ) )
err , number ,
)
return false , err
}
if ! match ( tag , op , reflect . ValueOf ( value ) , events ) {
return false
match , err := match ( eventAttr , op , reflect . ValueOf ( value ) , events )
if err != nil {
return false , err
}
if ! match {
return false , nil
}
} else {
value , err := strconv . ParseInt ( number , 10 , 64 )
if err != nil {
panic ( fmt . Sprintf (
err = fmt . Error f(
"got %v while trying to parse %s as int64 (should never happen if the grammar is correct)" ,
err ,
number ) )
err , number ,
)
return false , err
}
if ! match ( tag , op , reflect . ValueOf ( value ) , events ) {
return false
match , err := match ( eventAttr , op , reflect . ValueOf ( value ) , events )
if err != nil {
return false , err
}
if ! match {
return false , nil
}
}
case ruletime :
value , err := time . Parse ( TimeLayout , buffer [ begin : end ] )
if err != nil {
panic ( fmt . Sprintf (
err = fmt . Error f(
"got %v while trying to parse %s as time.Time / RFC3339 (should never happen if the grammar is correct)" ,
err ,
buffer [ begin : end ] ) )
err , buffer [ begin : end ] ,
)
return false , err
}
if ! match ( tag , op , reflect . ValueOf ( value ) , events ) {
return false
match , err := match ( eventAttr , op , reflect . ValueOf ( value ) , events )
if err != nil {
return false , err
}
if ! match {
return false , nil
}
case ruledate :
value , err := time . Parse ( "2006-01-02" , buffer [ begin : end ] )
if err != nil {
panic ( fmt . Sprintf (
err = fmt . Error f(
"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 ] ) )
err , buffer [ begin : end ] ,
)
return false , err
}
if ! match ( tag , op , reflect . ValueOf ( value ) , events ) {
return false
match , err := match ( eventAttr , op , reflect . ValueOf ( value ) , events )
if err != nil {
return false , err
}
if ! match {
return false , nil
}
}
}
return true
return true , nil
}
// match returns true if the given triplet (tag, operator, operand) matches any
// value in an event for that key.
// match returns true if the given triplet (attribute, operator, operand) matches
// any value in an event for that attribute. If any match fails with an error,
// that error is returned.
//
// First, it looks up the key in the events and if it finds one, tries to compare
// all the values from it to the operand using the operator.
//
// "tx.gas", "=", "7", {"tx": [{"gas": 7, "ID": "4AE393495334"}]}
func match ( tag string , op Operator , operand reflect . Value , events map [ string ] [ ] string ) bool {
func match ( attr string , op Operator , operand reflect . Value , events map [ string ] [ ] string ) ( bool , error ) {
// look up the tag from the query in tags
values , ok := events [ tag ]
values , ok := events [ attr ]
if ! ok {
return false
return false , nil
}
for _ , value := range values {
// return true if any value in the set of the event's values matches
if matchValue ( value , op , operand ) {
return true
match , err := matchValue ( value , op , operand )
if err != nil {
return false , err
}
if match {
return true , nil
}
}
return false
return false , nil
}
// matchValue will attempt to match a string value against an operation an
// operand. A boolean is returned representing the match result. It will panic
// if an error occurs or if the operand is invalid .
func matchValue ( value string , op Operator , operand reflect . Value ) bool {
// matchValue will attempt to match a string value against an operator an
// operand. A boolean is returned representing the match result. It will return
// an error if the value cannot be parsed and matched against the operand type .
func matchValue ( value string , op Operator , operand reflect . Value ) ( bool , error ) {
switch operand . Kind ( ) {
case reflect . Struct : // time
operandAsTime := operand . Interface ( ) . ( time . Time )
@ -301,87 +382,94 @@ func matchValue(value string, op Operator, operand reflect.Value) bool {
v , err = time . Parse ( DateLayout , value )
}
if err != nil {
panic ( fmt . Sprintf ( "failed to convert value %v from tag to time.Time: %v ", value , err ) )
return false , errors . Wrapf ( err , "failed to convert value %v from event attribute to time.Time ", value )
}
switch op {
case OpLessEqual :
return v . Before ( operandAsTime ) || v . Equal ( operandAsTime )
return ( v . Before ( operandAsTime ) || v . Equal ( operandAsTime ) ) , nil
case OpGreaterEqual :
return v . Equal ( operandAsTime ) || v . After ( operandAsTime )
return ( v . Equal ( operandAsTime ) || v . After ( operandAsTime ) ) , nil
case OpLess :
return v . Before ( operandAsTime )
return v . Before ( operandAsTime ) , nil
case OpGreater :
return v . After ( operandAsTime )
return v . After ( operandAsTime ) , nil
case OpEqual :
return v . Equal ( operandAsTime )
return v . Equal ( operandAsTime ) , nil
}
case reflect . Float64 :
operandFloat64 := operand . Interface ( ) . ( float64 )
var v float64
operandFloat64 := operand . Interface ( ) . ( float64 )
filteredValue := numRegex . FindString ( value )
// try our best to convert value from tags to float64
v , err := strconv . ParseFloat ( value , 64 )
v , err := strconv . ParseFloat ( filteredV alue, 64 )
if err != nil {
panic ( fmt . Sprintf ( "failed to convert value %v from tag to float64: %v" , value , err ) )
return false , errors . Wrapf ( err , "failed to convert value %v from event attribute to float64" , filteredValue )
}
switch op {
case OpLessEqual :
return v <= operandFloat64
return v <= operandFloat64 , nil
case OpGreaterEqual :
return v >= operandFloat64
return v >= operandFloat64 , nil
case OpLess :
return v < operandFloat64
return v < operandFloat64 , nil
case OpGreater :
return v > operandFloat64
return v > operandFloat64 , nil
case OpEqual :
return v == operandFloat64
return v == operandFloat64 , nil
}
case reflect . Int64 :
operandInt := operand . Interface ( ) . ( int64 )
var v int64
operandInt := operand . Interface ( ) . ( int64 )
filteredValue := numRegex . FindString ( value )
// if value looks like float, we try to parse it as float
if strings . ContainsAny ( value , "." ) {
v1 , err := strconv . ParseFloat ( value , 64 )
if strings . ContainsAny ( filteredV alue, "." ) {
v1 , err := strconv . ParseFloat ( filteredV alue, 64 )
if err != nil {
panic ( fmt . Sprintf ( "failed to convert value %v from tag to float64: %v" , value , err ) )
return false , errors . Wrapf ( err , "failed to convert value %v from event attribute to float64" , filteredValue )
}
v = int64 ( v1 )
} else {
var err error
// try our best to convert value from tags to int64
v , err = strconv . ParseInt ( v alue, 10 , 64 )
v , err = strconv . ParseInt ( filteredV alue, 10 , 64 )
if err != nil {
panic ( fmt . Sprintf ( "failed to convert value %v from tag to int64: %v" , value , err ) )
return false , errors . Wrapf ( err , "failed to convert value %v from event attribute to int64" , filteredValue )
}
}
switch op {
case OpLessEqual :
return v <= operandInt
return v <= operandInt , nil
case OpGreaterEqual :
return v >= operandInt
return v >= operandInt , nil
case OpLess :
return v < operandInt
return v < operandInt , nil
case OpGreater :
return v > operandInt
return v > operandInt , nil
case OpEqual :
return v == operandInt
return v == operandInt , nil
}
case reflect . String :
switch op {
case OpEqual :
return value == operand . String ( )
return value == operand . String ( ) , nil
case OpContains :
return strings . Contains ( value , operand . String ( ) )
return strings . Contains ( value , operand . String ( ) ) , nil
}
default :
panic ( fmt . Sprint f( "unknown kind of operand %v" , operand . Kind ( ) ) )
return false , fmt . Error f( "unknown kind of operand %v" , operand . Kind ( ) )
}
return false
return false , nil
}