Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/executor/test/simpletest/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ go_test(
],
flaky = True,
race = "on",
shard_count = 11,
shard_count = 12,
deps = [
"//pkg/config",
"//pkg/parser/auth",
Expand Down
41 changes: 41 additions & 0 deletions pkg/executor/test/simpletest/simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -757,3 +757,44 @@ func TestKillStmt(t *testing.T) {
tk.MustExecToErr("kill rand()", "Invalid operation. Please use 'KILL TIDB [CONNECTION | QUERY] [connectionID | CONNECTION_ID()]' instead")
// remote kill is tested in `tests/globalkilltest`
}

func TestSelectWhereInvalidDSTTime(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("use test")
tk.MustExec("create table t (id int, ts timestamp)")
tk.MustExec(`set time_zone = "UTC"`)
tk.MustExec("insert into t values (1, '1970-01-01 00:00:01')")
tk.MustExec("insert into t values (2, '2025-03-30 00:59:59')")
tk.MustExec("insert into t values (3, '2025-03-30 01:00:00')")
tk.MustExec(`set time_zone = "Europe/Amsterdam"`)
tk.MustExec(`set sql_mode = ''`)
// This will be adjusted to '2025-03-30 03:00:00+02:00'
tk.MustExec("insert into t values (4, '2025-03-30 02:30:00')")
tk.MustQuery(`show warnings`).Check(testkit.Rows("Warning 1292 Incorrect timestamp value: '2025-03-30 02:30:00' for column 'ts' at row 1"))
tk.MustExec(`set sql_mode = DEFAULT`)
tk.MustQuery(`select *, unix_timestamp(ts) from t`).Sort().Check(testkit.Rows(""+
"1 1970-01-01 01:00:01 1",
"2 2025-03-30 01:59:59 1743296399",
"3 2025-03-30 03:00:00 1743296400",
"4 2025-03-30 03:00:00 1743296400"))

// Compares as DATETIME; every row is read and converted to DATETIME by current TIME_ZONE,
// and compared with the range which is in DATETIME
tk.MustQuery(`select *, unix_timestamp(ts) from t where ts between '2025-03-30 02:30:00' AND '2025-03-30 03:00:00'`).Check(testkit.Rows("3 2025-03-30 03:00:00 1743296400", "4 2025-03-30 03:00:00 1743296400"))
tk.MustQuery(`show warnings`).Sort().Check(testkit.Rows("Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 30 0 0}' for time zone 'Europe/Amsterdam'"))
explain := tk.MustQuery(`explain select *, unix_timestamp(ts) from t where ts between '2025-03-30 02:30:00' AND '2025-03-30 03:00:00'`)
explain.MultiCheckContain([]string{"TableFullScan", "ge(test.t.ts, 2025-03-30 02:30:00.000000)", "le(test.t.ts, 2025-03-30 03:00:00.000000)"})

// Compares as TIMESTAMP; the range is converted to TIMESTAMP by current TIME_ZONE,
// and then compared with the row which is TIMESTAMP.
tk.MustExec("alter table t add index idx_ts(ts)")
tk.MustQuery(`select *, unix_timestamp(ts) from t where ts between '2025-03-30 02:30:00' AND '2025-03-30 03:00:00'`).Check(testkit.Rows("3 2025-03-30 03:00:00 1743296400", "4 2025-03-30 03:00:00 1743296400"))
explain = tk.MustQuery(`explain select * from t where ts between '2025-03-30 02:30:00' AND '2025-03-30 03:00:00'`)
explain.MultiCheckContain([]string{"IndexLookUp", "range:[2025-03-30 03:00:00,2025-03-30 03:00:00]"})
explain.CheckNotContain("02:30:00")
// Why 3 warnings?!?
tk.MustQuery(`show warnings`).Check(testkit.Rows("Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 30 0 0}' for time zone 'Europe/Amsterdam'",
"Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 30 0 0}' for time zone 'Europe/Amsterdam'",
"Warning 8179 Timestamp is not valid, since it is in Daylight Saving Time transition '{2025 3 30 2 30 0 0}' for time zone 'Europe/Amsterdam'"))
}
41 changes: 31 additions & 10 deletions pkg/types/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,13 @@ func (t Time) Convert(ctx Context, tp uint8) (Time, error) {

t1.SetType(tp)
err := t1.Check(ctx)
if tp == mysql.TypeTimestamp && ErrTimestampInDSTTransition.Equal(err) {
tAdj, adjErr := t1.AdjustedGoTime(ctx.Location())
if adjErr == nil {
ctx.AppendWarning(err)
return Time{FromGoTime(tAdj)}, nil
}
}
return t1, errors.Trace(err)
}

Expand Down Expand Up @@ -2010,22 +2017,35 @@ func parseTime(ctx Context, str string, tp byte, fsp int, isFloat bool) (Time, e

t.SetType(tp)
if err = t.Check(ctx); err != nil {
minTS, maxTS := MinTimestamp, MaxTimestamp
minErr := minTS.ConvertTimeZone(gotime.UTC, ctx.Location())
maxErr := maxTS.ConvertTimeZone(gotime.UTC, ctx.Location())
if minErr == nil && maxErr == nil && tp == mysql.TypeTimestamp && !t.IsZero() &&
t.Compare(minTS) > 0 && t.Compare(maxTS) < 0 {
// Handle the case when the timestamp given is in the DST transition
if tAdjusted, err2 := t.AdjustedGoTime(ctx.Location()); err2 == nil {
t.SetCoreTime(FromGoTime(tAdjusted))
return t, errors.Trace(ErrTimestampInDSTTransition.GenWithStackByArgs(str, ctx.Location().String()))
if tp == mysql.TypeTimestamp && !t.IsZero() {
tAdjusted, errAdjusted := adjustTimestampErrForDST(ctx.Location(), str, tp, t, err)
if ErrTimestampInDSTTransition.Equal(errAdjusted) {
return tAdjusted, errors.Trace(errAdjusted)
}
}
return NewTime(ZeroCoreTime, tp, DefaultFsp), errors.Trace(err)
}
return t, nil
}

func adjustTimestampErrForDST(loc *gotime.Location, str string, tp byte, t Time, err error) (Time, error) {
if tp != mysql.TypeTimestamp || t.IsZero() {
return t, err
}
minTS, maxTS := MinTimestamp, MaxTimestamp
minErr := minTS.ConvertTimeZone(gotime.UTC, loc)
maxErr := maxTS.ConvertTimeZone(gotime.UTC, loc)
if minErr == nil && maxErr == nil &&
t.Compare(minTS) > 0 && t.Compare(maxTS) < 0 {
// Handle the case when the timestamp given is in the DST transition
if tAdjusted, err2 := t.AdjustedGoTime(loc); err2 == nil {
t.SetCoreTime(FromGoTime(tAdjusted))
return t, errors.Trace(ErrTimestampInDSTTransition.GenWithStackByArgs(str, loc.String()))
}
}
return t, err
}

// ParseDatetime is a helper function wrapping ParseTime with datetime type and default fsp.
func ParseDatetime(ctx Context, str string) (Time, error) {
return ParseTime(ctx, str, mysql.TypeDatetime, GetFsp(str))
Expand Down Expand Up @@ -2182,7 +2202,8 @@ func checkTimestampType(t CoreTime, tz *gotime.Location) error {
convertTime := NewTime(t, mysql.TypeTimestamp, DefaultFsp)
err := convertTime.ConvertTimeZone(tz, BoundTimezone)
if err != nil {
return err
_, err2 := adjustTimestampErrForDST(tz, t.String(), mysql.TypeTimestamp, Time{t}, err)
return err2
}
checkTime = convertTime.coreTime
} else {
Expand Down
Loading