Skip to content

Commit

Permalink
ddl: improve MySQL compatibility on time-to-year conversion (#26884)
Browse files Browse the repository at this point in the history
  • Loading branch information
tangenta authored Aug 13, 2021
1 parent ba5ed2e commit 73cadf0
Show file tree
Hide file tree
Showing 13 changed files with 73 additions and 45 deletions.
15 changes: 12 additions & 3 deletions ddl/column.go
Original file line number Diff line number Diff line change
Expand Up @@ -1391,15 +1391,24 @@ func (w *updateColumnWorker) getRowRecord(handle kv.Handle, recordKey []byte, ra
func (w *updateColumnWorker) reformatErrors(err error) error {
// Since row count is not precious in concurrent reorganization, here we substitute row count with datum value.
if types.ErrTruncated.Equal(err) {
err = types.ErrTruncated.GenWithStack("Data truncated for column '%s', value is '%s'", w.oldColInfo.Name, w.rowMap[w.oldColInfo.ID])
dStr := datumToStringNoErr(w.rowMap[w.oldColInfo.ID])
err = types.ErrTruncated.GenWithStack("Data truncated for column '%s', value is '%s'", w.oldColInfo.Name, dStr)
}

if types.ErrInvalidYear.Equal(err) {
err = types.ErrInvalidYear.GenWithStack("Invalid year value for column '%s', value is '%s'", w.oldColInfo.Name, w.rowMap[w.oldColInfo.ID])
if types.ErrWarnDataOutOfRange.Equal(err) {
dStr := datumToStringNoErr(w.rowMap[w.oldColInfo.ID])
err = types.ErrWarnDataOutOfRange.GenWithStack("Out of range value for column '%s', the value is '%s'", w.oldColInfo.Name, dStr)
}
return err
}

func datumToStringNoErr(d types.Datum) string {
if v, err := d.ToString(); err == nil {
return v
}
return fmt.Sprintf("%v", d.GetValue())
}

func (w *updateColumnWorker) cleanRowMap() {
for id := range w.rowMap {
delete(w.rowMap, id)
Expand Down
37 changes: 29 additions & 8 deletions ddl/column_type_change_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,12 @@ import (
)

var _ = SerialSuites(&testColumnTypeChangeSuite{})
var _ = SerialSuites(&testCTCSerialSuiteWrapper{&testColumnTypeChangeSuite{}})

type testColumnTypeChangeSuite struct {
store kv.Storage
dom *domain.Domain
}

type testCTCSerialSuiteWrapper struct {
*testColumnTypeChangeSuite
}

func (s *testColumnTypeChangeSuite) SetUpSuite(c *C) {
var err error
ddl.SetWaitTimeWhenErrorOccurred(1 * time.Microsecond)
Expand Down Expand Up @@ -934,15 +929,15 @@ func (s *testColumnTypeChangeSuite) TestColumnTypeChangeFromNumericToOthers(c *C
// year
reset(tk)
tk.MustExec("insert into t values (200805.11, 307.333, 2.55555, 98.1111111, 2154.00001, 20200805111307.11111111, b'10101')")
tk.MustGetErrMsg("alter table t modify d year", "[types:8033]Invalid year value for column 'd', value is 'KindMysqlDecimal 200805.1100000'")
tk.MustGetErrCode("alter table t modify n year", mysql.ErrInvalidYear)
tk.MustGetErrMsg("alter table t modify d year", "[types:1264]Out of range value for column 'd', the value is '200805.1100000'")
tk.MustGetErrCode("alter table t modify n year", mysql.ErrWarnDataOutOfRange)
// MySQL will get "ERROR 1264 (22001) Data truncation: Out of range value for column 'r' at row 1".
tk.MustExec("alter table t modify r year")
// MySQL will get "ERROR 1264 (22001) Data truncation: Out of range value for column 'db' at row 1".
tk.MustExec("alter table t modify db year")
// MySQL will get "ERROR 1264 (22001) Data truncation: Out of range value for column 'f32' at row 1".
tk.MustExec("alter table t modify f32 year")
tk.MustGetErrMsg("alter table t modify f64 year", "[types:8033]Invalid year value for column 'f64', value is 'KindFloat64 2.020080511130711e+13'")
tk.MustGetErrMsg("alter table t modify f64 year", "[types:1264]Out of range value for column 'f64', the value is '20200805111307.11'")
tk.MustExec("alter table t modify b year")
tk.MustQuery("select * from t").Check(testkit.Rows("200805.1100000 307.33 2003 1998 2154 20200805111307.11 2021"))

Expand Down Expand Up @@ -2131,6 +2126,32 @@ func (s *testColumnTypeChangeSuite) TestCastToTimeStampDecodeError(c *C) {
tk.MustQuery("select timestamp(cast('1000-11-11 12-3-1' as date));").Check(testkit.Rows("1000-11-11 00:00:00"))
}

func (s *testColumnTypeChangeSuite) TestChangeFromTimeToYear(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 time default 0);")
tk.MustExec("insert into t values ();")
tk.MustExec("insert into t values (NULL);")
tk.MustExec("insert into t values ('12');")
tk.MustExec("insert into t values ('00:19:59');")
tk.MustExec("insert into t values ('00:20:13');")
tk.MustExec("alter table t modify column a year;")
tk.MustQuery("select a from t;").Check(testkit.Rows("0", "<nil>", "2012", "1959", "2013"))

tk.MustExec("drop table if exists t;")
tk.MustExec("create table t (id bigint primary key, a time);")
tk.MustExec("replace into t values (1, '10:10:10');")
tk.MustGetErrCode("alter table t modify column a year;", mysql.ErrWarnDataOutOfRange)
tk.MustExec("replace into t values (1, '12:13:14');")
tk.MustGetErrCode("alter table t modify column a year;", mysql.ErrWarnDataOutOfRange)
tk.MustExec("set @@sql_mode = '';")
tk.MustExec("alter table t modify column a year;")
tk.MustQuery("show warnings").Check(
testkit.Rows("Warning 1264 Out of range value for column 'a', the value is '12:13:14'"))
}

// 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)
Expand Down
41 changes: 20 additions & 21 deletions ddl/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4491,7 +4491,7 @@ func (s *testSerialDBSuite) TestModifyColumnBetweenStringTypes(c *C) {
c.Assert(c2.FieldType.Tp, Equals, mysql.TypeBlob)

// text to set
tk.MustGetErrMsg("alter table tt change a a set('111', '2222');", "[types:1265]Data truncated for column 'a', value is 'KindString 10000'")
tk.MustGetErrMsg("alter table tt change a a set('111', '2222');", "[types:1265]Data truncated for column 'a', value is '10000'")
tk.MustExec("alter table tt change a a set('111', '10000');")
c2 = getModifyColumn(c, s.s.(sessionctx.Context), "test", "tt", "a", false)
c.Assert(c2.FieldType.Tp, Equals, mysql.TypeSet)
Expand All @@ -4504,7 +4504,7 @@ func (s *testSerialDBSuite) TestModifyColumnBetweenStringTypes(c *C) {
tk.MustQuery("select * from tt").Check(testkit.Rows("111", "10000"))

// set to enum
tk.MustGetErrMsg("alter table tt change a a enum('111', '2222');", "[types:1265]Data truncated for column 'a', value is 'KindMysqlSet 10000'")
tk.MustGetErrMsg("alter table tt change a a enum('111', '2222');", "[types:1265]Data truncated for column 'a', value is '10000'")
tk.MustExec("alter table tt change a a enum('111', '10000');")
c2 = getModifyColumn(c, s.s.(sessionctx.Context), "test", "tt", "a", false)
c.Assert(c2.FieldType.Tp, Equals, mysql.TypeEnum)
Expand All @@ -4516,7 +4516,7 @@ func (s *testSerialDBSuite) TestModifyColumnBetweenStringTypes(c *C) {
// no-strict mode
tk.MustExec(`set @@sql_mode="";`)
tk.MustExec("alter table tt change a a enum('111', '2222');")
tk.MustQuery("show warnings").Check(testutil.RowsWithSep("|", "Warning|1265|Data truncated for column 'a', value is 'KindMysqlEnum 10000'"))
tk.MustQuery("show warnings").Check(testutil.RowsWithSep("|", "Warning|1265|Data truncated for column 'a', value is '10000'"))

tk.MustExec("drop table tt;")
}
Expand Down Expand Up @@ -5173,23 +5173,23 @@ func (s *testSerialDBSuite) TestModifyColumnCharset(c *C) {
}

func (s *testDBSuite1) TestModifyColumnTime_TimeToYear(c *C) {
currentYear := strconv.Itoa(time.Now().Year())
outOfRangeCode := uint16(1264)
tests := []testModifyColumnTimeCase{
// time to year, it's reasonable to return current year and discard the time (even if MySQL may get data out of range error).
{"time", `"30 20:00:12"`, "year", currentYear, 0},
{"time", `"30 20:00"`, "year", currentYear, 0},
{"time", `"30 20"`, "year", currentYear, 0},
{"time", `"20:00:12"`, "year", currentYear, 0},
{"time", `"20:00"`, "year", currentYear, 0},
{"time", `"12"`, "year", currentYear, 0},
{"time", `"200012"`, "year", currentYear, 0},
{"time", `200012`, "year", currentYear, 0},
{"time", `0012`, "year", currentYear, 0},
{"time", `12`, "year", currentYear, 0},
{"time", `"30 20:00:12.498"`, "year", currentYear, 0},
{"time", `"20:00:12.498"`, "year", currentYear, 0},
{"time", `"200012.498"`, "year", currentYear, 0},
{"time", `200012.498`, "year", currentYear, 0},
{"time", `"30 20:00:12"`, "year", "", outOfRangeCode},
{"time", `"30 20:00"`, "year", "", outOfRangeCode},
{"time", `"30 20"`, "year", "", outOfRangeCode},
{"time", `"20:00:12"`, "year", "", outOfRangeCode},
{"time", `"20:00"`, "year", "", outOfRangeCode},
{"time", `"12"`, "year", "2012", 0},
{"time", `"200012"`, "year", "", outOfRangeCode},
{"time", `200012`, "year", "", outOfRangeCode},
{"time", `0012`, "year", "2012", 0},
{"time", `12`, "year", "2012", 0},
{"time", `"30 20:00:12.498"`, "year", "", outOfRangeCode},
{"time", `"20:00:12.498"`, "year", "", outOfRangeCode},
{"time", `"200012.498"`, "year", "", outOfRangeCode},
{"time", `200012.498`, "year", "", outOfRangeCode},
}
testModifyColumnTime(c, s.store, tests)
}
Expand Down Expand Up @@ -5393,9 +5393,8 @@ func (s *testDBSuite1) TestModifyColumnTime_DatetimeToYear(c *C) {
{"datetime", `20060102150405`, "year", "2006", 0},
{"datetime", `060102150405`, "year", "2006", 0},
{"datetime", `"2006-01-02 23:59:59.506"`, "year", "2006", 0},
// MySQL will get "Data truncation: Out of range value for column 'a' at row 1.
{"datetime", `"1000-01-02 23:59:59"`, "year", "", errno.ErrInvalidYear},
{"datetime", `"9999-01-02 23:59:59"`, "year", "", errno.ErrInvalidYear},
{"datetime", `"1000-01-02 23:59:59"`, "year", "", errno.ErrWarnDataOutOfRange},
{"datetime", `"9999-01-02 23:59:59"`, "year", "", errno.ErrWarnDataOutOfRange},
}
testModifyColumnTime(c, s.store, tests)
}
Expand Down
2 changes: 1 addition & 1 deletion executor/index_lookup_join.go
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ func (iw *innerWorker) constructDatumLookupKey(task *lookUpJoinTask, chkIdx, row
innerValue, err := outerValue.ConvertTo(sc, innerColType)
if err != nil && !(terror.ErrorEqual(err, types.ErrTruncated) && (innerColType.Tp == mysql.TypeSet || innerColType.Tp == mysql.TypeEnum)) {
// If the converted outerValue overflows or invalid to innerValue, we don't need to lookup it.
if terror.ErrorEqual(err, types.ErrOverflow) || terror.ErrorEqual(err, types.ErrInvalidYear) {
if terror.ErrorEqual(err, types.ErrOverflow) || terror.ErrorEqual(err, types.ErrWarnDataOutOfRange) {
return nil, nil, nil
}
return nil, nil, err
Expand Down
2 changes: 2 additions & 0 deletions executor/insert_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ func (e *InsertValues) handleErr(col *table.Column, val *types.Datum, rowIdx int
logutil.BgLogger().Debug("truncated/wrong value error", zap.Error(err1))
}
err = table.ErrTruncatedWrongValueForField.GenWithStackByArgs(types.TypeStr(colTp), valStr, colName, rowIdx+1)
} else if types.ErrWarnDataOutOfRange.Equal(err) {
err = types.ErrWarnDataOutOfRange.GenWithStackByArgs(colName, rowIdx+1)
}

if !e.ctx.GetSessionVars().StmtCtx.DupKeyAsWarning {
Expand Down
4 changes: 2 additions & 2 deletions executor/insert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,8 @@ func (s *testSuite3) TestInsertWrongValueForField(c *C) {

tk.MustExec(`drop table if exists t;`)
tk.MustExec(`create table t (a year);`)
_, err = tk.Exec(`insert into t values(2156);`)
c.Assert(err.Error(), Equals, `[types:8033]invalid year`)
tk.MustGetErrMsg(`insert into t values(2156);`,
"[types:1264]Out of range value for column 'a' at row 1")

tk.MustExec(`DROP TABLE IF EXISTS ts`)
tk.MustExec(`CREATE TABLE ts (id int DEFAULT NULL, time1 TIMESTAMP NULL DEFAULT NULL)`)
Expand Down
2 changes: 1 addition & 1 deletion expression/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8168,7 +8168,7 @@ func (s *testIntegrationSerialSuite) TestIssue19804(c *C) {
tk.MustExec(`create table t(a set('a', 'b', 'c'));`)
tk.MustExec(`alter table t change a a set('a', 'b', 'c', 'd');`)
tk.MustExec(`insert into t values('d');`)
tk.MustGetErrMsg(`alter table t change a a set('a', 'b', 'c', 'e', 'f');`, "[types:1265]Data truncated for column 'a', value is 'KindMysqlSet d'")
tk.MustGetErrMsg(`alter table t change a a set('a', 'b', 'c', 'e', 'f');`, "[types:1265]Data truncated for column 'a', value is 'd'")
}

func (s *testIntegrationSerialSuite) TestIssue20209(c *C) {
Expand Down
2 changes: 1 addition & 1 deletion types/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ func (s *testTypeConvertSuite) TestConvertType(c *C) {
c.Assert(v, Equals, int64(2015))
v, err = Convert(ZeroDuration, ft)
c.Assert(err, IsNil)
c.Assert(v, Equals, int64(time.Now().Year()))
c.Assert(v, Equals, int64(0))
bj1, err := json.ParseBinaryFromString("99")
c.Assert(err, IsNil)
v, err = Convert(bj1, ft)
Expand Down
2 changes: 0 additions & 2 deletions types/datum.go
Original file line number Diff line number Diff line change
Expand Up @@ -1415,8 +1415,6 @@ func (d *Datum) ConvertToMysqlYear(sc *stmtctx.StatementContext, target *FieldTy
}
case KindMysqlTime:
y = int64(d.GetMysqlTime().Year())
case KindMysqlDuration:
y = int64(time.Now().Year())
case KindMysqlJSON:
y, err = ConvertJSONToInt64(sc, d.GetMysqlJSON(), false)
if err != nil {
Expand Down
1 change: 0 additions & 1 deletion types/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ func (s testErrorSuite) TestError(c *C) {
ErrCastAsSignedOverflow,
ErrCastNegIntAsUnsigned,
ErrInvalidYearFormat,
ErrInvalidYear,
ErrTruncatedWrongVal,
ErrInvalidWeekModeFormat,
ErrWrongValue,
Expand Down
6 changes: 3 additions & 3 deletions types/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -1257,13 +1257,13 @@ func AdjustYear(y int64, adjustZero bool) (int64, error) {
}
y = int64(adjustYear(int(y)))
if y < 0 {
return 0, errors.Trace(ErrInvalidYear)
return 0, errors.Trace(ErrWarnDataOutOfRange)
}
if y < int64(MinYear) {
return int64(MinYear), errors.Trace(ErrInvalidYear)
return int64(MinYear), errors.Trace(ErrWarnDataOutOfRange)
}
if y > int64(MaxYear) {
return int64(MaxYear), errors.Trace(ErrInvalidYear)
return int64(MaxYear), errors.Trace(ErrWarnDataOutOfRange)
}

return y, nil
Expand Down
2 changes: 1 addition & 1 deletion util/ranger/points.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ func (r *builder) buildFromBinOp(expr *expression.ScalarFunction) []*point {
return err1
}
*value, err = value.ConvertToMysqlYear(r.sc, col.RetType)
if errors.ErrorEqual(err, types.ErrInvalidYear) {
if errors.ErrorEqual(err, types.ErrWarnDataOutOfRange) {
// Keep err for EQ and NE.
switch *op {
case ast.GT:
Expand Down
2 changes: 1 addition & 1 deletion util/ranger/ranger.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func convertPoint(sc *stmtctx.StatementContext, point *point, tp *types.FieldTyp
}
casted, err := point.value.ConvertTo(sc, tp)
if err != nil {
if tp.Tp == mysql.TypeYear && terror.ErrorEqual(err, types.ErrInvalidYear) {
if tp.Tp == mysql.TypeYear && terror.ErrorEqual(err, types.ErrWarnDataOutOfRange) {
// see issue #20101: overflow when converting integer to year
} else if tp.Tp == mysql.TypeBit && terror.ErrorEqual(err, types.ErrDataTooLong) {
// see issue #19067: we should ignore the types.ErrDataTooLong when we convert value to TypeBit value
Expand Down

0 comments on commit 73cadf0

Please sign in to comment.