From 1b3f5071e010b1337f8828ee162196d44b28403b Mon Sep 17 00:00:00 2001 From: tangenta Date: Thu, 24 Jun 2021 11:44:58 +0800 Subject: [PATCH 1/4] ddl: disallow change columns from zero int to time --- ddl/backfilling.go | 1 + ddl/column_type_change_test.go | 13 +++++++++++++ types/time.go | 13 ++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/ddl/backfilling.go b/ddl/backfilling.go index 0a9542ff4f39a..dcacef5fa6350 100644 --- a/ddl/backfilling.go +++ b/ddl/backfilling.go @@ -601,6 +601,7 @@ func (w *worker) writePhysicalTableRecord(t table.PhysicalTable, bfWorkerType ba backfillWorkers = append(backfillWorkers, idxWorker.backfillWorker) go idxWorker.backfillWorker.run(reorgInfo.d, idxWorker) case typeUpdateColumnWorker: + sessCtx.GetSessionVars().StmtCtx.InCreateOrAlterStmt = true updateWorker := newUpdateColumnWorker(sessCtx, w, i, t, oldColInfo, colInfo, decodeColMap, reorgInfo.ReorgMeta.SQLMode) updateWorker.priority = job.Priority backfillWorkers = append(backfillWorkers, updateWorker.backfillWorker) diff --git a/ddl/column_type_change_test.go b/ddl/column_type_change_test.go index 273522c00f874..9e9f3d5870628 100644 --- a/ddl/column_type_change_test.go +++ b/ddl/column_type_change_test.go @@ -2129,3 +2129,16 @@ 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")) } + +// https://github.com/pingcap/tidb/issues/25285. +func (s *testColumnTypeChangeSuite) TestCastFromZeroIntToTimeError(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 int);") + tk.MustExec("insert into t values (0);") + tk.MustGetErrCode("alter table t modify column a date;", mysql.ErrTruncatedWrongValue) + tk.MustGetErrCode("alter table t modify column a datetime;", mysql.ErrTruncatedWrongValue) + tk.MustGetErrCode("alter table t modify column a timestamp;", mysql.ErrTruncatedWrongValue) +} diff --git a/types/time.go b/types/time.go index 28a34b0284833..f0497e806dbc2 100644 --- a/types/time.go +++ b/types/time.go @@ -1974,7 +1974,18 @@ func ParseTimeFromYear(sc *stmtctx.StatementContext, year int64) (Time, error) { func ParseTimeFromNum(sc *stmtctx.StatementContext, num int64, tp byte, fsp int8) (Time, error) { // MySQL compatibility: 0 should not be converted to null, see #11203 if num == 0 { - return NewTime(ZeroCoreTime, tp, DefaultFsp), nil + zt := NewTime(ZeroCoreTime, tp, DefaultFsp) + if sc.InCreateOrAlterStmt { + switch tp { + case mysql.TypeTimestamp: + return zt, ErrTruncatedWrongVal.GenWithStackByArgs("timestamp", "0") + case mysql.TypeDate: + return zt, ErrTruncatedWrongVal.GenWithStackByArgs("date", "0") + case mysql.TypeDatetime: + return zt, ErrTruncatedWrongVal.GenWithStackByArgs("datetime", "0") + } + } + return zt, nil } fsp, err := CheckFsp(int(fsp)) if err != nil { From 2261a50b08a7929680b1834fca0b3d2572e8fd3e Mon Sep 17 00:00:00 2001 From: tangenta Date: Thu, 24 Jun 2021 15:10:05 +0800 Subject: [PATCH 2/4] fix integration tests --- ddl/column_type_change_test.go | 1 + ddl/db_test.go | 9 +++------ types/time.go | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/ddl/column_type_change_test.go b/ddl/column_type_change_test.go index 9e9f3d5870628..351efc6051c29 100644 --- a/ddl/column_type_change_test.go +++ b/ddl/column_type_change_test.go @@ -2141,4 +2141,5 @@ func (s *testColumnTypeChangeSuite) TestCastFromZeroIntToTimeError(c *C) { tk.MustGetErrCode("alter table t modify column a date;", mysql.ErrTruncatedWrongValue) tk.MustGetErrCode("alter table t modify column a datetime;", mysql.ErrTruncatedWrongValue) tk.MustGetErrCode("alter table t modify column a timestamp;", mysql.ErrTruncatedWrongValue) + tk.MustExec("drop table if exists t;") } diff --git a/ddl/db_test.go b/ddl/db_test.go index 16bc8e8c5563e..2b282a2adf17b 100644 --- a/ddl/db_test.go +++ b/ddl/db_test.go @@ -5144,8 +5144,7 @@ func (s *testDBSuite1) TestModifyColumnTime_YearToDate(c *C) { {"year", `"69"`, "date", "", errno.ErrTruncatedWrongValue}, {"year", `"70"`, "date", "", errno.ErrTruncatedWrongValue}, {"year", `"99"`, "date", "", errno.ErrTruncatedWrongValue}, - // MySQL will get "Data truncation: Incorrect date value: '0000'", but TiDB treat 00 as valid datetime. - {"year", `00`, "date", "0000-00-00", 0}, + {"year", `00`, "date", "", errno.ErrTruncatedWrongValue}, {"year", `69`, "date", "", errno.ErrTruncatedWrongValue}, {"year", `70`, "date", "", errno.ErrTruncatedWrongValue}, {"year", `99`, "date", "", errno.ErrTruncatedWrongValue}, @@ -5162,8 +5161,7 @@ func (s *testDBSuite1) TestModifyColumnTime_YearToDatetime(c *C) { {"year", `"69"`, "datetime", "", errno.ErrTruncatedWrongValue}, {"year", `"70"`, "datetime", "", errno.ErrTruncatedWrongValue}, {"year", `"99"`, "datetime", "", errno.ErrTruncatedWrongValue}, - // MySQL will get "Data truncation: Incorrect date value: '0000'", but TiDB treat 00 as valid datetime. - {"year", `00`, "datetime", "0000-00-00 00:00:00", 0}, + {"year", `00`, "datetime", "", errno.ErrTruncatedWrongValue}, {"year", `69`, "datetime", "", errno.ErrTruncatedWrongValue}, {"year", `70`, "datetime", "", errno.ErrTruncatedWrongValue}, {"year", `99`, "datetime", "", errno.ErrTruncatedWrongValue}, @@ -5180,8 +5178,7 @@ func (s *testDBSuite1) TestModifyColumnTime_YearToTimestamp(c *C) { {"year", `"69"`, "timestamp", "", errno.ErrTruncatedWrongValue}, {"year", `"70"`, "timestamp", "", errno.ErrTruncatedWrongValue}, {"year", `"99"`, "timestamp", "", errno.ErrTruncatedWrongValue}, - // MySQL will get "Data truncation: Incorrect date value: '0000'", but TiDB treat 00 as valid datetime. - {"year", `00`, "timestamp", "0000-00-00 00:00:00", 0}, + {"year", `00`, "timestamp", "", errno.ErrTruncatedWrongValue}, {"year", `69`, "timestamp", "", errno.ErrTruncatedWrongValue}, {"year", `70`, "timestamp", "", errno.ErrTruncatedWrongValue}, {"year", `99`, "timestamp", "", errno.ErrTruncatedWrongValue}, diff --git a/types/time.go b/types/time.go index f0497e806dbc2..23bcc34386cca 100644 --- a/types/time.go +++ b/types/time.go @@ -1975,7 +1975,7 @@ func ParseTimeFromNum(sc *stmtctx.StatementContext, num int64, tp byte, fsp int8 // MySQL compatibility: 0 should not be converted to null, see #11203 if num == 0 { zt := NewTime(ZeroCoreTime, tp, DefaultFsp) - if sc.InCreateOrAlterStmt { + if sc != nil && sc.InCreateOrAlterStmt { switch tp { case mysql.TypeTimestamp: return zt, ErrTruncatedWrongVal.GenWithStackByArgs("timestamp", "0") From af63713b091c8dcd329e0578026bbbc74270c2a8 Mon Sep 17 00:00:00 2001 From: tangenta Date: Tue, 29 Jun 2021 11:27:57 +0800 Subject: [PATCH 3/4] refine test and address comment --- ddl/column_type_change_test.go | 44 +++++++++++++++++++++++++++++----- expression/builtin_time.go | 2 +- expression/builtin_time_vec.go | 2 +- types/time.go | 8 +++---- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/ddl/column_type_change_test.go b/ddl/column_type_change_test.go index 351efc6051c29..c9e89e59c2db2 100644 --- a/ddl/column_type_change_test.go +++ b/ddl/column_type_change_test.go @@ -2135,11 +2135,43 @@ func (s *testColumnTypeChangeSuite) TestCastFromZeroIntToTimeError(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 int);") - tk.MustExec("insert into t values (0);") - tk.MustGetErrCode("alter table t modify column a date;", mysql.ErrTruncatedWrongValue) - tk.MustGetErrCode("alter table t modify column a datetime;", mysql.ErrTruncatedWrongValue) - tk.MustGetErrCode("alter table t modify column a timestamp;", mysql.ErrTruncatedWrongValue) + prepare := func() { + tk.MustExec("drop table if exists t;") + tk.MustExec("create table t (a int);") + tk.MustExec("insert into t values (0);") + } + const errCodeNone = -1 + testCases := []struct { + sqlMode string + errCode int + }{ + {"STRICT_TRANS_TABLES", mysql.ErrTruncatedWrongValue}, + {"STRICT_ALL_TABLES", mysql.ErrTruncatedWrongValue}, + {"NO_ZERO_IN_DATE", errCodeNone}, + {"NO_ZERO_DATE", errCodeNone}, + {"ALLOW_INVALID_DATES", errCodeNone}, + {"", errCodeNone}, + } + for _, tc := range testCases { + prepare() + tk.MustExec(fmt.Sprintf("set @@sql_mode = '%s';", tc.sqlMode)) + if tc.sqlMode == "NO_ZERO_DATE" { + tk.MustQuery(`select date(0);`).Check(testkit.Rows("")) + } else { + tk.MustQuery(`select date(0);`).Check(testkit.Rows("0000-00-00")) + } + tk.MustQuery(`select time(0);`).Check(testkit.Rows("00:00:00")) + if tc.errCode == errCodeNone { + tk.MustExec("alter table t modify column a date;") + prepare() + tk.MustExec("alter table t modify column a datetime;") + prepare() + tk.MustExec("alter table t modify column a timestamp;") + } else { + tk.MustGetErrCode("alter table t modify column a date;", mysql.ErrTruncatedWrongValue) + tk.MustGetErrCode("alter table t modify column a datetime;", mysql.ErrTruncatedWrongValue) + tk.MustGetErrCode("alter table t modify column a timestamp;", mysql.ErrTruncatedWrongValue) + } + } tk.MustExec("drop table if exists t;") } diff --git a/expression/builtin_time.go b/expression/builtin_time.go index b13303a197359..74e3bfea59277 100644 --- a/expression/builtin_time.go +++ b/expression/builtin_time.go @@ -299,7 +299,7 @@ func (b *builtinDateSig) evalTime(row chunk.Row) (types.Time, bool, error) { return types.ZeroTime, true, handleInvalidTimeError(b.ctx, err) } - if expr.IsZero() { + if expr.IsZero() && b.ctx.GetSessionVars().SQLMode.HasNoZeroDateMode() { return types.ZeroTime, true, handleInvalidTimeError(b.ctx, types.ErrWrongValue.GenWithStackByArgs(types.DateTimeStr, expr.String())) } diff --git a/expression/builtin_time_vec.go b/expression/builtin_time_vec.go index 2b29e952f3d83..adb7becc3c7ac 100644 --- a/expression/builtin_time_vec.go +++ b/expression/builtin_time_vec.go @@ -118,7 +118,7 @@ func (b *builtinDateSig) vecEvalTime(input *chunk.Chunk, result *chunk.Column) e if result.IsNull(i) { continue } - if times[i].IsZero() { + if times[i].IsZero() && b.ctx.GetSessionVars().SQLMode.HasNoZeroDateMode() { if err := handleInvalidTimeError(b.ctx, types.ErrWrongValue.GenWithStackByArgs(types.DateTimeStr, times[i].String())); err != nil { return err } diff --git a/types/time.go b/types/time.go index 4d7c12b483d8d..9db352fffd6ae 100644 --- a/types/time.go +++ b/types/time.go @@ -1975,14 +1975,14 @@ func ParseTimeFromNum(sc *stmtctx.StatementContext, num int64, tp byte, fsp int8 // MySQL compatibility: 0 should not be converted to null, see #11203 if num == 0 { zt := NewTime(ZeroCoreTime, tp, DefaultFsp) - if sc != nil && sc.InCreateOrAlterStmt { + if sc != nil && sc.InCreateOrAlterStmt && !sc.TruncateAsWarning { switch tp { case mysql.TypeTimestamp: - return zt, ErrTruncatedWrongVal.GenWithStackByArgs("timestamp", "0") + return zt, ErrTruncatedWrongVal.GenWithStackByArgs(TimestampStr, "0") case mysql.TypeDate: - return zt, ErrTruncatedWrongVal.GenWithStackByArgs("date", "0") + return zt, ErrTruncatedWrongVal.GenWithStackByArgs(DateStr, "0") case mysql.TypeDatetime: - return zt, ErrTruncatedWrongVal.GenWithStackByArgs("datetime", "0") + return zt, ErrTruncatedWrongVal.GenWithStackByArgs(DateTimeStr, "0") } } return zt, nil From dd2b846cf8938f77bf2ce4eacd377d53ccfc9f6f Mon Sep 17 00:00:00 2001 From: tangenta Date: Mon, 12 Jul 2021 10:42:05 +0800 Subject: [PATCH 4/4] add a comment for InCreateOrAlterStmt --- ddl/backfilling.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ddl/backfilling.go b/ddl/backfilling.go index dcacef5fa6350..9c38b02caee80 100644 --- a/ddl/backfilling.go +++ b/ddl/backfilling.go @@ -601,6 +601,7 @@ func (w *worker) writePhysicalTableRecord(t table.PhysicalTable, bfWorkerType ba backfillWorkers = append(backfillWorkers, idxWorker.backfillWorker) go idxWorker.backfillWorker.run(reorgInfo.d, idxWorker) case typeUpdateColumnWorker: + // Setting InCreateOrAlterStmt tells the difference between SELECT casting and ALTER COLUMN casting. sessCtx.GetSessionVars().StmtCtx.InCreateOrAlterStmt = true updateWorker := newUpdateColumnWorker(sessCtx, w, i, t, oldColInfo, colInfo, decodeColMap, reorgInfo.ReorgMeta.SQLMode) updateWorker.priority = job.Priority