diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index c2a7f3f3cd2..5c3960fdef5 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -196,12 +196,38 @@ struct ToDateTransform32Or64 static inline NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl & time_zone) { - return (from < 0xFFFF) ? from : time_zone.toDayNum(from); + return (from < 0xFFFF) ? from : time_zone.toDayNum(std::min(time_t(from), time_t(0xFFFFFFFF))); } }; -/** Special case of converting (U)Int32 or (U)Int64 (and also, for convenience, Float32, Float64) to Date. - * If number is less than 65536, then it is treated as DayNum, and if greater or equals, then as unix timestamp. +template +struct ToDateTransform32Or64Signed +{ + static constexpr auto name = "toDate"; + + static inline NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl & time_zone) + { + if (from < 0) return 0; + return (from < 0xFFFF) ? from : time_zone.toDayNum(std::min(time_t(from), time_t(0xFFFFFFFF))); + } +}; + +template +struct ToDateTransform8Or16Signed +{ + static constexpr auto name = "toDate"; + + static inline NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl &) + { + if (from < 0) return 0; + return from; + } +}; + +/** Special case of converting Int8, Int16, (U)Int32 or (U)Int64 (and also, for convenience, + * Float32, Float64) to Date. If the number is negative, saturate it to unix epoch time. If the + * number is less than 65536, then it is treated as DayNum, and if it's greater or equals to 65536, + * then treated as unix timestamp. If the number exceeds UInt32, saturate to MAX_UINT32 then as DayNum. * It's a bit illogical, as we actually have two functions in one. * But allows to support frequent case, * when user write toDate(UInt32), expecting conversion of unix timestamp to Date. @@ -211,14 +237,73 @@ template struct ConvertImpl : DateTimeTransformImpl> {}; template struct ConvertImpl : DateTimeTransformImpl> {}; +template struct ConvertImpl + : DateTimeTransformImpl> {}; +template struct ConvertImpl + : DateTimeTransformImpl> {}; template struct ConvertImpl - : DateTimeTransformImpl> {}; + : DateTimeTransformImpl> {}; template struct ConvertImpl - : DateTimeTransformImpl> {}; + : DateTimeTransformImpl> {}; template struct ConvertImpl - : DateTimeTransformImpl> {}; + : DateTimeTransformImpl> {}; template struct ConvertImpl - : DateTimeTransformImpl> {}; + : DateTimeTransformImpl> {}; + + +template +struct ToDateTimeTransform64 +{ + static constexpr auto name = "toDateTime"; + + static inline NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl &) + { + return std::min(time_t(from), time_t(0xFFFFFFFF)); + } +}; + +template +struct ToDateTimeTransformSigned +{ + static constexpr auto name = "toDateTime"; + + static inline NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl &) + { + if (from < 0) return 0; + return from; + } +}; + +template +struct ToDateTimeTransform64Signed +{ + static constexpr auto name = "toDateTime"; + + static inline NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl &) + { + if (from < 0) return 0; + return std::min(time_t(from), time_t(0xFFFFFFFF)); + } +}; + +/** Special case of converting Int8, Int16, Int32 or (U)Int64 (and also, for convenience, Float32, + * Float64) to DateTime. If the number is negative, saturate it to unix epoch time. If the number + * exceeds UInt32, saturate to MAX_UINT32. + */ +template struct ConvertImpl + : DateTimeTransformImpl> {}; +template struct ConvertImpl + : DateTimeTransformImpl> {}; +template struct ConvertImpl + : DateTimeTransformImpl> {}; +template struct ConvertImpl + : DateTimeTransformImpl> {}; +template struct ConvertImpl + : DateTimeTransformImpl> {}; +template struct ConvertImpl + : DateTimeTransformImpl> {}; +template struct ConvertImpl + : DateTimeTransformImpl> {}; /** Conversion of Date or DateTime to DateTime64: add zero sub-second part. @@ -1412,6 +1497,25 @@ struct ToNumberMonotonicity } }; +struct ToDateMonotonicity +{ + static bool has() { return true; } + + static IFunction::Monotonicity get(const IDataType & type, const Field & left, const Field & right) + { + auto which = WhichDataType(type); + if (which.isDateOrDateTime() || which.isInt8() || which.isInt16() || which.isUInt8() || which.isUInt16()) + return {true, true, true}; + else if ( + (which.isUInt() && ((left.isNull() || left.get() < 0xFFFF) && (right.isNull() || right.get() >= 0xFFFF))) + || (which.isInt() && ((left.isNull() || left.get() < 0xFFFF) && (right.isNull() || right.get() >= 0xFFFF))) + || (which.isFloat() && ((left.isNull() || left.get() < 0xFFFF) && (right.isNull() || right.get() >= 0xFFFF)))) + return {}; + else + return {true, true, true}; + } +}; + /** The monotonicity for the `toString` function is mainly determined for test purposes. * It is doubtful that anyone is looking to optimize queries with conditions `toString(CounterID) = 34`. */ @@ -1478,8 +1582,8 @@ using FunctionToInt32 = FunctionConvert>; using FunctionToFloat32 = FunctionConvert>; using FunctionToFloat64 = FunctionConvert>; -using FunctionToDate = FunctionConvert>; -using FunctionToDateTime = FunctionConvert>; +using FunctionToDate = FunctionConvert; +using FunctionToDateTime = FunctionConvert; using FunctionToDateTime64 = FunctionConvert; using FunctionToUUID = FunctionConvert>; using FunctionToString = FunctionConvert; diff --git a/tests/queries/0_stateless/01440_to_date_monotonicity.reference b/tests/queries/0_stateless/01440_to_date_monotonicity.reference new file mode 100644 index 00000000000..529601fb398 --- /dev/null +++ b/tests/queries/0_stateless/01440_to_date_monotonicity.reference @@ -0,0 +1,2 @@ +0 +1970-01-01 2106-02-07 1970-04-11 1970-01-01 2106-02-07 diff --git a/tests/queries/0_stateless/01440_to_date_monotonicity.sql b/tests/queries/0_stateless/01440_to_date_monotonicity.sql new file mode 100644 index 00000000000..0355d1fec30 --- /dev/null +++ b/tests/queries/0_stateless/01440_to_date_monotonicity.sql @@ -0,0 +1,9 @@ +DROP TABLE IF EXISTS tdm; +CREATE TABLE tdm (x DateTime) ENGINE = MergeTree ORDER BY x SETTINGS write_final_mark = 0; +INSERT INTO tdm VALUES (now()); +SELECT count(x) FROM tdm WHERE toDate(x) < today() SETTINGS max_rows_to_read = 1; + +SELECT toDate(-1), toDate(10000000000000), toDate(100), toDate(65536), toDate(65535); +SELECT toDateTime(-1), toDateTime(10000000000000), toDateTime(1000); + +DROP TABLE tdm;