Everett
Loading...
Searching...
No Matches
index_builder.h
Go to the documentation of this file.
1
13#pragma once
14
16
17#include <everett/sampling.h>
18
19#include <algorithm>
20#include <concepts>
21#include <cstddef>
22#include <cstdint>
23#include <limits>
24#include <memory>
25#include <optional>
26#include <stdexcept>
27#include <type_traits>
28#include <utility>
29#include <vector>
30
31namespace everett {
32 // Incremental ordinary-FC index construction over an unchanged native array.
33 // One incoming and one outgoing sample provide explicit backpressure. The
34 // source wrapper may expire: native_ retains its actual encoded allocation.
35 // step() budgets merged occurrences, not key bytes, allocations or final EF
36 // construction. Inputs must come from the exact target's trusted sampler;
37 // finish() checks its count but does not rescan it to authenticate every key.
38 // Coded handoff queues a backspace count, suffix and target ordinal. Backspace
39 // counts P units relative to the preceding emitted sample. The incoming decoder
40 // owns one reusable key: queued input until consumed, then the preceding
41 // borrowed key until the next push. The outgoing encoder owns its own context.
42 // Output has nonthrowing move assignment, starts empty/active and consumes
43 // append_known(key, exact_bit_lcp)
44 // synchronously. Its finish takes owning navigation metadata and may return
45 // an artifact or receipt. Any step/finalization failure poisons this builder.
46 template <class P, class Native, class Output>
48 using policy_type = P;
49 using native_type = Native;
50 using output_type = Output;
51 static_assert(std::same_as<typename Output::policy_type, P>);
52 static_assert(std::is_nothrow_move_assignable_v<Output>,
53 "index output move assignment must not throw");
54 static_assert(noexcept(std::declval<Output const &>().finished()));
55 static_assert(noexcept(std::declval<Output const &>().failed()));
56 static_assert(std::same_as<typename Native::policy_type, P>);
60 static constexpr std::uint64_t group_size = P::group_size;
61
62 explicit index_builder(blob_type const & source)
63 requires (std::same_as<Native, typename blob_type::native_array> &&
64 std::same_as<Output, profile_detail::index_output<P>>)
65 : index_builder(source.native_) {}
66
67 explicit index_builder(std::shared_ptr<Native const> native)
68 requires std::is_default_constructible_v<Output>
69 : index_builder(Output{}, std::move(native)) {}
70 index_builder(Output output, std::shared_ptr<Native const> native)
71 : native_(checked_native(std::move(native))), native_cursor_(native_->view()),
72 writer_(checked_output(std::move(output))) {}
73
74 index_builder(index_builder const &) = delete;
78
79 bool needs_input() const noexcept {
80 return native_ && !finished_ && !failed_ && !input_closed_ && !incoming_;
81 }
82 bool has_output() const noexcept { return bool(outgoing_); }
83 bool done() const noexcept {
85 }
86 bool finished() const noexcept { return finished_; }
87 bool failed() const noexcept { return failed_; }
88 std::uint64_t size() const noexcept { return virtual_count_; }
89 std::uint64_t received_samples() const noexcept { return received_; }
90
91 void push(bit_view key, std::uint64_t target_ordinal) {
93 if (input_mode_ == input_mode::coded) error_detail::raise<std::logic_error>("cannot mix coded and full index inputs");
94 push_key(key, target_ordinal);
96 }
97
98 void push(coded_sample_type const & sample) {
100 if (input_mode_ == input_mode::full) error_detail::raise<std::logic_error>("cannot mix coded and full index inputs");
102 auto comparison = incoming_decoder_.accept_compared(sample).second;
103 accept_key(comparison);
105 }
106
107 // EOF can accompany a final queued sample. An empty queue alone never
108 // authorizes consuming native keys: a later borrowed key may precede them.
109 void close_input() {
110 check_active();
111 if (input_closed_) error_detail::raise<std::logic_error>("index input is already closed");
112 input_closed_ = true;
113 }
114
115 std::uint64_t step(std::uint64_t budget_entries) {
116 check_active();
117 std::uint64_t consumed = 0;
118 try {
119 while (consumed < budget_entries && !outgoing_) {
120 if ((!incoming_ && !input_closed_) || (!incoming_ && native_cursor_.done())) break;
121 auto comparison = compare_heads();
122 auto take_borrowed = comparison.order > 0;
123 if (incoming_ && !comparison.order) incoming_false_ = true;
124 auto key = take_borrowed ? incoming_decoder_.key() : native_cursor_.peek().key.prefix;
125 auto edge = take_borrowed ? incoming_common_ : native_common_;
126 outgoing_common_ = std::min(outgoing_common_, edge);
127 if (borrowed_count_) pending_common_ = std::min(pending_common_, edge);
128 auto boundary = virtual_count_ % group_size == 0;
129 if (boundary) {
130 outgoing_.emplace(outgoing_encoder_.encode_known(key, virtual_count_, outgoing_common_));
131 outgoing_common_ = key.size();
133 current_class_ = 0;
135 }
136 if (take_borrowed) {
137 // Output borrows this key only for the call. It is encoded now,
138 // before the decoder can replace its suffix on a later push.
139 writer_.append_known(key, incoming_borrowed_common_);
140 incoming_ = false;
142 incoming_false_ = false;
143 pending_common_ = key.size();
144 native_common_ = comparison.common_bits;
145 auto ordinal = borrowed_count_++;
146 if ((ordinal & 7) == 0) false_borrows_.push_back(std::byte{0});
147 if (previous_false_) false_borrows_.back() |= std::byte(1u << (ordinal & 7));
149 } else {
150 incoming_common_ = comparison.common_bits;
152 native_common_ = next ? next->common_bits : 0;
153 }
155 ++consumed;
156 }
157 } catch (...) {
158 // Partial encoding after an allocation/codec failure is private and
159 // cannot be resumed or published as though the failed step committed.
160 failed_ = true;
161 outgoing_.reset();
162 throw;
163 }
164 return consumed;
165 }
166
168 check_active();
169 if (!outgoing_) error_detail::raise<std::logic_error>("index builder has no outgoing sample");
170 // Compatibility path: materialize a full queued key only when requested.
171 sample_type result{bit_string::copy(outgoing_encoder_.key()), outgoing_->target_ordinal};
172 outgoing_.reset();
173 return result;
174 }
175
176 // Frames depend on every preceding emitted sample, including any retrieved
177 // through the full-key compatibility interface. Consume coded frames in order.
179 check_active();
180 if (!outgoing_) error_detail::raise<std::logic_error>("index builder has no outgoing sample");
181 auto result = std::move(*outgoing_);
182 outgoing_.reset();
183 return result;
184 }
185
186 // Finalizes encoded offset/rank metadata; this can perform linear work.
187 // A nonempty sampled stream requires the exact completed target pair.
188 // Empty targets may be retained too. No durable publication is implied.
189 blob_type finish(std::shared_ptr<blob_type const> target = {})
190 requires (std::same_as<Native, typename blob_type::native_array> &&
191 std::same_as<Output, profile_detail::index_output<P>>) {
192 auto index = finish_index(target ? target->virtual_size() : 0);
193 try {
194 blob_type result;
195 result.native_ = native_;
196 result.borrowed_ = std::move(index.borrowed_);
197 result.interleave_ = std::move(index.interleave_);
198 result.false_borrows_ = std::move(index.false_borrows_);
199 result.virtual_count_ = index.virtual_count_;
200 result.cut_lcps_ = std::move(index.cut_lcps_);
201 result.target_ = std::move(target);
202 return result;
203 } catch (...) {
204 failed_ = true;
205 throw;
206 }
207 }
208
209 // Keep native bytes in their original storage. The extent authenticates
210 // only the sample count; the caller supplies samples from the exact target
211 // and retains source/target pins until the resulting index is published.
212 auto finish_index(std::uint64_t target_count)
213 -> decltype(std::declval<Output &>().finish(std::declval<profile_detail::index_metadata<P>>())) {
214 check_active();
215 if (!done()) error_detail::raise<std::logic_error>("index builder is not drained at EOF");
216 auto expected = target_count / group_size + (target_count % group_size != 0);
217 if (expected != received_) error_detail::raise<std::invalid_argument>("index samples disagree with target extent");
218 try {
219 // Completed groups are already packed. Admit the final partial group
220 // before irreversible final output writes.
221 if (virtual_count_) {
222 auto tail = virtual_count_ % group_size;
223 classes_.append(current_class_, tail ? tail : group_size);
224 }
225 auto interleave = classes_.finish();
226 profile_detail::index_metadata<P> metadata{std::move(interleave),
227 std::move(false_borrows_), std::move(cut_lcps_), virtual_count_};
228 auto result = writer_.finish(std::move(metadata));
229 finished_ = true;
230 return result;
231 } catch (...) {
232 failed_ = true;
233 throw;
234 }
235 }
236
237 private:
238 enum class input_mode { unset, full, coded };
239
240 std::shared_ptr<Native const> native_;
242 Output writer_;
243 // Queued: received == borrowed_count + 1; otherwise they are equal.
244 // The decoder holds the queued key or the last consumed borrowed key.
245 // Output has already encoded exactly borrowed_count successful records.
246 bool incoming_ = false;
247 std::optional<coded_sample_type> outgoing_;
251 std::uint64_t current_class_ = 0;
252 std::vector<std::byte> false_borrows_;
253 std::vector<std::uint64_t> cut_lcps_;
254 std::uint64_t virtual_count_ = 0;
255 std::uint64_t borrowed_count_ = 0;
256 std::uint64_t received_ = 0;
257 // Exact bit LCPs from the last consumed merged key to each live head.
258 std::uint64_t native_common_ = 0, incoming_common_ = 0;
259 // Minima of adjacent merged-key LCPs since each retained anchor. Sorted
260 // strings make these the exact LCPs with the last consumed merged key.
261 std::uint64_t outgoing_common_ = 0, pending_common_ = 0;
262 std::uint64_t incoming_borrowed_common_ = 0;
263 input_mode input_mode_ = input_mode::unset;
264 bool incoming_false_ = false;
265 bool previous_false_ = false;
266 bool input_closed_ = false;
267 bool finished_ = false;
268 bool failed_ = false;
269
270 void check_active() const {
271 if (!native_ || finished_ || failed_) error_detail::raise<std::logic_error>("index builder is no longer active");
272 }
273 static Output checked_output(Output output) {
274 if (output.size() || output.finished() || output.failed())
275 error_detail::raise<std::invalid_argument>("index builder requires an empty active output");
276 return output;
277 }
278 static std::shared_ptr<Native const> checked_native(std::shared_ptr<Native const> native) {
279 if (!native) error_detail::raise<std::invalid_argument>("index builder requires a pinned native source");
280 return native;
281 }
282 void check_input_slot(std::uint64_t target_ordinal) const {
283 if (!needs_input()) error_detail::raise<std::logic_error>("index builder cannot accept another lookahead");
284 if (received_ > std::numeric_limits<std::uint64_t>::max() / group_size ||
285 target_ordinal != received_ * group_size)
286 error_detail::raise<std::invalid_argument>("index samples must name consecutive target groups");
287 if (received_ == std::numeric_limits<std::uint64_t>::max() - native_->size())
288 error_detail::raise<std::length_error>("index augmented count overflows");
289 }
290 void push_key(bit_view key, std::uint64_t target_ordinal) {
291 check_input_slot(target_ordinal);
292 auto comparison = incoming_decoder_.accept_full(key, target_ordinal);
293 accept_key(comparison);
294 }
295 // A new input is admitted only after its predecessor was consumed. The
296 // decoder therefore compares against the last borrowed key, even though
297 // the active merged frontier can advance through native keys afterward.
298 void accept_key(bit_comparison comparison) noexcept {
299 incoming_ = true;
300 incoming_false_ = !comparison.order && previous_false_;
301 incoming_common_ = incoming_borrowed_common_ = comparison.common_bits;
302 ++received_;
303 }
305 if (!incoming_) return {0, -1};
306 if (native_cursor_.done()) return {0, 1};
307 if (native_common_ != incoming_common_)
308 return {std::min(native_common_, incoming_common_), native_common_ > incoming_common_ ? -1 : 1};
309 auto native = native_cursor_.peek().key.prefix;
310 auto incoming = incoming_decoder_.key();
311 // Revisit at most seven already equal bits to keep byte-aligned loads.
312 auto start = native_common_ & ~std::uint64_t{7};
313 if (!start) return compare_common_bits<typename P::architecture>(native, incoming);
314 auto comparison = compare_common_bits<typename P::architecture>(native.subview(start, native.size() - start),
315 incoming.subview(start, incoming.size() - start));
316 comparison.common_bits += start;
317 return comparison;
318 }
319
320 };
321}
Outlines exceptional check failures while preserving their types and messages.
Definition active_engine.h:18
Declares Everett's sequential sampling of pinned encoded blob pairs.
Definition profile.h:209
static bit_string copy(bit_view source)
Definition profile.h:181
Definition profile.h:56
Definition index_builder.h:47
static constexpr std::uint64_t group_size
Definition index_builder.h:60
std::vector< std::byte > false_borrows_
Definition index_builder.h:252
profile_sample_decoder< P > incoming_decoder_
Definition index_builder.h:249
index_builder(Output output, std::shared_ptr< Native const > native)
Definition index_builder.h:70
Output writer_
Definition index_builder.h:242
input_mode input_mode_
Definition index_builder.h:263
index_builder(index_builder const &)=delete
void push(coded_sample_type const &sample)
Definition index_builder.h:98
void check_input_slot(std::uint64_t target_ordinal) const
Definition index_builder.h:282
void push(bit_view key, std::uint64_t target_ordinal)
Definition index_builder.h:91
P policy_type
Definition index_builder.h:48
std::uint64_t incoming_borrowed_common_
Definition index_builder.h:262
bit_comparison compare_heads() const
Definition index_builder.h:304
std::uint64_t received_
Definition index_builder.h:256
bool failed_
Definition index_builder.h:268
std::optional< coded_sample_type > outgoing_
Definition index_builder.h:247
Native native_type
Definition index_builder.h:49
std::shared_ptr< Native const > native_
Definition index_builder.h:240
std::uint64_t size() const noexcept
Definition index_builder.h:88
profile_cursor< P, stream_role::native > native_cursor_
Definition index_builder.h:241
bool needs_input() const noexcept
Definition index_builder.h:79
std::uint64_t native_common_
Definition index_builder.h:258
Output output_type
Definition index_builder.h:50
index_builder & operator=(index_builder const &)=delete
std::uint64_t step(std::uint64_t budget_entries)
Definition index_builder.h:115
bool finished_
Definition index_builder.h:267
bool incoming_false_
Definition index_builder.h:264
std::uint64_t incoming_common_
Definition index_builder.h:258
auto finish_index(std::uint64_t target_count) -> decltype(std::declval< Output & >().finish(std::declval< profile_detail::index_metadata< P > >()))
Definition index_builder.h:212
void push_key(bit_view key, std::uint64_t target_ordinal)
Definition index_builder.h:290
std::uint64_t pending_common_
Definition index_builder.h:261
sample_type take_output()
Definition index_builder.h:167
std::uint64_t virtual_count_
Definition index_builder.h:254
bool finished() const noexcept
Definition index_builder.h:86
static Output checked_output(Output output)
Definition index_builder.h:273
profile_sample_encoder< P > outgoing_encoder_
Definition index_builder.h:248
bool failed() const noexcept
Definition index_builder.h:87
index_builder(index_builder &&)=default
static std::shared_ptr< Native const > checked_native(std::shared_ptr< Native const > native)
Definition index_builder.h:278
bool done() const noexcept
Definition index_builder.h:83
blob_type finish(std::shared_ptr< blob_type const > target={})
Definition index_builder.h:189
void close_input()
Definition index_builder.h:109
std::uint64_t outgoing_common_
Definition index_builder.h:261
std::uint64_t borrowed_count_
Definition index_builder.h:255
bool has_output() const noexcept
Definition index_builder.h:82
index_builder(std::shared_ptr< Native const > native)
Definition index_builder.h:67
bool previous_false_
Definition index_builder.h:265
void check_active() const
Definition index_builder.h:270
void accept_key(bit_comparison comparison) noexcept
Definition index_builder.h:298
bool incoming_
Definition index_builder.h:246
std::vector< std::uint64_t > cut_lcps_
Definition index_builder.h:253
index_builder(blob_type const &source)
Definition index_builder.h:62
rank_groups_builder< group_size > classes_
Definition index_builder.h:250
coded_sample_type take_coded_output()
Definition index_builder.h:178
std::uint64_t current_class_
Definition index_builder.h:251
bool input_closed_
Definition index_builder.h:266
input_mode
Definition index_builder.h:238
std::uint64_t received_samples() const noexcept
Definition index_builder.h:89
index_builder & operator=(index_builder &&)=default
Definition profile_blob.h:187
std::shared_ptr< native_array const > native_
Definition profile_blob.h:271
Definition sampling.h:46
std::uint64_t target_ordinal
Definition sampling.h:50
Definition profile.h:1066
bool done() const noexcept
Definition profile.h:1091
profile_item< P > peek() const &
Definition profile.h:1093
std::optional< bit_comparison > advance_comparison()
Definition profile.h:1108
Definition profile_index.h:28
Definition profile_index.h:86
Definition sampling.h:119
bit_comparison accept_full(bit_view key, std::uint64_t ordinal)
Definition sampling.h:132
bit_view key() const &
Definition sampling.h:124
Definition sampling.h:82
Definition sampling.h:29
Definition rank_groups.h:283
void append(std::uint64_t population, std::uint64_t width=K)
Definition rank_groups.h:305