Skip to content

Commit 21c7502

Browse files
Backport #53309 to 23.3: Fix adding sub-second intervals to DateTime
1 parent 3390d54 commit 21c7502

File tree

6 files changed

+123
-127
lines changed

6 files changed

+123
-127
lines changed

src/Core/DecimalFunctions.h

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,37 @@ struct DataTypeDecimalTrait
8282
}
8383
};
8484

85+
/// Calculates result = x * multiplier + delta.
86+
/// If the multiplication or the addition overflows, returns false or throws DECIMAL_OVERFLOW.
87+
template <typename T, bool throw_on_error>
88+
inline bool multiplyAdd(const T & x, const T & multiplier, const T & delta, T & result)
89+
{
90+
T multiplied = 0;
91+
if (common::mulOverflow(x, multiplier, multiplied))
92+
{
93+
if constexpr (throw_on_error)
94+
throw Exception(ErrorCodes::DECIMAL_OVERFLOW, "Decimal math overflow");
95+
return false;
96+
}
97+
98+
if (common::addOverflow(multiplied, delta, result))
99+
{
100+
if constexpr (throw_on_error)
101+
throw Exception(ErrorCodes::DECIMAL_OVERFLOW, "Decimal math overflow");
102+
return false;
103+
}
104+
105+
return true;
106+
}
107+
108+
template <typename T>
109+
inline T multiplyAdd(const T & x, const T & multiplier, const T & delta)
110+
{
111+
T res;
112+
multiplyAdd<T, true>(x, multiplier, delta, res);
113+
return res;
114+
}
115+
85116
/** Make a decimal value from whole and fractional components with given scale multiplier.
86117
* where scale_multiplier = scaleMultiplier<T>(scale)
87118
* this is to reduce number of calls to scaleMultiplier when scale is known.
@@ -100,23 +131,10 @@ inline bool decimalFromComponentsWithMultiplierImpl(
100131
{
101132
using T = typename DecimalType::NativeType;
102133
const auto fractional_sign = whole < 0 ? -1 : 1;
103-
104-
T whole_scaled = 0;
105-
if (common::mulOverflow(whole, scale_multiplier, whole_scaled))
106-
{
107-
if constexpr (throw_on_error)
108-
throw Exception(ErrorCodes::DECIMAL_OVERFLOW, "Decimal math overflow");
109-
return false;
110-
}
111-
112134
T value;
113-
if (common::addOverflow(whole_scaled, fractional_sign * (fractional % scale_multiplier), value))
114-
{
115-
if constexpr (throw_on_error)
116-
throw Exception(ErrorCodes::DECIMAL_OVERFLOW, "Decimal math overflow");
135+
if (!multiplyAdd<T, throw_on_error>(
136+
whole, scale_multiplier, fractional_sign * (fractional % scale_multiplier), value))
117137
return false;
118-
}
119-
120138
result = DecimalType(value);
121139
return true;
122140
}

src/Functions/FunctionDateOrDateTimeAddInterval.h

Lines changed: 37 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22
#include <type_traits>
33
#include <Core/AccurateComparison.h>
4+
#include <Core/DecimalFunctions.h>
45
#include <Common/DateLUTImpl.h>
56

67
#include <DataTypes/DataTypeDate.h>
@@ -14,7 +15,6 @@
1415
#include <Functions/FunctionHelpers.h>
1516
#include <Functions/castTypeToEither.h>
1617
#include <Functions/extractTimeZoneFromFunctionArguments.h>
17-
#include <Functions/TransformDateTime64.h>
1818

1919
#include <IO/WriteHelpers.h>
2020

@@ -36,7 +36,9 @@ namespace ErrorCodes
3636
/// Corresponding types:
3737
/// - UInt16 => DataTypeDate
3838
/// - UInt32 => DataTypeDateTime
39+
/// - Int32 => DataTypeDate32
3940
/// - DateTime64 => DataTypeDateTime64
41+
/// - Int8 => error
4042
/// Please note that INPUT and OUTPUT types may differ, e.g.:
4143
/// - 'AddSecondsImpl::execute(UInt32, ...) -> UInt32' is available to the ClickHouse users as 'addSeconds(DateTime, ...) -> DateTime'
4244
/// - 'AddSecondsImpl::execute(UInt16, ...) -> UInt32' is available to the ClickHouse users as 'addSeconds(Date, ...) -> DateTime'
@@ -45,140 +47,98 @@ struct AddNanosecondsImpl
4547
{
4648
static constexpr auto name = "addNanoseconds";
4749

48-
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
49-
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl &, UInt16 scale = DataTypeDateTime64::default_scale)
50-
{
51-
Int64 multiplier = DecimalUtils::scaleMultiplier<DateTime64>(9 - scale);
52-
auto division = std::div(t.fractional * multiplier + delta, static_cast<Int64>(1000000000));
53-
return {t.whole * multiplier + division.quot, t.fractional * multiplier + delta};
54-
}
55-
5650
static inline NO_SANITIZE_UNDEFINED DateTime64
5751
execute(DateTime64 t, Int64 delta, const DateLUTImpl &, UInt16 scale = 0)
5852
{
5953
Int64 multiplier = DecimalUtils::scaleMultiplier<DateTime64>(9 - scale);
60-
return t * multiplier + delta;
54+
return DateTime64(DecimalUtils::multiplyAdd(t.value, multiplier, delta));
6155
}
6256

63-
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
57+
static inline NO_SANITIZE_UNDEFINED DateTime64 execute(UInt32 t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
6458
{
6559
Int64 multiplier = DecimalUtils::scaleMultiplier<DateTime64>(9);
66-
return static_cast<UInt32>(t * multiplier + delta);
60+
return DateTime64(DecimalUtils::multiplyAdd(static_cast<Int64>(t), multiplier, delta));
6761
}
6862

69-
static inline NO_SANITIZE_UNDEFINED DateTime64 execute(UInt16, Int64, const DateLUTImpl &, UInt16 = 0)
63+
static inline NO_SANITIZE_UNDEFINED Int8 execute(UInt16, Int64, const DateLUTImpl &, UInt16 = 0)
7064
{
71-
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "addNanoSeconds() cannot be used with Date");
65+
throw Exception(ErrorCodes::LOGICAL_ERROR, "addNanoseconds() cannot be used with Date");
7266
}
7367

74-
static inline NO_SANITIZE_UNDEFINED DateTime64 execute(Int32, Int64, const DateLUTImpl &, UInt16 = 0)
68+
static inline NO_SANITIZE_UNDEFINED Int8 execute(Int32, Int64, const DateLUTImpl &, UInt16 = 0)
7569
{
76-
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "addNanoSeconds() cannot be used with Date32");
70+
throw Exception(ErrorCodes::LOGICAL_ERROR, "addNanoseconds() cannot be used with Date32");
7771
}
7872
};
7973

8074
struct AddMicrosecondsImpl
8175
{
8276
static constexpr auto name = "addMicroseconds";
8377

84-
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
85-
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl &, UInt16 scale = 0)
86-
{
87-
Int64 multiplier = DecimalUtils::scaleMultiplier<DateTime64>(std::abs(6 - scale));
88-
if (scale <= 6)
89-
{
90-
auto division = std::div((t.fractional + delta), static_cast<Int64>(10e6));
91-
return {t.whole * multiplier + division.quot, division.rem};
92-
}
93-
else
94-
{
95-
auto division = std::div((t.fractional + delta * multiplier), static_cast<Int64>(10e6 * multiplier));
96-
return {t.whole + division.quot, division.rem};
97-
}
98-
}
99-
10078
static inline NO_SANITIZE_UNDEFINED DateTime64
10179
execute(DateTime64 t, Int64 delta, const DateLUTImpl &, UInt16 scale = 0)
10280
{
10381
Int64 multiplier = DecimalUtils::scaleMultiplier<DateTime64>(std::abs(6 - scale));
104-
return scale <= 6 ? t * multiplier + delta : t + delta * multiplier;
82+
return DateTime64(scale <= 6
83+
? DecimalUtils::multiplyAdd(t.value, multiplier, delta)
84+
: DecimalUtils::multiplyAdd(delta, multiplier, t.value));
10585
}
10686

107-
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
87+
static inline NO_SANITIZE_UNDEFINED DateTime64 execute(UInt32 t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
10888
{
10989
Int64 multiplier = DecimalUtils::scaleMultiplier<DateTime64>(6);
110-
return static_cast<UInt32>(t * multiplier + delta);
90+
return DateTime64(DecimalUtils::multiplyAdd(static_cast<Int64>(t), multiplier, delta));
11191
}
11292

113-
static inline NO_SANITIZE_UNDEFINED DateTime64 execute(UInt16, Int64, const DateLUTImpl &, UInt16 = 0)
93+
static inline NO_SANITIZE_UNDEFINED Int8 execute(UInt16, Int64, const DateLUTImpl &, UInt16 = 0)
11494
{
115-
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "addMicroSeconds() cannot be used with Date");
95+
throw Exception(ErrorCodes::LOGICAL_ERROR, "addMicroseconds() cannot be used with Date");
11696
}
11797

118-
static inline NO_SANITIZE_UNDEFINED DateTime64 execute(Int32, Int64, const DateLUTImpl &, UInt16 = 0)
98+
static inline NO_SANITIZE_UNDEFINED Int8 execute(Int32, Int64, const DateLUTImpl &, UInt16 = 0)
11999
{
120-
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "addMicroSeconds() cannot be used with Date32");
100+
throw Exception(ErrorCodes::LOGICAL_ERROR, "addMicroseconds() cannot be used with Date32");
121101
}
122102
};
123103

124104
struct AddMillisecondsImpl
125105
{
126106
static constexpr auto name = "addMilliseconds";
127107

128-
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
129-
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl &, UInt16 scale = DataTypeDateTime64::default_scale)
130-
{
131-
Int64 multiplier = DecimalUtils::scaleMultiplier<DateTime64>(std::abs(3 - scale));
132-
if (scale <= 3)
133-
{
134-
auto division = std::div((t.fractional + delta), static_cast<Int64>(1000));
135-
return {t.whole * multiplier + division.quot, division.rem};
136-
}
137-
else
138-
{
139-
auto division = std::div((t.fractional + delta * multiplier), static_cast<Int64>(1000 * multiplier));
140-
return {t.whole + division.quot,division.rem};
141-
}
142-
}
143-
144108
static inline NO_SANITIZE_UNDEFINED DateTime64
145109
execute(DateTime64 t, Int64 delta, const DateLUTImpl &, UInt16 scale = 0)
146110
{
147111
Int64 multiplier = DecimalUtils::scaleMultiplier<DateTime64>(std::abs(3 - scale));
148-
return scale <= 3 ? t * multiplier + delta : t + delta * multiplier;
112+
return DateTime64(scale <= 3
113+
? DecimalUtils::multiplyAdd(t.value, multiplier, delta)
114+
: DecimalUtils::multiplyAdd(delta, multiplier, t.value));
149115
}
150116

151-
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
117+
static inline NO_SANITIZE_UNDEFINED DateTime64 execute(UInt32 t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
152118
{
153119
Int64 multiplier = DecimalUtils::scaleMultiplier<DateTime64>(3);
154-
return static_cast<UInt32>(t * multiplier + delta);
120+
return DateTime64(DecimalUtils::multiplyAdd(static_cast<Int64>(t), multiplier, delta));
155121
}
156122

157-
static inline NO_SANITIZE_UNDEFINED DateTime64 execute(UInt16, Int64, const DateLUTImpl &, UInt16 = 0)
123+
static inline NO_SANITIZE_UNDEFINED Int8 execute(UInt16, Int64, const DateLUTImpl &, UInt16 = 0)
158124
{
159-
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "addMilliSeconds() cannot be used with Date");
125+
throw Exception(ErrorCodes::LOGICAL_ERROR, "addMilliseconds() cannot be used with Date");
160126
}
161127

162-
static inline NO_SANITIZE_UNDEFINED DateTime64 execute(Int32, Int64, const DateLUTImpl &, UInt16 = 0)
128+
static inline NO_SANITIZE_UNDEFINED Int8 execute(Int32, Int64, const DateLUTImpl &, UInt16 = 0)
163129
{
164-
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "addMilliSeconds() cannot be used with Date32");
130+
throw Exception(ErrorCodes::LOGICAL_ERROR, "addMilliseconds() cannot be used with Date32");
165131
}
166132
};
167133

168134
struct AddSecondsImpl
169135
{
170136
static constexpr auto name = "addSeconds";
171137

172-
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
173-
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
174-
{
175-
return {t.whole + delta, t.fractional};
176-
}
177-
178138
static inline NO_SANITIZE_UNDEFINED DateTime64
179139
execute(DateTime64 t, Int64 delta, const DateLUTImpl &, UInt16 scale = 0)
180140
{
181-
return t + delta * DecimalUtils::scaleMultiplier<DateTime64>(scale);
141+
return DateTime64(DecimalUtils::multiplyAdd(delta, DecimalUtils::scaleMultiplier<DateTime64>(scale), t.value));
182142
}
183143

184144
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
@@ -189,6 +149,7 @@ struct AddSecondsImpl
189149
static inline NO_SANITIZE_UNDEFINED Int64 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
190150
{
191151
// use default datetime64 scale
152+
static_assert(DataTypeDateTime64::default_scale == 3, "");
192153
return (time_zone.fromDayNum(ExtendedDayNum(d)) + delta) * 1000;
193154
}
194155

@@ -202,12 +163,6 @@ struct AddMinutesImpl
202163
{
203164
static constexpr auto name = "addMinutes";
204165

205-
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
206-
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
207-
{
208-
return {t.whole + delta * 60, t.fractional};
209-
}
210-
211166
static inline NO_SANITIZE_UNDEFINED DateTime64
212167
execute(DateTime64 t, Int64 delta, const DateLUTImpl &, UInt16 scale = 0)
213168
{
@@ -222,6 +177,7 @@ struct AddMinutesImpl
222177
static inline NO_SANITIZE_UNDEFINED Int64 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
223178
{
224179
// use default datetime64 scale
180+
static_assert(DataTypeDateTime64::default_scale == 3, "");
225181
return (time_zone.fromDayNum(ExtendedDayNum(d)) + delta * 60) * 1000;
226182
}
227183

@@ -235,12 +191,6 @@ struct AddHoursImpl
235191
{
236192
static constexpr auto name = "addHours";
237193

238-
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
239-
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
240-
{
241-
return {t.whole + delta * 3600, t.fractional};
242-
}
243-
244194
static inline NO_SANITIZE_UNDEFINED DateTime64
245195
execute(DateTime64 t, Int64 delta, const DateLUTImpl &, UInt16 scale = 0)
246196
{
@@ -255,6 +205,7 @@ struct AddHoursImpl
255205
static inline NO_SANITIZE_UNDEFINED Int64 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
256206
{
257207
// use default datetime64 scale
208+
static_assert(DataTypeDateTime64::default_scale == 3, "");
258209
return (time_zone.fromDayNum(ExtendedDayNum(d)) + delta * 3600) * 1000;
259210
}
260211

@@ -268,12 +219,6 @@ struct AddDaysImpl
268219
{
269220
static constexpr auto name = "addDays";
270221

271-
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
272-
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
273-
{
274-
return {time_zone.addDays(t.whole, delta), t.fractional};
275-
}
276-
277222
static inline NO_SANITIZE_UNDEFINED DateTime64
278223
execute(DateTime64 t, Int64 delta, const DateLUTImpl & time_zone, UInt16 scale = 0)
279224
{
@@ -302,12 +247,6 @@ struct AddWeeksImpl
302247
{
303248
static constexpr auto name = "addWeeks";
304249

305-
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
306-
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
307-
{
308-
return {time_zone.addWeeks(t.whole, delta), t.fractional};
309-
}
310-
311250
static inline NO_SANITIZE_UNDEFINED DateTime64
312251
execute(DateTime64 t, Int64 delta, const DateLUTImpl & time_zone, UInt16 scale = 0)
313252
{
@@ -336,12 +275,6 @@ struct AddMonthsImpl
336275
{
337276
static constexpr auto name = "addMonths";
338277

339-
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
340-
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
341-
{
342-
return {time_zone.addMonths(t.whole, delta), t.fractional};
343-
}
344-
345278
static inline NO_SANITIZE_UNDEFINED DateTime64
346279
execute(DateTime64 t, Int64 delta, const DateLUTImpl & time_zone, UInt16 scale = 0)
347280
{
@@ -370,12 +303,6 @@ struct AddQuartersImpl
370303
{
371304
static constexpr auto name = "addQuarters";
372305

373-
static inline DecimalUtils::DecimalComponents<DateTime64>
374-
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
375-
{
376-
return {time_zone.addQuarters(t.whole, delta), t.fractional};
377-
}
378-
379306
static inline NO_SANITIZE_UNDEFINED DateTime64
380307
execute(DateTime64 t, Int64 delta, const DateLUTImpl & time_zone, UInt16 scale = 0)
381308
{
@@ -404,12 +331,6 @@ struct AddYearsImpl
404331
{
405332
static constexpr auto name = "addYears";
406333

407-
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
408-
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
409-
{
410-
return {time_zone.addYears(t.whole, delta), t.fractional};
411-
}
412-
413334
static inline NO_SANITIZE_UNDEFINED DateTime64
414335
execute(DateTime64 t, Int64 delta, const DateLUTImpl & time_zone, UInt16 scale = 0)
415336
{
@@ -581,11 +502,11 @@ namespace date_and_time_type_details
581502
// Compile-time mapping of value (DataType::FieldType) types to corresponding DataType
582503
template <typename FieldType> struct ResultDataTypeMap {};
583504
template <> struct ResultDataTypeMap<UInt16> { using ResultDataType = DataTypeDate; };
584-
template <> struct ResultDataTypeMap<Int16> { using ResultDataType = DataTypeDate; };
585505
template <> struct ResultDataTypeMap<UInt32> { using ResultDataType = DataTypeDateTime; };
586506
template <> struct ResultDataTypeMap<Int32> { using ResultDataType = DataTypeDate32; };
587507
template <> struct ResultDataTypeMap<DateTime64> { using ResultDataType = DataTypeDateTime64; };
588508
template <> struct ResultDataTypeMap<Int64> { using ResultDataType = DataTypeDateTime64; };
509+
template <> struct ResultDataTypeMap<Int8> { using ResultDataType = DataTypeInt8; }; // error
589510
}
590511

591512
template <typename Transform>
@@ -705,6 +626,10 @@ class FunctionDateOrDateTimeAddInterval : public IFunction
705626

706627
return std::make_shared<DataTypeDateTime64>(target_scale.value_or(DataTypeDateTime64::default_scale), std::move(timezone));
707628
}
629+
else if constexpr (std::is_same_v<ResultDataType, DataTypeInt8>)
630+
{
631+
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "{} cannot be used with {}", getName(), arguments[0].type->getName());
632+
}
708633

709634
throw Exception(ErrorCodes::LOGICAL_ERROR, "Unexpected result type in datetime add interval function");
710635
}

0 commit comments

Comments
 (0)