Coverage Report

Created: 2026-08-25 20:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exprs/vdirect_in_predicate.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
18
#pragma once
19
20
#include <memory>
21
#include <utility>
22
23
#include "common/logging.h"
24
#include "common/status.h"
25
#include "core/field.h"
26
#include "core/types.h"
27
#include "exprs/expr_zonemap_filter.h"
28
#include "exprs/hybrid_set.h"
29
#include "exprs/hybrid_set_min_max.h"
30
#include "exprs/vexpr.h"
31
#include "exprs/vin_predicate.h"
32
#include "exprs/vliteral.h"
33
#include "exprs/vslot_ref.h"
34
35
namespace doris {
36
37
class VDirectInPredicate final : public VExpr {
38
    ENABLE_FACTORY_CREATOR(VDirectInPredicate);
39
40
public:
41
    // `hybrid_set_values_match_child_type` tells whether values in `filter` can be interpreted with
42
    // the child expression type. Parquet/ORC dictionary-filter rewrites evaluate the original
43
    // logical predicate against dictionary entries and then rewrite it to matched physical
44
    // dictionary codes, for example `col IN ('a', 'b')` becomes `dict_code IN (0, 1)`. In that
45
    // shape the HybridSet stores TYPE_INT dictionary codes while the child slot still has the
46
    // original logical type such as STRING. Callers must pass false to disable zonemap
47
    // min/max preparation and slot-IN rewrite that would otherwise rebuild child-typed literals from
48
    // dictionary codes.
49
    VDirectInPredicate(const TExprNode& node, const std::shared_ptr<HybridSetBase>& filter,
50
                       bool hybrid_set_values_match_child_type = true)
51
2.79k
            : VExpr(node),
52
2.79k
              _filter(filter),
53
2.79k
              _hybrid_set_values_match_child_type(hybrid_set_values_match_child_type),
54
2.79k
              _expr_name("direct_in_predicate") {}
55
2.80k
    ~VDirectInPredicate() override = default;
56
57
#ifdef BE_TEST
58
    VDirectInPredicate() = default;
59
#endif
60
61
    Status prepare(RuntimeState* state, const RowDescriptor& row_desc,
62
2.69k
                   VExprContext* context) override {
63
2.69k
        RETURN_IF_ERROR_OR_PREPARED(VExpr::prepare(state, row_desc, context));
64
2.69k
        _prepare_zonemap_min_max();
65
2.69k
        _prepare_finished = true;
66
2.69k
        return Status::OK();
67
2.69k
    }
68
69
    Status open(RuntimeState* state, VExprContext* context,
70
3.25k
                FunctionContext::FunctionStateScope scope) override {
71
3.25k
        DCHECK(_prepare_finished);
72
3.25k
        RETURN_IF_ERROR(VExpr::open(state, context, scope));
73
3.25k
        _open_finished = true;
74
3.25k
        return Status::OK();
75
3.25k
    }
76
77
    Status execute_column_impl(VExprContext* context, const Block* block, const Selector* selector,
78
54
                               size_t count, ColumnPtr& result_column) const override {
79
54
        return _do_execute(context, block, nullptr, selector, count, result_column, nullptr);
80
54
    }
81
82
    Status execute_runtime_filter(VExprContext* context, const Block* block,
83
                                  const uint8_t* __restrict filter, size_t count,
84
440
                                  ColumnPtr& result_column, ColumnPtr* arg_column) const override {
85
440
        return _do_execute(context, block, filter, nullptr, count, result_column, arg_column);
86
440
    }
87
88
2.68k
    const std::string& expr_name() const override { return _expr_name; }
89
90
2.67k
    std::shared_ptr<HybridSetBase> get_set_func() const override { return _filter; }
91
92
18
    ZoneMapFilterResult evaluate_zonemap_filter(const ZoneMapEvalContext& ctx) const override {
93
18
        DORIS_CHECK(_zonemap_min_max != nullptr);
94
18
        DORIS_CHECK(_filter != nullptr);
95
18
        return expr_zonemap::eval_in_zonemap(ctx, get_child(0), false, *_zonemap_min_max, *_filter);
96
18
    }
97
98
563
    bool can_evaluate_zonemap_filter() const override {
99
563
        return _zonemap_min_max != nullptr &&
100
563
               std::dynamic_pointer_cast<VSlotRef>(get_child(0)) != nullptr;
101
563
    }
102
103
    ZoneMapFilterResult evaluate_dictionary_filter(
104
4
            const DictionaryEvalContext& ctx) const override {
105
4
        DORIS_CHECK(_filter != nullptr);
106
4
        return expr_zonemap::eval_in_dictionary(ctx, get_child(0), false, *_filter);
107
4
    }
108
109
17
    bool can_evaluate_dictionary_filter() const override {
110
17
        return _zonemap_min_max != nullptr &&
111
17
               std::dynamic_pointer_cast<VSlotRef>(get_child(0)) != nullptr;
112
17
    }
113
114
    bool can_execute_on_raw_fixed_values(const DataTypePtr& data_type,
115
52
                                         int column_id) const override {
116
52
        if (!_hybrid_set_values_match_child_type || data_type == nullptr || _filter == nullptr ||
117
52
            get_num_children() != 1) {
118
0
            return false;
119
0
        }
120
52
        const auto slot = std::dynamic_pointer_cast<VSlotRef>(get_child(0));
121
52
        if (slot == nullptr || slot->column_id() != column_id) {
122
0
            return false;
123
0
        }
124
52
        const auto raw_type = remove_nullable(data_type);
125
52
        if (!remove_nullable(slot->data_type())->equals(*raw_type)) {
126
0
            return false;
127
0
        }
128
52
        return _raw_fixed_value_size(raw_type->get_primitive_type()) != 0;
129
52
    }
130
131
    // matches is an in/out selection mask forwarded to the HybridSet batch probe.
132
    Status execute_on_raw_fixed_values(
133
            const uint8_t* values, size_t num_values, size_t value_width,
134
            const DataTypePtr& data_type, int column_id,
135
7
            uint8_t* matches) const override { // NOLINT(readability-non-const-parameter)
136
7
        if (!can_execute_on_raw_fixed_values(data_type, column_id)) {
137
0
            return Status::NotSupported(
138
0
                    "Direct IN predicate cannot evaluate raw fixed-width values");
139
0
        }
140
7
        DORIS_CHECK(values != nullptr || num_values == 0);
141
7
        DORIS_CHECK(matches != nullptr || num_values == 0);
142
7
        const size_t expected_width =
143
7
                _raw_fixed_value_size(remove_nullable(data_type)->get_primitive_type());
144
7
        if (value_width != expected_width) {
145
0
            return Status::Corruption("Raw direct IN width {} does not match expected {}",
146
0
                                      value_width, expected_width);
147
0
        }
148
        // Dispatch once per decoder batch so large runtime-filter sets retain the typed HybridSet
149
        // loop instead of paying a virtual lookup for every physical value.
150
7
        _filter->find_batch_raw_fixed(values, num_values, value_width, matches);
151
7
        return Status::OK();
152
7
    }
153
154
    bool can_execute_on_raw_binary_values(const DataTypePtr& data_type,
155
9
                                          int column_id) const override {
156
9
        if (!_hybrid_set_values_match_child_type || data_type == nullptr || _filter == nullptr ||
157
9
            get_num_children() != 1) {
158
0
            return false;
159
0
        }
160
9
        const auto slot = std::dynamic_pointer_cast<VSlotRef>(get_child(0));
161
9
        if (slot == nullptr || slot->column_id() != column_id || slot->data_type() == nullptr) {
162
0
            return false;
163
0
        }
164
9
        return is_string_type(remove_nullable(data_type)->get_primitive_type()) &&
165
9
               is_string_type(remove_nullable(slot->data_type())->get_primitive_type());
166
9
    }
167
168
    // matches is an in/out selection mask forwarded to the HybridSet batch probe.
169
    Status execute_on_raw_binary_values(
170
            const StringRef* values, size_t num_values, const DataTypePtr& data_type, int column_id,
171
1
            uint8_t* matches) const override { // NOLINT(readability-non-const-parameter)
172
1
        if (!can_execute_on_raw_binary_values(data_type, column_id)) {
173
0
            return Status::NotSupported("Direct IN predicate cannot evaluate raw binary values");
174
0
        }
175
1
        DORIS_CHECK(values != nullptr || num_values == 0);
176
1
        DORIS_CHECK(matches != nullptr || num_values == 0);
177
        // Probe immutable decoder slices directly; constructing ColumnString first would copy
178
        // every rejected payload and defeat predicate-only late materialization.
179
1
        _filter->find_batch_raw_binary(values, num_values, matches);
180
1
        return Status::OK();
181
1
    }
182
183
4
    Status clone_node(VExprSPtr* cloned_expr) const override {
184
4
        DORIS_CHECK(cloned_expr != nullptr);
185
4
        auto cloned = VDirectInPredicate::create_shared(clone_texpr_node(), _filter,
186
4
                                                        _hybrid_set_values_match_child_type);
187
        // clone_node is never concurrent with prepare. Copy already-prepared bounds so file-local
188
        // split clones can prune without traversing the set again.
189
4
        cloned->_zonemap_min_max = _zonemap_min_max;
190
4
        *cloned_expr = std::move(cloned);
191
4
        return Status::OK();
192
4
    }
193
194
10
    bool get_slot_in_expr(VExprSPtr& new_root) const {
195
10
        if (!_hybrid_set_values_match_child_type) {
196
1
            return false;
197
1
        }
198
9
        if (!get_child(0)->is_slot_ref()) {
199
0
            return false;
200
0
        }
201
202
9
        auto* slot_ref = assert_cast<VSlotRef*>(get_child(0).get());
203
9
        auto slot_data_type = remove_nullable(slot_ref->data_type());
204
9
        {
205
9
            TTypeDesc type_desc = create_type_desc(PrimitiveType::TYPE_BOOLEAN);
206
9
            TExprNode node;
207
9
            node.__set_type(type_desc);
208
9
            node.__set_node_type(TExprNodeType::IN_PRED);
209
9
            node.in_predicate.__set_is_not_in(false);
210
9
            node.__set_opcode(TExprOpcode::FILTER_IN);
211
            // VdirectInPredicate assume is_nullable = false.
212
9
            node.__set_is_nullable(false);
213
9
            new_root = VInPredicate::create_shared(node);
214
9
        }
215
9
        {
216
            // add slot
217
9
            new_root->add_child(children().at(0));
218
9
        }
219
9
        {
220
9
            auto iter = get_set_func()->begin();
221
3.09k
            while (iter->has_next()) {
222
3.08k
                DCHECK(iter->get_value() != nullptr);
223
3.08k
                const void* value = iter->get_value();
224
225
3.08k
                TExprNode node = expr_zonemap::create_texpr_node_from_hybrid_set_value(
226
3.08k
                        value, slot_data_type->get_primitive_type(),
227
3.08k
                        slot_data_type->get_precision(), slot_data_type->get_scale());
228
3.08k
                new_root->add_child(VLiteral::create_shared(node));
229
3.08k
                iter->next();
230
3.08k
            }
231
9
        }
232
9
        return true;
233
9
    }
234
235
2.53k
    uint64_t get_digest(uint64_t seed) const override {
236
2.53k
        seed = _children[0]->get_digest(seed);
237
2.53k
        if (seed) {
238
2.53k
            return _filter->get_digest(seed);
239
2.53k
        }
240
18.4E
        return seed;
241
2.53k
    }
242
243
private:
244
59
    static size_t _raw_fixed_value_size(PrimitiveType primitive_type) {
245
59
        switch (primitive_type) {
246
0
#define RETURN_RAW_FIXED_SIZE(TYPE) \
247
56
    case TYPE:                      \
248
56
        return sizeof(typename PrimitiveTypeTraits<TYPE>::CppType)
249
1
            RETURN_RAW_FIXED_SIZE(TYPE_BOOLEAN);
250
8
            RETURN_RAW_FIXED_SIZE(TYPE_TINYINT);
251
1
            RETURN_RAW_FIXED_SIZE(TYPE_SMALLINT);
252
16
            RETURN_RAW_FIXED_SIZE(TYPE_INT);
253
1
            RETURN_RAW_FIXED_SIZE(TYPE_BIGINT);
254
1
            RETURN_RAW_FIXED_SIZE(TYPE_LARGEINT);
255
4
            RETURN_RAW_FIXED_SIZE(TYPE_FLOAT);
256
4
            RETURN_RAW_FIXED_SIZE(TYPE_DOUBLE);
257
1
            RETURN_RAW_FIXED_SIZE(TYPE_DATE);
258
1
            RETURN_RAW_FIXED_SIZE(TYPE_DATETIME);
259
1
            RETURN_RAW_FIXED_SIZE(TYPE_DATEV2);
260
1
            RETURN_RAW_FIXED_SIZE(TYPE_DATETIMEV2);
261
1
            RETURN_RAW_FIXED_SIZE(TYPE_TIMESTAMPTZ);
262
1
            RETURN_RAW_FIXED_SIZE(TYPE_TIMEV2);
263
1
            RETURN_RAW_FIXED_SIZE(TYPE_DECIMAL32);
264
8
            RETURN_RAW_FIXED_SIZE(TYPE_DECIMAL64);
265
1
            RETURN_RAW_FIXED_SIZE(TYPE_DECIMALV2);
266
1
            RETURN_RAW_FIXED_SIZE(TYPE_DECIMAL128I);
267
1
            RETURN_RAW_FIXED_SIZE(TYPE_DECIMAL256);
268
1
            RETURN_RAW_FIXED_SIZE(TYPE_IPV4);
269
1
            RETURN_RAW_FIXED_SIZE(TYPE_IPV6);
270
0
#undef RETURN_RAW_FIXED_SIZE
271
3
        default:
272
3
            return 0;
273
59
        }
274
59
    }
275
276
    // arg_column optionally returns the fully materialized argument column to the caller.
277
    Status _do_execute(VExprContext* context, const Block* block, const uint8_t* __restrict filter,
278
                       const Selector* selector, size_t count, ColumnPtr& result_column,
279
493
                       ColumnPtr* arg_column) const { // NOLINT(readability-non-const-parameter)
280
493
        DCHECK(_open_finished || block == nullptr);
281
18.4E
        DCHECK(!(filter != nullptr && selector != nullptr))
282
18.4E
                << "filter and selector can not be both set";
283
493
        ColumnPtr argument_column;
284
493
        RETURN_IF_ERROR(
285
493
                _children[0]->execute_column(context, block, selector, count, argument_column));
286
493
        argument_column = argument_column->convert_to_full_column_if_const();
287
288
493
        if (arg_column != nullptr) {
289
440
            *arg_column = argument_column;
290
440
        }
291
292
493
        size_t sz = argument_column->size();
293
493
        auto res_data_column = ColumnUInt8::create(sz);
294
493
        res_data_column->resize(sz);
295
296
493
        if (const auto* nullable = check_and_get_column<ColumnNullable>(argument_column.get())) {
297
481
            auto column_nested = nullable->get_nested_column_ptr();
298
481
            const auto& null_map = nullable->get_null_map_data();
299
481
            _filter->find_batch_nullable(*column_nested, sz, null_map, res_data_column->get_data(),
300
481
                                         filter);
301
481
        } else {
302
12
            _filter->find_batch(*argument_column, sz, res_data_column->get_data(), filter);
303
12
        }
304
305
493
        DCHECK(!_data_type->is_nullable());
306
493
        result_column = std::move(res_data_column);
307
493
        return Status::OK();
308
493
    }
309
310
2.69k
    void _prepare_zonemap_min_max() {
311
2.69k
        if (!_hybrid_set_values_match_child_type) {
312
1
            _zonemap_min_max.reset();
313
1
            return;
314
1
        }
315
2.69k
        if (_zonemap_min_max != nullptr) {
316
0
            return;
317
0
        }
318
2.69k
        DORIS_CHECK(_filter != nullptr);
319
2.69k
        const auto& data_type = remove_nullable(get_child(0)->data_type());
320
2.69k
        auto zonemap_min_max = std::make_shared<HybridSetMinMax>();
321
2.69k
        expr_zonemap::get_hybrid_set_min_max_for_zonemap_filter(_filter, data_type,
322
2.69k
                                                                *zonemap_min_max);
323
2.69k
        _zonemap_min_max = std::move(zonemap_min_max);
324
2.69k
    }
325
326
    std::shared_ptr<HybridSetBase> _filter;
327
    // Dictionary-filter rewrites may store physical dictionary codes in the HybridSet while the
328
    // child slot keeps the original logical type. Such values must not be interpreted as child-type
329
    // bounds for zonemap pruning or literals for slot-IN rewrite.
330
    bool _hybrid_set_values_match_child_type = true;
331
    std::string _expr_name;
332
    std::shared_ptr<const HybridSetMinMax> _zonemap_min_max;
333
};
334
335
} // namespace doris