From 4244a2a881f0f1e1a3f7178efe37393e4541788f Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Wed, 22 Oct 2025 11:19:29 -0700 Subject: [PATCH 1/4] Respect precision when casting to datetime --- enginetest/queries/queries.go | 12 ------------ sql/expression/comparison.go | 4 +++- sql/expression/convert.go | 2 +- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/enginetest/queries/queries.go b/enginetest/queries/queries.go index 59c09b1f79..ae68ed948a 100644 --- a/enginetest/queries/queries.go +++ b/enginetest/queries/queries.go @@ -4246,48 +4246,36 @@ SELECT * FROM cte WHERE d = 2;`, Expected: []sql.Row{{time.Date(2020, time.January, 1, 12, 34, 56, 0, time.UTC)}}, }, { - // TODO: implement precision for datetime casting - Skip: true, Query: "select cast('2020-01-01 12:34:56.123456abc' as datetime(1));", ExpectedWarning: 1292, ExpectedWarningsCount: 1, Expected: []sql.Row{{time.Date(2020, time.January, 1, 12, 34, 56, 100000000, time.UTC)}}, }, { - // TODO: implement precision for datetime casting - Skip: true, Query: "select cast('2020-01-01 12:34:56.123456abc' as datetime(2));", ExpectedWarning: 1292, ExpectedWarningsCount: 1, Expected: []sql.Row{{time.Date(2020, time.January, 1, 12, 34, 56, 120000000, time.UTC)}}, }, { - // TODO: implement precision for datetime casting - Skip: true, Query: "select cast('2020-01-01 12:34:56.123456abc' as datetime(3));", ExpectedWarning: 1292, ExpectedWarningsCount: 1, Expected: []sql.Row{{time.Date(2020, time.January, 1, 12, 34, 56, 123000000, time.UTC)}}, }, { - // TODO: implement precision for datetime casting - Skip: true, Query: "select cast('2020-01-01 12:34:56.123456abc' as datetime(4));", ExpectedWarning: 1292, ExpectedWarningsCount: 1, Expected: []sql.Row{{time.Date(2020, time.January, 1, 12, 34, 56, 123500000, time.UTC)}}, }, { - // TODO: implement precision for datetime casting - Skip: true, Query: "select cast('2020-01-01 12:34:56.123456abc' as datetime(5));", ExpectedWarning: 1292, ExpectedWarningsCount: 1, Expected: []sql.Row{{time.Date(2020, time.January, 1, 12, 34, 56, 123460000, time.UTC)}}, }, { - // TODO: implement precision for datetime casting - Skip: true, Query: "select cast('2020-01-01 12:34:56.123456abc' as datetime(6));", ExpectedWarning: 1292, ExpectedWarningsCount: 1, diff --git a/sql/expression/comparison.go b/sql/expression/comparison.go index 97e5f4ba6e..3fc4075b87 100644 --- a/sql/expression/comparison.go +++ b/sql/expression/comparison.go @@ -222,12 +222,13 @@ func (c *comparison) castLeftAndRight(ctx *sql.Context, left, right interface{}) } if types.IsTime(leftType) || types.IsTime(rightType) { + // TODO: We need to set to actual Datetime type l, r, err := convertLeftAndRight(ctx, left, right, ConvertToDatetime) if err != nil { return nil, nil, nil, err } - return l, r, types.DatetimeMaxPrecision, nil + return l, r, types.DatetimeDefaultPrecision, nil } // Rely on types.JSON.Compare to handle JSON comparisons @@ -302,6 +303,7 @@ func (c *comparison) castLeftAndRight(ctx *sql.Context, left, right interface{}) } func convertLeftAndRight(ctx *sql.Context, left, right interface{}, convertTo string) (interface{}, interface{}, error) { + // TODO: typeLength and typeScale should be the actual length and scale of the type to be converted l, err := convertValue(ctx, left, convertTo, nil, 0, 0) if err != nil { return nil, nil, err diff --git a/sql/expression/convert.go b/sql/expression/convert.go index 7cfa69ee25..9d5d7af6bd 100644 --- a/sql/expression/convert.go +++ b/sql/expression/convert.go @@ -341,7 +341,7 @@ func convertValue(ctx *sql.Context, val interface{}, castTo string, originType s if !(isTime || isString || isBinary) { return nil, nil } - d, _, err := types.DatetimeDefaultPrecision.Convert(ctx, val) + d, _, err := types.MustCreateDatetimeType(sqltypes.Datetime, typeLength).Convert(ctx, val) if err != nil { if !sql.ErrTruncatedIncorrect.Is(err) { return nil, err From c5d4cc2c19a764d36b9069c235da5d81e2925ad3 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Wed, 22 Oct 2025 11:54:06 -0700 Subject: [PATCH 2/4] use max datetime precision for comparisons --- sql/expression/comparison.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sql/expression/comparison.go b/sql/expression/comparison.go index 3fc4075b87..78d644afe6 100644 --- a/sql/expression/comparison.go +++ b/sql/expression/comparison.go @@ -222,13 +222,15 @@ func (c *comparison) castLeftAndRight(ctx *sql.Context, left, right interface{}) } if types.IsTime(leftType) || types.IsTime(rightType) { - // TODO: We need to set to actual Datetime type - l, r, err := convertLeftAndRight(ctx, left, right, ConvertToDatetime) + l, _, err := types.DatetimeMaxPrecision.Convert(ctx, left) if err != nil { return nil, nil, nil, err } - - return l, r, types.DatetimeDefaultPrecision, nil + r, _, err := types.DatetimeMaxPrecision.Convert(ctx, right) + if err != nil { + return nil, nil, nil, err + } + return l, r, types.DatetimeMaxPrecision, nil } // Rely on types.JSON.Compare to handle JSON comparisons From 8a1d37e6351089e3458aad2f01d2e1f0e95e28b2 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Wed, 22 Oct 2025 12:11:40 -0700 Subject: [PATCH 3/4] explicitly cast to max precision for comparisons --- sql/expression/comparison.go | 15 +++++++-------- sql/types/datetime.go | 12 +++++++----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/sql/expression/comparison.go b/sql/expression/comparison.go index 78d644afe6..e7cb0b8e15 100644 --- a/sql/expression/comparison.go +++ b/sql/expression/comparison.go @@ -222,11 +222,7 @@ func (c *comparison) castLeftAndRight(ctx *sql.Context, left, right interface{}) } if types.IsTime(leftType) || types.IsTime(rightType) { - l, _, err := types.DatetimeMaxPrecision.Convert(ctx, left) - if err != nil { - return nil, nil, nil, err - } - r, _, err := types.DatetimeMaxPrecision.Convert(ctx, right) + l, r, err := convertLeftAndRight(ctx, left, right, ConvertToDatetime) if err != nil { return nil, nil, nil, err } @@ -305,13 +301,16 @@ func (c *comparison) castLeftAndRight(ctx *sql.Context, left, right interface{}) } func convertLeftAndRight(ctx *sql.Context, left, right interface{}, convertTo string) (interface{}, interface{}, error) { - // TODO: typeLength and typeScale should be the actual length and scale of the type to be converted - l, err := convertValue(ctx, left, convertTo, nil, 0, 0) + typeLength := 0 + if convertTo == ConvertToDatetime { + typeLength = types.MaxDatetimePrecision + } + l, err := convertValue(ctx, left, convertTo, nil, typeLength, 0) if err != nil { return nil, nil, err } - r, err := convertValue(ctx, right, convertTo, nil, 0, 0) + r, err := convertValue(ctx, right, convertTo, nil, typeLength, 0) if err != nil { return nil, nil, err } diff --git a/sql/types/datetime.go b/sql/types/datetime.go index 60d87b6ad6..b536c2ace9 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -36,6 +36,8 @@ const ZeroTimestampDatetimeStr = "0000-00-00 00:00:00" const MinDatetimeStringLength = 8 // length of "2000-1-1" +const MaxDatetimePrecision = 6 + var ( // ErrConvertingToTime is thrown when a value cannot be converted to a Time ErrConvertingToTime = errors.NewKind("Incorrect datetime value: '%v'") @@ -105,13 +107,13 @@ var ( // DatetimeDefaultPrecision is a date and a time without a specified precision DatetimeDefaultPrecision = MustCreateDatetimeType(sqltypes.Datetime, 0) // DatetimeMaxPrecision is a date and a time with maximum precision - DatetimeMaxPrecision = MustCreateDatetimeType(sqltypes.Datetime, 6) + DatetimeMaxPrecision = MustCreateDatetimeType(sqltypes.Datetime, MaxDatetimePrecision) // Timestamp is a UNIX timestamp with default precision (no fractional seconds). Timestamp = MustCreateDatetimeType(sqltypes.Timestamp, 0) // TimestampMaxPrecision is a UNIX timestamp with maximum precision - TimestampMaxPrecision = MustCreateDatetimeType(sqltypes.Timestamp, 6) + TimestampMaxPrecision = MustCreateDatetimeType(sqltypes.Timestamp, MaxDatetimePrecision) // DatetimeMaxRange is a date and a time with maximum precision and maximum range. - DatetimeMaxRange = MustCreateDatetimeType(sqltypes.Datetime, 6) + DatetimeMaxRange = MustCreateDatetimeType(sqltypes.Datetime, MaxDatetimePrecision) datetimeValueType = reflect.TypeOf(time.Time{}) ) @@ -128,7 +130,7 @@ var _ sql.CollationCoercible = datetimeType{} func CreateDatetimeType(baseType query.Type, precision int) (sql.DatetimeType, error) { switch baseType { case sqltypes.Date, sqltypes.Datetime, sqltypes.Timestamp: - if precision < 0 || precision > 6 { + if precision < 0 || precision > MaxDatetimePrecision { return nil, fmt.Errorf("precision must be between 0 and 6, got %d", precision) } return datetimeType{ @@ -221,7 +223,7 @@ func ConvertToTime(ctx context.Context, v interface{}, t datetimeType) (time.Tim } // Round the date to the precision of this type - if t.precision < 6 { + if t.precision < MaxDatetimePrecision { truncationDuration := time.Second / time.Duration(precisionConversion[t.precision]) res = res.Round(truncationDuration) } else { From 62efe690ace1363639adc27cfc5858d64413a489 Mon Sep 17 00:00:00 2001 From: Angela Xie Date: Wed, 22 Oct 2025 12:20:41 -0700 Subject: [PATCH 4/4] add datetime comparison test --- enginetest/queries/queries.go | 4 ++++ sql/types/datetime.go | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/enginetest/queries/queries.go b/enginetest/queries/queries.go index ae68ed948a..0dfb832220 100644 --- a/enginetest/queries/queries.go +++ b/enginetest/queries/queries.go @@ -4281,6 +4281,10 @@ SELECT * FROM cte WHERE d = 2;`, ExpectedWarningsCount: 1, Expected: []sql.Row{{time.Date(2020, time.January, 1, 12, 34, 56, 123456000, time.UTC)}}, }, + { + Query: "select cast('2020-01-01 12:34:56.123456' as datetime(6)) > cast('2020-01-01 12:34:56' as datetime)", + Expected: []sql.Row{{true}}, + }, { Query: `SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM othertable) othertable_one) othertable_two) othertable_three WHERE s2 = 'first'`, Expected: []sql.Row{ diff --git a/sql/types/datetime.go b/sql/types/datetime.go index b536c2ace9..387956f0fb 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -104,8 +104,6 @@ var ( Date = MustCreateDatetimeType(sqltypes.Date, 0) // Datetime is a date and a time with default precision (no fractional seconds). Datetime = MustCreateDatetimeType(sqltypes.Datetime, 0) - // DatetimeDefaultPrecision is a date and a time without a specified precision - DatetimeDefaultPrecision = MustCreateDatetimeType(sqltypes.Datetime, 0) // DatetimeMaxPrecision is a date and a time with maximum precision DatetimeMaxPrecision = MustCreateDatetimeType(sqltypes.Datetime, MaxDatetimePrecision) // Timestamp is a UNIX timestamp with default precision (no fractional seconds).