Skip to content

Commit

Permalink
ddl: fix cast date as timestamp will write invalid value (pingcap#26362)
Browse files Browse the repository at this point in the history
  • Loading branch information
AilinKid authored Jul 20, 2021
1 parent 39c9a4d commit 6329c86
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 2 deletions.
50 changes: 50 additions & 0 deletions ddl/column_type_change_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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'")
Expand All @@ -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")
}
4 changes: 3 additions & 1 deletion sessionctx/stmtctx/stmtctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions table/column.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion types/datum.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 6329c86

Please sign in to comment.