be/src/exprs/function/function_datasketches_hll.cpp
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 | | |
18 | | #include <algorithm> |
19 | | #include <cstddef> |
20 | | #include <cstdint> |
21 | | #include <exception> |
22 | | #include <hll.hpp> |
23 | | #include <memory> |
24 | | #include <optional> |
25 | | #include <string> |
26 | | #include <utility> |
27 | | |
28 | | #include "common/check.h" |
29 | | #include "common/exception.h" |
30 | | #include "common/status.h" |
31 | | #include "core/assert_cast.h" |
32 | | #include "core/block/block.h" |
33 | | #include "core/block/column_numbers.h" |
34 | | #include "core/block/column_with_type_and_name.h" |
35 | | #include "core/column/column.h" |
36 | | #include "core/column/column_const.h" |
37 | | #include "core/column/column_nullable.h" |
38 | | #include "core/column/column_string.h" |
39 | | #include "core/column/column_varbinary.h" |
40 | | #include "core/column/column_vector.h" |
41 | | #include "core/data_type/data_type.h" |
42 | | #include "core/data_type/data_type_nullable.h" |
43 | | #include "core/data_type/data_type_string.h" |
44 | | #include "core/data_type/data_type_varbinary.h" |
45 | | #include "core/string_ref.h" |
46 | | #include "exprs/datasketches_hll_util.h" |
47 | | #include "exprs/function/function.h" |
48 | | #include "exprs/function/simple_function_factory.h" |
49 | | #include "exprs/function_context.h" |
50 | | #include "util/string_util.h" |
51 | | |
52 | | namespace doris { |
53 | | |
54 | | // datasketches_hll_downsample(sketch, lg_k [, tgt_type]) rewrites a serialized DataSketches HLL |
55 | | // sketch so that its lgK is min(lg_k, source lgK) and its HLL type is tgt_type (HLL_4 by default). |
56 | | // |
57 | | // A sketch that already has lgK <= lg_k and type tgt_type is returned byte for byte after it is |
58 | | // fully deserialized. Callers rely on this: `datasketches_hll_downsample(sk, k, t) <> sk` is true |
59 | | // exactly when sk's lgK is above k or its type differs from t, which tells whether a row still |
60 | | // needs to be rewritten. For example, with lg_k = 12 and tgt_type = HLL_4: |
61 | | // lgK 14 HLL_8 -> lgK 12 HLL_4 (new bytes) |
62 | | // lgK 10 HLL_8 -> lgK 10 HLL_4 (new bytes, only the type changes) |
63 | | // lgK 10 HLL_4 -> the input bytes |
64 | | class FunctionDataSketchesHllDownsample : public IFunction { |
65 | | public: |
66 | | static constexpr auto name = "datasketches_hll_downsample"; |
67 | | |
68 | 3.76k | static FunctionPtr create() { return std::make_shared<FunctionDataSketchesHllDownsample>(); } |
69 | | |
70 | 0 | String get_name() const override { return name; } |
71 | | |
72 | 0 | size_t get_number_of_arguments() const override { return 0; } |
73 | | |
74 | 3.75k | bool is_variadic() const override { return true; } |
75 | | |
76 | 3.75k | DataTypePtr get_return_type_impl(const DataTypes& arguments) const override { |
77 | | // use_default_implementation_for_nulls() is disabled below, so, unlike the usual |
78 | | // get_return_type_impl contract, arguments here keep their original nullability and the |
79 | | // wrapping is not automatic: replicate it by hand from every argument, as FE does for a |
80 | | // PropagateNullable function. |
81 | 3.75k | const bool nullable = std::ranges::any_of( |
82 | 11.2k | arguments, [](const DataTypePtr& type) { return type->is_nullable(); }); |
83 | 3.75k | DataTypePtr base = remove_nullable(arguments[0]); |
84 | 3.75k | return nullable ? make_nullable(base) : base; |
85 | 3.75k | } |
86 | | |
87 | | // lg_k and tgt_type are validated before the NULL check of the sketch, so the generic |
88 | | // nullable-argument shortcut must not return NULL for, e.g., downsample(NULL, 30) first. |
89 | 7.51k | bool use_default_implementation_for_nulls() const override { return false; } |
90 | | |
91 | 7.51k | Status open(FunctionContext* context, FunctionContext::FunctionStateScope scope) override { |
92 | 7.51k | if (scope == FunctionContext::FunctionStateScope::THREAD_LOCAL) { |
93 | 3.75k | return Status::OK(); |
94 | 3.75k | } |
95 | 3.75k | auto state = std::make_shared<DownsampleState>(); |
96 | | // FE folds the arguments to literals when it can. A constant FE cannot fold, e.g. |
97 | | // `10 + crc32('') % 2`, may not be evaluated in open either, then it is read from the |
98 | | // first row in execute. |
99 | 3.75k | if (context->is_col_constant(1)) { |
100 | 3.75k | RETURN_IF_ERROR(parse_lg_k(*context->get_constant_col(1)->column_ptr, state->lg_k)); |
101 | 3.75k | } |
102 | 3.75k | if (context->get_num_args() == 2) { |
103 | 4 | state->tgt_type = datasketches::HLL_4; |
104 | 3.75k | } else if (context->is_col_constant(2)) { |
105 | 3.74k | RETURN_IF_ERROR( |
106 | 3.74k | parse_tgt_type(*context->get_constant_col(2)->column_ptr, state->tgt_type)); |
107 | 3.74k | } |
108 | 3.75k | context->set_function_state(scope, state); |
109 | 3.75k | return Status::OK(); |
110 | 3.75k | } |
111 | | |
112 | | Status execute_impl(FunctionContext* context, Block& block, const ColumnNumbers& arguments, |
113 | 3.75k | uint32_t result, size_t input_rows_count) const override { |
114 | 3.75k | const auto* state = reinterpret_cast<const DownsampleState*>( |
115 | 3.75k | context->get_function_state(FunctionContext::FunctionStateScope::FRAGMENT_LOCAL)); |
116 | 3.75k | DORIS_CHECK(state != nullptr); |
117 | 3.75k | uint8_t lg_k = 0; |
118 | 3.75k | if (state->lg_k.has_value()) { |
119 | 3.74k | lg_k = *state->lg_k; |
120 | 3.74k | } else { |
121 | 7 | RETURN_IF_ERROR(read_lg_k(*block.get_by_position(arguments[1]).column, lg_k)); |
122 | 7 | } |
123 | 3.74k | datasketches::target_hll_type tgt_type = datasketches::HLL_4; |
124 | 3.74k | if (state->tgt_type.has_value()) { |
125 | 3.74k | tgt_type = *state->tgt_type; |
126 | 3.74k | } else { |
127 | 3 | RETURN_IF_ERROR(read_tgt_type(*block.get_by_position(arguments[2]).column, tgt_type)); |
128 | 3 | } |
129 | | |
130 | 3.74k | const ColumnWithTypeAndName& sketch_arg = block.get_by_position(arguments[0]); |
131 | 3.74k | if (sketch_arg.type->is_nullable() && sketch_arg.get_nullable_column_info().only_null) { |
132 | 6 | block.get_by_position(result).column = |
133 | 6 | block.get_by_position(result).type->create_column_const(input_rows_count, |
134 | 6 | Field()); |
135 | 6 | return Status::OK(); |
136 | 6 | } |
137 | 3.74k | const auto [sketch_column, sketch_const] = unpack_if_const(sketch_arg.column); |
138 | 3.74k | const NullMap* null_map = nullptr; |
139 | 3.74k | ColumnPtr sketch_values = sketch_column; |
140 | 3.74k | if (const auto* nullable = check_and_get_column<ColumnNullable>(sketch_column.get())) { |
141 | 12 | null_map = &nullable->get_null_map_data(); |
142 | 12 | sketch_values = nullable->get_nested_column_ptr(); |
143 | 12 | } |
144 | | |
145 | 3.74k | MutableColumnPtr res; |
146 | 3.74k | if (const auto* strings = check_and_get_column<ColumnString>(sketch_values.get())) { |
147 | 3.49k | res = execute_column(*strings, null_map, sketch_const, input_rows_count, lg_k, |
148 | 3.49k | tgt_type); |
149 | 3.49k | } else if (const auto* binaries = |
150 | 246 | check_and_get_column<ColumnVarbinary>(sketch_values.get())) { |
151 | 246 | res = execute_column(*binaries, null_map, sketch_const, input_rows_count, lg_k, |
152 | 246 | tgt_type); |
153 | 246 | } else { |
154 | 0 | return Status::RuntimeError("Illegal column {} of argument of function {}", |
155 | 0 | sketch_arg.column->get_name(), get_name()); |
156 | 0 | } |
157 | | |
158 | 3.74k | if (!block.get_by_position(result).type->is_nullable()) { |
159 | 3.02k | block.replace_by_position(result, std::move(res)); |
160 | 3.02k | return Status::OK(); |
161 | 3.02k | } |
162 | 715 | if (sketch_const) { |
163 | | // wrap_in_nullable skips constant arguments; a constant sketch here is not NULL, since |
164 | | // an all-NULL sketch returned above. |
165 | 0 | block.replace_by_position( |
166 | 0 | result, ColumnNullable::create(std::move(res), |
167 | 0 | ColumnUInt8::create(input_rows_count, 0))); |
168 | 0 | return Status::OK(); |
169 | 0 | } |
170 | 715 | block.replace_by_position(result, wrap_in_nullable(ColumnPtr(std::move(res)), block, |
171 | 715 | arguments, input_rows_count)); |
172 | 715 | return Status::OK(); |
173 | 715 | } |
174 | | |
175 | | private: |
176 | | // Same bounds as the DataSketches library accepts for a sketch. |
177 | | static constexpr int64_t MIN_LG_K = datasketches::hll_constants::MIN_LOG_K; |
178 | | static constexpr int64_t MAX_LG_K = datasketches::hll_constants::MAX_LOG_K; |
179 | | |
180 | | struct DownsampleState { |
181 | | // Empty when the argument is not evaluated in open; it is then read in execute. |
182 | | std::optional<uint8_t> lg_k; |
183 | | std::optional<datasketches::target_hll_type> tgt_type; |
184 | | }; |
185 | | |
186 | 3.75k | static Status parse_lg_k(const IColumn& column, std::optional<uint8_t>& lg_k) { |
187 | 3.75k | uint8_t value = 0; |
188 | 3.75k | RETURN_IF_ERROR(read_lg_k(column, value)); |
189 | 3.74k | lg_k = value; |
190 | 3.74k | return Status::OK(); |
191 | 3.75k | } |
192 | | |
193 | | static Status parse_tgt_type(const IColumn& column, |
194 | 3.74k | std::optional<datasketches::target_hll_type>& tgt_type) { |
195 | 3.74k | datasketches::target_hll_type value = datasketches::HLL_4; |
196 | 3.74k | RETURN_IF_ERROR(read_tgt_type(column, value)); |
197 | 3.74k | tgt_type = value; |
198 | 3.74k | return Status::OK(); |
199 | 3.74k | } |
200 | | |
201 | | // Reads lg_k from the first row. FE rejects a literal outside the range or NULL, so these |
202 | | // errors are only reachable through a constant evaluated by BE. |
203 | 3.75k | static Status read_lg_k(const IColumn& column, uint8_t& lg_k) { |
204 | 3.75k | if (column.is_null_at(0)) { |
205 | 2 | return Status::InvalidArgument("{}'s lg_k must be between {} and {}, but was NULL", |
206 | 2 | name, MIN_LG_K, MAX_LG_K); |
207 | 2 | } |
208 | 3.75k | const int64_t value = remove_nullable(column.get_ptr())->get_int(0); |
209 | 3.75k | if (value < MIN_LG_K || value > MAX_LG_K) { |
210 | 6 | return Status::InvalidArgument("{}'s lg_k must be between {} and {}, but was {}", name, |
211 | 6 | MIN_LG_K, MAX_LG_K, value); |
212 | 6 | } |
213 | 3.75k | lg_k = static_cast<uint8_t>(value); |
214 | 3.75k | return Status::OK(); |
215 | 3.75k | } |
216 | | |
217 | 3.74k | static Status read_tgt_type(const IColumn& column, datasketches::target_hll_type& tgt_type) { |
218 | 3.74k | if (column.is_null_at(0)) { |
219 | 2 | return Status::InvalidArgument( |
220 | 2 | "{}'s tgt_type must be one of HLL_4, HLL_6, HLL_8, but was NULL", name); |
221 | 2 | } |
222 | 3.74k | const std::string value = remove_nullable(column.get_ptr())->get_data_at(0).to_string(); |
223 | 3.74k | const std::string lower = to_lower(value); |
224 | 3.74k | if (lower == "hll_4") { |
225 | 1.31k | tgt_type = datasketches::HLL_4; |
226 | 2.43k | } else if (lower == "hll_6") { |
227 | 1.21k | tgt_type = datasketches::HLL_6; |
228 | 1.21k | } else if (lower == "hll_8") { |
229 | 1.21k | tgt_type = datasketches::HLL_8; |
230 | 1.21k | } else { |
231 | 2 | return Status::InvalidArgument( |
232 | 2 | "{}'s tgt_type must be one of HLL_4, HLL_6, HLL_8, but was '{}'", name, value); |
233 | 2 | } |
234 | 3.74k | return Status::OK(); |
235 | 3.74k | } |
236 | | |
237 | | template <typename ColumnType> |
238 | | static MutableColumnPtr execute_column(const ColumnType& sketches, const NullMap* null_map, |
239 | | bool sketch_const, size_t input_rows_count, uint8_t lg_k, |
240 | 3.74k | datasketches::target_hll_type tgt_type) { |
241 | 3.74k | auto res = ColumnType::create(); |
242 | 7.55k | for (size_t i = 0; i < input_rows_count; ++i) { |
243 | 3.81k | const size_t row = index_check_const(i, sketch_const); |
244 | 3.81k | if (null_map != nullptr && (*null_map)[row]) { |
245 | 12 | res->insert_default(); |
246 | 12 | continue; |
247 | 12 | } |
248 | 3.80k | downsample(sketches.get_data_at(row), lg_k, tgt_type, *res); |
249 | 3.80k | } |
250 | 3.74k | return res; |
251 | 3.74k | } _ZN5doris33FunctionDataSketchesHllDownsample14execute_columnINS_9ColumnStrIjEEEENS_3COWINS_7IColumnEE11mutable_ptrIS5_EERKT_PKNS_8PODArrayIhLm4096ENS_9AllocatorILb0ELb0ELb0ENS_22DefaultMemoryAllocatorELb1EEELm16ELm15EEEbmhN12datasketches15target_hll_typeE Line | Count | Source | 240 | 3.49k | datasketches::target_hll_type tgt_type) { | 241 | 3.49k | auto res = ColumnType::create(); | 242 | 7.03k | for (size_t i = 0; i < input_rows_count; ++i) { | 243 | 3.54k | const size_t row = index_check_const(i, sketch_const); | 244 | 3.54k | if (null_map != nullptr && (*null_map)[row]) { | 245 | 8 | res->insert_default(); | 246 | 8 | continue; | 247 | 8 | } | 248 | 3.53k | downsample(sketches.get_data_at(row), lg_k, tgt_type, *res); | 249 | 3.53k | } | 250 | 3.49k | return res; | 251 | 3.49k | } |
_ZN5doris33FunctionDataSketchesHllDownsample14execute_columnINS_15ColumnVarbinaryEEENS_3COWINS_7IColumnEE11mutable_ptrIS4_EERKT_PKNS_8PODArrayIhLm4096ENS_9AllocatorILb0ELb0ELb0ENS_22DefaultMemoryAllocatorELb1EEELm16ELm15EEEbmhN12datasketches15target_hll_typeE Line | Count | Source | 240 | 246 | datasketches::target_hll_type tgt_type) { | 241 | 246 | auto res = ColumnType::create(); | 242 | 516 | for (size_t i = 0; i < input_rows_count; ++i) { | 243 | 270 | const size_t row = index_check_const(i, sketch_const); | 244 | 270 | if (null_map != nullptr && (*null_map)[row]) { | 245 | 4 | res->insert_default(); | 246 | 4 | continue; | 247 | 4 | } | 248 | 266 | downsample(sketches.get_data_at(row), lg_k, tgt_type, *res); | 249 | 266 | } | 250 | 246 | return res; | 251 | 246 | } |
|
252 | | |
253 | | template <typename ColumnType> |
254 | | static void downsample(const StringRef& value, uint8_t lg_k, |
255 | 3.80k | datasketches::target_hll_type tgt_type, ColumnType& res) { |
256 | 3.80k | const datasketches_hll::Sketch sketch = datasketches_hll::deserialize(value, "downsample"); |
257 | 3.80k | if (sketch.get_lg_config_k() <= lg_k && sketch.get_target_type() == tgt_type) { |
258 | 1.82k | res.insert_data(value.data, value.size); |
259 | 1.82k | return; |
260 | 1.82k | } |
261 | 1.97k | try { |
262 | | // A union capped at min(lg_k, source lgK) folds the registers of a dense sketch and |
263 | | // replays the coupons of a LIST/SET sketch, without ever raising the source lgK. |
264 | 1.97k | datasketches_hll::Union hll_union(std::min(lg_k, sketch.get_lg_config_k()), |
265 | 1.97k | datasketches_hll::Alloc()); |
266 | 1.97k | hll_union.update(sketch); |
267 | 1.97k | const auto bytes = hll_union.get_result(tgt_type).serialize_compact(); |
268 | 1.97k | res.insert_data(reinterpret_cast<const char*>(bytes.data()), bytes.size()); |
269 | 1.97k | } catch (const doris::Exception& e) { |
270 | 56 | throw Exception(e.code(), "Internal error happened when downsample HLL sketch: {}", |
271 | 56 | e.to_string()); |
272 | 56 | } catch (const std::exception& e) { |
273 | 0 | throw Exception(ErrorCode::INTERNAL_ERROR, |
274 | 0 | "Internal error happened when downsample HLL sketch: {}", e.what()); |
275 | 0 | } catch (...) { |
276 | 0 | throw Exception( |
277 | 0 | ErrorCode::INTERNAL_ERROR, |
278 | 0 | "Internal error happened when downsample HLL sketch: unknown exception."); |
279 | 0 | } |
280 | 1.97k | } _ZN5doris33FunctionDataSketchesHllDownsample10downsampleINS_9ColumnStrIjEEEEvRKNS_9StringRefEhN12datasketches15target_hll_typeERT_ Line | Count | Source | 255 | 3.53k | datasketches::target_hll_type tgt_type, ColumnType& res) { | 256 | 3.53k | const datasketches_hll::Sketch sketch = datasketches_hll::deserialize(value, "downsample"); | 257 | 3.53k | if (sketch.get_lg_config_k() <= lg_k && sketch.get_target_type() == tgt_type) { | 258 | 1.81k | res.insert_data(value.data, value.size); | 259 | 1.81k | return; | 260 | 1.81k | } | 261 | 1.71k | try { | 262 | | // A union capped at min(lg_k, source lgK) folds the registers of a dense sketch and | 263 | | // replays the coupons of a LIST/SET sketch, without ever raising the source lgK. | 264 | 1.71k | datasketches_hll::Union hll_union(std::min(lg_k, sketch.get_lg_config_k()), | 265 | 1.71k | datasketches_hll::Alloc()); | 266 | 1.71k | hll_union.update(sketch); | 267 | 1.71k | const auto bytes = hll_union.get_result(tgt_type).serialize_compact(); | 268 | 1.71k | res.insert_data(reinterpret_cast<const char*>(bytes.data()), bytes.size()); | 269 | 1.71k | } catch (const doris::Exception& e) { | 270 | 56 | throw Exception(e.code(), "Internal error happened when downsample HLL sketch: {}", | 271 | 56 | e.to_string()); | 272 | 56 | } catch (const std::exception& e) { | 273 | 0 | throw Exception(ErrorCode::INTERNAL_ERROR, | 274 | 0 | "Internal error happened when downsample HLL sketch: {}", e.what()); | 275 | 0 | } catch (...) { | 276 | 0 | throw Exception( | 277 | 0 | ErrorCode::INTERNAL_ERROR, | 278 | 0 | "Internal error happened when downsample HLL sketch: unknown exception."); | 279 | 0 | } | 280 | 1.71k | } |
_ZN5doris33FunctionDataSketchesHllDownsample10downsampleINS_15ColumnVarbinaryEEEvRKNS_9StringRefEhN12datasketches15target_hll_typeERT_ Line | Count | Source | 255 | 266 | datasketches::target_hll_type tgt_type, ColumnType& res) { | 256 | 266 | const datasketches_hll::Sketch sketch = datasketches_hll::deserialize(value, "downsample"); | 257 | 266 | if (sketch.get_lg_config_k() <= lg_k && sketch.get_target_type() == tgt_type) { | 258 | 8 | res.insert_data(value.data, value.size); | 259 | 8 | return; | 260 | 8 | } | 261 | 258 | try { | 262 | | // A union capped at min(lg_k, source lgK) folds the registers of a dense sketch and | 263 | | // replays the coupons of a LIST/SET sketch, without ever raising the source lgK. | 264 | 258 | datasketches_hll::Union hll_union(std::min(lg_k, sketch.get_lg_config_k()), | 265 | 258 | datasketches_hll::Alloc()); | 266 | 258 | hll_union.update(sketch); | 267 | 258 | const auto bytes = hll_union.get_result(tgt_type).serialize_compact(); | 268 | 258 | res.insert_data(reinterpret_cast<const char*>(bytes.data()), bytes.size()); | 269 | 258 | } catch (const doris::Exception& e) { | 270 | 0 | throw Exception(e.code(), "Internal error happened when downsample HLL sketch: {}", | 271 | 0 | e.to_string()); | 272 | 0 | } catch (const std::exception& e) { | 273 | 0 | throw Exception(ErrorCode::INTERNAL_ERROR, | 274 | 0 | "Internal error happened when downsample HLL sketch: {}", e.what()); | 275 | 0 | } catch (...) { | 276 | 0 | throw Exception( | 277 | 0 | ErrorCode::INTERNAL_ERROR, | 278 | 0 | "Internal error happened when downsample HLL sketch: unknown exception."); | 279 | 0 | } | 280 | 258 | } |
|
281 | | }; |
282 | | |
283 | 1 | void register_function_datasketches_hll(SimpleFunctionFactory& factory) { |
284 | 1 | factory.register_function<FunctionDataSketchesHllDownsample>(); |
285 | 1 | } |
286 | | |
287 | | } // namespace doris |