From 9d4da4f3fb055ac8a22ceeda2af4406d4be88f3a Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Fri, 10 Dec 2021 10:49:57 +0100 Subject: [PATCH] *: query failed after add index / timestamp out-of-range (#28424) (#29323) --- planner/core/integration_test.go | 44 +++++++++++++++++++++++++++++--- table/column.go | 14 ++++++++++ types/datum.go | 3 ++- util/ranger/ranger.go | 3 +++ 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/planner/core/integration_test.go b/planner/core/integration_test.go index e58579c5bcb58..c3e09b82c2409 100644 --- a/planner/core/integration_test.go +++ b/planner/core/integration_test.go @@ -4604,10 +4604,48 @@ func (s *testIntegrationSuite) TestIssue27242(c *C) { tk.MustExec("use test") tk.MustExec("drop table if exists UK_MU16407") tk.MustExec("CREATE TABLE UK_MU16407 (COL3 timestamp NULL DEFAULT NULL, UNIQUE KEY U3(COL3));") + defer tk.MustExec("DROP TABLE UK_MU16407") tk.MustExec(`insert into UK_MU16407 values("1985-08-31 18:03:27");`) - err := tk.ExecToErr(`SELECT COL3 FROM UK_MU16407 WHERE COL3>_utf8mb4'2039-1-19 3:14:40';`) - c.Assert(err, NotNil) - c.Assert(err.Error(), Matches, ".*Incorrect timestamp value.*") + tk.MustExec(`SELECT COL3 FROM UK_MU16407 WHERE COL3>_utf8mb4'2039-1-19 3:14:40';`) +} + +func verifyTimestampOutOfRange(tk *testkit.TestKit) { + tk.MustQuery(`select * from t28424 where t != "2038-1-19 3:14:08"`).Sort().Check(testkit.Rows("1970-01-01 00:00:01]\n[2038-01-19 03:14:07")) + tk.MustQuery(`select * from t28424 where t < "2038-1-19 3:14:08"`).Sort().Check(testkit.Rows("1970-01-01 00:00:01]\n[2038-01-19 03:14:07")) + tk.MustQuery(`select * from t28424 where t <= "2038-1-19 3:14:08"`).Sort().Check(testkit.Rows("1970-01-01 00:00:01]\n[2038-01-19 03:14:07")) + tk.MustQuery(`select * from t28424 where t >= "2038-1-19 3:14:08"`).Check(testkit.Rows()) + tk.MustQuery(`select * from t28424 where t > "2038-1-19 3:14:08"`).Check(testkit.Rows()) + tk.MustQuery(`select * from t28424 where t != "1970-1-1 0:0:0"`).Sort().Check(testkit.Rows("1970-01-01 00:00:01]\n[2038-01-19 03:14:07")) + tk.MustQuery(`select * from t28424 where t < "1970-1-1 0:0:0"`).Check(testkit.Rows()) + tk.MustQuery(`select * from t28424 where t <= "1970-1-1 0:0:0"`).Check(testkit.Rows()) + tk.MustQuery(`select * from t28424 where t >= "1970-1-1 0:0:0"`).Sort().Check(testkit.Rows("1970-01-01 00:00:01]\n[2038-01-19 03:14:07")) + tk.MustQuery(`select * from t28424 where t > "1970-1-1 0:0:0"`).Sort().Check(testkit.Rows("1970-01-01 00:00:01]\n[2038-01-19 03:14:07")) +} + +func (s *testIntegrationSuite) TestIssue28424(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("use test") + tk.MustExec("drop table if exists t28424, dt28242") + + tk.MustExec(`set time_zone='+00:00'`) + tk.MustExec(`drop table if exists t28424,dt28424`) + tk.MustExec(`create table t28424 (t timestamp)`) + defer tk.MustExec("DROP TABLE t28424") + tk.MustExec(`insert into t28424 values ("2038-01-19 03:14:07"), ("1970-01-01 00:00:01")`) + + verifyTimestampOutOfRange(tk) + tk.MustExec(`alter table t28424 add unique index (t)`) + verifyTimestampOutOfRange(tk) + tk.MustExec(`create table dt28424 (dt datetime)`) + defer tk.MustExec("DROP TABLE dt28424") + tk.MustExec(`insert into dt28424 values ("2038-01-19 03:14:07"), ("1970-01-01 00:00:01")`) + tk.MustExec(`insert into dt28424 values ("1969-12-31 23:59:59"), ("1970-01-01 00:00:00"), ("2038-03-19 03:14:08")`) + tk.MustQuery(`select * from t28424 right join dt28424 on t28424.t = dt28424.dt`).Sort().Check(testkit.Rows( + "1970-01-01 00:00:01 1970-01-01 00:00:01]\n" + + "[2038-01-19 03:14:07 2038-01-19 03:14:07]\n" + + "[ 1969-12-31 23:59:59]\n" + + "[ 1970-01-01 00:00:00]\n" + + "[ 2038-03-19 03:14:08")) } func (s *testIntegrationSerialSuite) TestTemporaryTableForCte(c *C) { diff --git a/table/column.go b/table/column.go index 445a169a82b59..0225a88556c0b 100644 --- a/table/column.go +++ b/table/column.go @@ -192,6 +192,12 @@ func handleWrongCharsetValue(ctx sessionctx.Context, col *model.ColumnInfo, str return err } +// handleZeroDatetime handles Timestamp/Datetime/Date zero date and invalid dates. +// Currently only called from CastValue. +// returns: +// value (possibly adjusted) +// boolean; true if break error/warning handling in CastValue and return what was returned from this +// error func handleZeroDatetime(ctx sessionctx.Context, col *model.ColumnInfo, casted types.Datum, str string, tmIsInvalid bool) (types.Datum, bool, error) { sc := ctx.GetSessionVars().StmtCtx tm := casted.GetMysqlTime() @@ -242,6 +248,14 @@ func handleZeroDatetime(ctx sessionctx.Context, col *model.ColumnInfo, casted ty sc.AppendWarning(innerErr) } return types.NewDatum(zeroV), true, nil + } else if tmIsInvalid && col.Tp == mysql.TypeTimestamp { + // Prevent from being stored! Invalid timestamp! + if mode.HasStrictMode() { + return types.NewDatum(zeroV), true, types.ErrWrongValue.GenWithStackByArgs(zeroT, str) + } + // no strict mode, truncate to 0000-00-00 00:00:00 + sc.AppendWarning(types.ErrWrongValue.GenWithStackByArgs(zeroT, str)) + return types.NewDatum(zeroV), true, nil } else if tm.IsZero() || tm.InvalidZero() { if tm.IsZero() { // Don't care NoZeroDate mode if time val is invalid. diff --git a/types/datum.go b/types/datum.go index f81c3c7a94d14..3e9990d1070dc 100644 --- a/types/datum.go +++ b/types/datum.go @@ -1273,7 +1273,8 @@ func (d *Datum) convertToMysqlTimestamp(sc *stmtctx.StatementContext, target *Fi case KindMysqlTime: t, err = d.GetMysqlTime().Convert(sc, target.Tp) if err != nil { - ret.SetMysqlTime(ZeroTimestamp) + // t might be an invalid Timestamp, but should still be comparable, since same representation (KindMysqlTime) + ret.SetMysqlTime(t) return ret, errors.Trace(ErrWrongValue.GenWithStackByArgs(TimestampStr, t.String())) } t, err = t.RoundFrac(sc, fsp) diff --git a/util/ranger/ranger.go b/util/ranger/ranger.go index d95517fdcc50e..f365a5fa0dd5f 100644 --- a/util/ranger/ranger.go +++ b/util/ranger/ranger.go @@ -108,6 +108,9 @@ func convertPoint(sctx sessionctx.Context, point *point, tp *types.FieldType) (* // Ignore the types.ErrOverflow when we convert TypeNewDecimal values. // A trimmed valid boundary point value would be returned then. Accordingly, the `excl` of the point // would be adjusted. Impossible ranges would be skipped by the `validInterval` call later. + } else if point.value.Kind() == types.KindMysqlTime && tp.Tp == mysql.TypeTimestamp && terror.ErrorEqual(err, types.ErrWrongValue) { + // See issue #28424: query failed after add index + // Ignore conversion from Date[Time] to Timestamp since it must be either out of range or impossible date, which will not match a point select } else if tp.Tp == mysql.TypeEnum && terror.ErrorEqual(err, types.ErrTruncated) { // Ignore the types.ErrorTruncated when we convert TypeEnum values. // We should cover Enum upper overflow, and convert to the biggest value.