Add bitwise AND, OR and XOR operators to InfluxQL.

pull/8163/head
Tom Young 2017-03-19 09:58:09 +00:00
parent 211e7ea65d
commit d2fd3f50aa
8 changed files with 277 additions and 15 deletions

View File

@ -14,6 +14,7 @@
- [#6541](https://github.com/influxdata/influxdb/issues/6541): Support timezone offsets for queries.
- [#8194](https://github.com/influxdata/influxdb/pull/8194): Add "integral" function to InfluxQL.
- [#7393](https://github.com/influxdata/influxdb/issues/7393): Add "non_negative_difference" function to InfluxQL.
- [#8042](https://github.com/influxdata/influxdb/issues/8042): Add bitwise AND, OR and XOR operators to the query language.
### Bugfixes

View File

@ -839,8 +839,8 @@ with_tag_clause = "WITH KEY" ( "=" tag_key | "!=" tag_key | "=~" regex_lit | "IN
## Expressions
```
binary_op = "+" | "-" | "*" | "/" | "AND" | "OR" | "=" | "!=" | "<>" | "<" |
"<=" | ">" | ">=" .
binary_op = "+" | "-" | "*" | "/" | "%" | "&" | "|" | "^" | "AND" |
"OR" | "=" | "!=" | "<>" | "<" | "<=" | ">" | ">=" .
expr = unary_expr { binary_op unary_expr } .

View File

@ -4304,6 +4304,12 @@ func evalBinaryExpr(expr *BinaryExpr, m map[string]interface{}) interface{} {
return ok && (lhs && rhs)
case OR:
return ok && (lhs || rhs)
case BITWISE_AND:
return ok && (lhs && rhs)
case BITWISE_OR:
return ok && (lhs || rhs)
case BITWISE_XOR:
return ok && (lhs != rhs)
case EQ:
return ok && (lhs == rhs)
case NEQ:
@ -4438,6 +4444,21 @@ func evalBinaryExpr(expr *BinaryExpr, m map[string]interface{}) interface{} {
return int64(0)
}
return lhs % rhs
case BITWISE_AND:
if !ok {
return nil
}
return lhs & rhs
case BITWISE_OR:
if !ok {
return nil
}
return lhs | rhs
case BITWISE_XOR:
if !ok {
return nil
}
return lhs ^ rhs
}
}
case string:
@ -4687,6 +4708,12 @@ func reduceBinaryExprBooleanLHS(op Token, lhs *BooleanLiteral, rhs Expr) Expr {
return &BooleanLiteral{Val: lhs.Val && rhs.Val}
case OR:
return &BooleanLiteral{Val: lhs.Val || rhs.Val}
case BITWISE_AND:
return &BooleanLiteral{Val: lhs.Val && rhs.Val}
case BITWISE_OR:
return &BooleanLiteral{Val: lhs.Val || rhs.Val}
case BITWISE_XOR:
return &BooleanLiteral{Val: lhs.Val != rhs.Val}
}
case *nilLiteral:
return &BooleanLiteral{Val: false}
@ -4780,6 +4807,12 @@ func reduceBinaryExprIntegerLHS(op Token, lhs *IntegerLiteral, rhs Expr) Expr {
return &IntegerLiteral{Val: 0}
}
return &IntegerLiteral{Val: lhs.Val % rhs.Val}
case BITWISE_AND:
return &IntegerLiteral{Val: lhs.Val & rhs.Val}
case BITWISE_OR:
return &IntegerLiteral{Val: lhs.Val | rhs.Val}
case BITWISE_XOR:
return &IntegerLiteral{Val: lhs.Val ^ rhs.Val}
case EQ:
return &BooleanLiteral{Val: lhs.Val == rhs.Val}
case NEQ:

View File

@ -1266,6 +1266,12 @@ func TestReduce(t *testing.T) {
{in: `5 % 2`, out: `1`},
{in: `2 % 0`, out: `0`},
{in: `2.5 % 0`, out: `NaN`},
{in: `254 & 3`, out: `2`},
{in: `254 | 3`, out: `255`},
{in: `254 ^ 3`, out: `253`},
{in: `-3 & 3`, out: `1`},
{in: `8 & -3`, out: `8`},
{in: `8.5 & -3`, out: `8.500 & -3`},
{in: `4 = 4`, out: `true`},
{in: `4 <> 4`, out: `false`},
{in: `6 > 4`, out: `true`},

View File

@ -84,6 +84,12 @@ func (s *Scanner) Scan() (tok Token, pos Pos, lit string) {
return DIV, pos, ""
case '%':
return MOD, pos, ""
case '&':
return BITWISE_AND, pos, ""
case '|':
return BITWISE_OR, pos, ""
case '^':
return BITWISE_XOR, pos, ""
case '=':
if ch1, _ := s.r.read(); ch1 == '~' {
return EQREGEX, pos, ""

View File

@ -1179,6 +1179,43 @@ func buildRHSTransformIterator(lhs Iterator, rhs Literal, op Token, opt Iterator
return nil
}
bp := &BooleanPoint{
Name: p.Name,
Tags: p.Tags,
Time: p.Time,
Aux: p.Aux,
}
if p.Nil {
bp.Nil = true
} else {
bp.Value = fn(p.Value, val)
}
return bp
},
}, nil
case func(bool, bool) bool:
var input BooleanIterator
switch lhs := lhs.(type) {
case BooleanIterator:
input = lhs
default:
return nil, fmt.Errorf("type mismatch on LHS, unable to use %T as an BooleanIterator", lhs)
}
var val bool
switch rhs := rhs.(type) {
case *BooleanLiteral:
val = rhs.Val
default:
return nil, fmt.Errorf("type mismatch on RHS, unable to use %T as an BooleanLiteral", rhs)
}
return &booleanTransformIterator{
input: input,
fn: func(p *BooleanPoint) *BooleanPoint {
if p == nil {
return nil
}
bp := &BooleanPoint{
Name: p.Name,
Tags: p.Tags,
@ -1358,6 +1395,43 @@ func buildLHSTransformIterator(lhs Literal, rhs Iterator, op Token, opt Iterator
return nil
}
bp := &BooleanPoint{
Name: p.Name,
Tags: p.Tags,
Time: p.Time,
Aux: p.Aux,
}
if p.Nil {
bp.Nil = true
} else {
bp.Value = fn(val, p.Value)
}
return bp
},
}, nil
case func(bool, bool) bool:
var input BooleanIterator
switch rhs := rhs.(type) {
case BooleanIterator:
input = rhs
default:
return nil, fmt.Errorf("type mismatch on RHS, unable to use %T as an BooleanIterator", rhs)
}
var val bool
switch lhs := lhs.(type) {
case *BooleanLiteral:
val = lhs.Val
default:
return nil, fmt.Errorf("type mismatch on LHS, unable to use %T as a BooleanLiteral", lhs)
}
return &booleanTransformIterator{
input: input,
fn: func(p *BooleanPoint) *BooleanPoint {
if p == nil {
return nil
}
bp := &BooleanPoint{
Name: p.Name,
Tags: p.Tags,
@ -1448,9 +1522,19 @@ func buildTransformIterator(lhs Iterator, rhs Iterator, op Token, opt IteratorOp
}
right, ok := rhs.(IntegerIterator)
if !ok {
return nil, fmt.Errorf("type mismatch on LHS, unable to use %T as a IntegerIterator", rhs)
return nil, fmt.Errorf("type mismatch on RHS, unable to use %T as a IntegerIterator", rhs)
}
return newIntegerBooleanExprIterator(left, right, opt, fn), nil
case func(bool, bool) bool:
left, ok := lhs.(BooleanIterator)
if !ok {
return nil, fmt.Errorf("type mismatch on LHS, unable to use %T as a BooleanIterator", lhs)
}
right, ok := rhs.(BooleanIterator)
if !ok {
return nil, fmt.Errorf("type mismatch on RHS, unable to use %T as a BooleanIterator", rhs)
}
return newBooleanExprIterator(left, right, opt, fn), nil
}
return nil, fmt.Errorf("unable to construct transform iterator from %T and %T", lhs, rhs)
}
@ -1497,6 +1581,8 @@ func binaryExprFunc(typ1 DataType, typ2 DataType, op Token) interface{} {
default:
fn = integerBinaryExprFunc(op)
}
case Boolean:
fn = booleanBinaryExprFunc(op)
}
return fn
}
@ -1556,6 +1642,12 @@ func integerBinaryExprFunc(op Token) interface{} {
}
return lhs % rhs
}
case BITWISE_AND:
return func(lhs, rhs int64) int64 { return lhs & rhs }
case BITWISE_OR:
return func(lhs, rhs int64) int64 { return lhs | rhs }
case BITWISE_XOR:
return func(lhs, rhs int64) int64 { return lhs ^ rhs }
case EQ:
return func(lhs, rhs int64) bool { return lhs == rhs }
case NEQ:
@ -1572,6 +1664,18 @@ func integerBinaryExprFunc(op Token) interface{} {
return nil
}
func booleanBinaryExprFunc(op Token) interface{} {
switch op {
case BITWISE_AND:
return func(lhs, rhs bool) bool { return lhs && rhs }
case BITWISE_OR:
return func(lhs, rhs bool) bool { return lhs || rhs }
case BITWISE_XOR:
return func(lhs, rhs bool) bool { return lhs != rhs }
}
return nil
}
// stringSetSlice returns a sorted slice of keys from a string set.
func stringSetSlice(m map[string]struct{}) []string {
if m == nil {

View File

@ -2284,6 +2284,33 @@ func TestSelect_BinaryExpr_Integer(t *testing.T) {
{&influxql.FloatPoint{Name: "cpu", Time: 9 * Second, Value: 1}},
},
},
{
Name: "rhs binary bitwise-and integer",
Statement: `SELECT value & 254 FROM cpu`,
Points: [][]influxql.Point{
{&influxql.IntegerPoint{Name: "cpu", Time: 0 * Second, Value: 20}},
{&influxql.IntegerPoint{Name: "cpu", Time: 5 * Second, Value: 10}},
{&influxql.IntegerPoint{Name: "cpu", Time: 9 * Second, Value: 18}},
},
},
{
Name: "lhs binary bitwise-or integer",
Statement: `SELECT 4 | value FROM cpu`,
Points: [][]influxql.Point{
{&influxql.IntegerPoint{Name: "cpu", Time: 0 * Second, Value: 20}},
{&influxql.IntegerPoint{Name: "cpu", Time: 5 * Second, Value: 14}},
{&influxql.IntegerPoint{Name: "cpu", Time: 9 * Second, Value: 23}},
},
},
{
Name: "two variable binary bitwise-xor",
Statement: `SELECT value ^ value FROM cpu`,
Points: [][]influxql.Point{
{&influxql.IntegerPoint{Name: "cpu", Time: 0 * Second, Value: 0}},
{&influxql.IntegerPoint{Name: "cpu", Time: 5 * Second, Value: 0}},
{&influxql.IntegerPoint{Name: "cpu", Time: 9 * Second, Value: 0}},
},
},
} {
stmt, err := MustParseSelectStatement(test.Statement).RewriteFields(&ic)
if err != nil {
@ -2382,6 +2409,85 @@ func TestSelect_BinaryExpr_Mixed(t *testing.T) {
}
}
// Ensure a SELECT binary expr queries can be executed as booleans.
func TestSelect_BinaryExpr_Boolean(t *testing.T) {
var ic IteratorCreator
ic.CreateIteratorFn = func(m *influxql.Measurement, opt influxql.IteratorOptions) (influxql.Iterator, error) {
if m.Name != "cpu" {
t.Fatalf("unexpected source: %s", m.Name)
}
makeAuxFields := func(value bool) []interface{} {
aux := make([]interface{}, len(opt.Aux))
for i := range aux {
aux[i] = value
}
return aux
}
return &BooleanIterator{Points: []influxql.BooleanPoint{
{Name: "cpu", Time: 0 * Second, Value: true, Aux: makeAuxFields(true)},
{Name: "cpu", Time: 5 * Second, Value: false, Aux: makeAuxFields(false)},
{Name: "cpu", Time: 9 * Second, Value: true, Aux: makeAuxFields(true)},
}}, nil
}
ic.FieldDimensionsFn = func(m *influxql.Measurement) (map[string]influxql.DataType, map[string]struct{}, error) {
if m.Name != "cpu" {
t.Fatalf("unexpected source: %s", m.Name)
}
return map[string]influxql.DataType{
"one": influxql.Boolean,
"two": influxql.Boolean,
}, nil, nil
}
for _, test := range []struct {
Name string
Statement string
Points [][]influxql.Point
}{
{
Name: "rhs binary bitwise-xor",
Statement: `SELECT one ^ true FROM cpu`,
Points: [][]influxql.Point{
{&influxql.BooleanPoint{Name: "cpu", Time: 0 * Second, Value: false}},
{&influxql.BooleanPoint{Name: "cpu", Time: 5 * Second, Value: true}},
{&influxql.BooleanPoint{Name: "cpu", Time: 9 * Second, Value: false}},
},
},
{
Name: "lhs binary or",
Statement: `SELECT true | two FROM cpu`,
Points: [][]influxql.Point{
{&influxql.BooleanPoint{Name: "cpu", Time: 0 * Second, Value: true}},
{&influxql.BooleanPoint{Name: "cpu", Time: 5 * Second, Value: true}},
{&influxql.BooleanPoint{Name: "cpu", Time: 9 * Second, Value: true}},
},
},
{
Name: "two series bitwise-and",
Statement: `SELECT one & two FROM cpu`,
Points: [][]influxql.Point{
{&influxql.BooleanPoint{Name: "cpu", Time: 0 * Second, Value: true}},
{&influxql.BooleanPoint{Name: "cpu", Time: 5 * Second, Value: false}},
{&influxql.BooleanPoint{Name: "cpu", Time: 9 * Second, Value: true}},
},
},
} {
stmt, err := MustParseSelectStatement(test.Statement).RewriteFields(&ic)
if err != nil {
t.Errorf("%s: rewrite error: %s", test.Name, err)
}
itrs, err := influxql.Select(stmt, &ic, nil)
if err != nil {
t.Errorf("%s: parse error: %s", test.Name, err)
} else if a, err := Iterators(itrs).ReadAll(); err != nil {
t.Fatalf("%s: unexpected error: %s", test.Name, err)
} else if !deep.Equal(a, test.Points) {
t.Errorf("%s: unexpected points: %s", test.Name, spew.Sdump(a))
}
}
}
// Ensure a SELECT binary expr with nil values can be executed.
// Nil values may be present when a field is missing from one iterator,
// but not the other.

View File

@ -33,11 +33,14 @@ const (
operatorBeg
// ADD and the following are InfluxQL Operators
ADD // +
SUB // -
MUL // *
DIV // /
MOD // %
ADD // +
SUB // -
MUL // *
DIV // /
MOD // %
BITWISE_AND // &
BITWISE_OR // |
BITWISE_XOR // ^
AND // AND
OR // OR
@ -153,11 +156,14 @@ var tokens = [...]string{
FALSE: "FALSE",
REGEX: "REGEX",
ADD: "+",
SUB: "-",
MUL: "*",
DIV: "/",
MOD: "%",
ADD: "+",
SUB: "-",
MUL: "*",
DIV: "/",
MOD: "%",
BITWISE_AND: "&",
BITWISE_OR: "|",
BITWISE_XOR: "^",
AND: "AND",
OR: "OR",
@ -285,9 +291,9 @@ func (tok Token) Precedence() int {
return 2
case EQ, NEQ, EQREGEX, NEQREGEX, LT, LTE, GT, GTE:
return 3
case ADD, SUB:
case ADD, SUB, BITWISE_OR, BITWISE_XOR:
return 4
case MUL, DIV, MOD:
case MUL, DIV, MOD, BITWISE_AND:
return 5
}
return 0