diff --git a/executor/executor_test.go b/executor/executor_test.go index 9c6cabce9e3d7..316e0dc6e8ffd 100644 --- a/executor/executor_test.go +++ b/executor/executor_test.go @@ -9426,6 +9426,44 @@ func (s *testSerialSuite) TestIssue28650(c *C) { } } +func (s *testSerialSuite) TestIssue29498(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("use test") + tk.MustExec("DROP TABLE IF EXISTS t1;") + tk.MustExec("CREATE TABLE t1 (t3 TIME(3), d DATE, t TIME);") + tk.MustExec("INSERT INTO t1 VALUES ('00:00:00.567', '2002-01-01', '00:00:02');") + + res := tk.MustQuery("SELECT CONCAT(IFNULL(t3, d)) AS col1 FROM t1;") + row := res.Rows()[0][0].(string) + c.Assert(len(row), Equals, mysql.MaxDatetimeWidthNoFsp+3+1) + c.Assert(row[len(row)-12:], Equals, "00:00:00.567") + + res = tk.MustQuery("SELECT IFNULL(t3, d) AS col1 FROM t1;") + row = res.Rows()[0][0].(string) + c.Assert(len(row), Equals, mysql.MaxDatetimeWidthNoFsp+3+1) + c.Assert(row[len(row)-12:], Equals, "00:00:00.567") + + res = tk.MustQuery("SELECT CONCAT(IFNULL(t, d)) AS col1 FROM t1;") + row = res.Rows()[0][0].(string) + c.Assert(len(row), Equals, mysql.MaxDatetimeWidthNoFsp) + c.Assert(row[len(row)-8:], Equals, "00:00:02") + + res = tk.MustQuery("SELECT IFNULL(t, d) AS col1 FROM t1;") + row = res.Rows()[0][0].(string) + c.Assert(len(row), Equals, mysql.MaxDatetimeWidthNoFsp) + c.Assert(row[len(row)-8:], Equals, "00:00:02") + + res = tk.MustQuery("SELECT CONCAT(xx) FROM (SELECT t3 AS xx FROM t1 UNION SELECT d FROM t1) x ORDER BY -xx LIMIT 1;") + row = res.Rows()[0][0].(string) + c.Assert(len(row), Equals, mysql.MaxDatetimeWidthNoFsp+3+1) + c.Assert(row[len(row)-12:], Equals, "00:00:00.567") + + res = tk.MustQuery("SELECT CONCAT(CASE WHEN d IS NOT NULL THEN t3 ELSE d END) AS col1 FROM t1;") + row = res.Rows()[0][0].(string) + c.Assert(len(row), Equals, mysql.MaxDatetimeWidthNoFsp+3+1) + c.Assert(row[len(row)-12:], Equals, "00:00:00.567") +} + // Details at https://github.com/pingcap/tidb/issues/31038 func (s *testSerialSuite) TestFix31038(c *C) { defer config.RestoreFunc()() diff --git a/expression/builtin_control.go b/expression/builtin_control.go index 6ea2c119176c7..51162c6a6ccc5 100644 --- a/expression/builtin_control.go +++ b/expression/builtin_control.go @@ -155,6 +155,8 @@ func InferType4ControlFuncs(ctx sessionctx.Context, funcName string, lexp, rexp if resultFieldType.Tp == mysql.TypeEnum || resultFieldType.Tp == mysql.TypeSet { resultFieldType.Tp = mysql.TypeVarchar } + } else if resultFieldType.Tp == mysql.TypeDatetime { + types.TryToFixFlenOfDatetime(resultFieldType) } return resultFieldType, nil } @@ -204,6 +206,7 @@ func (c *caseWhenFunctionClass) getFunction(ctx sessionctx.Context, args []Expre decimal = 0 } fieldTp.Decimal, fieldTp.Flen = decimal, flen + types.TryToFixFlenOfDatetime(fieldTp) if fieldTp.EvalType().IsStringKind() && !isBinaryStr { fieldTp.Charset, fieldTp.Collate = DeriveCollationFromExprs(ctx, args...) if fieldTp.Charset == charset.CharsetBin && fieldTp.Collate == charset.CollationBin { diff --git a/expression/typeinfer_test.go b/expression/typeinfer_test.go index ccfedeca8fd07..48d8f8f2e54c7 100644 --- a/expression/typeinfer_test.go +++ b/expression/typeinfer_test.go @@ -835,6 +835,8 @@ func (s *testInferTypeSuite) createTestCase4ControlFuncs() []typeInferTestCase { {"ifnull(null, null)", mysql.TypeNull, charset.CharsetBin, mysql.BinaryFlag, 0, 0}, {"ifnull(c_double_d, c_timestamp_d)", mysql.TypeVarchar, charset.CharsetUTF8MB4, 0, 22, types.UnspecifiedLength}, {"ifnull(c_json, c_decimal)", mysql.TypeLongBlob, charset.CharsetUTF8MB4, 0, math.MaxUint32, types.UnspecifiedLength}, + {"ifnull(c_time, c_date)", mysql.TypeDatetime, charset.CharsetUTF8MB4, 0, mysql.MaxDatetimeWidthNoFsp + 3 + 1, 3}, + {"ifnull(c_time_d, c_date)", mysql.TypeDatetime, charset.CharsetUTF8MB4, 0, mysql.MaxDatetimeWidthNoFsp, 0}, {"if(c_int_d, c_decimal, c_int_d)", mysql.TypeNewDecimal, charset.CharsetBin, mysql.BinaryFlag, 14, 3}, {"if(c_int_d, c_char, c_int_d)", mysql.TypeString, charset.CharsetUTF8MB4, mysql.BinaryFlag, 20, types.UnspecifiedLength}, {"if(c_int_d, c_binary, c_int_d)", mysql.TypeString, charset.CharsetBin, mysql.BinaryFlag, 20, types.UnspecifiedLength}, @@ -849,6 +851,8 @@ func (s *testInferTypeSuite) createTestCase4ControlFuncs() []typeInferTestCase { {"case when c_int_d > 1 then c_double_d else c_bchar end", mysql.TypeString, charset.CharsetUTF8MB4, mysql.BinaryFlag, 22, types.UnspecifiedLength}, {"case when c_int_d > 2 then c_double_d when c_int_d < 1 then c_decimal else c_double_d end", mysql.TypeDouble, charset.CharsetBin, mysql.BinaryFlag, 22, 3}, {"case when c_double_d > 2 then c_decimal else 1 end", mysql.TypeNewDecimal, charset.CharsetBin, mysql.BinaryFlag, 6, 3}, + {"case when c_time is not null then c_time else c_date end", mysql.TypeDatetime, charset.CharsetUTF8MB4, mysql.BinaryFlag, mysql.MaxDatetimeWidthNoFsp + 3 + 1, 3}, + {"case when c_time_d is not null then c_time_d else c_date end", mysql.TypeDatetime, charset.CharsetUTF8MB4, mysql.BinaryFlag, mysql.MaxDatetimeWidthNoFsp, 0}, {"case when null then null else null end", mysql.TypeNull, charset.CharsetBin, mysql.BinaryFlag, 0, types.UnspecifiedLength}, } } diff --git a/planner/core/logical_plan_builder.go b/planner/core/logical_plan_builder.go index 693089dde6d17..97c729e6ce00d 100644 --- a/planner/core/logical_plan_builder.go +++ b/planner/core/logical_plan_builder.go @@ -1367,6 +1367,7 @@ func unionJoinFieldType(a, b *types.FieldType) *types.FieldType { resultTp.Decimal = mathutil.Max(a.Decimal, b.Decimal) // `Flen - Decimal` is the fraction before '.' resultTp.Flen = mathutil.Max(a.Flen-a.Decimal, b.Flen-b.Decimal) + resultTp.Decimal + types.TryToFixFlenOfDatetime(resultTp) if resultTp.EvalType() != types.ETInt && (a.EvalType() == types.ETInt || b.EvalType() == types.ETInt) && resultTp.Flen < mysql.MaxIntWidth { resultTp.Flen = mysql.MaxIntWidth } diff --git a/types/field_type.go b/types/field_type.go index c7d96dda80255..aadd3e5d19a67 100644 --- a/types/field_type.go +++ b/types/field_type.go @@ -103,6 +103,16 @@ func AggFieldType(tps []*FieldType) *FieldType { return &currType } +// TryToFixFlenOfDatetime try to fix flen of Datetime for specific func or other field merge cases +func TryToFixFlenOfDatetime(resultTp *FieldType) { + if resultTp.Tp == mysql.TypeDatetime { + resultTp.Flen = mysql.MaxDatetimeWidthNoFsp + if resultTp.Decimal > 0 { + resultTp.Flen += resultTp.Decimal + 1 + } + } +} + // AggregateEvalType aggregates arguments' EvalType of a multi-argument function. func AggregateEvalType(fts []*FieldType, flag *uint) EvalType { var (