diff --git a/expression/builtin_cast.go b/expression/builtin_cast.go index d64e83928b1e6..ba8aaaf03d0d8 100644 --- a/expression/builtin_cast.go +++ b/expression/builtin_cast.go @@ -1137,6 +1137,12 @@ func (b *builtinCastStringAsIntSig) evalInt(row chunk.Row) (res int64, isNull bo if b.args[0].GetType().Hybrid() || IsBinaryLiteral(b.args[0]) { return b.args[0].EvalInt(b.ctx, row) } + + // Take the implicit evalInt path if possible. + if CanImplicitEvalInt(b.args[0]) { + return b.args[0].EvalInt(b.ctx, row) + } + val, isNull, err := b.args[0].EvalString(b.ctx, row) if isNull || err != nil { return res, isNull, err @@ -1185,6 +1191,12 @@ func (b *builtinCastStringAsRealSig) evalReal(row chunk.Row) (res float64, isNul if IsBinaryLiteral(b.args[0]) { return b.args[0].EvalReal(b.ctx, row) } + + // Take the implicit evalReal path if possible. + if CanImplicitEvalReal(b.args[0]) { + return b.args[0].EvalReal(b.ctx, row) + } + val, isNull, err := b.args[0].EvalString(b.ctx, row) if isNull || err != nil { return res, isNull, err @@ -1745,18 +1757,31 @@ func (i inCastContext) String() string { // @see BuildCastFunction4Union const inUnionCastContext inCastContext = 0 -// hasSpecialCast checks if this expr has its own special cast function. -// for example(#9713): when doing arithmetic using results of function DayName, -// "Monday" should be regarded as 0, "Tuesday" should be regarded as 1 and so on. -func hasSpecialCast(ctx sessionctx.Context, expr Expression, tp *types.FieldType) bool { +// CanImplicitEvalInt represents the builtin functions that have an implicit path to evaluate as integer, +// regardless of the type that type inference decides it to be. +// This is a nasty way to match the weird behavior of MySQL functions like `dayname()` being implicitly evaluated as integer. +// See https://github.com/mysql/mysql-server/blob/ee4455a33b10f1b1886044322e4893f587b319ed/sql/item_timefunc.h#L423 for details. +func CanImplicitEvalInt(expr Expression) bool { switch f := expr.(type) { case *ScalarFunction: switch f.FuncName.L { case ast.DayName: - switch tp.EvalType() { - case types.ETInt, types.ETReal: - return true - } + return true + } + } + return false +} + +// CanImplicitEvalReal represents the builtin functions that have an implicit path to evaluate as real, +// regardless of the type that type inference decides it to be. +// This is a nasty way to match the weird behavior of MySQL functions like `dayname()` being implicitly evaluated as real. +// See https://github.com/mysql/mysql-server/blob/ee4455a33b10f1b1886044322e4893f587b319ed/sql/item_timefunc.h#L423 for details. +func CanImplicitEvalReal(expr Expression) bool { + switch f := expr.(type) { + case *ScalarFunction: + switch f.FuncName.L { + case ast.DayName: + return true } } return false @@ -1774,10 +1799,6 @@ func BuildCastFunction4Union(ctx sessionctx.Context, expr Expression, tp *types. // BuildCastFunction builds a CAST ScalarFunction from the Expression. func BuildCastFunction(ctx sessionctx.Context, expr Expression, tp *types.FieldType) (res Expression) { - if hasSpecialCast(ctx, expr, tp) { - return expr - } - var fc functionClass switch tp.EvalType() { case types.ETInt: diff --git a/expression/builtin_cast_vec.go b/expression/builtin_cast_vec.go index f42eb9f1cc58e..27282460d9e4c 100644 --- a/expression/builtin_cast_vec.go +++ b/expression/builtin_cast_vec.go @@ -852,6 +852,12 @@ func (b *builtinCastStringAsIntSig) vecEvalInt(input *chunk.Chunk, result *chunk if b.args[0].GetType().Hybrid() || IsBinaryLiteral(b.args[0]) { return b.args[0].VecEvalInt(b.ctx, input, result) } + + // Take the implicit evalInt path if possible. + if CanImplicitEvalInt(b.args[0]) { + return b.args[0].VecEvalInt(b.ctx, input, result) + } + result.ResizeInt64(n, false) buf, err := b.bufAllocator.get(types.ETString, n) if err != nil { @@ -1557,6 +1563,12 @@ func (b *builtinCastStringAsRealSig) vecEvalReal(input *chunk.Chunk, result *chu if IsBinaryLiteral(b.args[0]) { return b.args[0].VecEvalReal(b.ctx, input, result) } + + // Take the implicit evalReal path if possible. + if CanImplicitEvalReal(b.args[0]) { + return b.args[0].VecEvalReal(b.ctx, input, result) + } + n := input.NumRows() buf, err := b.bufAllocator.get(types.ETString, n) if err != nil { diff --git a/expression/expression.go b/expression/expression.go index 29aee092a42b4..646534f05c6e2 100644 --- a/expression/expression.go +++ b/expression/expression.go @@ -333,16 +333,24 @@ func VecEvalBool(ctx sessionctx.Context, exprList CNFExprs, input *chunk.Chunk, for _, expr := range exprList { tp := expr.GetType() eType := tp.EvalType() + if CanImplicitEvalReal(expr) { + eType = types.ETReal + } buf, err := globalColumnAllocator.get(eType, n) if err != nil { return nil, nil, err } - if err := EvalExpr(ctx, expr, eType, input, buf); err != nil { + // Take the implicit evalReal path if possible. + if CanImplicitEvalReal(expr) { + if err := implicitEvalReal(ctx, expr, input, buf); err != nil { + return nil, nil, err + } + } else if err := EvalExpr(ctx, expr, eType, input, buf); err != nil { return nil, nil, err } - err = toBool(ctx.GetSessionVars().StmtCtx, tp, buf, sel, isZero) + err = toBool(ctx.GetSessionVars().StmtCtx, tp, eType, buf, sel, isZero) if err != nil { return nil, nil, err } @@ -382,8 +390,7 @@ func VecEvalBool(ctx sessionctx.Context, exprList CNFExprs, input *chunk.Chunk, return selected, nulls, nil } -func toBool(sc *stmtctx.StatementContext, tp *types.FieldType, buf *chunk.Column, sel []int, isZero []int8) error { - eType := tp.EvalType() +func toBool(sc *stmtctx.StatementContext, tp *types.FieldType, eType types.EvalType, buf *chunk.Column, sel []int, isZero []int8) error { switch eType { case types.ETInt: i64s := buf.Int64s() @@ -499,6 +506,30 @@ func toBool(sc *stmtctx.StatementContext, tp *types.FieldType, buf *chunk.Column return nil } +func implicitEvalReal(ctx sessionctx.Context, expr Expression, input *chunk.Chunk, result *chunk.Column) (err error) { + if expr.Vectorized() && ctx.GetSessionVars().EnableVectorizedExpression { + err = expr.VecEvalReal(ctx, input, result) + } else { + ind, n := 0, input.NumRows() + iter := chunk.NewIterator4Chunk(input) + result.ResizeFloat64(n, false) + f64s := result.Float64s() + for it := iter.Begin(); it != iter.End(); it = iter.Next() { + value, isNull, err := expr.EvalReal(ctx, it) + if err != nil { + return err + } + if isNull { + result.SetNull(ind, isNull) + } else { + f64s[ind] = value + } + ind++ + } + } + return +} + // EvalExpr evaluates this expr according to its type. // And it selects the method for evaluating expression based on // the environment variables and whether the expression can be vectorized. diff --git a/expression/integration_test.go b/expression/integration_test.go index c612fa7b5c8b6..fd926064dc31b 100644 --- a/expression/integration_test.go +++ b/expression/integration_test.go @@ -1965,6 +1965,21 @@ func (s *testIntegrationSuite2) TestTimeBuiltin(c *C) { "Warning|1292|Incorrect datetime value: '0000-00-00 00:00:00.000000'", "Warning|1292|Incorrect datetime value: '0000-01-00 00:00:00.000000'", "Warning|1292|Incorrect datetime value: '0000-01-00 00:00:00.000000'")) + // for dayname implicit cast to boolean and real + result = tk.MustQuery(`select 1 from dual where dayname('2016-03-07')`) + result.Check(testkit.Rows()) + result = tk.MustQuery(`select 1 from dual where dayname('2016-03-07') is true`) + result.Check(testkit.Rows()) + result = tk.MustQuery(`select 1 from dual where dayname('2016-03-07') is false`) + result.Check(testkit.Rows("1")) + result = tk.MustQuery(`select 1 from dual where dayname('2016-03-08')`) + result.Check(testkit.Rows("1")) + result = tk.MustQuery(`select 1 from dual where dayname('2016-03-08') is true`) + result.Check(testkit.Rows("1")) + result = tk.MustQuery(`select 1 from dual where dayname('2016-03-08') is false`) + result.Check(testkit.Rows()) + result = tk.MustQuery(`select cast(dayname("2016-03-07") as double), cast(dayname("2016-03-08") as double)`) + result.Check(testkit.Rows("0 1")) // for sec_to_time result = tk.MustQuery("select sec_to_time(NULL)")