1#include <DataTypes/DataTypeArray.h>
2#include <DataTypes/DataTypeDateTime.h>
3#include <DataTypes/DataTypeDateTime64.h>
4#include <Columns/ColumnArray.h>
5#include <Columns/ColumnsNumber.h>
6
7#include <Functions/IFunctionImpl.h>
8#include <Functions/FunctionFactory.h>
9#include <Functions/FunctionHelpers.h>
10#include <Functions/extractTimeZoneFromFunctionArguments.h>
11
12#include <IO/WriteHelpers.h>
13
14namespace DB
15{
16
17namespace ErrorCodes
18{
19 extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
20 extern const int ILLEGAL_TYPE_OF_ARGUMENT;
21 extern const int ILLEGAL_COLUMN;
22}
23
24/** timeSlots(StartTime, Duration)
25 * - for the time interval beginning at `StartTime` and continuing `Duration` seconds,
26 * returns an array of time points, consisting of rounding down to half an hour (default; or another value) of points from this interval.
27 * For example, timeSlots(toDateTime('2012-01-01 12:20:00'), 600) = [toDateTime('2012-01-01 12:00:00'), toDateTime('2012-01-01 12:30:00')].
28 * This is necessary to search for hits that are part of the corresponding visit.
29 *
30 * This is obsolete function. It was developed for Yandex.Metrica, but no longer used in Yandex.
31 * But this function was adopted by wider audience.
32 */
33
34template <typename DurationType>
35struct TimeSlotsImpl
36{
37 static void vector_vector(
38 const PaddedPODArray<UInt32> & starts, const PaddedPODArray<DurationType> & durations, UInt32 time_slot_size,
39 PaddedPODArray<UInt32> & result_values, ColumnArray::Offsets & result_offsets)
40 {
41 size_t size = starts.size();
42
43 result_offsets.resize(size);
44 result_values.reserve(size);
45
46 ColumnArray::Offset current_offset = 0;
47 for (size_t i = 0; i < size; ++i)
48 {
49 for (UInt32 value = starts[i] / time_slot_size, end = (starts[i] + durations[i]) / time_slot_size; value <= end; ++value)
50 {
51 result_values.push_back(value * time_slot_size);
52 ++current_offset;
53 }
54
55 result_offsets[i] = current_offset;
56 }
57 }
58
59 static void vector_constant(
60 const PaddedPODArray<UInt32> & starts, DurationType duration, UInt32 time_slot_size,
61 PaddedPODArray<UInt32> & result_values, ColumnArray::Offsets & result_offsets)
62 {
63 size_t size = starts.size();
64
65 result_offsets.resize(size);
66 result_values.reserve(size);
67
68 ColumnArray::Offset current_offset = 0;
69 for (size_t i = 0; i < size; ++i)
70 {
71 for (UInt32 value = starts[i] / time_slot_size, end = (starts[i] + duration) / time_slot_size; value <= end; ++value)
72 {
73 result_values.push_back(value * time_slot_size);
74 ++current_offset;
75 }
76
77 result_offsets[i] = current_offset;
78 }
79 }
80
81 static void constant_vector(
82 UInt32 start, const PaddedPODArray<DurationType> & durations, UInt32 time_slot_size,
83 PaddedPODArray<UInt32> & result_values, ColumnArray::Offsets & result_offsets)
84 {
85 size_t size = durations.size();
86
87 result_offsets.resize(size);
88 result_values.reserve(size);
89
90 ColumnArray::Offset current_offset = 0;
91 for (size_t i = 0; i < size; ++i)
92 {
93 for (UInt32 value = start / time_slot_size, end = (start + durations[i]) / time_slot_size; value <= end; ++value)
94 {
95 result_values.push_back(value * time_slot_size);
96 ++current_offset;
97 }
98
99 result_offsets[i] = current_offset;
100 }
101 }
102};
103
104
105class FunctionTimeSlots : public IFunction
106{
107public:
108 static constexpr auto name = "timeSlots";
109 static constexpr UInt32 TIME_SLOT_SIZE = 1800;
110 static FunctionPtr create(const Context &) { return std::make_shared<FunctionTimeSlots>(); }
111
112 String getName() const override
113 {
114 return name;
115 }
116
117 bool isVariadic() const override { return true; }
118 size_t getNumberOfArguments() const override { return 0; }
119 bool useDefaultImplementationForConstants() const override { return true; }
120 ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {2}; }
121
122 DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override
123 {
124 if (arguments.size() != 2 && arguments.size() != 3)
125 throw Exception("Number of arguments for function " + getName() + " doesn't match: passed "
126 + toString(arguments.size()) + ", should be 2 or 3",
127 ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
128
129 if (!WhichDataType(arguments[0].type).isDateTime())
130 throw Exception("Illegal type " + arguments[0].type->getName() + " of first argument of function " + getName() + ". Must be DateTime.",
131 ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
132
133 if (!WhichDataType(arguments[1].type).isUInt32())
134 throw Exception("Illegal type " + arguments[1].type->getName() + " of second argument of function " + getName() + ". Must be UInt32.",
135 ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
136
137 if (arguments.size() == 3 && !WhichDataType(arguments[2].type).isNativeUInt())
138 throw Exception("Illegal type " + arguments[2].type->getName() + " of third argument of function " + getName() + ". Must be UInt32.",
139 ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
140
141 /// If time zone is specified for source data type, attach it to the resulting type.
142 /// Note that there is no explicit time zone argument for this function (we specify 2 as an argument number with explicit time zone).
143 return std::make_shared<DataTypeArray>(std::make_shared<DataTypeDateTime>(extractTimeZoneNameFromFunctionArguments(arguments, 3, 0)));
144 }
145
146 void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t) override
147 {
148 auto starts = checkAndGetColumn<ColumnUInt32>(block.getByPosition(arguments[0]).column.get());
149 auto const_starts = checkAndGetColumnConst<ColumnUInt32>(block.getByPosition(arguments[0]).column.get());
150
151 auto durations = checkAndGetColumn<ColumnUInt32>(block.getByPosition(arguments[1]).column.get());
152 auto const_durations = checkAndGetColumnConst<ColumnUInt32>(block.getByPosition(arguments[1]).column.get());
153
154 auto res = ColumnArray::create(ColumnUInt32::create());
155 ColumnUInt32::Container & res_values = typeid_cast<ColumnUInt32 &>(res->getData()).getData();
156
157 auto time_slot_size = TIME_SLOT_SIZE;
158
159 if (arguments.size() == 3)
160 {
161 auto time_slot_column = checkAndGetColumn<ColumnConst>(block.getByPosition(arguments[2]).column.get());
162 if (!time_slot_column)
163 throw Exception("Third argument for function " + getName() + " must be constant UInt32", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
164
165 if (time_slot_size = time_slot_column->getValue<UInt32>(); time_slot_size == 0)
166 throw Exception("Third argument for function " + getName() + " must be greater than zero", ErrorCodes::ILLEGAL_COLUMN);
167 }
168
169 if (starts && durations)
170 {
171 TimeSlotsImpl<UInt32>::vector_vector(starts->getData(), durations->getData(), time_slot_size, res_values, res->getOffsets());
172 block.getByPosition(result).column = std::move(res);
173 }
174 else if (starts && const_durations)
175 {
176 TimeSlotsImpl<UInt32>::vector_constant(starts->getData(), const_durations->getValue<UInt32>(), time_slot_size, res_values, res->getOffsets());
177 block.getByPosition(result).column = std::move(res);
178 }
179 else if (const_starts && durations)
180 {
181 TimeSlotsImpl<UInt32>::constant_vector(const_starts->getValue<UInt32>(), durations->getData(), time_slot_size, res_values, res->getOffsets());
182 block.getByPosition(result).column = std::move(res);
183 }
184 else
185 throw Exception("Illegal columns " + block.getByPosition(arguments[0]).column->getName()
186 + ", " + block.getByPosition(arguments[1]).column->getName()
187 + ", " + block.getByPosition(arguments[2]).column->getName()
188 + " of arguments of function " + getName(),
189 ErrorCodes::ILLEGAL_COLUMN);
190 }
191};
192
193void registerFunctionTimeSlots(FunctionFactory & factory)
194{
195 factory.registerFunction<FunctionTimeSlots>();
196}
197
198}
199