Coverage Report

Created: 2026-09-20 12:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/storage/predicate/like_column_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
#pragma once
18
19
#include <glog/logging.h>
20
#include <stdint.h>
21
22
#include <boost/iterator/iterator_facade.hpp>
23
#include <functional>
24
#include <memory>
25
#include <ostream>
26
#include <string>
27
#include <string_view>
28
#include <utility>
29
30
#include "common/status.h"
31
#include "core/column/column.h"
32
#include "core/column/column_dictionary.h"
33
#include "core/column/column_nullable.h"
34
#include "core/string_ref.h"
35
#include "core/types.h"
36
#include "exprs/function/like.h"
37
#include "storage/index/bloom_filter/bloom_filter.h"
38
#include "storage/predicate/column_predicate.h"
39
40
namespace roaring {
41
class Roaring;
42
} // namespace roaring
43
44
namespace doris {
45
class FunctionContext;
46
47
class LikeColumnPredicate final : public ColumnPredicate {
48
public:
49
    ENABLE_FACTORY_CREATOR(LikeColumnPredicate);
50
    LikeColumnPredicate(bool opposite, uint32_t column_id, std::string col_name,
51
                        doris::FunctionContext* fn_ctx, doris::StringRef val);
52
0
    ~LikeColumnPredicate() override = default;
53
    LikeColumnPredicate(const LikeColumnPredicate& other, uint32_t col_id)
54
0
            : ColumnPredicate(other, col_id) {
55
0
        _origin = other._origin;
56
0
        pattern = other.pattern;
57
0
        _state = other._state;
58
0
        _opposite = other._opposite;
59
0
        THROW_IF_ERROR(_state->search_state.clone(_like_state));
60
0
    }
61
    LikeColumnPredicate(const LikeColumnPredicate& other) = delete;
62
0
    std::shared_ptr<ColumnPredicate> clone(uint32_t col_id) const override {
63
0
        return LikeColumnPredicate::create_shared(*this, col_id);
64
0
    }
65
0
    std::string debug_string() const override {
66
0
        fmt::memory_buffer debug_string_buffer;
67
0
        fmt::format_to(debug_string_buffer, "LikeColumnPredicate({}, pattern={}, origin={})",
68
0
                       ColumnPredicate::debug_string(), pattern, _origin);
69
0
        return fmt::to_string(debug_string_buffer);
70
0
    }
71
72
0
    PredicateType type() const override { return PredicateType::LIKE; }
73
    void evaluate_vec(const IColumn& column, uint16_t size, bool* flags) const override;
74
75
    void evaluate_and_vec(const IColumn& column, uint16_t size, bool* flags) const override;
76
77
0
    std::string get_search_str() const override {
78
0
        return std::string(reinterpret_cast<const char*>(pattern.data), pattern.size);
79
0
    }
80
0
    bool is_opposite() const { return _opposite; }
81
82
0
    void set_page_ng_bf(std::unique_ptr<segment_v2::BloomFilter> src) override {
83
0
        _page_ng_bf = std::move(src);
84
0
    }
85
0
    bool evaluate_and(const BloomFilter* bf) const override {
86
        // like predicate can not use normal bf, just return true to accept
87
0
        if (!bf->is_ngram_bf()) return true;
88
0
        if (_page_ng_bf) {
89
0
            return bf->contains(*_page_ng_bf);
90
0
        }
91
0
        return true;
92
0
    }
93
0
    bool can_do_bloom_filter(bool ngram) const override {
94
0
        if (!ngram) {
95
0
            return false;
96
0
        }
97
        // A pattern that can carry an escape is not supported by the ngram index.
98
0
        if (_state->has_custom_escape) {
99
0
            return false;
100
0
        }
101
0
        return std::string_view(reinterpret_cast<const char*>(pattern.data), pattern.size)
102
0
                       .find('\\') == std::string_view::npos;
103
0
    }
104
105
private:
106
    uint16_t _evaluate_inner(const IColumn& column, uint16_t* sel, uint16_t size) const override;
107
108
    template <bool is_and>
109
0
    void _evaluate_vec(const IColumn& column, uint16_t size, bool* flags) const {
110
0
        if (is_column_nullable(column)) {
111
0
            auto* nullable_col = assert_cast<const ColumnNullable*>(&column);
112
0
            auto& null_map_data = nullable_col->get_null_map_column().get_data();
113
0
            auto& nested_col = nullable_col->get_nested_column();
114
0
            if (nested_col.is_column_dictionary()) {
115
0
                auto* nested_col_ptr = assert_cast<const ColumnDictI32*>(&nested_col);
116
0
                const auto& dict_res = _find_code_from_dictionary_column(*nested_col_ptr);
117
0
                auto& data_array = nested_col_ptr->get_data();
118
0
                for (uint16_t i = 0; i < size; i++) {
119
0
                    if (null_map_data[i]) {
120
0
                        if constexpr (is_and) {
121
0
                            flags[i] &= _opposite;
122
0
                        } else {
123
0
                            flags[i] = _opposite;
124
0
                        }
125
0
                        continue;
126
0
                    }
127
128
0
                    unsigned char flag = dict_res[data_array[i]];
129
0
                    if constexpr (is_and) {
130
0
                        flags[i] &= _opposite ^ flag;
131
0
                    } else {
132
0
                        flags[i] = _opposite ^ flag;
133
0
                    }
134
0
                }
135
0
            } else {
136
0
                throw Exception(Status::FatalError(
137
0
                        "vectorized (not) like predicates should be dict column"));
138
0
            }
139
0
        } else {
140
0
            if (column.is_column_dictionary()) {
141
0
                auto* nested_col_ptr = assert_cast<const ColumnDictI32*>(&column);
142
0
                auto& data_array = nested_col_ptr->get_data();
143
0
                const auto& dict_res = _find_code_from_dictionary_column(*nested_col_ptr);
144
0
                for (uint16_t i = 0; i < size; i++) {
145
0
                    unsigned char flag = dict_res[data_array[i]];
146
0
                    if constexpr (is_and) {
147
0
                        flags[i] &= _opposite ^ flag;
148
0
                    } else {
149
0
                        flags[i] = _opposite ^ flag;
150
0
                    }
151
0
                }
152
0
            } else {
153
0
                throw Exception(Status::FatalError(
154
0
                        "vectorized (not) like predicates should be dict column"));
155
0
            }
156
0
        }
157
0
    }
Unexecuted instantiation: _ZNK5doris19LikeColumnPredicate13_evaluate_vecILb0EEEvRKNS_7IColumnEtPb
Unexecuted instantiation: _ZNK5doris19LikeColumnPredicate13_evaluate_vecILb1EEEvRKNS_7IColumnEtPb
158
    std::vector<bool> __attribute__((flatten))
159
0
    _find_code_from_dictionary_column(const ColumnDictI32& column) const {
160
0
        std::vector<bool> res;
161
0
        if (_segment_id_to_cached_res_flags.if_contains(
162
0
                    column.get_rowset_segment_id(),
163
0
                    [&res](const auto& pair) { res = pair.second; })) {
164
0
            return res;
165
0
        }
166
167
0
        std::vector<bool> tmp_res(column.dict_size(), false);
168
0
        for (int i = 0; i < column.dict_size(); i++) {
169
0
            StringRef cell_value = column.get_value(i);
170
0
            unsigned char flag = 0;
171
0
            THROW_IF_ERROR((_state->scalar_function)(
172
0
                    &_like_state, StringRef(cell_value.data, cell_value.size), pattern, &flag));
173
0
            tmp_res[i] = flag;
174
0
        }
175
        // Sometimes the dict is not initialized when run comparison predicate here, for example,
176
        // the full page is null, then the reader will skip read, so that the dictionary is not
177
        // inited. The cached code is wrong during this case, because the following page maybe not
178
        // null, and the dict should have items in the future.
179
        //
180
        // Cached code may have problems, so that add a config here, if not opened, then
181
        // we will return the code and not cache it.
182
0
        if (!column.is_dict_empty() && config::enable_low_cardinality_cache_code) {
183
0
            _segment_id_to_cached_res_flags.emplace(
184
0
                    std::pair {column.get_rowset_segment_id(), tmp_res});
185
0
        }
186
187
0
        return tmp_res;
188
0
    }
189
190
    mutable phmap::parallel_flat_hash_map<
191
            std::pair<RowsetId, uint32_t>, std::vector<bool>,
192
            phmap::priv::hash_default_hash<std::pair<RowsetId, uint32_t>>,
193
            phmap::priv::hash_default_eq<std::pair<RowsetId, uint32_t>>,
194
            std::allocator<std::pair<const std::pair<RowsetId, uint32_t>, int32_t>>, 4,
195
            std::shared_mutex>
196
            _segment_id_to_cached_res_flags;
197
198
    std::string _origin;
199
    // lifetime controlled by scan node
200
    using StateType = LikeState;
201
    StringRef pattern;
202
203
    StateType* _state = nullptr;
204
205
    // A separate scratch region is required for every concurrent caller of the
206
    // Hyperscan API. So here _like_state is separate for each instance of
207
    // LikeColumnPredicate.
208
    LikeSearchState _like_state;
209
    std::shared_ptr<segment_v2::BloomFilter> _page_ng_bf; // for ngram-bf index
210
};
211
212
} // namespace doris