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 |