diff --git a/ddl/column_type_change_test.go b/ddl/column_type_change_test.go index 273522c00f874..26cefde312af8 100644 --- a/ddl/column_type_change_test.go +++ b/ddl/column_type_change_test.go @@ -2112,6 +2112,7 @@ func (s *testColumnTypeChangeSuite) TestCastToTimeStampDecodeError(c *C) { tk := testkit.NewTestKit(c, s.store) tk.MustExec("use test;") + tk.MustExec("drop table if exists t") tk.MustExec("CREATE TABLE `t` (" + " `a` datetime DEFAULT '1764-06-11 02:46:14'" + ") ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin COMMENT='7b84832e-f857-4116-8872-82fc9dcc4ab3'") @@ -2129,3 +2130,52 @@ func (s *testColumnTypeChangeSuite) TestCastToTimeStampDecodeError(c *C) { // Normal cast datetime to timestamp can succeed. tk.MustQuery("select timestamp(cast('1000-11-11 12-3-1' as date));").Check(testkit.Rows("1000-11-11 00:00:00")) } + +// Fix issue: https://github.com/pingcap/tidb/issues/26292 +// Cast date to timestamp has two kind behavior: cast("3977-02-22" as date) +// For select statement, it truncate the string and return no errors. (which is 3977-02-22 00:00:00 here) +// For ddl reorging or changing column in ctc, it need report some errors. +func (s *testColumnTypeChangeSuite) TestCastDateToTimestampInReorgAttribute(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("use test;") + + tk.MustExec("drop table if exists t") + tk.MustExec("CREATE TABLE `t` (`a` DATE NULL DEFAULT '8497-01-06')") + tk.MustExec("insert into t values(now())") + + originalHook := s.dom.DDL().GetHook() + defer s.dom.DDL().(ddl.DDLForTest).SetHook(originalHook) + + // use new session to check meta in callback function. + internalTK := testkit.NewTestKit(c, s.store) + internalTK.MustExec("use test") + + tbl := testGetTableByName(c, tk.Se, "test", "t") + c.Assert(tbl, NotNil) + c.Assert(len(tbl.Cols()), Equals, 1) + + hook := &ddl.TestDDLCallback{} + var ( + checkErr1 error + checkErr2 error + ) + hook.OnJobRunBeforeExported = func(job *model.Job) { + if checkErr1 != nil || checkErr2 != nil { + return + } + if tbl.Meta().ID != job.TableID { + return + } + switch job.SchemaState { + case model.StateWriteOnly: + _, checkErr1 = internalTK.Exec("insert into `t` set `a` = '3977-02-22'") // this(string) will be cast to a as date, then cast a(date) as timestamp to changing column. + _, checkErr2 = internalTK.Exec("update t set `a` = '3977-02-22'") + } + } + s.dom.DDL().(ddl.DDLForTest).SetHook(hook) + + tk.MustExec("alter table t modify column a TIMESTAMP NULL DEFAULT '2021-04-28 03:35:11' FIRST") + c.Assert(checkErr1.Error(), Equals, "[types:1292]Incorrect datetime value: '3977-02-22 00:00:00'") + c.Assert(checkErr2.Error(), Equals, "[types:1292]Incorrect datetime value: '3977-02-22 00:00:00'") + tk.MustExec("drop table if exists t") +} diff --git a/sessionctx/stmtctx/stmtctx.go b/sessionctx/stmtctx/stmtctx.go index 51e753b6b72b4..2396299cdbd8e 100644 --- a/sessionctx/stmtctx/stmtctx.go +++ b/sessionctx/stmtctx/stmtctx.go @@ -63,7 +63,9 @@ type StatementContext struct { // IsDDLJobInQueue is used to mark whether the DDL job is put into the queue. // If IsDDLJobInQueue is true, it means the DDL job is in the queue of storage, and it can be handled by the DDL worker. - IsDDLJobInQueue bool + IsDDLJobInQueue bool + // InReorgAttribute is indicated for cast function that the transition is a kind of reorg process. + InReorgAttribute bool InInsertStmt bool InUpdateStmt bool InDeleteStmt bool diff --git a/table/column.go b/table/column.go index 59e499e190231..481c08553bb96 100644 --- a/table/column.go +++ b/table/column.go @@ -248,6 +248,14 @@ func handleZeroDatetime(ctx sessionctx.Context, col *model.ColumnInfo, casted ty // TODO: change the third arg to TypeField. Not pass ColumnInfo. func CastValue(ctx sessionctx.Context, val types.Datum, col *model.ColumnInfo, returnErr, forceIgnoreTruncate bool) (casted types.Datum, err error) { sc := ctx.GetSessionVars().StmtCtx + // Set the reorg attribute for cast value functionality. + if col.ChangeStateInfo != nil { + origin := ctx.GetSessionVars().StmtCtx.InReorgAttribute + ctx.GetSessionVars().StmtCtx.InReorgAttribute = true + defer func() { + ctx.GetSessionVars().StmtCtx.InReorgAttribute = origin + }() + } casted, err = val.ConvertTo(sc, &col.FieldType) // TODO: make sure all truncate errors are handled by ConvertTo. if returnErr && err != nil { diff --git a/types/datum.go b/types/datum.go index 123d6e0670348..6103909066714 100644 --- a/types/datum.go +++ b/types/datum.go @@ -1130,7 +1130,7 @@ func (d *Datum) convertToMysqlTimestamp(sc *stmtctx.StatementContext, target *Fi case KindMysqlTime: // `select timestamp(cast("1000-01-02 23:59:59" as date)); ` casts usage will succeed. // Alter datetime("1000-01-02 23:59:59") to timestamp will error. - if sc.IsDDLJobInQueue { + if sc.InReorgAttribute { t, err = d.GetMysqlTime().Convert(sc, target.Tp) if err != nil { ret.SetMysqlTime(t)