1#include <DataTypes/DataTypeDateTime.h>
2#include <DataTypes/DataTypeDateTime64.h>
3#include <DataTypes/DataTypesNumber.h>
4#include <Columns/ColumnString.h>
5#include <Columns/ColumnsNumber.h>
6
7#include <Functions/IFunctionImpl.h>
8#include <Functions/FunctionHelpers.h>
9#include <Functions/FunctionFactory.h>
10#include <Functions/extractTimeZoneFromFunctionArguments.h>
11#include <Functions/DateTimeTransforms.h>
12
13#include <IO/WriteHelpers.h>
14
15#include <common/find_symbols.h>
16
17#include <type_traits>
18
19
20namespace DB
21{
22
23namespace ErrorCodes
24{
25 extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
26 extern const int ILLEGAL_TYPE_OF_ARGUMENT;
27 extern const int ILLEGAL_COLUMN;
28 extern const int BAD_ARGUMENTS;
29}
30
31/** dateDiff('unit', t1, t2, [timezone])
32 * t1 and t2 can be Date or DateTime
33 *
34 * If timezone is specified, it applied to both arguments.
35 * If not, timezones from datatypes t1 and t2 are used.
36 * If that timezones are not the same, the result is unspecified.
37 *
38 * Timezone matters because days can have different length.
39 */
40class FunctionDateDiff : public IFunction
41{
42public:
43 static constexpr auto name = "dateDiff";
44 static FunctionPtr create(const Context &) { return std::make_shared<FunctionDateDiff>(); }
45
46 String getName() const override
47 {
48 return name;
49 }
50
51 bool isVariadic() const override { return true; }
52 size_t getNumberOfArguments() const override { return 0; }
53
54 DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override
55 {
56 if (arguments.size() != 3 && arguments.size() != 4)
57 throw Exception("Number of arguments for function " + getName() + " doesn't match: passed "
58 + toString(arguments.size()) + ", should be 3 or 4",
59 ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
60
61 if (!isString(arguments[0]))
62 throw Exception("First argument for function " + getName() + " (unit) must be String",
63 ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
64
65 if (!isDateOrDateTime(arguments[1]))
66 throw Exception("Second argument for function " + getName() + " must be Date or DateTime",
67 ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
68
69 if (!isDateOrDateTime(arguments[2]))
70 throw Exception("Third argument for function " + getName() + " must be Date or DateTime",
71 ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
72
73 if (arguments.size() == 4 && !isString(arguments[3]))
74 throw Exception("Fourth argument for function " + getName() + " (timezone) must be String",
75 ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
76
77 return std::make_shared<DataTypeInt64>();
78 }
79
80 bool useDefaultImplementationForConstants() const override { return true; }
81 ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {0, 3}; }
82
83 void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) override
84 {
85 auto * unit_column = checkAndGetColumnConst<ColumnString>(block.getByPosition(arguments[0]).column.get());
86 if (!unit_column)
87 throw Exception("First argument for function " + getName() + " must be constant String", ErrorCodes::ILLEGAL_COLUMN);
88
89 String unit = Poco::toLower(unit_column->getValue<String>());
90
91 const IColumn & x = *block.getByPosition(arguments[1]).column;
92 const IColumn & y = *block.getByPosition(arguments[2]).column;
93
94 size_t rows = input_rows_count;
95 auto res = ColumnInt64::create(rows);
96
97 const DateLUTImpl & timezone_x = extractTimeZoneFromFunctionArguments(block, arguments, 3, 1);
98 const DateLUTImpl & timezone_y = extractTimeZoneFromFunctionArguments(block, arguments, 3, 2);
99
100 if (unit == "year" || unit == "yy" || unit == "yyyy")
101 dispatchForColumns<ToRelativeYearNumImpl>(x, y, timezone_x, timezone_y, res->getData());
102 else if (unit == "quarter" || unit == "qq" || unit == "q")
103 dispatchForColumns<ToRelativeQuarterNumImpl>(x, y, timezone_x, timezone_y, res->getData());
104 else if (unit == "month" || unit == "mm" || unit == "m")
105 dispatchForColumns<ToRelativeMonthNumImpl>(x, y, timezone_x, timezone_y, res->getData());
106 else if (unit == "week" || unit == "wk" || unit == "ww")
107 dispatchForColumns<ToRelativeWeekNumImpl>(x, y, timezone_x, timezone_y, res->getData());
108 else if (unit == "day" || unit == "dd" || unit == "d")
109 dispatchForColumns<ToRelativeDayNumImpl>(x, y, timezone_x, timezone_y, res->getData());
110 else if (unit == "hour" || unit == "hh")
111 dispatchForColumns<ToRelativeHourNumImpl>(x, y, timezone_x, timezone_y, res->getData());
112 else if (unit == "minute" || unit == "mi" || unit == "n")
113 dispatchForColumns<ToRelativeMinuteNumImpl>(x, y, timezone_x, timezone_y, res->getData());
114 else if (unit == "second" || unit == "ss" || unit == "s")
115 dispatchForColumns<ToRelativeSecondNumImpl>(x, y, timezone_x, timezone_y, res->getData());
116 else
117 throw Exception("Function " + getName() + " does not support '" + unit + "' unit", ErrorCodes::BAD_ARGUMENTS);
118
119 block.getByPosition(result).column = std::move(res);
120 }
121
122private:
123 template <typename Transform>
124 void dispatchForColumns(
125 const IColumn & x, const IColumn & y,
126 const DateLUTImpl & timezone_x, const DateLUTImpl & timezone_y,
127 ColumnInt64::Container & result)
128 {
129 if (auto * x_vec_16 = checkAndGetColumn<ColumnUInt16>(&x))
130 dispatchForSecondColumn<Transform>(*x_vec_16, y, timezone_x, timezone_y, result);
131 else if (auto * x_vec_32 = checkAndGetColumn<ColumnUInt32>(&x))
132 dispatchForSecondColumn<Transform>(*x_vec_32, y, timezone_x, timezone_y, result);
133 else if (auto * x_const_16 = checkAndGetColumnConst<ColumnUInt16>(&x))
134 dispatchConstForSecondColumn<Transform>(x_const_16->getValue<UInt16>(), y, timezone_x, timezone_y, result);
135 else if (auto * x_const_32 = checkAndGetColumnConst<ColumnUInt32>(&x))
136 dispatchConstForSecondColumn<Transform>(x_const_32->getValue<UInt32>(), y, timezone_x, timezone_y, result);
137 else
138 throw Exception("Illegal column for first argument of function " + getName() + ", must be Date or DateTime", ErrorCodes::ILLEGAL_COLUMN);
139 }
140
141 template <typename Transform, typename T1>
142 void dispatchForSecondColumn(
143 const ColumnVector<T1> & x, const IColumn & y,
144 const DateLUTImpl & timezone_x, const DateLUTImpl & timezone_y,
145 ColumnInt64::Container & result)
146 {
147 if (auto * y_vec_16 = checkAndGetColumn<ColumnUInt16>(&y))
148 vector_vector<Transform>(x, *y_vec_16, timezone_x, timezone_y, result);
149 else if (auto * y_vec_32 = checkAndGetColumn<ColumnUInt32>(&y))
150 vector_vector<Transform>(x, *y_vec_32, timezone_x, timezone_y, result);
151 else if (auto * y_const_16 = checkAndGetColumnConst<ColumnUInt16>(&y))
152 vector_constant<Transform>(x, y_const_16->getValue<UInt16>(), timezone_x, timezone_y, result);
153 else if (auto * y_const_32 = checkAndGetColumnConst<ColumnUInt32>(&y))
154 vector_constant<Transform>(x, y_const_32->getValue<UInt32>(), timezone_x, timezone_y, result);
155 else
156 throw Exception("Illegal column for second argument of function " + getName() + ", must be Date or DateTime", ErrorCodes::ILLEGAL_COLUMN);
157 }
158
159 template <typename Transform, typename T1>
160 void dispatchConstForSecondColumn(
161 T1 x, const IColumn & y,
162 const DateLUTImpl & timezone_x, const DateLUTImpl & timezone_y,
163 ColumnInt64::Container & result)
164 {
165 if (auto * y_vec_16 = checkAndGetColumn<ColumnUInt16>(&y))
166 constant_vector<Transform>(x, *y_vec_16, timezone_x, timezone_y, result);
167 else if (auto * y_vec_32 = checkAndGetColumn<ColumnUInt32>(&y))
168 constant_vector<Transform>(x, *y_vec_32, timezone_x, timezone_y, result);
169 else
170 throw Exception("Illegal column for second argument of function " + getName() + ", must be Date or DateTime", ErrorCodes::ILLEGAL_COLUMN);
171 }
172
173 template <typename Transform, typename T1, typename T2>
174 void vector_vector(
175 const ColumnVector<T1> & x, const ColumnVector<T2> & y,
176 const DateLUTImpl & timezone_x, const DateLUTImpl & timezone_y,
177 ColumnInt64::Container & result)
178 {
179 const auto & x_data = x.getData();
180 const auto & y_data = y.getData();
181 for (size_t i = 0, size = x.size(); i < size; ++i)
182 result[i] = calculate<Transform>(x_data[i], y_data[i], timezone_x, timezone_y);
183 }
184
185 template <typename Transform, typename T1, typename T2>
186 void vector_constant(
187 const ColumnVector<T1> & x, T2 y,
188 const DateLUTImpl & timezone_x, const DateLUTImpl & timezone_y,
189 ColumnInt64::Container & result)
190 {
191 const auto & x_data = x.getData();
192 for (size_t i = 0, size = x.size(); i < size; ++i)
193 result[i] = calculate<Transform>(x_data[i], y, timezone_x, timezone_y);
194 }
195
196 template <typename Transform, typename T1, typename T2>
197 void constant_vector(
198 T1 x, const ColumnVector<T2> & y,
199 const DateLUTImpl & timezone_x, const DateLUTImpl & timezone_y,
200 ColumnInt64::Container & result)
201 {
202 const auto & y_data = y.getData();
203 for (size_t i = 0, size = y.size(); i < size; ++i)
204 result[i] = calculate<Transform>(x, y_data[i], timezone_x, timezone_y);
205 }
206
207 template <typename Transform, typename T1, typename T2>
208 Int64 calculate(T1 x, T2 y, const DateLUTImpl & timezone_x, const DateLUTImpl & timezone_y)
209 {
210 return Int64(Transform::execute(y, timezone_y))
211 - Int64(Transform::execute(x, timezone_x));
212 }
213};
214
215void registerFunctionDateDiff(FunctionFactory & factory)
216{
217 factory.registerFunction<FunctionDateDiff>(FunctionFactory::CaseInsensitive);
218}
219
220}
221
222