Skip to content
This repository was archived by the owner on Jan 28, 2021. It is now read-only.

Commit ded9391

Browse files
committed
Fixed bug in is false evaluation for nil values. Added tests for bool evaluation of string fields. This required allowing conversion of strings to bool (always false, as MySQL). This in turn exposed a bug in optimization, where non-column expressions were inappropriately getting coerced into boolean values. Removed the boolean casting, which is slightly less performant but correct in all cases.
Signed-off-by: Zach Musgrave <[email protected]>
1 parent f86f7e2 commit ded9391

File tree

6 files changed

+73
-31
lines changed

6 files changed

+73
-31
lines changed

engine_test.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ var queries = []struct {
108108
"SELECT i FROM mytable ORDER BY i DESC;",
109109
[]sql.Row{{int64(3)}, {int64(2)}, {int64(1)}},
110110
},
111+
{
112+
"SELECT i FROM mytable WHERE 'hello';",
113+
[]sql.Row{},
114+
},
115+
{
116+
"SELECT i FROM mytable WHERE not 'hello';",
117+
[]sql.Row{{int64(1)}, {int64(2)}, {int64(3)}},
118+
},
111119
{
112120
"SELECT i FROM mytable WHERE s = 'first row' ORDER BY i DESC;",
113121
[]sql.Row{{int64(1)}},
@@ -157,12 +165,12 @@ var queries = []struct {
157165
[]sql.Row{{int64(2)}, {nil}, {nil}},
158166
},
159167
{
160-
"SELECT i FROM niltable WHERE b IS FALSE",
161-
[]sql.Row{{int64(2)}, {nil}, {nil}},
168+
"SELECT f FROM niltable WHERE b IS FALSE",
169+
[]sql.Row{{3.0}},
162170
},
163171
{
164172
"SELECT i FROM niltable WHERE b IS NOT FALSE",
165-
[]sql.Row{{int64(1)}, {int64(4)}},
173+
[]sql.Row{{int64(1)}, {int64(2)}, {int64(4)}, {nil}},
166174
},
167175
{
168176
"SELECT COUNT(*) FROM mytable;",

sql/analyzer/optimization_rules.go

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -380,37 +380,20 @@ func evalFilter(ctx *sql.Context, a *Analyzer, node sql.Node) (sql.Node, error)
380380
return e.Left, nil
381381
}
382382

383+
return e, nil
384+
case *expression.Literal, expression.Tuple:
383385
return e, nil
384386
default:
385387
if !isEvaluable(e) {
386388
return e, nil
387389
}
388390

389-
if _, ok := e.(*expression.Literal); ok {
390-
return e, nil
391-
}
392-
393-
// UnaryMinus expressions come back from the parser when a negative float is evaluated. Treat them just like
394-
// normal literal expressions.
395-
if um, ok := e.(*expression.UnaryMinus); ok {
396-
negated, err := e.Eval(ctx, nil)
397-
if err != nil {
398-
return nil, err
399-
}
400-
return expression.NewLiteral(negated, um.Type()), nil
401-
}
402-
391+
// All other expressions types can be evaluated once and turned into literals for the rest of query execution
403392
val, err := e.Eval(ctx, nil)
404393
if err != nil {
405394
return e, nil
406395
}
407-
408-
val, err = sql.Boolean.Convert(val)
409-
if err != nil {
410-
return e, nil
411-
}
412-
413-
return expression.NewLiteral(val.(bool), sql.Boolean), nil
396+
return expression.NewLiteral(val, e.Type()), nil
414397
}
415398
})
416399
if err != nil {

sql/expression/istrue.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package expression
22

33
import "github.com/src-d/go-mysql-server/sql"
44

5-
// IsNull is an expression that checks if an expression is true.
5+
// IsTrue is an expression that checks if an expression is true.
66
type IsTrue struct {
77
UnaryExpression
88
invert bool
@@ -40,7 +40,7 @@ func (e *IsTrue) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
4040

4141
var boolVal interface{}
4242
if v == nil {
43-
boolVal = false
43+
return false, nil
4444
} else {
4545
boolVal, err = sql.Boolean.Convert(v)
4646
if err != nil {
@@ -70,8 +70,7 @@ func (e *IsTrue) TransformUp(f sql.TransformExprFunc) (sql.Expression, error) {
7070
}
7171
if e.invert {
7272
return f(NewIsFalse(child))
73-
} else {
74-
return f(NewIsTrue(child))
7573
}
74+
return f(NewIsTrue(child))
7675
}
7776

sql/expression/istrue_test.go

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,24 @@ func TestIsTrue(t *testing.T) {
2525
require.Equal(true, eval(t, e, sql.NewRow(100)))
2626
require.Equal(true, eval(t, e, sql.NewRow(-1)))
2727
require.Equal(false, eval(t, e, sql.NewRow(0)))
28+
29+
floatF := NewGetField(0, sql.Float64, "col1", true)
30+
e = NewIsTrue(floatF)
31+
require.Equal(sql.Boolean, e.Type())
32+
require.False(e.IsNullable())
33+
require.Equal(false, eval(t, e, sql.NewRow(nil)))
34+
require.Equal(true, eval(t, e, sql.NewRow(1.5)))
35+
require.Equal(true, eval(t, e, sql.NewRow(-1.5)))
36+
require.Equal(false, eval(t, e, sql.NewRow(0)))
37+
38+
stringF := NewGetField(0, sql.Text, "col1", true)
39+
e = NewIsTrue(stringF)
40+
require.Equal(sql.Boolean, e.Type())
41+
require.False(e.IsNullable())
42+
require.Equal(false, eval(t, e, sql.NewRow(nil)))
43+
require.Equal(false, eval(t, e, sql.NewRow("")))
44+
require.Equal(false, eval(t, e, sql.NewRow("false")))
45+
require.Equal(false, eval(t, e, sql.NewRow("true")))
2846
}
2947

3048
func TestIsFalse(t *testing.T) {
@@ -34,16 +52,34 @@ func TestIsFalse(t *testing.T) {
3452
e := NewIsFalse(boolF)
3553
require.Equal(sql.Boolean, e.Type())
3654
require.False(e.IsNullable())
37-
require.Equal(true, eval(t, e, sql.NewRow(nil)))
55+
require.Equal(false, eval(t, e, sql.NewRow(nil)))
3856
require.Equal(false, eval(t, e, sql.NewRow(true)))
3957
require.Equal(true, eval(t, e, sql.NewRow(false)))
4058

4159
intF := NewGetField(0, sql.Int64, "col1", true)
4260
e = NewIsFalse(intF)
4361
require.Equal(sql.Boolean, e.Type())
4462
require.False(e.IsNullable())
45-
require.Equal(true, eval(t, e, sql.NewRow(nil)))
63+
require.Equal(false, eval(t, e, sql.NewRow(nil)))
4664
require.Equal(false, eval(t, e, sql.NewRow(100)))
4765
require.Equal(false, eval(t, e, sql.NewRow(-1)))
4866
require.Equal(true, eval(t, e, sql.NewRow(0)))
67+
68+
floatF := NewGetField(0, sql.Float64, "col1", true)
69+
e = NewIsFalse(floatF)
70+
require.Equal(sql.Boolean, e.Type())
71+
require.False(e.IsNullable())
72+
require.Equal(false, eval(t, e, sql.NewRow(nil)))
73+
require.Equal(false, eval(t, e, sql.NewRow(1.5)))
74+
require.Equal(false, eval(t, e, sql.NewRow(-1.5)))
75+
require.Equal(true, eval(t, e, sql.NewRow(0)))
76+
77+
stringF := NewGetField(0, sql.Text, "col1", true)
78+
e = NewIsFalse(stringF)
79+
require.Equal(sql.Boolean, e.Type())
80+
require.False(e.IsNullable())
81+
require.Equal(false, eval(t, e, sql.NewRow(nil)))
82+
require.Equal(true, eval(t, e, sql.NewRow("")))
83+
require.Equal(true, eval(t, e, sql.NewRow("false")))
84+
require.Equal(true, eval(t, e, sql.NewRow("true")))
4985
}

sql/type.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,7 @@ func (t booleanT) Convert(v interface{}) (interface{}, error) {
708708
case float32, float64:
709709
return int(math.Round(v.(float64))) != 0, nil
710710
case string:
711-
return false, fmt.Errorf("unable to cast string to bool")
711+
return false, nil
712712

713713
case nil:
714714
return nil, fmt.Errorf("unable to cast nil to bool")

sql/type_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ func TestText(t *testing.T) {
3030
convert(t, Text, var3, "abc")
3131
}
3232

33+
func TestBoolean(t *testing.T) {
34+
convert(t, Boolean, "", false)
35+
convert(t, Boolean, "true", false)
36+
convert(t, Boolean, 0, false)
37+
convert(t, Boolean, 1, true)
38+
convert(t, Boolean, -1, true)
39+
convert(t, Boolean, 0.0, false)
40+
convert(t, Boolean, 0.4, false)
41+
convert(t, Boolean, 0.5, true)
42+
convert(t, Boolean, 1.0, true)
43+
convert(t, Boolean, -1.0, true)
44+
45+
eq(t, Boolean, true, true)
46+
eq(t, Boolean, false, false)
47+
}
48+
3349
func TestInt32(t *testing.T) {
3450
convert(t, Int32, int32(1), int32(1))
3551
convert(t, Int32, 1, int32(1))

0 commit comments

Comments
 (0)