Coverage Report

Created: 2026-09-28 21:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/storage/index/inverted/tokenizer/tokenizer.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 <unicode/utf8.h>
21
22
#include <algorithm>
23
#include <cstdint>
24
#include <functional>
25
#include <span>
26
#include <string_view>
27
#include <vector>
28
29
#include "storage/index/inverted/char_filter/char_filter.h"
30
#include "storage/index/inverted/token_stream.h"
31
32
namespace doris::segment_v2::inverted_index {
33
34
class DorisTokenizer : public Tokenizer, public DorisTokenStream {
35
public:
36
811
    DorisTokenizer() = default;
37
811
    ~DorisTokenizer() override = default;
38
39
3.88k
    void set_reader(const ReaderPtr& in) {
40
3.88k
        if (in == nullptr) {
41
0
            throw Exception(ErrorCode::INVALID_ARGUMENT, "reader must not be null");
42
0
        }
43
3.88k
        _in_pending = in;
44
3.88k
    }
45
46
    using Tokenizer::reset;
47
    // Only use the parameterless reset method
48
3.88k
    void reset() override {
49
3.88k
        _in = _in_pending;
50
3.88k
        _source_byte_offsets.clear();
51
3.88k
        _source_byte_end_offsets.clear();
52
3.88k
        release_oversized_scratch(_source_byte_offsets);
53
3.88k
        release_oversized_scratch(_source_byte_end_offsets);
54
3.88k
        release_oversized_scratch(_source_offsets_scratch);
55
3.88k
    };
56
57
92.7k
    std::span<const int32_t> get_source_byte_offsets() const override {
58
92.7k
        return _source_byte_offsets_enabled ? std::span<const int32_t> {_source_byte_offsets}
59
92.7k
                                            : std::span<const int32_t> {};
60
92.7k
    }
61
62
4.24k
    std::span<const int32_t> get_source_byte_end_offsets() const override {
63
4.24k
        return _source_byte_offsets_enabled ? std::span<const int32_t> {_source_byte_end_offsets}
64
4.24k
                                            : std::span<const int32_t> {};
65
4.24k
    }
66
67
99
    void set_source_byte_offsets_enabled(bool enabled) override {
68
99
        _source_byte_offsets_enabled = enabled;
69
99
    }
70
71
14
    size_t source_byte_offsets_capacity_for_test() const {
72
14
        return _source_byte_offsets.capacity() + _source_byte_end_offsets.capacity();
73
14
    }
74
75
protected:
76
309k
    int32_t correct_source_offset(int32_t offset) const {
77
309k
        const auto* char_filter = dynamic_cast<const DorisCharFilter*>(_in.get());
78
309k
        return char_filter == nullptr ? offset : char_filter->correct_offset(offset);
79
309k
    }
80
81
    // Correct the offset a term starts at; see DorisCharFilter::correct_start_offset().
82
308k
    int32_t correct_source_start_offset(int32_t offset) const {
83
308k
        const auto* char_filter = dynamic_cast<const DorisCharFilter*>(_in.get());
84
308k
        return char_filter == nullptr ? offset : char_filter->correct_start_offset(offset);
85
308k
    }
86
87
209k
    void set_source_byte_offsets(std::string_view term, int32_t source_start) {
88
209k
        set_source_byte_offsets(term, term, source_start);
89
209k
    }
90
91
    void set_source_byte_offsets(std::string_view term, std::string_view source,
92
210k
                                 int32_t source_start) {
93
210k
        _source_byte_offsets.clear();
94
210k
        _source_byte_end_offsets.clear();
95
210k
        if (!_source_byte_offsets_enabled) {
96
209k
            return;
97
209k
        }
98
99
151
        const auto* char_filter = dynamic_cast<const DorisCharFilter*>(_in.get());
100
151
        const int32_t corrected_start = char_filter == nullptr
101
151
                                                ? source_start
102
151
                                                : char_filter->correct_start_offset(source_start);
103
151
        std::vector<int32_t>& source_offsets = _source_offsets_scratch;
104
151
        source_offsets.clear();
105
151
        source_offsets.push_back(0);
106
151
        const char* data = source.data();
107
151
        const auto length = static_cast<int32_t>(source.size());
108
151
        int32_t offset = 0;
109
4.40M
        while (offset < length) {
110
4.40M
            UChar32 code_point;
111
4.40M
            U8_NEXT(data, offset, length, code_point);
112
4.40M
            if (code_point < 0) {
113
4
                return;
114
4
            }
115
4.40M
            source_offsets.push_back(char_filter == nullptr
116
4.40M
                                             ? offset
117
4.40M
                                             : char_filter->correct_offset(source_start + offset) -
118
31
                                                       corrected_start);
119
4.40M
        }
120
121
147
        const int32_t term_runes = count_utf8_runes(term);
122
147
        if (term_runes < 0) {
123
0
            return;
124
0
        }
125
147
        publish_source_byte_offsets(term_runes, source_offsets);
126
147
    }
127
128
    // Publish per-rune source boundaries for a term, widening repeated boundaries into
129
    // conservative start/end spans so no rune claims an empty source range. source_offsets is
130
    // the caller's reusable scratch: the common case swaps it with the published vector so
131
    // both keep their capacity and ordinary tokens stop allocating after warm-up.
132
4.26k
    void publish_source_byte_offsets(int32_t term_runes, std::vector<int32_t>& source_offsets) {
133
4.26k
        _source_byte_offsets.clear();
134
4.26k
        _source_byte_end_offsets.clear();
135
4.26k
        if (!_source_byte_offsets_enabled || term_runes < 0 || source_offsets.empty()) {
136
0
            return;
137
0
        }
138
4.26k
        if (static_cast<size_t>(term_runes + 1) == source_offsets.size()) {
139
4.26k
            const bool strictly_increasing =
140
4.26k
                    std::ranges::adjacent_find(source_offsets, std::greater_equal<>()) ==
141
4.26k
                    source_offsets.end();
142
4.26k
            if (strictly_increasing) {
143
4.26k
                _source_byte_offsets.swap(source_offsets);
144
4.26k
                return;
145
4.26k
            }
146
147
3
            _source_byte_offsets.resize(source_offsets.size());
148
3
            _source_byte_end_offsets.resize(term_runes);
149
10
            for (int32_t i = 0; i < term_runes; ++i) {
150
7
                int32_t start = source_offsets[i];
151
7
                int32_t end = source_offsets[i + 1];
152
7
                if (start == end) {
153
3
                    int32_t previous = i;
154
6
                    while (previous > 0 && source_offsets[previous] == start) {
155
3
                        --previous;
156
3
                    }
157
3
                    if (source_offsets[previous] != start) {
158
3
                        start = source_offsets[previous];
159
3
                    } else {
160
0
                        int32_t next = i + 1;
161
0
                        while (next < term_runes && source_offsets[next] == end) {
162
0
                            ++next;
163
0
                        }
164
0
                        end = source_offsets[next];
165
0
                    }
166
3
                }
167
7
                _source_byte_offsets[i] = start;
168
7
                _source_byte_end_offsets[i] = end;
169
7
            }
170
3
            _source_byte_offsets.back() = source_offsets.back();
171
3
            return;
172
4.26k
        }
173
174
0
        const int32_t source_length = source_offsets.back();
175
0
        _source_byte_offsets.assign(term_runes + 1, 0);
176
0
        _source_byte_offsets.back() = source_length;
177
0
        _source_byte_end_offsets.assign(term_runes, source_length);
178
0
    }
179
180
147
    static int32_t count_utf8_runes(std::string_view text) {
181
147
        const char* data = text.data();
182
147
        const auto length = static_cast<int32_t>(text.size());
183
147
        int32_t offset = 0;
184
147
        int32_t runes = 0;
185
4.40M
        while (offset < length) {
186
4.40M
            UChar32 code_point;
187
4.40M
            U8_NEXT(data, offset, length, code_point);
188
4.40M
            if (code_point < 0) {
189
0
                return -1;
190
0
            }
191
4.40M
            ++runes;
192
4.40M
        }
193
147
        return runes;
194
147
    }
195
196
    ReaderPtr _in;
197
    ReaderPtr _in_pending;
198
    std::vector<int32_t> _source_byte_offsets;
199
    std::vector<int32_t> _source_byte_end_offsets;
200
    std::vector<int32_t> _source_offsets_scratch;
201
    bool _source_byte_offsets_enabled {false};
202
};
203
using TokenizerPtr = std::shared_ptr<DorisTokenizer>;
204
205
} // namespace doris::segment_v2::inverted_index