Merge pull request #5745 from influxdata/js-5740-mixed-float-integer-math

Update binary expressions to handle mixed math between integers and floats
pull/5763/head
Jonathan A. Sternberg 2016-02-19 16:05:16 -05:00
commit 328f505c93
2 changed files with 190 additions and 150 deletions

View File

@ -372,16 +372,22 @@ func buildExprIterator(expr Expr, ic IteratorCreator, opt IteratorOptions) (Iter
}
func buildRHSTransformIterator(lhs Iterator, rhs Literal, op Token, ic IteratorCreator, opt IteratorOptions) (Iterator, error) {
fn := binaryExprFunc(iteratorDataType(lhs), op)
fn := binaryExprFunc(iteratorDataType(lhs), literalDataType(rhs), op)
switch fn := fn.(type) {
case func(float64, float64) float64:
input, ok := lhs.(FloatIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected rhs to be FloatIterator, got %T", rhs)
var input FloatIterator
switch lhs := lhs.(type) {
case FloatIterator:
input = lhs
case IntegerIterator:
input = &integerFloatCastIterator{input: lhs}
default:
return nil, fmt.Errorf("type mismatch on LHS, unable to use %T as a FloatIterator", lhs)
}
lit, ok := rhs.(*NumberLiteral)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be NumberLiteral, got %T", lhs)
return nil, fmt.Errorf("type mismatch on RHS, unable to use %T as a NumberLiteral", lhs)
}
return &floatTransformIterator{
input: input,
@ -393,33 +399,20 @@ func buildRHSTransformIterator(lhs Iterator, rhs Literal, op Token, ic IteratorC
return p
},
}, nil
case func(int64, int64) int64:
input, ok := lhs.(IntegerIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected rhs to be IntegerIterator, got %T", rhs)
}
lit, ok := rhs.(*NumberLiteral)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be NumberLiteral, got %T", lhs)
}
return &integerTransformIterator{
input: input,
fn: func(p *IntegerPoint) *IntegerPoint {
if p == nil {
return nil
}
p.Value = fn(p.Value, int64(lit.Val))
return p
},
}, nil
case func(float64, float64) bool:
input, ok := lhs.(FloatIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be FloatIterator, got %T", lhs)
var input FloatIterator
switch lhs := lhs.(type) {
case FloatIterator:
input = lhs
case IntegerIterator:
input = &integerFloatCastIterator{input: lhs}
default:
return nil, fmt.Errorf("type mismatch on LHS, unable to use %T as a FloatIterator", lhs)
}
lit, ok := rhs.(*NumberLiteral)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be NumberLiteral, got %T", rhs)
return nil, fmt.Errorf("type mismatch on RHS, unable to use %T as a NumberLiteral", lhs)
}
return &floatBoolTransformIterator{
input: input,
@ -436,45 +429,27 @@ func buildRHSTransformIterator(lhs Iterator, rhs Literal, op Token, ic IteratorC
}
},
}, nil
case func(int64, int64) bool:
input, ok := lhs.(IntegerIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be IntegerIterator, got %T", lhs)
}
lit, ok := rhs.(*NumberLiteral)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be NumberLiteral, got %T", rhs)
}
return &integerBoolTransformIterator{
input: input,
fn: func(p *IntegerPoint) *BooleanPoint {
if p == nil {
return nil
}
return &BooleanPoint{
Name: p.Name,
Tags: p.Tags,
Time: p.Time,
Value: fn(p.Value, int64(lit.Val)),
Aux: p.Aux,
}
},
}, nil
}
return nil, fmt.Errorf("unable to construct rhs transform iterator from %T and %T", lhs, rhs)
}
func buildLHSTransformIterator(lhs Literal, rhs Iterator, op Token, ic IteratorCreator, opt IteratorOptions) (Iterator, error) {
fn := binaryExprFunc(iteratorDataType(rhs), op)
fn := binaryExprFunc(literalDataType(lhs), iteratorDataType(rhs), op)
switch fn := fn.(type) {
case func(float64, float64) float64:
var input FloatIterator
switch rhs := rhs.(type) {
case FloatIterator:
input = rhs
case IntegerIterator:
input = &integerFloatCastIterator{input: rhs}
default:
return nil, fmt.Errorf("type mismatch on RHS, unable to use %T as a FloatIterator", rhs)
}
lit, ok := lhs.(*NumberLiteral)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be NumberLiteral, got %T", lhs)
}
input, ok := rhs.(FloatIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected rhs to be FloatIterator, got %T", rhs)
return nil, fmt.Errorf("type mismatch on LHS, unable to use %T as a NumberLiteral", lhs)
}
return &floatTransformIterator{
input: input,
@ -486,33 +461,20 @@ func buildLHSTransformIterator(lhs Literal, rhs Iterator, op Token, ic IteratorC
return p
},
}, nil
case func(int64, int64) int64:
lit, ok := lhs.(*NumberLiteral)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be NumberLiteral, got %T", lhs)
}
input, ok := rhs.(IntegerIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected rhs to be IntegerIterator, got %T", rhs)
}
return &integerTransformIterator{
input: input,
fn: func(p *IntegerPoint) *IntegerPoint {
if p == nil {
return nil
}
p.Value = fn(int64(lit.Val), p.Value)
return p
},
}, nil
case func(float64, float64) bool:
var input FloatIterator
switch rhs := rhs.(type) {
case FloatIterator:
input = rhs
case IntegerIterator:
input = &integerFloatCastIterator{input: rhs}
default:
return nil, fmt.Errorf("type mismatch on RHS, unable to use %T as a FloatIterator", rhs)
}
lit, ok := lhs.(*NumberLiteral)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be NumberLiteral, got %T", lhs)
}
input, ok := rhs.(FloatIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be FloatIterator, got %T", rhs)
return nil, fmt.Errorf("type mismatch on LHS, unable to use %T as a NumberLiteral", lhs)
}
return &floatBoolTransformIterator{
input: input,
@ -529,45 +491,32 @@ func buildLHSTransformIterator(lhs Literal, rhs Iterator, op Token, ic IteratorC
}
},
}, nil
case func(int64, int64) bool:
lit, ok := lhs.(*NumberLiteral)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be NumberLiteral, got %T", lhs)
}
input, ok := rhs.(IntegerIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be IntegerIterator, got %T", rhs)
}
return &integerBoolTransformIterator{
input: input,
fn: func(p *IntegerPoint) *BooleanPoint {
if p == nil {
return nil
}
return &BooleanPoint{
Name: p.Name,
Tags: p.Tags,
Time: p.Time,
Value: fn(int64(lit.Val), p.Value),
Aux: p.Aux,
}
},
}, nil
}
return nil, fmt.Errorf("unable to construct lhs transform iterator from %T and %T", lhs, rhs)
}
func buildTransformIterator(lhs Iterator, rhs Iterator, op Token, ic IteratorCreator, opt IteratorOptions) (Iterator, error) {
fn := binaryExprFunc(iteratorDataType(lhs), op)
fn := binaryExprFunc(iteratorDataType(lhs), iteratorDataType(rhs), op)
switch fn := fn.(type) {
case func(float64, float64) float64:
left, ok := lhs.(FloatIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be FloatIterator, got %T", lhs)
var left FloatIterator
switch lhs := lhs.(type) {
case FloatIterator:
left = lhs
case IntegerIterator:
left = &integerFloatCastIterator{input: lhs}
default:
return nil, fmt.Errorf("type mismatch on LHS, unable to use %T as a FloatIterator", lhs)
}
right, ok := rhs.(FloatIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be FloatIterator, got %T", rhs)
var right FloatIterator
switch rhs := rhs.(type) {
case FloatIterator:
right = rhs
case IntegerIterator:
right = &integerFloatCastIterator{input: rhs}
default:
return nil, fmt.Errorf("type mismatch on RHS, unable to use %T as a FloatIterator", rhs)
}
return &floatTransformIterator{
input: left,
@ -586,11 +535,11 @@ func buildTransformIterator(lhs Iterator, rhs Iterator, op Token, ic IteratorCre
case func(int64, int64) int64:
left, ok := lhs.(IntegerIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be IntegerIterator, got %T", lhs)
return nil, fmt.Errorf("type mismatch on LHS, unable to use %T as a IntegerIterator", lhs)
}
right, ok := rhs.(IntegerIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be IntegerIterator, got %T", rhs)
return nil, fmt.Errorf("type mismatch on RHS, unable to use %T as a IntegerIterator", rhs)
}
return &integerTransformIterator{
input: left,
@ -607,13 +556,24 @@ func buildTransformIterator(lhs Iterator, rhs Iterator, op Token, ic IteratorCre
},
}, nil
case func(float64, float64) bool:
left, ok := lhs.(FloatIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be FloatIterator, got %T", lhs)
var left FloatIterator
switch lhs := lhs.(type) {
case FloatIterator:
left = lhs
case IntegerIterator:
left = &integerFloatCastIterator{input: lhs}
default:
return nil, fmt.Errorf("type mismatch on LHS, unable to use %T as a FloatIterator", lhs)
}
right, ok := rhs.(FloatIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be FloatIterator, got %T", rhs)
var right FloatIterator
switch rhs := rhs.(type) {
case FloatIterator:
right = rhs
case IntegerIterator:
right = &integerFloatCastIterator{input: rhs}
default:
return nil, fmt.Errorf("type mismatch on RHS, unable to use %T as a FloatIterator", rhs)
}
return &floatBoolTransformIterator{
input: left,
@ -637,11 +597,11 @@ func buildTransformIterator(lhs Iterator, rhs Iterator, op Token, ic IteratorCre
case func(int64, int64) bool:
left, ok := lhs.(IntegerIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be IntegerIterator, got %T", lhs)
return nil, fmt.Errorf("type mismatch on LHS, unable to use %T as a IntegerIterator", lhs)
}
right, ok := rhs.(IntegerIterator)
if !ok {
return nil, fmt.Errorf("type mismatch, expected lhs to be IntegerIterator, got %T", rhs)
return nil, fmt.Errorf("type mismatch on LHS, unable to use %T as a IntegerIterator", rhs)
}
return &integerBoolTransformIterator{
input: left,
@ -681,13 +641,31 @@ func iteratorDataType(itr Iterator) DataType {
}
}
func binaryExprFunc(typ DataType, op Token) interface{} {
func literalDataType(lit Literal) DataType {
switch lit.(type) {
case *NumberLiteral:
return Float
case *StringLiteral:
return String
case *BooleanLiteral:
return Boolean
default:
return Unknown
}
}
func binaryExprFunc(typ1 DataType, typ2 DataType, op Token) interface{} {
var fn interface{}
switch typ {
switch typ1 {
case Float:
fn = floatBinaryExprFunc(op)
case Integer:
fn = integerBinaryExprFunc(op)
switch typ2 {
case Float:
fn = floatBinaryExprFunc(op)
default:
fn = integerBinaryExprFunc(op)
}
}
return fn
}

View File

@ -1304,18 +1304,18 @@ func TestSelect_BinaryExpr_Integer(t *testing.T) {
Name: "rhs binary add",
Statement: `SELECT value + 2 FROM cpu`,
Points: [][]influxql.Point{
{&influxql.IntegerPoint{Name: "cpu", Time: 0 * Second, Value: 22}},
{&influxql.IntegerPoint{Name: "cpu", Time: 5 * Second, Value: 12}},
{&influxql.IntegerPoint{Name: "cpu", Time: 9 * Second, Value: 21}},
{&influxql.FloatPoint{Name: "cpu", Time: 0 * Second, Value: 22}},
{&influxql.FloatPoint{Name: "cpu", Time: 5 * Second, Value: 12}},
{&influxql.FloatPoint{Name: "cpu", Time: 9 * Second, Value: 21}},
},
},
{
Name: "lhs binary add",
Statement: `SELECT 2 + value FROM cpu`,
Points: [][]influxql.Point{
{&influxql.IntegerPoint{Name: "cpu", Time: 0 * Second, Value: 22}},
{&influxql.IntegerPoint{Name: "cpu", Time: 5 * Second, Value: 12}},
{&influxql.IntegerPoint{Name: "cpu", Time: 9 * Second, Value: 21}},
{&influxql.FloatPoint{Name: "cpu", Time: 0 * Second, Value: 22}},
{&influxql.FloatPoint{Name: "cpu", Time: 5 * Second, Value: 12}},
{&influxql.FloatPoint{Name: "cpu", Time: 9 * Second, Value: 21}},
},
},
{
@ -1331,18 +1331,18 @@ func TestSelect_BinaryExpr_Integer(t *testing.T) {
Name: "rhs binary multiply",
Statement: `SELECT value * 2 FROM cpu`,
Points: [][]influxql.Point{
{&influxql.IntegerPoint{Name: "cpu", Time: 0 * Second, Value: 40}},
{&influxql.IntegerPoint{Name: "cpu", Time: 5 * Second, Value: 20}},
{&influxql.IntegerPoint{Name: "cpu", Time: 9 * Second, Value: 38}},
{&influxql.FloatPoint{Name: "cpu", Time: 0 * Second, Value: 40}},
{&influxql.FloatPoint{Name: "cpu", Time: 5 * Second, Value: 20}},
{&influxql.FloatPoint{Name: "cpu", Time: 9 * Second, Value: 38}},
},
},
{
Name: "lhs binary multiply",
Statement: `SELECT 2 * value FROM cpu`,
Points: [][]influxql.Point{
{&influxql.IntegerPoint{Name: "cpu", Time: 0 * Second, Value: 40}},
{&influxql.IntegerPoint{Name: "cpu", Time: 5 * Second, Value: 20}},
{&influxql.IntegerPoint{Name: "cpu", Time: 9 * Second, Value: 38}},
{&influxql.FloatPoint{Name: "cpu", Time: 0 * Second, Value: 40}},
{&influxql.FloatPoint{Name: "cpu", Time: 5 * Second, Value: 20}},
{&influxql.FloatPoint{Name: "cpu", Time: 9 * Second, Value: 38}},
},
},
{
@ -1358,18 +1358,18 @@ func TestSelect_BinaryExpr_Integer(t *testing.T) {
Name: "rhs binary subtract",
Statement: `SELECT value - 2 FROM cpu`,
Points: [][]influxql.Point{
{&influxql.IntegerPoint{Name: "cpu", Time: 0 * Second, Value: 18}},
{&influxql.IntegerPoint{Name: "cpu", Time: 5 * Second, Value: 8}},
{&influxql.IntegerPoint{Name: "cpu", Time: 9 * Second, Value: 17}},
{&influxql.FloatPoint{Name: "cpu", Time: 0 * Second, Value: 18}},
{&influxql.FloatPoint{Name: "cpu", Time: 5 * Second, Value: 8}},
{&influxql.FloatPoint{Name: "cpu", Time: 9 * Second, Value: 17}},
},
},
{
Name: "lhs binary subtract",
Statement: `SELECT 2 - value FROM cpu`,
Points: [][]influxql.Point{
{&influxql.IntegerPoint{Name: "cpu", Time: 0 * Second, Value: -18}},
{&influxql.IntegerPoint{Name: "cpu", Time: 5 * Second, Value: -8}},
{&influxql.IntegerPoint{Name: "cpu", Time: 9 * Second, Value: -17}},
{&influxql.FloatPoint{Name: "cpu", Time: 0 * Second, Value: -18}},
{&influxql.FloatPoint{Name: "cpu", Time: 5 * Second, Value: -8}},
{&influxql.FloatPoint{Name: "cpu", Time: 9 * Second, Value: -17}},
},
},
{
@ -1385,18 +1385,18 @@ func TestSelect_BinaryExpr_Integer(t *testing.T) {
Name: "rhs binary division",
Statement: `SELECT value / 2 FROM cpu`,
Points: [][]influxql.Point{
{&influxql.IntegerPoint{Name: "cpu", Time: 0 * Second, Value: 10}},
{&influxql.IntegerPoint{Name: "cpu", Time: 5 * Second, Value: 5}},
{&influxql.IntegerPoint{Name: "cpu", Time: 9 * Second, Value: 9}},
{&influxql.FloatPoint{Name: "cpu", Time: 0 * Second, Value: 10}},
{&influxql.FloatPoint{Name: "cpu", Time: 5 * Second, Value: 5}},
{&influxql.FloatPoint{Name: "cpu", Time: 9 * Second, Value: 9.5}},
},
},
{
Name: "lhs binary division",
Statement: `SELECT 38 / value FROM cpu`,
Points: [][]influxql.Point{
{&influxql.IntegerPoint{Name: "cpu", Time: 0 * Second, Value: 1}},
{&influxql.IntegerPoint{Name: "cpu", Time: 5 * Second, Value: 3}},
{&influxql.IntegerPoint{Name: "cpu", Time: 9 * Second, Value: 2}},
{&influxql.FloatPoint{Name: "cpu", Time: 0 * Second, Value: 1.9}},
{&influxql.FloatPoint{Name: "cpu", Time: 5 * Second, Value: 3.8}},
{&influxql.FloatPoint{Name: "cpu", Time: 9 * Second, Value: 2.0}},
},
},
{
@ -1418,6 +1418,68 @@ func TestSelect_BinaryExpr_Integer(t *testing.T) {
}
}
// Ensure a SELECT binary expr queries can be executed on mixed iterators.
func TestSelect_BinaryExpr_Mixed(t *testing.T) {
var ic IteratorCreator
ic.CreateIteratorFn = func(opt influxql.IteratorOptions) (influxql.Iterator, error) {
return &IntegerIterator{Points: []influxql.IntegerPoint{
{Name: "cpu", Time: 0 * Second, Value: 20, Aux: []interface{}{float64(20), int64(10)}},
{Name: "cpu", Time: 5 * Second, Value: 10, Aux: []interface{}{float64(10), int64(15)}},
{Name: "cpu", Time: 9 * Second, Value: 19, Aux: []interface{}{float64(19), int64(5)}},
}}, nil
}
for _, test := range []struct {
Name string
Statement string
Points [][]influxql.Point
}{
{
Name: "mixed binary add",
Statement: `SELECT total + value FROM cpu`,
Points: [][]influxql.Point{
{&influxql.FloatPoint{Name: "cpu", Time: 0 * Second, Value: 30}},
{&influxql.FloatPoint{Name: "cpu", Time: 5 * Second, Value: 25}},
{&influxql.FloatPoint{Name: "cpu", Time: 9 * Second, Value: 24}},
},
},
{
Name: "mixed binary subtract",
Statement: `SELECT total - value FROM cpu`,
Points: [][]influxql.Point{
{&influxql.FloatPoint{Name: "cpu", Time: 0 * Second, Value: 10}},
{&influxql.FloatPoint{Name: "cpu", Time: 5 * Second, Value: -5}},
{&influxql.FloatPoint{Name: "cpu", Time: 9 * Second, Value: 14}},
},
},
{
Name: "mixed binary multiply",
Statement: `SELECT total * value FROM cpu`,
Points: [][]influxql.Point{
{&influxql.FloatPoint{Name: "cpu", Time: 0 * Second, Value: 200}},
{&influxql.FloatPoint{Name: "cpu", Time: 5 * Second, Value: 150}},
{&influxql.FloatPoint{Name: "cpu", Time: 9 * Second, Value: 95}},
},
},
{
Name: "mixed binary division",
Statement: `SELECT total / value FROM cpu`,
Points: [][]influxql.Point{
{&influxql.FloatPoint{Name: "cpu", Time: 0 * Second, Value: 2}},
{&influxql.FloatPoint{Name: "cpu", Time: 5 * Second, Value: float64(10) / float64(15)}},
{&influxql.FloatPoint{Name: "cpu", Time: 9 * Second, Value: float64(19) / float64(5)}},
},
},
} {
itrs, err := influxql.Select(MustParseSelectStatement(test.Statement), &ic, nil)
if err != nil {
t.Errorf("%s: parse error: %s", test.Name, err)
} else if a := Iterators(itrs).ReadAll(); !deep.Equal(a, test.Points) {
t.Errorf("%s: unexpected points: %s", test.Name, spew.Sdump(a))
}
}
}
// Ensure a SELECT (...) query can be executed.
func TestSelect_ParenExpr(t *testing.T) {
var ic IteratorCreator