From 7c6be645f2ea171c40eabbc1c9a26dddbf645cea Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Thu, 13 Nov 2025 11:03:35 -0800 Subject: [PATCH 01/30] add day tests, move log tests out of broken tests section --- enginetest/queries/function_queries.go | 33 ++++++++++++++++++-------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index 4d60fbe4b9..bda8d2fb69 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -1601,7 +1601,15 @@ var FunctionQueryTests = []QueryTest{ Query: "select abs(-i) from mytable order by 1", Expected: []sql.Row{{1}, {2}, {3}}, }, - + // https://github.com/dolthub/dolt/issues/9735 + { + Query: "select log('10asdf', '100f')", + Expected: []sql.Row{{float64(2)}}, + }, + { + Query: "select log('a10asdf', 'b100f')", + Expected: []sql.Row{{nil}}, + }, // Date Manipulation Function Tests { Query: "SELECT TIMESTAMPADD(DAY, 1, '2018-05-02')", @@ -1755,17 +1763,22 @@ var FunctionQueryTests = []QueryTest{ {uint32(1000)}, }, }, -} - -// BrokenFunctionQueryTests contains SQL function call queries that don't match MySQL behavior -var BrokenFunctionQueryTests = []QueryTest{ - // https://github.com/dolthub/dolt/issues/9735 + // date-related functions { - Query: "select log('10asdf', '100f')", - Expected: []sql.Row{{float64(2)}}, + Query: "select day(0)", + Expected: []sql.Row{{0}}, }, { - Query: "select log('a10asdf', 'b100f')", - Expected: []sql.Row{{nil}}, + Query: "select day(false)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select day(true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, }, } + +// BrokenFunctionQueryTests contains SQL function call queries that don't match MySQL behavior +var BrokenFunctionQueryTests = []QueryTest{} From cbfb02c1b9465e97f9b9152c10e827a6a75d22d0 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Thu, 13 Nov 2025 11:56:33 -0800 Subject: [PATCH 02/30] add more tests, clean ups --- enginetest/queries/function_queries.go | 172 +++++++++++++++++++++++++ sql/expression/function/registry.go | 2 +- sql/expression/function/time.go | 1 - sql/types/datetime.go | 5 + 4 files changed, 178 insertions(+), 2 deletions(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index bda8d2fb69..e338c4433c 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -1778,6 +1778,178 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarning: mysql.ERTruncatedWrongValue, ExpectedWarningsCount: 1, }, + { + Query: "select dayname(0)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select dayname(false)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select dayname(true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select dayofmonth(0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select dayofmonth(false)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select dayofmonth(true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select dayofweek(0)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select dayofweek(false)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select dayofweek(true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select dayofyear(0)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select dayofyear(false)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select dayofyear(true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select month(0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select month(false)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select month(true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select monthname(0)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select monthname(false)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select monthname(true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select week(0)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select week(false)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select week(true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select weekday(0)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select weekday(false)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select weekday(true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select weekofyear(0)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select weekofyear(false)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select weekofyear(true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select yearweek(0)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select yearweek(false)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select yearweek(true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, } // BrokenFunctionQueryTests contains SQL function call queries that don't match MySQL behavior diff --git a/sql/expression/function/registry.go b/sql/expression/function/registry.go index 8a6fccbc66..6b00c9b48e 100644 --- a/sql/expression/function/registry.go +++ b/sql/expression/function/registry.go @@ -320,7 +320,6 @@ var BuiltIns = []sql.Function{ sql.Function0{Name: "uuid", Fn: NewUUIDFunc}, sql.Function0{Name: "uuid_short", Fn: NewUUIDShortFunc}, sql.FunctionN{Name: "uuid_to_bin", Fn: NewUUIDToBin}, - sql.FunctionN{Name: "week", Fn: NewWeek}, sql.Function1{Name: "values", Fn: NewValues}, sql.Function1{Name: "validate_password_strength", Fn: NewValidatePasswordStrength}, sql.Function1{Name: "variance", Fn: func(e sql.Expression) sql.Expression { return aggregation.NewVarPop(e) }}, @@ -338,6 +337,7 @@ var BuiltIns = []sql.Function{ sql.Function1{Name: "vector_to_string", Fn: vector.NewVectorToString}, sql.Function1{Name: "from_vector", Fn: vector.NewVectorToString}, sql.Function1{Name: "vec_totext", Fn: vector.NewVectorToString}, + sql.FunctionN{Name: "week", Fn: NewWeek}, sql.Function1{Name: "weekday", Fn: NewWeekday}, sql.Function1{Name: "weekofyear", Fn: NewWeekOfYear}, sql.Function1{Name: "year", Fn: NewYear}, diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index decbb17340..e6bd22c5e9 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -52,7 +52,6 @@ func getDate(ctx *sql.Context, if err != nil { ctx.Warn(1292, "Incorrect datetime value: '%s'", val) return nil, nil - //date = types.DatetimeMaxPrecision.Zero().(time.Time) } return date, nil diff --git a/sql/types/datetime.go b/sql/types/datetime.go index c10dc759fb..475bb5e02d 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -365,6 +365,11 @@ func (t datetimeType) ConvertWithoutRangeCheck(ctx context.Context, v interface{ return zeroTime, ErrConvertingToTime.New(v) } return nowTime.Add(value.AsTimeDuration()), nil + case bool: + if !value { + return zeroTime, nil + } + return zeroTime, ErrConvertingToTime.New(v) default: return zeroTime, sql.ErrConvertToSQL.New(value, t) } From 1b24b75831957cc82fe984491f35343ab69c005a Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Thu, 13 Nov 2025 14:37:45 -0800 Subject: [PATCH 03/30] new value for zeroTime, update tests, add more datetime function test queries --- enginetest/queries/function_queries.go | 134 +++++++++++++++++++++++++ enginetest/queries/queries.go | 7 -- enginetest/queries/script_queries.go | 6 +- enginetest/queries/update_queries.go | 2 +- sql/types/datetime.go | 5 +- 5 files changed, 141 insertions(+), 13 deletions(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index e338c4433c..400b504069 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -1778,6 +1778,16 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarning: mysql.ERTruncatedWrongValue, ExpectedWarningsCount: 1, }, + { + Query: "select day('0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select day('0000-01-01')", + Expected: []sql.Row{{0}}, + }, { Query: "select dayname(0)", Expected: []sql.Row{{nil}}, @@ -1796,6 +1806,16 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarning: mysql.ERTruncatedWrongValue, ExpectedWarningsCount: 1, }, + { + Query: "select dayname('0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select dayname('0000-01-01')", + Expected: []sql.Row{{"Sunday"}}, + }, { Query: "select dayofmonth(0)", Expected: []sql.Row{{0}}, @@ -1810,6 +1830,16 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarning: mysql.ERTruncatedWrongValue, ExpectedWarningsCount: 1, }, + { + Query: "select dayofmonth('0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select dayofmonth('0000-01-01')", + Expected: []sql.Row{{1}}, + }, { Query: "select dayofweek(0)", Expected: []sql.Row{{nil}}, @@ -1828,6 +1858,16 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarning: mysql.ERTruncatedWrongValue, ExpectedWarningsCount: 1, }, + { + Query: "select dayofweek('0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select dayofweek('0000-01-01')", + Expected: []sql.Row{{1}}, + }, { Query: "select dayofyear(0)", Expected: []sql.Row{{nil}}, @@ -1846,6 +1886,16 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarning: mysql.ERTruncatedWrongValue, ExpectedWarningsCount: 1, }, + { + Query: "select dayofyear('0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select dayofyear('0000-01-01')", + Expected: []sql.Row{{1}}, + }, { Query: "select month(0)", Expected: []sql.Row{{0}}, @@ -1860,6 +1910,16 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarning: mysql.ERTruncatedWrongValue, ExpectedWarningsCount: 1, }, + { + Query: "select month('0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select month('0000-01-01')", + Expected: []sql.Row{{1}}, + }, { Query: "select monthname(0)", Expected: []sql.Row{{nil}}, @@ -1878,6 +1938,16 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarning: mysql.ERTruncatedWrongValue, ExpectedWarningsCount: 1, }, + { + Query: "select monthname('0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select monthname('0000-01-01')", + Expected: []sql.Row{{"January"}}, + }, { Query: "select week(0)", Expected: []sql.Row{{nil}}, @@ -1896,6 +1966,16 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarning: mysql.ERTruncatedWrongValue, ExpectedWarningsCount: 1, }, + { + Query: "select week('0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select week('0000-01-01')", + Expected: []sql.Row{{1}}, + }, { Query: "select weekday(0)", Expected: []sql.Row{{nil}}, @@ -1914,6 +1994,16 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarning: mysql.ERTruncatedWrongValue, ExpectedWarningsCount: 1, }, + { + Query: "select weekday('0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select weekday('0000-01-01')", + Expected: []sql.Row{{6}}, + }, { Query: "select weekofyear(0)", Expected: []sql.Row{{nil}}, @@ -1932,6 +2022,16 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarning: mysql.ERTruncatedWrongValue, ExpectedWarningsCount: 1, }, + { + Query: "select weekofyear('0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select weekofyear('0000-01-01')", + Expected: []sql.Row{{52}}, + }, { Query: "select yearweek(0)", Expected: []sql.Row{{nil}}, @@ -1950,6 +2050,40 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarning: mysql.ERTruncatedWrongValue, ExpectedWarningsCount: 1, }, + { + Query: "select yearweek('0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select yearweek('0000-01-01')", + Expected: []sql.Row{{1}}, + }, + { + Query: "select quarter(0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select quarter(false)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select quarter(true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select quarter('0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select quarter('0000-01-01')", + Expected: []sql.Row{{1}}, + }, } // BrokenFunctionQueryTests contains SQL function call queries that don't match MySQL behavior diff --git a/enginetest/queries/queries.go b/enginetest/queries/queries.go index 7a989e04c0..541aae2b49 100644 --- a/enginetest/queries/queries.go +++ b/enginetest/queries/queries.go @@ -8346,13 +8346,6 @@ from typestable`, }, }, }, - { - // TODO: This goes past MySQL's range - Query: "select dayname('0000-00-00')", - Expected: []sql.Row{ - {"Saturday"}, - }, - }, { Query: "select * from mytable order by dayname(i)", Expected: []sql.Row{ diff --git a/enginetest/queries/script_queries.go b/enginetest/queries/script_queries.go index 387449357d..5de345fa06 100644 --- a/enginetest/queries/script_queries.go +++ b/enginetest/queries/script_queries.go @@ -6175,10 +6175,10 @@ CREATE TABLE tab3 ( "0", float64(0), float64(0), - time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), types.Timespan(0), - time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), - time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), + time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), 0, "", "", diff --git a/enginetest/queries/update_queries.go b/enginetest/queries/update_queries.go index 86663d6cb5..34dae84137 100644 --- a/enginetest/queries/update_queries.go +++ b/enginetest/queries/update_queries.go @@ -1011,7 +1011,7 @@ var UpdateErrorScripts = []ScriptTest{ }, } -var ZeroTime = time.Date(0000, time.January, 1, 0, 0, 0, 0, time.UTC) +var ZeroTime = time.Date(0000, 0, 0, 0, 0, 0, 0, time.UTC) var Jan1Noon = time.Date(2000, time.January, 1, 12, 0, 0, 0, time.UTC) var Dec15_1_30 = time.Date(2023, time.December, 15, 1, 30, 0, 0, time.UTC) var Oct2Midnight = time.Date(2020, time.October, 2, 0, 0, 0, 0, time.UTC) diff --git a/sql/types/datetime.go b/sql/types/datetime.go index 475bb5e02d..400b1a3d0e 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -98,8 +98,9 @@ var ( "20060102150405", }, DateOnlyLayouts...) - // zeroTime is 0000-01-01 00:00:00 UTC which is the closest Go can get to 0000-00-00 00:00:00 - zeroTime = time.Unix(-62167219200, 0).UTC() + // zeroTime is -0001-11-30 00:00:00 UTC which is the closest Go can get to 0000-00-00 00:00:00 without conflicting + // with a valid timestamp in MySQL + zeroTime = time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC) // Date is a date with day, month and year. Date = MustCreateDatetimeType(sqltypes.Date, 0) From 0d42aa089d815ff1dd1c6331c27e503e6ea23f52 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Thu, 13 Nov 2025 15:08:08 -0800 Subject: [PATCH 04/30] add query test for 0000-01-01 timestamp --- enginetest/queries/function_queries.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index 400b504069..76d11b4893 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -2084,7 +2084,8 @@ var FunctionQueryTests = []QueryTest{ Query: "select quarter('0000-01-01')", Expected: []sql.Row{{1}}, }, + { + Query: "select date('0000-01-01')", + Expected: []sql.Row{{"0000-01-01"}}, + }, } - -// BrokenFunctionQueryTests contains SQL function call queries that don't match MySQL behavior -var BrokenFunctionQueryTests = []QueryTest{} From bce68e6b4bb0be2be0bf8dd15a88693211ad9449 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Thu, 13 Nov 2025 15:23:04 -0800 Subject: [PATCH 05/30] day function has been fixed --- enginetest/queries/function_queries.go | 9 ++- sql/expression/function/time.go | 9 ++- sql/types/datetime.go | 82 +++++++++++++------------- 3 files changed, 53 insertions(+), 47 deletions(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index 76d11b4893..40e9ab568e 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -1779,14 +1779,13 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarningsCount: 1, }, { - Query: "select day('0000-00-00')", - Expected: []sql.Row{{nil}}, - ExpectedWarning: mysql.ERTruncatedWrongValue, - ExpectedWarningsCount: 1, + // This is not a valid time string in MySQL but we allow it + Query: "select day('0000-00-00')", + Expected: []sql.Row{{0}}, }, { Query: "select day('0000-01-01')", - Expected: []sql.Row{{0}}, + Expected: []sql.Row{{1}}, }, { Query: "select dayname(0)", diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index e6bd22c5e9..1b86a060d6 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -546,7 +546,14 @@ func datePartFunc(fn func(time.Time) int) func(interface{}) interface{} { return nil } - return int32(fn(v.(time.Time))) + if vTime, ok := v.(time.Time); ok { + if vTime.Equal(types.ZeroTime) { + return 0 + } + + return int32(fn(vTime)) + } + return nil } } diff --git a/sql/types/datetime.go b/sql/types/datetime.go index 400b1a3d0e..aca5ac146c 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -100,7 +100,7 @@ var ( // zeroTime is -0001-11-30 00:00:00 UTC which is the closest Go can get to 0000-00-00 00:00:00 without conflicting // with a valid timestamp in MySQL - zeroTime = time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC) + ZeroTime = time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC) // Date is a date with day, month and year. Date = MustCreateDatetimeType(sqltypes.Date, 0) @@ -223,8 +223,8 @@ func ConvertToTime(ctx context.Context, v interface{}, t datetimeType) (time.Tim return time.Time{}, err } - if res.Equal(zeroTime) { - return zeroTime, nil + if res.Equal(ZeroTime) { + return ZeroTime, nil } // Round the date to the precision of this type @@ -276,13 +276,13 @@ func (t datetimeType) ConvertWithoutRangeCheck(ctx context.Context, v interface{ switch value := v.(type) { case string: if value == ZeroDateStr || value == ZeroTimestampDatetimeStr { - return zeroTime, nil + return ZeroTime, nil } // TODO: consider not using time.Parse if we want to match MySQL exactly ('2010-06-03 11:22.:.:.:.:' is a valid timestamp) var parsed bool res, parsed, err = parseDatetime(value) if !parsed { - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) } case time.Time: res = value.UTC() @@ -290,89 +290,89 @@ func (t datetimeType) ConvertWithoutRangeCheck(ctx context.Context, v interface{ // is zero values, which are important when converting from postgres defaults. case int: if value == 0 { - return zeroTime, nil + return ZeroTime, nil } - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) case int8: if value == 0 { - return zeroTime, nil + return ZeroTime, nil } - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) case int16: if value == 0 { - return zeroTime, nil + return ZeroTime, nil } - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) case int32: if value == 0 { - return zeroTime, nil + return ZeroTime, nil } - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) case int64: if value == 0 { - return zeroTime, nil + return ZeroTime, nil } - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) case uint: if value == 0 { - return zeroTime, nil + return ZeroTime, nil } - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) case uint8: if value == 0 { - return zeroTime, nil + return ZeroTime, nil } - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) case uint16: if value == 0 { - return zeroTime, nil + return ZeroTime, nil } - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) case uint32: if value == 0 { - return zeroTime, nil + return ZeroTime, nil } - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) case uint64: if value == 0 { - return zeroTime, nil + return ZeroTime, nil } - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) case float32: if value == 0 { - return zeroTime, nil + return ZeroTime, nil } - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) case float64: if value == 0 { - return zeroTime, nil + return ZeroTime, nil } - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) case decimal.Decimal: if value.IsZero() { - return zeroTime, nil + return ZeroTime, nil } - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) case decimal.NullDecimal: if value.Valid && value.Decimal.IsZero() { - return zeroTime, nil + return ZeroTime, nil } - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) case Timespan: // when receiving TIME, MySQL fills in date with today nowTimeStr := sql.Now().Format("2006-01-02") nowTime, err := time.Parse("2006-01-02", nowTimeStr) if err != nil { - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) } return nowTime.Add(value.AsTimeDuration()), nil case bool: if !value { - return zeroTime, nil + return ZeroTime, nil } - return zeroTime, ErrConvertingToTime.New(v) + return ZeroTime, ErrConvertingToTime.New(v) default: - return zeroTime, sql.ErrConvertToSQL.New(value, t) + return ZeroTime, sql.ErrConvertToSQL.New(value, t) } if t.baseType == sqltypes.Date { @@ -458,21 +458,21 @@ func (t datetimeType) SQL(ctx *sql.Context, dest []byte, v interface{}) (sqltype switch t.baseType { case sqltypes.Date: typ = sqltypes.Date - if vt.Equal(zeroTime) { + if vt.Equal(ZeroTime) { val = vt.AppendFormat(dest, ZeroDateStr) } else { val = vt.AppendFormat(dest, sql.DateLayout) } case sqltypes.Datetime: typ = sqltypes.Datetime - if vt.Equal(zeroTime) { + if vt.Equal(ZeroTime) { val = vt.AppendFormat(dest, ZeroTimestampDatetimeStr) } else { val = vt.AppendFormat(dest, sql.TimestampDatetimeLayout) } case sqltypes.Timestamp: typ = sqltypes.Timestamp - if vt.Equal(zeroTime) { + if vt.Equal(ZeroTime) { val = vt.AppendFormat(dest, ZeroTimestampDatetimeStr) } else { val = vt.AppendFormat(dest, sql.TimestampDatetimeLayout) @@ -535,7 +535,7 @@ func (t datetimeType) ValueType() reflect.Type { } func (t datetimeType) Zero() interface{} { - return zeroTime + return ZeroTime } // CollationCoercibility implements sql.CollationCoercible interface. From 9a812be8b9803ee4db087b9f8c19ad89473867a7 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Thu, 13 Nov 2025 16:59:02 -0800 Subject: [PATCH 06/30] making more progress on getting tests to pass --- enginetest/queries/function_queries.go | 80 ++++++++++++++------------ sql/expression/function/time.go | 64 ++++++++++++++------- 2 files changed, 87 insertions(+), 57 deletions(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index 40e9ab568e..ec78aa3e1a 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -1779,7 +1779,7 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarningsCount: 1, }, { - // This is not a valid time string in MySQL but we allow it + // This is not a valid time string in MySQL, but we allow it Query: "select day('0000-00-00')", Expected: []sql.Row{{0}}, }, @@ -1812,8 +1812,15 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarningsCount: 1, }, { - Query: "select dayname('0000-01-01')", - Expected: []sql.Row{{"Sunday"}}, + Query: "select dayname('0000-01-01')", + // This is Sunday in MySQL. It seems like Go's time library considers 0000-02-29 a valid date but MySQL does + // not. This is why the days of the week are off. 0000 is not a real year anyway. This test is to make sure + // 0000-01-01 is not interpreted as zero time + Expected: []sql.Row{{"Saturday"}}, + }, + { + Query: "select dayname('2025-11-13')", + Expected: []sql.Row{{"Thursday"}}, }, { Query: "select dayofmonth(0)", @@ -1830,26 +1837,23 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarningsCount: 1, }, { - Query: "select dayofmonth('0000-00-00')", - Expected: []sql.Row{{nil}}, - ExpectedWarning: mysql.ERTruncatedWrongValue, - ExpectedWarningsCount: 1, + // This is not a valid time string in MySQL, but we allow it + Query: "select dayofmonth('0000-00-00')", + Expected: []sql.Row{{0}}, }, { Query: "select dayofmonth('0000-01-01')", Expected: []sql.Row{{1}}, }, { - Query: "select dayofweek(0)", - Expected: []sql.Row{{nil}}, - ExpectedWarning: mysql.ERTruncatedWrongValue, - ExpectedWarningsCount: 1, + Query: "select dayofweek(0)", + Expected: []sql.Row{{nil}}, + // MySQL has a warning }, { - Query: "select dayofweek(false)", - Expected: []sql.Row{{nil}}, - ExpectedWarning: mysql.ERTruncatedWrongValue, - ExpectedWarningsCount: 1, + Query: "select dayofweek(false)", + Expected: []sql.Row{{nil}}, + // MySQL has a warning }, { Query: "select dayofweek(true)", @@ -1858,26 +1862,30 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarningsCount: 1, }, { - Query: "select dayofweek('0000-00-00')", - Expected: []sql.Row{{nil}}, - ExpectedWarning: mysql.ERTruncatedWrongValue, - ExpectedWarningsCount: 1, + Query: "select dayofweek('0000-00-00')", + Expected: []sql.Row{{nil}}, + // MySQL has a warning }, { - Query: "select dayofweek('0000-01-01')", - Expected: []sql.Row{{1}}, + Query: "select dayofweek('0000-01-01')", + // This is 1 (Sunday) in MySQL. It seems like Go's time library considers 0000-02-29 a valid date but MySQL does + // not. This is why the days of the week are off. 0000 is not a real year anyway. This test is to make sure + // 0000-01-01 is not interpreted as zero time + Expected: []sql.Row{{7}}, }, { - Query: "select dayofyear(0)", - Expected: []sql.Row{{nil}}, - ExpectedWarning: mysql.ERTruncatedWrongValue, - ExpectedWarningsCount: 1, + Query: "select dayofweek('2025-11-13')", + Expected: []sql.Row{{5}}, }, { - Query: "select dayofyear(false)", - Expected: []sql.Row{{nil}}, - ExpectedWarning: mysql.ERTruncatedWrongValue, - ExpectedWarningsCount: 1, + Query: "select dayofyear(0)", + Expected: []sql.Row{{nil}}, + // MySQL has a warning + }, + { + Query: "select dayofyear(false)", + Expected: []sql.Row{{nil}}, + // MySQL has a warning }, { Query: "select dayofyear(true)", @@ -1886,10 +1894,9 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarningsCount: 1, }, { - Query: "select dayofyear('0000-00-00')", - Expected: []sql.Row{{nil}}, - ExpectedWarning: mysql.ERTruncatedWrongValue, - ExpectedWarningsCount: 1, + Query: "select dayofyear('0000-00-00')", + Expected: []sql.Row{{nil}}, + // MySQL has a warning }, { Query: "select dayofyear('0000-01-01')", @@ -1910,10 +1917,9 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarningsCount: 1, }, { - Query: "select month('0000-00-00')", - Expected: []sql.Row{{nil}}, - ExpectedWarning: mysql.ERTruncatedWrongValue, - ExpectedWarningsCount: 1, + // This is not a valid time string in MySQL, but we allow it + Query: "select month('0000-00-00')", + Expected: []sql.Row{{0}}, }, { Query: "select month('0000-01-01')", diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index 1b86a060d6..3e5dfbfd11 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -17,6 +17,7 @@ package function import ( "context" "fmt" + "math" "strings" "time" @@ -159,7 +160,7 @@ func (q *Quarter) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return nil, nil } - return (mon.(int32)-1)/3 + 1, nil + return (mon.(int) / 3) + int(math.Ceil(float64(mon.(int))/3)), nil } // WithChildren implements the Expression interface. @@ -540,20 +541,13 @@ func (d *DayOfYear) WithChildren(children ...sql.Expression) (sql.Expression, er return NewDayOfYear(children[0]), nil } -func datePartFunc(fn func(time.Time) int) func(interface{}) interface{} { +func datePartFunc(fn func(time.Time) interface{}) func(interface{}) interface{} { return func(v interface{}) interface{} { if v == nil { return nil } - if vTime, ok := v.(time.Time); ok { - if vTime.Equal(types.ZeroTime) { - return 0 - } - - return int32(fn(vTime)) - } - return nil + return fn(v.(time.Time)) } } @@ -877,15 +871,45 @@ func calcDaynr(yyyy, mm, dd int32) int32 { } var ( - year = datePartFunc((time.Time).Year) - month = datePartFunc(func(t time.Time) int { return int(t.Month()) }) - day = datePartFunc((time.Time).Day) - weekday = datePartFunc(func(t time.Time) int { return (int(t.Weekday()) + 6) % 7 }) - hour = datePartFunc((time.Time).Hour) - minute = datePartFunc((time.Time).Minute) - second = datePartFunc((time.Time).Second) - dayOfWeek = datePartFunc(func(t time.Time) int { return int(t.Weekday()) + 1 }) - dayOfYear = datePartFunc((time.Time).YearDay) + year = datePartFunc(func(t time.Time) interface{} { + if t.Equal(types.ZeroTime) { + return 0 + } + return t.Year() + }) + month = datePartFunc(func(t time.Time) interface{} { + if t.Equal(types.ZeroTime) { + return 0 + } + return int(t.Month()) + }) + day = datePartFunc(func(t time.Time) interface{} { + if t.Equal(types.ZeroTime) { + return 0 + } + return t.Day() + }) + weekday = datePartFunc(func(t time.Time) interface{} { + if t.Equal(types.ZeroTime) { + return nil + } + return (int(t.Weekday()) + 6) % 7 + }) + hour = datePartFunc(func(t time.Time) interface{} { return t.Hour() }) + minute = datePartFunc(func(t time.Time) interface{} { return t.Minute() }) + second = datePartFunc(func(t time.Time) interface{} { return t.Second() }) + dayOfWeek = datePartFunc(func(t time.Time) interface{} { + if t.Equal(types.ZeroTime) { + return nil + } + return int(t.Weekday()) + 1 + }) + dayOfYear = datePartFunc(func(t time.Time) interface{} { + if t.Equal(types.ZeroTime) { + return nil + } + return t.YearDay() + }) ) const maxCurrTimestampPrecision = 6 @@ -1345,7 +1369,7 @@ func (d *DayName) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { } t, ok := val.(time.Time) - if !ok { + if !ok || t.Equal(types.ZeroTime) { ctx.Warn(1292, "%s", types.ErrConvertingToTime.New(val).Error()) return nil, nil } From 76cb5d0e0abf4ff5d789204f6a8edb74a6c19f2d Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Fri, 14 Nov 2025 10:54:30 -0800 Subject: [PATCH 07/30] fix monthname --- sql/expression/function/time.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index 3e5dfbfd11..9ccec4da92 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -1455,11 +1455,16 @@ func (*MonthName) CollationCoercibility(ctx *sql.Context) (collation sql.Collati func (d *MonthName) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { val, err := d.EvalChild(ctx, row) if err != nil { - return nil, err + ctx.Warn(1292, "%s", types.ErrConvertingToTime.New(val).Error()) + return nil, nil } switch v := val.(type) { case time.Time: + if v.Equal(types.ZeroTime) { + ctx.Warn(1292, "%s", types.ErrConvertingToTime.New(val).Error()) + return nil, nil + } return v.Month().String(), nil case nil: return nil, nil From ed696d2286035d9dd63fd81089c191acd559ce89 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Fri, 14 Nov 2025 11:31:05 -0800 Subject: [PATCH 08/30] fix week, modify getDate to use evaluated value --- sql/expression/function/extract.go | 6 ++- sql/expression/function/time.go | 61 +++++++++++++++++++----------- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/sql/expression/function/extract.go b/sql/expression/function/extract.go index 3d2fbcf689..aca101ef4e 100644 --- a/sql/expression/function/extract.go +++ b/sql/expression/function/extract.go @@ -128,7 +128,11 @@ func (td *Extract) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { case "MONTH": return int(dateTime.Month()), nil case "WEEK": - date, err := getDate(ctx, expression.UnaryExpression{Child: td.RightChild}, row) + dateVal, err := td.RightChild.Eval(ctx, row) + if err != nil { + return nil, err + } + date, err := getDate(ctx, dateVal) if err != nil { return nil, err } diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index 9ccec4da92..7506ef515c 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -36,15 +36,7 @@ var ErrUnknownType = errors.NewKind("function '%s' encountered unknown type %T") var ErrTooHighPrecision = errors.NewKind("Too-big precision %d for '%s'. Maximum is %d.") -func getDate(ctx *sql.Context, - u expression.UnaryExpression, - row sql.Row) (interface{}, error) { - - val, err := u.Child.Eval(ctx, row) - if err != nil { - return nil, err - } - +func getDate(ctx *sql.Context, val interface{}) (interface{}, error) { if val == nil { return nil, nil } @@ -62,8 +54,12 @@ func getDatePart(ctx *sql.Context, u expression.UnaryExpression, row sql.Row, f func(interface{}) interface{}) (interface{}, error) { + val, err := u.Child.Eval(ctx, row) + if err != nil { + return nil, err + } - date, err := getDate(ctx, u, row) + date, err := getDate(ctx, val) if err != nil { return nil, err } @@ -602,22 +598,26 @@ func (*YearWeek) CollationCoercibility(ctx *sql.Context) (collation sql.Collatio // Eval implements the Expression interface. func (d *YearWeek) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { - date, err := getDate(ctx, expression.UnaryExpression{Child: d.date}, row) + dateVal, err := d.date.Eval(ctx, row) + if err != nil { + return nil, err + } + date, err := getDate(ctx, dateVal) if err != nil { return nil, err } if date == nil { return nil, nil } - yyyy, ok := year(date).(int32) + yyyy, ok := year(date).(int) if !ok { return nil, sql.ErrInvalidArgumentDetails.New("YEARWEEK", "invalid year") } - mm, ok := month(date).(int32) + mm, ok := month(date).(int) if !ok { return nil, sql.ErrInvalidArgumentDetails.New("YEARWEEK", "invalid month") } - dd, ok := day(date).(int32) + dd, ok := day(date).(int) if !ok { return nil, sql.ErrInvalidArgumentDetails.New("YEARWEEK", "invalid day") } @@ -634,9 +634,9 @@ func (d *YearWeek) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { } } } - yyyy, week := calcWeek(yyyy, mm, dd, weekMode(mode)|weekBehaviourYear) + yr, week := calcWeek(int32(yyyy), int32(mm), int32(dd), weekMode(mode)|weekBehaviourYear) - return (yyyy * 100) + week, nil + return (yr * 100) + week, nil } // Resolved implements the Expression interface. @@ -710,20 +710,34 @@ func (*Week) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, // Eval implements the Expression interface. func (d *Week) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { - date, err := getDate(ctx, expression.UnaryExpression{Child: d.date}, row) + dateVal, err := d.date.Eval(ctx, row) + if err != nil { + return nil, err + } + + date, err := getDate(ctx, dateVal) if err != nil { return nil, err } + if date == nil { + return nil, nil + } + + dateTime, ok := date.(time.Time) + if !ok || dateTime.Equal(types.ZeroTime) { + ctx.Warn(1292, "%s", types.ErrConvertingToTime.New(dateVal).Error()) + return nil, nil + } - yyyy, ok := year(date).(int32) + yyyy, ok := year(date).(int) if !ok { return nil, sql.ErrInvalidArgumentDetails.New("WEEK", "invalid year") } - mm, ok := month(date).(int32) + mm, ok := month(date).(int) if !ok { return nil, sql.ErrInvalidArgumentDetails.New("WEEK", "invalid month") } - dd, ok := day(date).(int32) + dd, ok := day(date).(int) if !ok { return nil, sql.ErrInvalidArgumentDetails.New("WEEK", "invalid day") } @@ -741,11 +755,12 @@ func (d *Week) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { } } - yearForWeek, week := calcWeek(yyyy, mm, dd, weekMode(mode)|weekBehaviourYear) + yr := int32(yyyy) + yearForWeek, week := calcWeek(yr, int32(mm), int32(dd), weekMode(mode)|weekBehaviourYear) - if yearForWeek < yyyy { + if yearForWeek < yr { week = 0 - } else if yearForWeek > yyyy { + } else if yearForWeek > yr { week = 53 } From 8acd85d3b065555819453005b52399d378efc143 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Fri, 14 Nov 2025 11:42:27 -0800 Subject: [PATCH 09/30] fix weekday, add warnings to getDatePart --- enginetest/queries/function_queries.go | 53 ++++++++++++++++---------- sql/expression/function/time.go | 9 ++++- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index ec78aa3e1a..cf39366a7e 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -1846,14 +1846,16 @@ var FunctionQueryTests = []QueryTest{ Expected: []sql.Row{{1}}, }, { - Query: "select dayofweek(0)", - Expected: []sql.Row{{nil}}, - // MySQL has a warning + Query: "select dayofweek(0)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, }, { - Query: "select dayofweek(false)", - Expected: []sql.Row{{nil}}, - // MySQL has a warning + Query: "select dayofweek(false)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, }, { Query: "select dayofweek(true)", @@ -1862,9 +1864,10 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarningsCount: 1, }, { - Query: "select dayofweek('0000-00-00')", - Expected: []sql.Row{{nil}}, - // MySQL has a warning + Query: "select dayofweek('0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, }, { Query: "select dayofweek('0000-01-01')", @@ -1878,14 +1881,16 @@ var FunctionQueryTests = []QueryTest{ Expected: []sql.Row{{5}}, }, { - Query: "select dayofyear(0)", - Expected: []sql.Row{{nil}}, - // MySQL has a warning + Query: "select dayofyear(0)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, }, { - Query: "select dayofyear(false)", - Expected: []sql.Row{{nil}}, - // MySQL has a warning + Query: "select dayofyear(false)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, }, { Query: "select dayofyear(true)", @@ -1894,9 +1899,10 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarningsCount: 1, }, { - Query: "select dayofyear('0000-00-00')", - Expected: []sql.Row{{nil}}, - // MySQL has a warning + Query: "select dayofyear('0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, }, { Query: "select dayofyear('0000-01-01')", @@ -2006,8 +2012,15 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarningsCount: 1, }, { - Query: "select weekday('0000-01-01')", - Expected: []sql.Row{{6}}, + Query: "select weekday('0000-01-01')", + // This is 6 (Sunday) in MySQL. It seems like Go's time library considers 0000-02-29 a valid date but MySQL does + // not. This is why the days of the week are off. 0000 is not a real year anyway. This test is to make sure + // 0000-01-01 is not interpreted as zero time + Expected: []sql.Row{{5}}, + }, + { + Query: "select weekday('2025-11-13')", + Expected: []sql.Row{{3}}, }, { Query: "select weekofyear(0)", diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index 7506ef515c..b86c4a55ce 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -63,8 +63,15 @@ func getDatePart(ctx *sql.Context, if err != nil { return nil, err } + if date == nil { + return nil, nil + } - return f(date), nil + part := f(date) + if part == nil { + ctx.Warn(1292, "Incorrect datetime value: '%s'", val) + } + return part, nil } // Year is a function that returns the year of a date. From 2330e414c7851e946f89ff2e9187a65d9c50eabe Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Fri, 14 Nov 2025 11:59:58 -0800 Subject: [PATCH 10/30] fix weekofyear --- sql/expression/function/time.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index b86c4a55ce..aad804110e 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -1336,7 +1336,11 @@ func (dtf *UnaryDatetimeFunc) EvalChild(ctx *sql.Context, row sql.Row) (interfac } ret, _, err := types.DatetimeMaxPrecision.Convert(ctx, val) - return ret, err + if err != nil { + ctx.Warn(1292, "%s", types.ErrConvertingToTime.New(val).Error()) + return nil, nil + } + return ret, nil } // String implements the fmt.Stringer interface. @@ -1477,8 +1481,7 @@ func (*MonthName) CollationCoercibility(ctx *sql.Context) (collation sql.Collati func (d *MonthName) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { val, err := d.EvalChild(ctx, row) if err != nil { - ctx.Warn(1292, "%s", types.ErrConvertingToTime.New(val).Error()) - return nil, nil + return nil, err } switch v := val.(type) { @@ -1579,6 +1582,10 @@ func (m *WeekOfYear) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { switch v := val.(type) { case time.Time: + if v.Equal(types.ZeroTime) { + ctx.Warn(1292, "%s", types.ErrConvertingToTime.New(val).Error()) + return nil, nil + } _, wk := v.ISOWeek() return wk, nil case nil: From ae3a154660c29c1d6d7ca7cba5089f35d307cede Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Fri, 14 Nov 2025 12:05:59 -0800 Subject: [PATCH 11/30] fix yearweek, update test case for quarter --- enginetest/queries/function_queries.go | 7 +++---- sql/expression/function/time.go | 7 +++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index cf39366a7e..cd04943a37 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -2093,10 +2093,9 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarningsCount: 1, }, { - Query: "select quarter('0000-00-00')", - Expected: []sql.Row{{nil}}, - ExpectedWarning: mysql.ERTruncatedWrongValue, - ExpectedWarningsCount: 1, + // This is not a valid time string in MySQL, but we allow it + Query: "select quarter('0000-00-00')", + Expected: []sql.Row{{nil}}, }, { Query: "select quarter('0000-01-01')", diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index aad804110e..6f7ff48fad 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -616,6 +616,13 @@ func (d *YearWeek) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { if date == nil { return nil, nil } + + dateTime, ok := date.(time.Time) + if !ok || dateTime.Equal(types.ZeroTime) { + ctx.Warn(1292, "%s", types.ErrConvertingToTime.New(dateVal).Error()) + return nil, nil + } + yyyy, ok := year(date).(int) if !ok { return nil, sql.ErrInvalidArgumentDetails.New("YEARWEEK", "invalid year") From 713f050893782ec04767cce023e309a128710aac Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Fri, 14 Nov 2025 12:11:13 -0800 Subject: [PATCH 12/30] fix int values for extract week --- sql/expression/function/extract.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sql/expression/function/extract.go b/sql/expression/function/extract.go index aca101ef4e..1ac812aea0 100644 --- a/sql/expression/function/extract.go +++ b/sql/expression/function/extract.go @@ -136,22 +136,23 @@ func (td *Extract) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { if err != nil { return nil, err } - yyyy, ok := year(date).(int32) + yyyy, ok := year(date).(int) if !ok { return nil, sql.ErrInvalidArgumentDetails.New("WEEK", "invalid year") } - mm, ok := month(date).(int32) + mm, ok := month(date).(int) if !ok { return nil, sql.ErrInvalidArgumentDetails.New("WEEK", "invalid month") } - dd, ok := day(date).(int32) + dd, ok := day(date).(int) if !ok { return nil, sql.ErrInvalidArgumentDetails.New("WEEK", "invalid day") } - yearForWeek, week := calcWeek(yyyy, mm, dd, weekBehaviourYear) - if yearForWeek < yyyy { + yr := int32(yyyy) + yearForWeek, week := calcWeek(yr, int32(mm), int32(dd), weekBehaviourYear) + if yearForWeek < yr { week = 0 - } else if yearForWeek > yyyy { + } else if yearForWeek > yr { week = 53 } return int(week), nil From 50a441f70d728511f7536d0928d97a799a4a3fb0 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Fri, 14 Nov 2025 12:52:17 -0800 Subject: [PATCH 13/30] update quarter zero timestamp test --- enginetest/queries/function_queries.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index cd04943a37..7d934e71ec 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -2095,7 +2095,7 @@ var FunctionQueryTests = []QueryTest{ { // This is not a valid time string in MySQL, but we allow it Query: "select quarter('0000-00-00')", - Expected: []sql.Row{{nil}}, + Expected: []sql.Row{{0}}, }, { Query: "select quarter('0000-01-01')", From f449846b92f52468ac57cee1f8e091bbfa70c90c Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Fri, 14 Nov 2025 13:02:21 -0800 Subject: [PATCH 14/30] fix to_days function --- sql/expression/function/days.go | 3 +++ sql/expression/function/days_test.go | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/sql/expression/function/days.go b/sql/expression/function/days.go index 0fd34033a8..5208b42dd7 100644 --- a/sql/expression/function/days.go +++ b/sql/expression/function/days.go @@ -98,6 +98,9 @@ func (t *ToDays) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return nil, nil } d := date.(time.Time) + if d.Equal(types.ZeroTime) { + return nil, nil + } // Using zeroTime.Sub(date) doesn't work because it overflows time.Duration // so we need to calculate the number of days manually diff --git a/sql/expression/function/days_test.go b/sql/expression/function/days_test.go index eb162b2832..9179333465 100644 --- a/sql/expression/function/days_test.go +++ b/sql/expression/function/days_test.go @@ -45,7 +45,10 @@ func TestToDays(t *testing.T) { arg: expression.NewLiteral("-10", types.Int32), exp: nil, }, - + { + arg: expression.NewLiteral("0", types.Int32), + exp: nil, + }, { arg: expression.NewLiteral("0000-00-00", types.Text), exp: nil, From 700dd84d27438cdadf6fd240314d9e52610246f5 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Fri, 14 Nov 2025 13:15:29 -0800 Subject: [PATCH 15/30] fix quarter --- sql/expression/function/time.go | 18 +++++-------- sql/expression/function/time_test.go | 38 ++++++++++++++-------------- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index 6f7ff48fad..e45fcd8593 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -17,7 +17,6 @@ package function import ( "context" "fmt" - "math" "strings" "time" @@ -154,16 +153,7 @@ func (q *Quarter) CollationCoercibility(ctx *sql.Context) (collation sql.Collati // Eval implements the Expression interface. func (q *Quarter) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { - mon, err := getDatePart(ctx, q.UnaryExpression, row, month) - if err != nil { - return nil, err - } - - if mon == nil { - return nil, nil - } - - return (mon.(int) / 3) + int(math.Ceil(float64(mon.(int))/3)), nil + return getDatePart(ctx, q.UnaryExpression, row, quarter) } // WithChildren implements the Expression interface. @@ -939,6 +929,12 @@ var ( } return t.YearDay() }) + quarter = datePartFunc(func(t time.Time) interface{} { + if t.Equal(types.ZeroTime) { + return 0 + } + return (int(t.Month())-1)/3 + 1 + }) ) const maxCurrTimestampPrecision = 6 diff --git a/sql/expression/function/time_test.go b/sql/expression/function/time_test.go index 985907b1d0..59dbc378a8 100644 --- a/sql/expression/function/time_test.go +++ b/sql/expression/function/time_test.go @@ -45,8 +45,8 @@ func TestTime_Year(t *testing.T) { err bool }{ {"invalid type", sql.NewRow([]byte{0, 1, 2}), nil, false}, - {"date as string", sql.NewRow(stringDate), int32(2007), false}, - {"date as time", sql.NewRow(time.Now()), int32(time.Now().UTC().Year()), false}, + {"date as string", sql.NewRow(stringDate), 2007, false}, + {"date as time", sql.NewRow(time.Now()), time.Now().UTC().Year(), false}, } for _, tt := range testCases { @@ -75,8 +75,8 @@ func TestTime_Month(t *testing.T) { }{ {"null date", sql.NewRow(nil), nil, false}, {"invalid type", sql.NewRow([]byte{0, 1, 2}), nil, false}, - {"date as string", sql.NewRow(stringDate), int32(1), false}, - {"date as time", sql.NewRow(time.Now()), int32(time.Now().UTC().Month()), false}, + {"date as string", sql.NewRow(stringDate), 1, false}, + {"date as time", sql.NewRow(time.Now()), int(time.Now().UTC().Month()), false}, } for _, tt := range testCases { @@ -126,77 +126,77 @@ func TestTime_Quarter(t *testing.T) { { name: "date as string", row: sql.NewRow(stringDate), - expected: int32(1), + expected: 1, }, { name: "another date as string", row: sql.NewRow("2008-08-01"), - expected: int32(3), + expected: 3, }, { name: "january", row: sql.NewRow("2008-01-01"), - expected: int32(1), + expected: 1, }, { name: "february", row: sql.NewRow("2008-02-01"), - expected: int32(1), + expected: 1, }, { name: "march", row: sql.NewRow("2008-03-01"), - expected: int32(1), + expected: 1, }, { name: "april", row: sql.NewRow("2008-04-01"), - expected: int32(2), + expected: 2, }, { name: "may", row: sql.NewRow("2008-05-01"), - expected: int32(2), + expected: 2, }, { name: "june", row: sql.NewRow("2008-06-01"), - expected: int32(2), + expected: 2, }, { name: "july", row: sql.NewRow("2008-07-01"), - expected: int32(3), + expected: 3, }, { name: "august", row: sql.NewRow("2008-08-01"), - expected: int32(3), + expected: 3, }, { name: "septemeber", row: sql.NewRow("2008-09-01"), - expected: int32(3), + expected: 3, }, { name: "october", row: sql.NewRow("2008-10-01"), - expected: int32(4), + expected: 4, }, { name: "november", row: sql.NewRow("2008-11-01"), - expected: int32(4), + expected: 4, }, { name: "december", row: sql.NewRow("2008-12-01"), - expected: int32(4), + expected: 4, }, { name: "date as time", row: sql.NewRow(time.Now()), - expected: int32((time.Now().UTC().Month()-1)/3 + 1), + expected: (int(time.Now().UTC().Month())-1)/3 + 1, }, } From e839a230cff07616da6e814c6550b40660ae9a01 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Fri, 14 Nov 2025 13:40:32 -0800 Subject: [PATCH 16/30] update time function tests --- sql/expression/function/time.go | 21 ++++++----------- sql/expression/function/time_test.go | 34 ++++++++++++++-------------- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index e45fcd8593..73c0065f35 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -935,6 +935,12 @@ var ( } return (int(t.Month())-1)/3 + 1 }) + microsecond = datePartFunc(func(t time.Time) interface{} { + if t.Equal(types.ZeroTime) { + return 0 + } + return uint64(t.Nanosecond()) / uint64(time.Microsecond) + }) ) const maxCurrTimestampPrecision = 6 @@ -1436,20 +1442,7 @@ func NewMicrosecond(arg sql.Expression) sql.Expression { } func (m *Microsecond) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { - val, err := m.EvalChild(ctx, row) - if err != nil { - return nil, err - } - - switch v := val.(type) { - case time.Time: - return uint64(v.Nanosecond()) / uint64(time.Microsecond), nil - case nil: - return nil, nil - default: - ctx.Warn(1292, "%s", types.ErrConvertingToTime.New(val).Error()) - return nil, nil - } + return getDatePart(ctx, m.UnaryExpression, row, microsecond) } func (m *Microsecond) WithChildren(children ...sql.Expression) (sql.Expression, error) { diff --git a/sql/expression/function/time_test.go b/sql/expression/function/time_test.go index 59dbc378a8..ed82743acf 100644 --- a/sql/expression/function/time_test.go +++ b/sql/expression/function/time_test.go @@ -226,8 +226,8 @@ func TestTime_Day(t *testing.T) { }{ {"null date", sql.NewRow(nil), nil, false}, {"invalid type", sql.NewRow([]byte{0, 1, 2}), nil, false}, - {"date as string", sql.NewRow(stringDate), int32(2), false}, - {"date as time", sql.NewRow(time.Now()), int32(time.Now().UTC().Day()), false}, + {"date as string", sql.NewRow(stringDate), 2, false}, + {"date as time", sql.NewRow(time.Now()), time.Now().UTC().Day(), false}, } for _, tt := range testCases { @@ -256,8 +256,8 @@ func TestTime_Weekday(t *testing.T) { }{ {"null date", sql.NewRow(nil), nil, false}, {"invalid type", sql.NewRow([]byte{0, 1, 2}), nil, false}, - {"date as string", sql.NewRow(stringDate), int32(1), false}, - {"date as time", sql.NewRow(time.Now()), int32(time.Now().UTC().Weekday()+6) % 7, false}, + {"date as string", sql.NewRow(stringDate), 1, false}, + {"date as time", sql.NewRow(time.Now()), int(time.Now().UTC().Weekday()+6) % 7, false}, } for _, tt := range testCases { @@ -286,8 +286,8 @@ func TestTime_Hour(t *testing.T) { }{ {"null date", sql.NewRow(nil), nil, false}, {"invalid type", sql.NewRow([]byte{0, 1, 2}), nil, false}, - {"date as string", sql.NewRow(stringDate), int32(14), false}, - {"date as time", sql.NewRow(time.Now()), int32(time.Now().UTC().Hour()), false}, + {"date as string", sql.NewRow(stringDate), 14, false}, + {"date as time", sql.NewRow(time.Now()), time.Now().UTC().Hour(), false}, } for _, tt := range testCases { @@ -316,8 +316,8 @@ func TestTime_Minute(t *testing.T) { }{ {"null date", sql.NewRow(nil), nil, false}, {"invalid type", sql.NewRow([]byte{0, 1, 2}), nil, false}, - {"date as string", sql.NewRow(stringDate), int32(15), false}, - {"date as time", sql.NewRow(time.Now()), int32(time.Now().UTC().Minute()), false}, + {"date as string", sql.NewRow(stringDate), 15, false}, + {"date as time", sql.NewRow(time.Now()), time.Now().UTC().Minute(), false}, } for _, tt := range testCases { @@ -346,8 +346,8 @@ func TestTime_Second(t *testing.T) { }{ {"null date", sql.NewRow(nil), nil, false}, {"invalid type", sql.NewRow([]byte{0, 1, 2}), nil, false}, - {"date as string", sql.NewRow(stringDate), int32(16), false}, - {"date as time", sql.NewRow(time.Now()), int32(time.Now().UTC().Second()), false}, + {"date as string", sql.NewRow(stringDate), 16, false}, + {"date as time", sql.NewRow(time.Now()), time.Now().UTC().Second(), false}, } for _, tt := range testCases { @@ -376,7 +376,7 @@ func TestTime_Microsecond(t *testing.T) { err bool }{ {"null date", sql.NewRow(nil), nil, false}, - {"invalid type", sql.NewRow([]byte{0, 1, 2}), nil, true}, + {"invalid type", sql.NewRow([]byte{0, 1, 2}), nil, false}, {"date as string", sql.NewRow(stringDate), uint64(0), false}, {"date as time", sql.NewRow(currTime), uint64(math.Round(float64(currTime.Nanosecond()) / float64(time.Microsecond))), false}, } @@ -407,8 +407,8 @@ func TestTime_DayOfWeek(t *testing.T) { }{ {"null date", sql.NewRow(nil), nil, false}, {"invalid type", sql.NewRow([]byte{0, 1, 2}), nil, false}, - {"date as string", sql.NewRow(stringDate), int32(3), false}, - {"date as time", sql.NewRow(time.Now()), int32(time.Now().UTC().Weekday() + 1), false}, + {"date as string", sql.NewRow(stringDate), 3, false}, + {"date as time", sql.NewRow(time.Now()), int(time.Now().UTC().Weekday() + 1), false}, } for _, tt := range testCases { @@ -437,8 +437,8 @@ func TestTime_DayOfYear(t *testing.T) { }{ {"null date", sql.NewRow(nil), nil, false}, {"invalid type", sql.NewRow([]byte{0, 1, 2}), nil, false}, - {"date as string", sql.NewRow(stringDate), int32(2), false}, - {"date as time", sql.NewRow(time.Now()), int32(time.Now().UTC().YearDay()), false}, + {"date as string", sql.NewRow(stringDate), 2, false}, + {"date as time", sql.NewRow(time.Now()), time.Now().UTC().YearDay(), false}, } for _, tt := range testCases { @@ -468,7 +468,7 @@ func TestTime_WeekOfYear(t *testing.T) { err bool }{ {"null date", sql.NewRow(nil), nil, false}, - {"invalid type", sql.NewRow([]byte{0, 1, 2}), int32(1), true}, + {"invalid type", sql.NewRow([]byte{0, 1, 2}), nil, false}, {"date as string", sql.NewRow(stringDate), 1, false}, {"date as time", sql.NewRow(currTime), week, false}, } @@ -822,7 +822,7 @@ func TestTime_MonthName(t *testing.T) { err bool }{ {"null date", sql.NewRow(nil), nil, false}, - {"invalid type", sql.NewRow([]byte{0, 1, 2}), nil, true}, + {"invalid type", sql.NewRow([]byte{0, 1, 2}), nil, false}, {"time as string", sql.NewRow(stringDate), "January", false}, } From 50099a584353bf538917a6abd27be2975f1911ac Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Fri, 14 Nov 2025 13:53:49 -0800 Subject: [PATCH 17/30] update zeroTime value in tests --- sql/rowexec/insert_test.go | 8 ++--- sql/types/datetime_test.go | 72 +++++++++++++++++++------------------- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/sql/rowexec/insert_test.go b/sql/rowexec/insert_test.go index 8012440d17..9500a4ff17 100644 --- a/sql/rowexec/insert_test.go +++ b/sql/rowexec/insert_test.go @@ -15,12 +15,10 @@ package rowexec import ( - "math" - "testing" - "time" - "github.com/dolthub/vitess/go/sqltypes" "github.com/stretchr/testify/require" + "math" + "testing" "github.com/dolthub/go-mysql-server/memory" "github.com/dolthub/go-mysql-server/sql" @@ -63,7 +61,7 @@ func TestInsert(t *testing.T) { colType: types.Datetime, value: "dadasd", valueType: types.Text, - expected: time.Unix(-62167219200, 0).UTC(), + expected: types.ZeroTime, warning: true, ignore: true, }, diff --git a/sql/types/datetime_test.go b/sql/types/datetime_test.go index 6efc77af6c..b785ada21c 100644 --- a/sql/types/datetime_test.go +++ b/sql/types/datetime_test.go @@ -270,53 +270,53 @@ func TestDatetimeConvert(t *testing.T) { {Date, "", nil, true}, {Date, "0500-01-01", time.Date(500, 1, 1, 0, 0, 0, 0, time.UTC), false}, {Date, "10000-01-01", nil, true}, - {Date, int(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Date, int8(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Date, int16(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Date, int32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Date, int64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Date, uint(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Date, uint8(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Date, uint16(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Date, uint32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Date, uint64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Date, float32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Date, float64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {Date, int(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {Date, int8(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {Date, int16(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {Date, int32(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {Date, int64(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {Date, uint(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {Date, uint8(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {Date, uint16(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {Date, uint32(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {Date, uint64(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {Date, float32(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {Date, float64(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, {Date, []byte{0}, nil, true}, {DatetimeMaxPrecision, "0500-01-01 01:01:01", time.Date(500, 1, 1, 1, 1, 1, 0, time.UTC), false}, {DatetimeMaxPrecision, "0000-01-01 00:00:00", time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, {DatetimeMaxPrecision, time.Date(10000, 1, 1, 1, 1, 1, 1, time.UTC), nil, true}, - {DatetimeMaxPrecision, int(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {DatetimeMaxPrecision, int8(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {DatetimeMaxPrecision, int16(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {DatetimeMaxPrecision, int32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {DatetimeMaxPrecision, int64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {DatetimeMaxPrecision, uint(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {DatetimeMaxPrecision, uint8(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {DatetimeMaxPrecision, uint16(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {DatetimeMaxPrecision, uint32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {DatetimeMaxPrecision, uint64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {DatetimeMaxPrecision, float32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {DatetimeMaxPrecision, float64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, int(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, int8(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, int16(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, int32(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, int64(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, uint(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, uint8(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, uint16(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, uint32(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, uint64(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, float32(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, float64(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, {DatetimeMaxPrecision, []byte{0}, nil, true}, {TimestampMaxPrecision, time.Date(1960, 1, 1, 1, 1, 1, 1, time.UTC), nil, true}, {TimestampMaxPrecision, "1970-01-01 00:00:00", nil, true}, {TimestampMaxPrecision, "1970-01-01 00:00:01", time.Date(1970, 1, 1, 0, 0, 1, 0, time.UTC), false}, {TimestampMaxPrecision, time.Date(2040, 1, 1, 1, 1, 1, 1, time.UTC), nil, true}, - {TimestampMaxPrecision, int(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {TimestampMaxPrecision, int8(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {TimestampMaxPrecision, int16(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {TimestampMaxPrecision, int32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {TimestampMaxPrecision, int64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {TimestampMaxPrecision, uint(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {TimestampMaxPrecision, uint8(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {TimestampMaxPrecision, uint16(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {TimestampMaxPrecision, uint32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {TimestampMaxPrecision, uint64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {TimestampMaxPrecision, float32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {TimestampMaxPrecision, float64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, int(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, int8(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, int16(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, int32(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, int64(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, uint(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, uint8(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, uint16(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, uint32(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, uint64(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, float32(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, float64(0), time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), false}, {TimestampMaxPrecision, []byte{0}, nil, true}, {Date, int(1), nil, true}, From ccedfebd76fb98079bdb5682a590e4054ed1c1a8 Mon Sep 17 00:00:00 2001 From: angelamayxie Date: Fri, 14 Nov 2025 21:55:48 +0000 Subject: [PATCH 18/30] [ga-format-pr] Run ./format_repo.sh to fix formatting --- sql/rowexec/insert_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sql/rowexec/insert_test.go b/sql/rowexec/insert_test.go index 9500a4ff17..d0c7c2836b 100644 --- a/sql/rowexec/insert_test.go +++ b/sql/rowexec/insert_test.go @@ -15,11 +15,12 @@ package rowexec import ( - "github.com/dolthub/vitess/go/sqltypes" - "github.com/stretchr/testify/require" "math" "testing" + "github.com/dolthub/vitess/go/sqltypes" + "github.com/stretchr/testify/require" + "github.com/dolthub/go-mysql-server/memory" "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/expression" From 855df2261a38adda008e96c21e915e9bf3f04203 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Fri, 14 Nov 2025 14:21:57 -0800 Subject: [PATCH 19/30] simplify microsecond function --- sql/expression/function/time.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index 73c0065f35..d301ff4d03 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -936,9 +936,6 @@ var ( return (int(t.Month())-1)/3 + 1 }) microsecond = datePartFunc(func(t time.Time) interface{} { - if t.Equal(types.ZeroTime) { - return 0 - } return uint64(t.Nanosecond()) / uint64(time.Microsecond) }) ) From b207193ab289b0c1f99e24b4dc3e5df53d861dc2 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Fri, 14 Nov 2025 15:02:52 -0800 Subject: [PATCH 20/30] update and add more tests --- enginetest/queries/function_queries.go | 8 ++++++++ enginetest/queries/insert_queries.go | 19 +++++++++++++++++++ sql/expression/function/time.go | 9 ++++++++- sql/rowexec/update_test.go | 6 ++---- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index 7d934e71ec..d3b021f58d 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -2105,4 +2105,12 @@ var FunctionQueryTests = []QueryTest{ Query: "select date('0000-01-01')", Expected: []sql.Row{{"0000-01-01"}}, }, + { + Query: "select date('0000-00-00')", + Expected: []sql.Row{{"0000-00-00"}}, + }, + { + Query: "select date(0)", + Expected: []sql.Row{{"0000-00-00"}}, + }, } diff --git a/enginetest/queries/insert_queries.go b/enginetest/queries/insert_queries.go index eff5a80b2b..4963489e6e 100644 --- a/enginetest/queries/insert_queries.go +++ b/enginetest/queries/insert_queries.go @@ -2349,6 +2349,25 @@ var InsertScripts = []ScriptTest{ }, }, }, + { + Name: "inserting zero date", + SetUpScript: []string{ + "create table t(d date)", + "insert into t values ('0000-00-00')", + "create table t2(d datetime)", + "insert into t2 values ('0000-00-00')", + }, + Assertions: []ScriptTestAssertion{ + { + Query: "select * from t", + Expected: []sql.Row{{types.ZeroTime}}, + }, + { + Query: "select * from t2", + Expected: []sql.Row{{types.ZeroTime}}, + }, + }, + }, } var InsertDuplicateKeyKeyless = []ScriptTest{ diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index d301ff4d03..154a72dc97 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -1300,7 +1300,14 @@ func (d *Date) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return nil } - return v.(time.Time).Format("2006-01-02") + date, ok := v.(time.Time) + if !ok { + return nil + } + if date.Equal(types.ZeroTime) { + return types.ZeroDateStr + } + return date.Format("2006-01-02") }) } diff --git a/sql/rowexec/update_test.go b/sql/rowexec/update_test.go index 363846675a..5782e68227 100644 --- a/sql/rowexec/update_test.go +++ b/sql/rowexec/update_test.go @@ -15,11 +15,9 @@ package rowexec import ( - "testing" - "time" - "github.com/dolthub/vitess/go/sqltypes" "github.com/stretchr/testify/require" + "testing" "github.com/dolthub/go-mysql-server/memory" "github.com/dolthub/go-mysql-server/sql" @@ -55,7 +53,7 @@ func TestUpdateIgnoreConversions(t *testing.T) { colType: types.Datetime, value: "dadasd", valueType: types.Text, - expected: time.Unix(-62167219200, 0).UTC(), + expected: types.ZeroTime, }, { name: "inserting a negative into an unsigned int results in 0", From f235adf93999b474982d1eb1def02974cab0bb02 Mon Sep 17 00:00:00 2001 From: angelamayxie Date: Fri, 14 Nov 2025 23:22:49 +0000 Subject: [PATCH 21/30] [ga-format-pr] Run ./format_repo.sh to fix formatting --- sql/rowexec/update_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sql/rowexec/update_test.go b/sql/rowexec/update_test.go index 5782e68227..4dbdf3e151 100644 --- a/sql/rowexec/update_test.go +++ b/sql/rowexec/update_test.go @@ -15,9 +15,10 @@ package rowexec import ( + "testing" + "github.com/dolthub/vitess/go/sqltypes" "github.com/stretchr/testify/require" - "testing" "github.com/dolthub/go-mysql-server/memory" "github.com/dolthub/go-mysql-server/sql" From da0a99d94f8473bfacd35c5f8afad890000a2714 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Fri, 14 Nov 2025 16:00:31 -0800 Subject: [PATCH 22/30] update microseconds test to be less flaky --- sql/expression/function/time_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sql/expression/function/time_test.go b/sql/expression/function/time_test.go index ed82743acf..a8ecd723ad 100644 --- a/sql/expression/function/time_test.go +++ b/sql/expression/function/time_test.go @@ -16,7 +16,6 @@ package function import ( "fmt" - "math" "testing" "time" @@ -378,7 +377,7 @@ func TestTime_Microsecond(t *testing.T) { {"null date", sql.NewRow(nil), nil, false}, {"invalid type", sql.NewRow([]byte{0, 1, 2}), nil, false}, {"date as string", sql.NewRow(stringDate), uint64(0), false}, - {"date as time", sql.NewRow(currTime), uint64(math.Round(float64(currTime.Nanosecond()) / float64(time.Microsecond))), false}, + {"date as time", sql.NewRow(currTime), uint64(currTime.Nanosecond()) / uint64(time.Microsecond), false}, } for _, tt := range testCases { From 06b56ecf3f0350c44e82afed8a6dce28d3bd9e85 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Mon, 17 Nov 2025 10:03:43 -0800 Subject: [PATCH 23/30] add extract tests --- enginetest/queries/function_queries.go | 209 +++++++++++++++++++++++++ 1 file changed, 209 insertions(+) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index d3b021f58d..8729586f89 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -2113,4 +2113,213 @@ var FunctionQueryTests = []QueryTest{ Query: "select date(0)", Expected: []sql.Row{{"0000-00-00"}}, }, + { + Query: "select extract(day from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(day from false)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(day from true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select extract(month from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(month from false)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(month from true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select extract(quarter from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(quarter from false)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(quarter from true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select extract(year from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(year from false)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(year from true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select extract(year_month from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(year_month from false)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(year_month from true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select extract(day_microsecond from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(day_microsecond from false)", + Expected: []sql.Row{{0}}, + }, + { + Skip: true, + Query: "select extract(day_microsecond from true)", + Expected: []sql.Row{{1000000}}, + }, + { + Query: "select extract(day_second from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(day_second from false)", + Expected: []sql.Row{{0}}, + }, + { + // https://github.com/dolthub/dolt/issues/10087 + Skip: true, + Query: "select extract(day_second from true)", + Expected: []sql.Row{{1}}, + }, + { + Query: "select extract(day_minute from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(day_minute from false)", + Expected: []sql.Row{{0}}, + }, + { + // https://github.com/dolthub/dolt/issues/10087 + Skip: true, + Query: "select extract(day_minute from true)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(day_hour from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(day_hour from false)", + Expected: []sql.Row{{0}}, + }, + { + // https://github.com/dolthub/dolt/issues/10087 + Skip: true, + Query: "select extract(day_hour from true)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(second_microsecond from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(second_microsecond from false)", + Expected: []sql.Row{{0}}, + }, + { + // https://github.com/dolthub/dolt/issues/10087 + Skip: true, + Query: "select extract(second_microsecond from true)", + Expected: []sql.Row{{1000000}}, + }, + { + Query: "select extract(minute_microsecond from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(minute_microsecond from false)", + Expected: []sql.Row{{0}}, + }, + { + // https://github.com/dolthub/dolt/issues/10087 + Skip: true, + Query: "select extract(minute_microsecond from true)", + Expected: []sql.Row{{1000000}}, + }, + { + Query: "select extract(minute_second from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(minute_second from false)", + Expected: []sql.Row{{0}}, + }, + { + // https://github.com/dolthub/dolt/issues/10087 + Skip: true, + Query: "select extract(minute_second from true)", + Expected: []sql.Row{{1}}, + }, + { + Query: "select extract(hour_microsecond from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(hour_microsecond from false)", + Expected: []sql.Row{{0}}, + }, + { + // https://github.com/dolthub/dolt/issues/10087 + Skip: true, + Query: "select extract(hour_microsecond from true)", + Expected: []sql.Row{{1000000}}, + }, + { + Query: "select extract(hour_second from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(hour_second from false)", + Expected: []sql.Row{{0}}, + }, + { + // https://github.com/dolthub/dolt/issues/10087 + Skip: true, + Query: "select extract(hour_second from true)", + Expected: []sql.Row{{1}}, + }, + { + Query: "select extract(hour_minute from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(hour_minute from false)", + Expected: []sql.Row{{0}}, + }, + { + // https://github.com/dolthub/dolt/issues/10087 + Skip: true, + Query: "select extract(hour_minute from true)", + Expected: []sql.Row{{0}}, + }, } From fd6b865c01f53629dc1f0bfaee2f7c807275cf43 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Mon, 17 Nov 2025 10:18:47 -0800 Subject: [PATCH 24/30] add more extract tests --- enginetest/queries/function_queries.go | 56 ++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index 8729586f89..ffd6593890 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -2322,4 +2322,60 @@ var FunctionQueryTests = []QueryTest{ Query: "select extract(hour_minute from true)", Expected: []sql.Row{{0}}, }, + { + Query: "select extract(microsecond from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(microsecond from false)", + Expected: []sql.Row{{0}}, + }, + { + // https://github.com/dolthub/dolt/issues/10087 + Skip: true, + Query: "select extract(microsecond from true)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(second from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(second from false)", + Expected: []sql.Row{{0}}, + }, + { + // https://github.com/dolthub/dolt/issues/10087 + Skip: true, + Query: "select extract(second from true)", + Expected: []sql.Row{{1}}, + }, + { + Query: "select extract(minute from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(minute from false)", + Expected: []sql.Row{{0}}, + }, + { + // https://github.com/dolthub/dolt/issues/10087 + Skip: true, + Query: "select extract(minute from true)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(hour from 0)", + Expected: []sql.Row{{0}}, + }, + { + Query: "select extract(hour from false)", + Expected: []sql.Row{{0}}, + }, + { + // https://github.com/dolthub/dolt/issues/10087 + Skip: true, + Query: "select extract(hour from true)", + Expected: []sql.Row{{0}}, + }, } From 4638265f5f6356970103478163e436114a87694f Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Mon, 17 Nov 2025 10:46:39 -0800 Subject: [PATCH 25/30] fix extract functions --- enginetest/queries/function_queries.go | 16 +++++++ sql/expression/function/extract.go | 63 +++++++++++++++----------- 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index ffd6593890..f7bc6a8afe 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -2127,6 +2127,22 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarning: mysql.ERTruncatedWrongValue, ExpectedWarningsCount: 1, }, + { + Query: "select extract(week from 0)", + // This is 613566757 in MySQL but that value seems related to this bug https://bugs.mysql.com/bug.php?id=71414&files=1 + Expected: []sql.Row{{1}}, + }, + { + Query: "select extract(week from false)", + // This is 613566757 in MySQL but that value seems related to this bug https://bugs.mysql.com/bug.php?id=71414&files=1 + Expected: []sql.Row{{1}}, + }, + { + Query: "select extract(week from true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, { Query: "select extract(month from 0)", Expected: []sql.Row{{0}}, diff --git a/sql/expression/function/extract.go b/sql/expression/function/extract.go index 1ac812aea0..7332a03570 100644 --- a/sql/expression/function/extract.go +++ b/sql/expression/function/extract.go @@ -114,7 +114,7 @@ func (td *Extract) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { switch unit { case "DAY": - return dateTime.Day(), nil + return day(dateTime), nil case "HOUR": return dateTime.Hour(), nil case "MINUTE": @@ -124,27 +124,19 @@ func (td *Extract) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { case "MICROSECOND": return dateTime.Nanosecond() / 1000, nil case "QUARTER": - return (int(dateTime.Month())-1)/3 + 1, nil + return quarter(dateTime), nil case "MONTH": - return int(dateTime.Month()), nil + return month(dateTime), nil case "WEEK": - dateVal, err := td.RightChild.Eval(ctx, row) - if err != nil { - return nil, err - } - date, err := getDate(ctx, dateVal) - if err != nil { - return nil, err - } - yyyy, ok := year(date).(int) + yyyy, ok := year(dateTime).(int) if !ok { return nil, sql.ErrInvalidArgumentDetails.New("WEEK", "invalid year") } - mm, ok := month(date).(int) + mm, ok := month(dateTime).(int) if !ok { return nil, sql.ErrInvalidArgumentDetails.New("WEEK", "invalid month") } - dd, ok := day(date).(int) + dd, ok := day(dateTime).(int) if !ok { return nil, sql.ErrInvalidArgumentDetails.New("WEEK", "invalid day") } @@ -157,29 +149,41 @@ func (td *Extract) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { } return int(week), nil case "YEAR": - return dateTime.Year(), nil + return year(dateTime), nil case "DAY_HOUR": - dd := dateTime.Day() * 1_00 + dd, ok := day(dateTime).(int) + if !ok { + return nil, sql.ErrInvalidArgumentDetails.New("DAY_HOUR", "invalid day") + } hh := dateTime.Hour() - return dd + hh, nil + return (dd * 1_00) + hh, nil case "DAY_MINUTE": - dd := dateTime.Day() * 1_00_00 + dd, ok := day(dateTime).(int) + if !ok { + return nil, sql.ErrInvalidArgumentDetails.New("DAY_MINUTE", "invalid day") + } hh := dateTime.Hour() * 1_00 mm := dateTime.Minute() - return dd + hh + mm, nil + return (dd * 1_00_00) + hh + mm, nil case "DAY_SECOND": - dd := dateTime.Day() * 1_00_00_00 + dd, ok := day(dateTime).(int) + if !ok { + return nil, sql.ErrInvalidArgumentDetails.New("DAY_SECOND", "invalid day") + } hh := dateTime.Hour() * 1_00_00 mm := dateTime.Minute() * 1_00 ss := dateTime.Second() - return dd + hh + mm + ss, nil + return (dd * 1_00_00_00) + hh + mm + ss, nil case "DAY_MICROSECOND": - dd := dateTime.Day() * 1_00_00_00_000000 + dd, ok := day(dateTime).(int) + if !ok { + return nil, sql.ErrInvalidArgumentDetails.New("DAY_MICROSECOND", "invalid day") + } hh := dateTime.Hour() * 1_00_00_000000 mm := dateTime.Minute() * 1_00_000000 ss := dateTime.Second() * 1_000000 mmmmmm := dateTime.Nanosecond() / 1000 - return dd + hh + mm + ss + mmmmmm, nil + return (dd * 1_00_00_00_000000) + hh + mm + ss + mmmmmm, nil case "HOUR_MINUTE": hh := dateTime.Hour() * 1_00 mm := dateTime.Minute() @@ -209,10 +213,15 @@ func (td *Extract) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { mmmmmm := dateTime.Nanosecond() / 1000 return ss + mmmmmm, nil case "YEAR_MONTH": - yyyy := dateTime.Year() * 1_00 - dateTime.Month() - mm := int(dateTime.Month()) - return yyyy + mm, nil + yyyy, ok := year(dateTime).(int) + if !ok { + return nil, sql.ErrInvalidArgumentDetails.New("YEAR_MONTH", "invalid year") + } + mm, ok := month(dateTime).(int) + if !ok { + return nil, sql.ErrInvalidArgumentDetails.New("YEAR_MONTH", "invalid month") + } + return (yyyy * 1_00) + mm, nil default: return nil, fmt.Errorf("invalid time unit") } From e4f775b4a639c35bd6caa57a8b4e63b65d1f9991 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Mon, 17 Nov 2025 11:15:36 -0800 Subject: [PATCH 26/30] fix date function --- enginetest/queries/function_queries.go | 24 +++++++++++++++---- sql/expression/function/time.go | 32 +++++++++++++++----------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index f7bc6a8afe..0447e2254a 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -2106,12 +2106,28 @@ var FunctionQueryTests = []QueryTest{ Expected: []sql.Row{{"0000-01-01"}}, }, { - Query: "select date('0000-00-00')", - Expected: []sql.Row{{"0000-00-00"}}, + Query: "select date('0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select date(0)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, }, { - Query: "select date(0)", - Expected: []sql.Row{{"0000-00-00"}}, + Query: "select date(false)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select date(true)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, }, { Query: "select extract(day from 0)", diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index 154a72dc97..294de72b93 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -1295,20 +1295,26 @@ func (*Date) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, // Eval implements the Expression interface. func (d *Date) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { - return getDatePart(ctx, d.UnaryExpression, row, func(v interface{}) interface{} { - if v == nil { - return nil - } + dateVal, err := d.Child.Eval(ctx, row) + if err != nil { + return nil, err + } - date, ok := v.(time.Time) - if !ok { - return nil - } - if date.Equal(types.ZeroTime) { - return types.ZeroDateStr - } - return date.Format("2006-01-02") - }) + date, err := getDate(ctx, dateVal) + if err != nil { + return nil, err + } + if date == nil { + return nil, nil + } + + dateTime, ok := date.(time.Time) + if !ok || dateTime.Equal(types.ZeroTime) { + ctx.Warn(1292, "%s", types.ErrConvertingToTime.New(dateVal).Error()) + return nil, nil + } + + return dateTime.Format("2006-01-02"), nil } // WithChildren implements the Expression interface. From 2047b5516c7595d826092ea8429c3782ab57e0fd Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Mon, 17 Nov 2025 11:56:14 -0800 Subject: [PATCH 27/30] add mysql dialect tag --- enginetest/queries/insert_queries.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enginetest/queries/insert_queries.go b/enginetest/queries/insert_queries.go index 4963489e6e..493eaa5445 100644 --- a/enginetest/queries/insert_queries.go +++ b/enginetest/queries/insert_queries.go @@ -2350,7 +2350,8 @@ var InsertScripts = []ScriptTest{ }, }, { - Name: "inserting zero date", + Name: "inserting zero date", + Dialect: "mysql", SetUpScript: []string{ "create table t(d date)", "insert into t values ('0000-00-00')", From 8d1d4b4f579f8ec2e9ac5d84ebcaf7e2924a21a1 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Mon, 17 Nov 2025 13:05:56 -0800 Subject: [PATCH 28/30] fix more time functions --- enginetest/queries/function_queries.go | 60 ++++++++++++++++++++++++++ sql/expression/arithmetic.go | 13 ++++++ sql/expression/function/time_math.go | 40 ++++++++++++----- 3 files changed, 103 insertions(+), 10 deletions(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index 0447e2254a..a9ad9f5f72 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -1615,6 +1615,18 @@ var FunctionQueryTests = []QueryTest{ Query: "SELECT TIMESTAMPADD(DAY, 1, '2018-05-02')", Expected: []sql.Row{{"2018-05-03"}}, }, + { + Query: "select timestampadd(day, 1, '0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select timestampadd(day, 1, 0)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, { Query: "SELECT DATE_ADD('2018-05-02', INTERVAL 1 day)", Expected: []sql.Row{{"2018-05-03"}}, @@ -1627,6 +1639,18 @@ var FunctionQueryTests = []QueryTest{ Query: "select date_add(time('12:13:14'), interval 1 minute);", Expected: []sql.Row{{types.Timespan(44054000000)}}, }, + { + Query: "select date_add(0, interval 1 day)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select date_sub(0, interval 1 day)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, { Query: "SELECT DATE_SUB('2018-05-02', INTERVAL 1 DAY)", Expected: []sql.Row{{"2018-05-01"}}, @@ -1671,6 +1695,42 @@ var FunctionQueryTests = []QueryTest{ Query: "SELECT DATE_ADD('9999-12-31 23:59:59', INTERVAL 1 DAY)", Expected: []sql.Row{{nil}}, }, + { + Query: "select 0 + interval 1 day", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select 0 - interval 1 day", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select datediff(0, '2020-10-10')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select datediff('0000-00-00', '2020-10-10')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select datediff('2020-10-10', 0)", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, + { + Query: "select datediff('2020-10-10', '0000-00-00')", + Expected: []sql.Row{{nil}}, + ExpectedWarning: mysql.ERTruncatedWrongValue, + ExpectedWarningsCount: 1, + }, { Query: "SELECT EXTRACT(DAY FROM '9999-12-31 23:59:59')", Expected: []sql.Row{{31}}, diff --git a/sql/expression/arithmetic.go b/sql/expression/arithmetic.go index f4a4c101ae..dc42d6a51d 100644 --- a/sql/expression/arithmetic.go +++ b/sql/expression/arithmetic.go @@ -440,6 +440,13 @@ func convertValueToType(ctx *sql.Context, typ sql.Type, val interface{}, isTimeT // the value is interpreted as 0, but we need to match the type of the other valid value // to avoid additional conversion, the nil value is handled in each operation } + if types.IsTime(typ) { + time, ok := cval.(time.Time) + if !ok || time.Equal(types.ZeroTime) { + ctx.Warn(1292, "Incorrect datetime value: '%s'", val) + return nil + } + } return cval } @@ -462,6 +469,9 @@ func convertTimeTypeToString(val interface{}) interface{} { } func plus(lval, rval interface{}) (interface{}, error) { + if lval == nil || rval == nil { + return nil, nil + } switch l := lval.(type) { case uint8: switch r := rval.(type) { @@ -536,6 +546,9 @@ func plus(lval, rval interface{}) (interface{}, error) { } func minus(lval, rval interface{}) (interface{}, error) { + if lval == nil || rval == nil { + return nil, nil + } switch l := lval.(type) { case uint8: switch r := rval.(type) { diff --git a/sql/expression/function/time_math.go b/sql/expression/function/time_math.go index bd42d8c877..30dbc144c3 100644 --- a/sql/expression/function/time_math.go +++ b/sql/expression/function/time_math.go @@ -82,37 +82,47 @@ func (d *DateDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return nil, nil } - expr1, err := d.LeftChild.Eval(ctx, row) + val1, err := d.LeftChild.Eval(ctx, row) if err != nil { return nil, err } - if expr1 == nil { + if val1 == nil { return nil, nil } - expr1, _, err = types.DatetimeMaxPrecision.Convert(ctx, expr1) + expr1, _, err := types.DatetimeMaxPrecision.Convert(ctx, val1) if err != nil { - return nil, err + ctx.Warn(1292, "Incorrect datetime value: '%s'", val1) + return nil, nil } expr1str := expr1.(time.Time).String()[:10] expr1, _, _ = types.DatetimeMaxPrecision.Convert(ctx, expr1str) + if expr1 == nil { + ctx.Warn(1292, "Incorrect datetime value: '%s'", val1) + return nil, nil + } - expr2, err := d.RightChild.Eval(ctx, row) + val2, err := d.RightChild.Eval(ctx, row) if err != nil { return nil, err } - if expr2 == nil { + if val2 == nil { return nil, nil } - expr2, _, err = types.DatetimeMaxPrecision.Convert(ctx, expr2) + expr2, _, err := types.DatetimeMaxPrecision.Convert(ctx, val2) if err != nil { - return nil, err + ctx.Warn(1292, "Incorrect datetime value: '%s'", val2) + return nil, nil } expr2str := expr2.(time.Time).String()[:10] expr2, _, _ = types.DatetimeMaxPrecision.Convert(ctx, expr2str) + if expr2 == nil { + ctx.Warn(1292, "Incorrect datetime value: '%s'", val2) + return nil, nil + } date1 := expr1.(time.Time) date2 := expr2.(time.Time) @@ -237,9 +247,14 @@ func (d *DateAdd) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { ctx.Warn(1292, "%s", err.Error()) return nil, nil } + datetime, ok := dateVal.(time.Time) + if ok || datetime.Equal(types.ZeroTime) { + ctx.Warn(1292, "Incorrect datetime value: '%s'", date) + return nil, nil + } // return appropriate type - res := types.ValidateTime(delta.Add(dateVal.(time.Time))) + res := types.ValidateTime(delta.Add(datetime)) if res == nil { return nil, nil } @@ -385,9 +400,14 @@ func (d *DateSub) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { ctx.Warn(1292, "%s", err.Error()) return nil, nil } + datetime, ok := dateVal.(time.Time) + if ok || datetime.Equal(types.ZeroTime) { + ctx.Warn(1292, "Incorrect datetime value: '%s'", date) + return nil, nil + } // return appropriate type - res := types.ValidateTime(delta.Sub(dateVal.(time.Time))) + res := types.ValidateTime(delta.Sub(datetime)) if res == nil { return nil, nil } From ed21f640820d0268c0f9ce752a2b719a603e0835 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Mon, 17 Nov 2025 13:31:42 -0800 Subject: [PATCH 29/30] fix missing --- sql/expression/function/time_math.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/expression/function/time_math.go b/sql/expression/function/time_math.go index 30dbc144c3..56db5ffd69 100644 --- a/sql/expression/function/time_math.go +++ b/sql/expression/function/time_math.go @@ -248,7 +248,7 @@ func (d *DateAdd) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return nil, nil } datetime, ok := dateVal.(time.Time) - if ok || datetime.Equal(types.ZeroTime) { + if !ok || datetime.Equal(types.ZeroTime) { ctx.Warn(1292, "Incorrect datetime value: '%s'", date) return nil, nil } @@ -401,7 +401,7 @@ func (d *DateSub) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return nil, nil } datetime, ok := dateVal.(time.Time) - if ok || datetime.Equal(types.ZeroTime) { + if !ok || datetime.Equal(types.ZeroTime) { ctx.Warn(1292, "Incorrect datetime value: '%s'", date) return nil, nil } From 718eb86b74320bb9fb6021c95beb5d30894dc6da Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Mon, 17 Nov 2025 15:48:43 -0800 Subject: [PATCH 30/30] update date function to allow for zero time --- enginetest/queries/function_queries.go | 22 ++++++---------------- sql/expression/function/time.go | 5 ++++- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/enginetest/queries/function_queries.go b/enginetest/queries/function_queries.go index a9ad9f5f72..954a3c3b3b 100644 --- a/enginetest/queries/function_queries.go +++ b/enginetest/queries/function_queries.go @@ -1839,7 +1839,6 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarningsCount: 1, }, { - // This is not a valid time string in MySQL, but we allow it Query: "select day('0000-00-00')", Expected: []sql.Row{{0}}, }, @@ -1897,7 +1896,6 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarningsCount: 1, }, { - // This is not a valid time string in MySQL, but we allow it Query: "select dayofmonth('0000-00-00')", Expected: []sql.Row{{0}}, }, @@ -1983,7 +1981,6 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarningsCount: 1, }, { - // This is not a valid time string in MySQL, but we allow it Query: "select month('0000-00-00')", Expected: []sql.Row{{0}}, }, @@ -2153,7 +2150,6 @@ var FunctionQueryTests = []QueryTest{ ExpectedWarningsCount: 1, }, { - // This is not a valid time string in MySQL, but we allow it Query: "select quarter('0000-00-00')", Expected: []sql.Row{{0}}, }, @@ -2166,22 +2162,16 @@ var FunctionQueryTests = []QueryTest{ Expected: []sql.Row{{"0000-01-01"}}, }, { - Query: "select date('0000-00-00')", - Expected: []sql.Row{{nil}}, - ExpectedWarning: mysql.ERTruncatedWrongValue, - ExpectedWarningsCount: 1, + Query: "select date('0000-00-00')", + Expected: []sql.Row{{"0000-00-00"}}, }, { - Query: "select date(0)", - Expected: []sql.Row{{nil}}, - ExpectedWarning: mysql.ERTruncatedWrongValue, - ExpectedWarningsCount: 1, + Query: "select date(0)", + Expected: []sql.Row{{"0000-00-00"}}, }, { - Query: "select date(false)", - Expected: []sql.Row{{nil}}, - ExpectedWarning: mysql.ERTruncatedWrongValue, - ExpectedWarningsCount: 1, + Query: "select date(false)", + Expected: []sql.Row{{"0000-00-00"}}, }, { Query: "select date(true)", diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index 294de72b93..76d3026fe0 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -1309,10 +1309,13 @@ func (d *Date) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { } dateTime, ok := date.(time.Time) - if !ok || dateTime.Equal(types.ZeroTime) { + if !ok { ctx.Warn(1292, "%s", types.ErrConvertingToTime.New(dateVal).Error()) return nil, nil } + if dateTime.Equal(types.ZeroTime) { + return types.ZeroDateStr, nil + } return dateTime.Format("2006-01-02"), nil }