be/src/exprs/aggregate/aggregate_function_foreach.h
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | // This file is copied from |
18 | | // https://github.com/ClickHouse/ClickHouse/blob/master/src/AggregateFunctions/Combinators/AggregateFunctionForEach.h |
19 | | // and modified by Doris |
20 | | |
21 | | #pragma once |
22 | | |
23 | | #include "common/status.h" |
24 | | #include "core/assert_cast.h" |
25 | | #include "core/column/column_nullable.h" |
26 | | #include "core/data_type/data_type_array.h" |
27 | | #include "core/data_type/data_type_nullable.h" |
28 | | #include "exec/common/arithmetic_overflow.h" |
29 | | #include "exprs/aggregate/aggregate_function.h" |
30 | | #include "exprs/function/array/function_array_utils.h" |
31 | | |
32 | | namespace doris { |
33 | | |
34 | | struct AggregateFunctionForEachData { |
35 | | size_t dynamic_array_size = 0; |
36 | | char* array_of_aggregate_datas = nullptr; |
37 | | }; |
38 | | |
39 | | /** Adaptor for aggregate functions. |
40 | | * Adding -ForEach suffix to aggregate function |
41 | | * will convert that aggregate function to a function, accepting arrays, |
42 | | * and applies aggregation for each corresponding elements of arrays independently, |
43 | | * returning arrays of aggregated values on corresponding positions. |
44 | | * |
45 | | * Example: sumForEach of: |
46 | | * [1, 2], |
47 | | * [3, 4, 5], |
48 | | * [6, 7] |
49 | | * will return: |
50 | | * [10, 13, 5] |
51 | | * |
52 | | * TODO Allow variable number of arguments. |
53 | | */ |
54 | | class AggregateFunctionForEach : public AggregateFunctionNonFinalBase, |
55 | | public IAggregateFunctionDataHelper<AggregateFunctionForEachData, |
56 | | AggregateFunctionForEach>, |
57 | | VarargsExpression, |
58 | | NullableAggregateFunction { |
59 | | protected: |
60 | | using Base = |
61 | | IAggregateFunctionDataHelper<AggregateFunctionForEachData, AggregateFunctionForEach>; |
62 | | |
63 | | AggregateFunctionPtr nested_function; |
64 | | const size_t nested_size_of_data; |
65 | | const size_t num_arguments; |
66 | | |
67 | | AggregateFunctionForEachData& ensure_aggregate_data(AggregateDataPtr __restrict place, |
68 | 37 | size_t new_size, Arena& arena) const { |
69 | 37 | AggregateFunctionForEachData& state = data(place); |
70 | | |
71 | | /// Ensure we have aggregate states for new_size elements, allocate |
72 | | /// from arena if needed. When reallocating, we can't copy the |
73 | | /// states to new buffer with memcpy, because they may contain pointers |
74 | | /// to themselves. In particular, this happens when a state contains |
75 | | /// a PODArrayWithStackMemory, which stores small number of elements |
76 | | /// inline. This is why we create new empty states in the new buffer, |
77 | | /// and merge the old states to them. |
78 | 37 | size_t old_size = state.dynamic_array_size; |
79 | 37 | if (old_size < new_size) { |
80 | 37 | static constexpr size_t MAX_ARRAY_SIZE = 100 * 1000000000ULL; |
81 | 37 | if (new_size > MAX_ARRAY_SIZE) { |
82 | 0 | throw Exception(ErrorCode::INTERNAL_ERROR, |
83 | 0 | "Suspiciously large array size ({}) in -ForEach aggregate function", |
84 | 0 | new_size); |
85 | 0 | } |
86 | | |
87 | 37 | size_t allocation_size = 0; |
88 | 37 | if (common::mul_overflow(new_size, nested_size_of_data, allocation_size)) { |
89 | 0 | throw Exception(ErrorCode::INTERNAL_ERROR, |
90 | 0 | "Allocation size ({} * {}) overflows in -ForEach aggregate " |
91 | 0 | "function, but it should've been prevented by previous checks", |
92 | 0 | new_size, nested_size_of_data); |
93 | 0 | } |
94 | | |
95 | 37 | char* old_state = state.array_of_aggregate_datas; |
96 | | |
97 | 37 | char* new_state = |
98 | 37 | arena.aligned_alloc(allocation_size, nested_function->align_of_data()); |
99 | | |
100 | 37 | size_t num_created = 0; |
101 | 37 | try { |
102 | 173 | for (; num_created < new_size; ++num_created) { |
103 | 136 | nested_function->create(&new_state[num_created * nested_size_of_data]); |
104 | 136 | } |
105 | | |
106 | 40 | for (size_t i = 0; i < old_size; ++i) { |
107 | 3 | nested_function->merge(&new_state[i * nested_size_of_data], |
108 | 3 | &old_state[i * nested_size_of_data], arena); |
109 | 3 | } |
110 | 37 | } catch (...) { |
111 | 4 | for (size_t i = 0; i < num_created; ++i) { |
112 | 3 | nested_function->destroy(&new_state[i * nested_size_of_data]); |
113 | 3 | } |
114 | | |
115 | 1 | throw; |
116 | 1 | } |
117 | | |
118 | 37 | for (size_t i = 0; i < old_size; ++i) { |
119 | 1 | nested_function->destroy(&old_state[i * nested_size_of_data]); |
120 | 1 | } |
121 | | |
122 | 36 | state.array_of_aggregate_datas = new_state; |
123 | 36 | state.dynamic_array_size = new_size; |
124 | 36 | } |
125 | | |
126 | 36 | return state; |
127 | 37 | } |
128 | | |
129 | | public: |
130 | | constexpr static auto AGG_FOREACH_SUFFIX = "_foreach"; |
131 | | AggregateFunctionForEach(AggregateFunctionPtr nested_function_, const DataTypes& arguments) |
132 | 5 | : Base(arguments), |
133 | 5 | nested_function {std::move(nested_function_)}, |
134 | 5 | nested_size_of_data(nested_function->size_of_data()), |
135 | 5 | num_arguments(arguments.size()) { |
136 | 5 | if (arguments.empty()) { |
137 | 0 | throw Exception(ErrorCode::INTERNAL_ERROR, |
138 | 0 | "Aggregate function {} require at least one argument", get_name()); |
139 | 0 | } |
140 | 5 | } |
141 | 2 | void set_version(const int version_) override { |
142 | 2 | Base::set_version(version_); |
143 | 2 | nested_function->set_version(version_); |
144 | 2 | } |
145 | | |
146 | 2 | String get_name() const override { return nested_function->get_name() + AGG_FOREACH_SUFFIX; } |
147 | | |
148 | 4 | DataTypePtr get_return_type() const override { |
149 | 4 | return std::make_shared<DataTypeArray>(nested_function->get_return_type()); |
150 | 4 | } |
151 | | |
152 | 35 | void destroy(AggregateDataPtr __restrict place) const noexcept override { |
153 | 35 | AggregateFunctionForEachData& state = data(place); |
154 | | |
155 | 35 | char* nested_state = state.array_of_aggregate_datas; |
156 | 167 | for (size_t i = 0; i < state.dynamic_array_size; ++i) { |
157 | 132 | nested_function->destroy(nested_state); |
158 | 132 | nested_state += nested_size_of_data; |
159 | 132 | } |
160 | 35 | } |
161 | | |
162 | 4 | bool is_trivial() const override { |
163 | 4 | return std::is_trivial_v<Data> && nested_function->is_trivial(); |
164 | 4 | } |
165 | | |
166 | | void merge(AggregateDataPtr __restrict place, ConstAggregateDataPtr rhs, |
167 | 9 | Arena& arena) const override { |
168 | 9 | const AggregateFunctionForEachData& rhs_state = data(rhs); |
169 | 9 | AggregateFunctionForEachData& state = |
170 | 9 | ensure_aggregate_data(place, rhs_state.dynamic_array_size, arena); |
171 | | |
172 | 9 | const char* rhs_nested_state = rhs_state.array_of_aggregate_datas; |
173 | 9 | char* nested_state = state.array_of_aggregate_datas; |
174 | | |
175 | 44 | for (size_t i = 0; i < state.dynamic_array_size && i < rhs_state.dynamic_array_size; ++i) { |
176 | 35 | nested_function->merge(nested_state, rhs_nested_state, arena); |
177 | | |
178 | 35 | rhs_nested_state += nested_size_of_data; |
179 | 35 | nested_state += nested_size_of_data; |
180 | 35 | } |
181 | 9 | } |
182 | | |
183 | 12 | void serialize(ConstAggregateDataPtr __restrict place, BufferWritable& buf) const override { |
184 | 12 | const AggregateFunctionForEachData& state = data(place); |
185 | 12 | buf.write_binary(state.dynamic_array_size); |
186 | 12 | const char* nested_state = state.array_of_aggregate_datas; |
187 | 56 | for (size_t i = 0; i < state.dynamic_array_size; ++i) { |
188 | 44 | nested_function->serialize(nested_state, buf); |
189 | 44 | nested_state += nested_size_of_data; |
190 | 44 | } |
191 | 12 | } |
192 | | |
193 | | void deserialize(AggregateDataPtr __restrict place, BufferReadable& buf, |
194 | 9 | Arena& arena) const override { |
195 | 9 | AggregateFunctionForEachData& state = data(place); |
196 | | |
197 | 9 | size_t new_size = 0; |
198 | 9 | buf.read_binary(new_size); |
199 | | |
200 | 9 | ensure_aggregate_data(place, new_size, arena); |
201 | | |
202 | 9 | char* nested_state = state.array_of_aggregate_datas; |
203 | 44 | for (size_t i = 0; i < new_size; ++i) { |
204 | 35 | nested_function->deserialize(nested_state, buf, arena); |
205 | 35 | nested_state += nested_size_of_data; |
206 | 35 | } |
207 | 9 | } |
208 | | |
209 | 13 | void insert_result_into(ConstAggregateDataPtr __restrict place, IColumn& to) const override { |
210 | 13 | const AggregateFunctionForEachData& state = data(place); |
211 | | |
212 | 13 | auto& arr_to = assert_cast<ColumnArray&, TypeCheckOnRelease::DISABLE>(to); |
213 | 13 | auto& offsets_to = arr_to.get_offsets(); |
214 | 13 | IColumn* elems_to = &arr_to.get_data(); |
215 | 13 | ColumnNullable* nullable_elems_to = nullptr; |
216 | 13 | if (!nested_function->get_return_type()->is_nullable()) { |
217 | 13 | nullable_elems_to = assert_cast<ColumnNullable*, TypeCheckOnRelease::DISABLE>(elems_to); |
218 | 13 | elems_to = nullable_elems_to->get_nested_column_ptr().get(); |
219 | 13 | } |
220 | | |
221 | 13 | char* nested_state = state.array_of_aggregate_datas; |
222 | 64 | for (size_t i = 0; i < state.dynamic_array_size; ++i) { |
223 | 51 | nested_function->insert_result_into(nested_state, *elems_to); |
224 | 51 | if (nullable_elems_to != nullptr) { |
225 | 51 | nullable_elems_to->get_null_map_data().push_back(0); |
226 | 51 | } |
227 | 51 | nested_state += nested_size_of_data; |
228 | 51 | } |
229 | | |
230 | 13 | offsets_to.push_back(offsets_to.back() + state.dynamic_array_size); |
231 | 13 | } |
232 | | |
233 | 12 | void check_result_column_type(const IColumn& to) const override { |
234 | 12 | const auto* arr_to = check_and_get_column<ColumnArray>(to); |
235 | 12 | if (UNLIKELY(arr_to == nullptr)) { |
236 | 0 | throw doris::Exception(Status::InternalError( |
237 | 0 | "Aggregate function {} result type check failed: Column type {} is not " |
238 | 0 | "ColumnArray", |
239 | 0 | get_name(), to.get_name())); |
240 | 0 | } |
241 | | |
242 | 12 | const IColumn* elems_to = &arr_to->get_data(); |
243 | 12 | if (!nested_function->get_return_type()->is_nullable()) { |
244 | 12 | const auto* nullable_elems_to = check_and_get_column<ColumnNullable>(*elems_to); |
245 | 12 | if (UNLIKELY(nullable_elems_to == nullptr)) { |
246 | 0 | throw doris::Exception(Status::InternalError( |
247 | 0 | "Aggregate function {} result type check failed: Array nested column " |
248 | 0 | "type {} is not ColumnNullable", |
249 | 0 | get_name(), elems_to->get_name())); |
250 | 0 | } |
251 | 12 | elems_to = &nullable_elems_to->get_nested_column(); |
252 | 12 | } |
253 | 12 | nested_function->check_result_column_type(*elems_to); |
254 | 12 | } |
255 | | |
256 | | void add(AggregateDataPtr __restrict place, const IColumn** columns, ssize_t row_num, |
257 | 19 | Arena& arena) const override { |
258 | 19 | std::vector<const IColumn*> nested(num_arguments); |
259 | | |
260 | 38 | for (size_t i = 0; i < num_arguments; ++i) { |
261 | 19 | nested[i] = &assert_cast<const ColumnArray&, TypeCheckOnRelease::DISABLE>(*columns[i]) |
262 | 19 | .get_data(); |
263 | 19 | } |
264 | | |
265 | 19 | const auto& first_array_column = |
266 | 19 | assert_cast<const ColumnArray&, TypeCheckOnRelease::DISABLE>(*columns[0]); |
267 | 19 | const auto& offsets = first_array_column.get_offsets(); |
268 | | |
269 | 19 | size_t begin = offsets[row_num - 1]; |
270 | 19 | size_t end = offsets[row_num]; |
271 | | |
272 | | /// Sanity check. NOTE We can implement specialization for a case with single argument, if the check will hurt performance. |
273 | 19 | for (size_t i = 1; i < num_arguments; ++i) { |
274 | 0 | const auto& ith_column = |
275 | 0 | assert_cast<const ColumnArray&, TypeCheckOnRelease::DISABLE>(*columns[i]); |
276 | 0 | const auto& ith_offsets = ith_column.get_offsets(); |
277 | |
|
278 | 0 | if (ith_offsets[row_num] != end || |
279 | 0 | (row_num != 0 && ith_offsets[row_num - 1] != begin)) { |
280 | 0 | throw Exception(ErrorCode::INTERNAL_ERROR, |
281 | 0 | "Arrays passed to {} aggregate function have different sizes", |
282 | 0 | get_name()); |
283 | 0 | } |
284 | 0 | } |
285 | | |
286 | 19 | AggregateFunctionForEachData& state = ensure_aggregate_data(place, end - begin, arena); |
287 | | |
288 | 19 | char* nested_state = state.array_of_aggregate_datas; |
289 | 82 | for (size_t i = begin; i < end; ++i) { |
290 | 63 | nested_function->add(nested_state, nested.data(), i, arena); |
291 | 63 | nested_state += nested_size_of_data; |
292 | 63 | } |
293 | 19 | } |
294 | | |
295 | 14 | void check_input_columns_type(const IColumn** columns) const override { |
296 | 14 | std::vector<const IColumn*> nested(num_arguments); |
297 | 28 | for (size_t i = 0; i < num_arguments; ++i) { |
298 | 14 | const auto* array_column = check_and_get_column<ColumnArray>(*columns[i]); |
299 | 14 | if (UNLIKELY(array_column == nullptr)) { |
300 | 0 | throw doris::Exception(Status::InternalError( |
301 | 0 | "Aggregate function {} argument {} type check failed: Column type {} is " |
302 | 0 | "not ColumnArray", |
303 | 0 | get_name(), i, columns[i]->get_name())); |
304 | 0 | } |
305 | 14 | nested[i] = &array_column->get_data(); |
306 | 14 | } |
307 | 14 | nested_function->check_input_columns_type(nested.data()); |
308 | 14 | } |
309 | | }; |
310 | | } // namespace doris |