Add bitwise AND, OR and XOR operators to InfluxQL.
parent
211e7ea65d
commit
d2fd3f50aa
|
@ -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
|
||||
|
||||
|
|
|
@ -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 } .
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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`},
|
||||
|
|
|
@ -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, ""
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue