Coverage Report

Created: 2026-10-29 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
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.89k
    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.88k
    bool is_variadic() const override { return true; }
75
76
3.88k
    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.88k
        const bool nullable = std::ranges::any_of(
82
11.4k
                arguments, [](const DataTypePtr& type) { return type->is_nullable(); });
83
3.88k
        DataTypePtr base = remove_nullable(arguments[0]);
84
3.88k
        return nullable ? make_nullable(base) : base;
85
3.88k
    }
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.77k
    bool use_default_implementation_for_nulls() const override { return false; }
90
91
7.96k
    Status open(FunctionContext* context, FunctionContext::FunctionStateScope scope) override {
92
7.96k
        if (scope == FunctionContext::FunctionStateScope::THREAD_LOCAL) {
93
4.07k
            return Status::OK();
94
4.07k
        }
95
3.88k
        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.88k
        if (context->is_col_constant(1)) {
100
3.87k
            RETURN_IF_ERROR(parse_lg_k(*context->get_constant_col(1)->column_ptr, state->lg_k));
101
3.87k
        }
102
3.88k
        if (context->get_num_args() == 2) {
103
35
            state->tgt_type = datasketches::HLL_4;
104
3.84k
        } else if (context->is_col_constant(2)) {
105
3.83k
            RETURN_IF_ERROR(
106
3.83k
                    parse_tgt_type(*context->get_constant_col(2)->column_ptr, state->tgt_type));
107
3.83k
        }
108
3.88k
        context->set_function_state(scope, state);
109
3.88k
        return Status::OK();
110
3.88k
    }
111
112
    Status execute_impl(FunctionContext* context, Block& block, const ColumnNumbers& arguments,
113
3.89k
                        uint32_t result, size_t input_rows_count) const override {
114
3.89k
        const auto* state = reinterpret_cast<const DownsampleState*>(
115
3.89k
                context->get_function_state(FunctionContext::FunctionStateScope::FRAGMENT_LOCAL));
116
3.89k
        DORIS_CHECK(state != nullptr);
117
3.89k
        uint8_t lg_k = 0;
118
3.89k
        if (state->lg_k.has_value()) {
119
3.88k
            lg_k = *state->lg_k;
120
3.88k
        } else {
121
12
            RETURN_IF_ERROR(read_lg_k(*block.get_by_position(arguments[1]).column, lg_k));
122
12
        }
123
3.88k
        datasketches::target_hll_type tgt_type = datasketches::HLL_4;
124
3.88k
        if (state->tgt_type.has_value()) {
125
3.88k
            tgt_type = *state->tgt_type;
126
3.88k
        } else {
127
5
            RETURN_IF_ERROR(read_tgt_type(*block.get_by_position(arguments[2]).column, tgt_type));
128
5
        }
129
130
3.88k
        const ColumnWithTypeAndName& sketch_arg = block.get_by_position(arguments[0]);
131
3.88k
        if (sketch_arg.type->is_nullable() && sketch_arg.get_nullable_column_info().only_null) {
132
7
            block.get_by_position(result).column =
133
7
                    block.get_by_position(result).type->create_column_const(input_rows_count,
134
7
                                                                            Field());
135
7
            return Status::OK();
136
7
        }
137
3.87k
        const auto [sketch_column, sketch_const] = unpack_if_const(sketch_arg.column);
138
3.87k
        const NullMap* null_map = nullptr;
139
3.87k
        ColumnPtr sketch_values = sketch_column;
140
3.87k
        if (const auto* nullable = check_and_get_column<ColumnNullable>(sketch_column.get())) {
141
118
            null_map = &nullable->get_null_map_data();
142
118
            sketch_values = nullable->get_nested_column_ptr();
143
118
        }
144
145
3.87k
        MutableColumnPtr res;
146
3.87k
        if (const auto* strings = check_and_get_column<ColumnString>(sketch_values.get())) {
147
3.60k
            res = execute_column(*strings, null_map, sketch_const, input_rows_count, lg_k,
148
3.60k
                                 tgt_type);
149
3.60k
        } else if (const auto* binaries =
150
270
                           check_and_get_column<ColumnVarbinary>(sketch_values.get())) {
151
270
            res = execute_column(*binaries, null_map, sketch_const, input_rows_count, lg_k,
152
270
                                 tgt_type);
153
270
        } 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.87k
        if (!block.get_by_position(result).type->is_nullable()) {
159
3.03k
            block.replace_by_position(result, std::move(res));
160
3.03k
            return Status::OK();
161
3.03k
        }
162
842
        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
842
        block.replace_by_position(result, wrap_in_nullable(ColumnPtr(std::move(res)), block,
171
842
                                                           arguments, input_rows_count));
172
842
        return Status::OK();
173
842
    }
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.87k
    static Status parse_lg_k(const IColumn& column, std::optional<uint8_t>& lg_k) {
187
3.87k
        uint8_t value = 0;
188
3.87k
        RETURN_IF_ERROR(read_lg_k(column, value));
189
3.87k
        lg_k = value;
190
3.87k
        return Status::OK();
191
3.87k
    }
192
193
    static Status parse_tgt_type(const IColumn& column,
194
3.83k
                                 std::optional<datasketches::target_hll_type>& tgt_type) {
195
3.83k
        datasketches::target_hll_type value = datasketches::HLL_4;
196
3.83k
        RETURN_IF_ERROR(read_tgt_type(column, value));
197
3.83k
        tgt_type = value;
198
3.83k
        return Status::OK();
199
3.83k
    }
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.88k
    static Status read_lg_k(const IColumn& column, uint8_t& lg_k) {
204
3.88k
        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.88k
        const int64_t value = remove_nullable(column.get_ptr())->get_int(0);
209
3.88k
        if (value < MIN_LG_K || value > MAX_LG_K) {
210
7
            return Status::InvalidArgument("{}'s lg_k must be between {} and {}, but was {}", name,
211
7
                                           MIN_LG_K, MAX_LG_K, value);
212
7
        }
213
3.87k
        lg_k = static_cast<uint8_t>(value);
214
3.87k
        return Status::OK();
215
3.88k
    }
216
217
3.84k
    static Status read_tgt_type(const IColumn& column, datasketches::target_hll_type& tgt_type) {
218
3.84k
        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.84k
        const std::string value = remove_nullable(column.get_ptr())->get_data_at(0).to_string();
223
3.84k
        const std::string lower = to_lower(value);
224
3.84k
        if (lower == "hll_4") {
225
1.35k
            tgt_type = datasketches::HLL_4;
226
2.49k
        } else if (lower == "hll_6") {
227
1.24k
            tgt_type = datasketches::HLL_6;
228
1.24k
        } else if (lower == "hll_8") {
229
1.24k
            tgt_type = datasketches::HLL_8;
230
1.24k
        } 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.84k
        return Status::OK();
235
3.84k
    }
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.87k
                                           datasketches::target_hll_type tgt_type) {
241
3.87k
        auto res = ColumnType::create();
242
8.02k
        for (size_t i = 0; i < input_rows_count; ++i) {
243
4.14k
            const size_t row = index_check_const(i, sketch_const);
244
4.14k
            if (null_map != nullptr && (*null_map)[row]) {
245
58
                res->insert_default();
246
58
                continue;
247
58
            }
248
4.08k
            downsample(sketches.get_data_at(row), lg_k, tgt_type, *res);
249
4.08k
        }
250
3.87k
        return res;
251
3.87k
    }
_ZN5doris33FunctionDataSketchesHllDownsample14execute_columnINS_9ColumnStrIjEEEENS_3COWINS_7IColumnEE11mutable_ptrIS5_EERKT_PKNS_8PODArrayIhLm4096ENS_9AllocatorILb0ELb0ELb0ENS_22DefaultMemoryAllocatorELb1EEELm16ELm15EEEbmhN12datasketches15target_hll_typeE
Line
Count
Source
240
3.60k
                                           datasketches::target_hll_type tgt_type) {
241
3.60k
        auto res = ColumnType::create();
242
7.43k
        for (size_t i = 0; i < input_rows_count; ++i) {
243
3.82k
            const size_t row = index_check_const(i, sketch_const);
244
3.82k
            if (null_map != nullptr && (*null_map)[row]) {
245
53
                res->insert_default();
246
53
                continue;
247
53
            }
248
3.77k
            downsample(sketches.get_data_at(row), lg_k, tgt_type, *res);
249
3.77k
        }
250
3.60k
        return res;
251
3.60k
    }
_ZN5doris33FunctionDataSketchesHllDownsample14execute_columnINS_15ColumnVarbinaryEEENS_3COWINS_7IColumnEE11mutable_ptrIS4_EERKT_PKNS_8PODArrayIhLm4096ENS_9AllocatorILb0ELb0ELb0ENS_22DefaultMemoryAllocatorELb1EEELm16ELm15EEEbmhN12datasketches15target_hll_typeE
Line
Count
Source
240
270
                                           datasketches::target_hll_type tgt_type) {
241
270
        auto res = ColumnType::create();
242
585
        for (size_t i = 0; i < input_rows_count; ++i) {
243
315
            const size_t row = index_check_const(i, sketch_const);
244
315
            if (null_map != nullptr && (*null_map)[row]) {
245
5
                res->insert_default();
246
5
                continue;
247
5
            }
248
310
            downsample(sketches.get_data_at(row), lg_k, tgt_type, *res);
249
310
        }
250
270
        return res;
251
270
    }
252
253
    template <typename ColumnType>
254
    static void downsample(const StringRef& value, uint8_t lg_k,
255
4.08k
                           datasketches::target_hll_type tgt_type, ColumnType& res) {
256
4.08k
        const datasketches_hll::Sketch sketch = datasketches_hll::deserialize(value, "downsample");
257
4.08k
        if (sketch.get_lg_config_k() <= lg_k && sketch.get_target_type() == tgt_type) {
258
1.90k
            res.insert_data(value.data, value.size);
259
1.90k
            return;
260
1.90k
        }
261
2.17k
        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
2.17k
            datasketches_hll::Union hll_union(std::min(lg_k, sketch.get_lg_config_k()),
265
2.17k
                                              datasketches_hll::Alloc());
266
2.17k
            hll_union.update(sketch);
267
2.17k
            const auto bytes = hll_union.get_result(tgt_type).serialize_compact();
268
2.17k
            res.insert_data(reinterpret_cast<const char*>(bytes.data()), bytes.size());
269
2.17k
        } 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
2.17k
    }
_ZN5doris33FunctionDataSketchesHllDownsample10downsampleINS_9ColumnStrIjEEEEvRKNS_9StringRefEhN12datasketches15target_hll_typeERT_
Line
Count
Source
255
3.77k
                           datasketches::target_hll_type tgt_type, ColumnType& res) {
256
3.77k
        const datasketches_hll::Sketch sketch = datasketches_hll::deserialize(value, "downsample");
257
3.77k
        if (sketch.get_lg_config_k() <= lg_k && sketch.get_target_type() == tgt_type) {
258
1.89k
            res.insert_data(value.data, value.size);
259
1.89k
            return;
260
1.89k
        }
261
1.88k
        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.88k
            datasketches_hll::Union hll_union(std::min(lg_k, sketch.get_lg_config_k()),
265
1.88k
                                              datasketches_hll::Alloc());
266
1.88k
            hll_union.update(sketch);
267
1.88k
            const auto bytes = hll_union.get_result(tgt_type).serialize_compact();
268
1.88k
            res.insert_data(reinterpret_cast<const char*>(bytes.data()), bytes.size());
269
1.88k
        } 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.88k
    }
_ZN5doris33FunctionDataSketchesHllDownsample10downsampleINS_15ColumnVarbinaryEEEvRKNS_9StringRefEhN12datasketches15target_hll_typeERT_
Line
Count
Source
255
310
                           datasketches::target_hll_type tgt_type, ColumnType& res) {
256
310
        const datasketches_hll::Sketch sketch = datasketches_hll::deserialize(value, "downsample");
257
310
        if (sketch.get_lg_config_k() <= lg_k && sketch.get_target_type() == tgt_type) {
258
14
            res.insert_data(value.data, value.size);
259
14
            return;
260
14
        }
261
296
        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
296
            datasketches_hll::Union hll_union(std::min(lg_k, sketch.get_lg_config_k()),
265
296
                                              datasketches_hll::Alloc());
266
296
            hll_union.update(sketch);
267
296
            const auto bytes = hll_union.get_result(tgt_type).serialize_compact();
268
296
            res.insert_data(reinterpret_cast<const char*>(bytes.data()), bytes.size());
269
296
        } 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
296
    }
281
};
282
283
8
void register_function_datasketches_hll(SimpleFunctionFactory& factory) {
284
8
    factory.register_function<FunctionDataSketchesHllDownsample>();
285
8
}
286
287
} // namespace doris