Everett
Loading...
Searching...
No Matches
query.h
Go to the documentation of this file.
1
13#pragma once
14
16
18
19#include <cstddef>
20#include <cstdint>
21#include <limits>
22#include <memory>
23#include <optional>
24#include <stdexcept>
25#include <type_traits>
26#include <unordered_set>
27#include <utility>
28#include <vector>
29
30namespace everett {
31 template <class P, class Blob = profile_blob<P>> struct query_root;
32 template <class P> struct query_root_builder;
33 template <class P, class Blob = profile_blob<P>> struct query_cursor;
34
35 // One native segment. Source identity is the exact pinned pair; encounter
36 // order in a catalog chain does not establish chronological composition.
37 template <class P, class Blob = profile_blob<P>> struct query_match {
38 using policy_type = P;
39 using blob_type = Blob;
40 using pair_type = std::shared_ptr<blob_type const>;
42 std::uint64_t ordinal = 0;
44 };
45
46 // A prepared immutable chain whose augmented head fits in one K-entry window.
47 // Exact sample contents remain a construction precondition: metadata checks
48 // cannot authenticate arbitrary equal-count samples pushed to index_builder.
49 // index_pipeline supplies samples from its pinned producer. Objects retained
50 // here must remain immutable through every alias for the owner's lifetime.
51 template <class P, class Blob> struct query_root {
52 using policy_type = P;
53 using blob_type = Blob;
54 using pair_type = std::shared_ptr<blob_type const>;
55 static_assert(std::is_same_v<typename blob_type::policy_type, P>);
56
57 static query_root build(pair_type source) requires std::is_same_v<Blob, profile_blob<P>>;
58 // An owner has already prepared this bounded routing head and bound exact
59 // dependencies. Adoption checks chain shapes and cycles, never sample keys.
61 validate(source);
62 if (source->virtual_size() > P::group_size)
63 error_detail::raise<std::invalid_argument>("query root head exceeds one cascade group");
64 return query_root(std::move(source));
65 }
66 pair_type head() const noexcept { return head_; }
68
69 private:
70 friend struct query_root_builder<P>;
71 explicit query_root(pair_type head) : head_(std::move(head)) {}
73
74 static void validate(pair_type const & source) {
75 static_assert(P::group_size >= 3);
76 if (!source) error_detail::raise<std::invalid_argument>("null query root");
77 std::unordered_set<blob_type const *> seen;
78 for (auto current = source; current; current = current->target()) {
79 if (!seen.insert(current.get()).second) error_detail::raise<std::invalid_argument>("cyclic query chain");
80 auto native = current->native().size(), borrowed = current->borrowed().size();
81 if (native > std::numeric_limits<std::uint64_t>::max() - borrowed ||
82 native + borrowed != current->virtual_size())
83 error_detail::raise<std::invalid_argument>("query catalog size mismatch");
84 auto groups = current->virtual_size() / P::group_size +
85 (current->virtual_size() % P::group_size != 0);
86 if (current->group_count() != groups)
87 error_detail::raise<std::invalid_argument>("query catalog group count mismatch");
88 auto target = current->target();
89 if (borrowed != (target ? target->group_count() : 0))
90 error_detail::raise<std::invalid_argument>("query chain sample count or target mismatch");
91 if (current->cut_lcps().size() != current->group_count())
92 error_detail::raise<std::invalid_argument>("query chain cut LCP count mismatch");
93 }
94 }
95 };
96
97 // Shape validation visits the existing chain once without decoding its keys.
98 // A head already of size <= K keeps its exact identity and needs no sampler.
99 // Larger heads acquire empty-native routing catalogs through one pipeline:
100 // a source scan and successively ceil(size/K) samples, with no full-key array.
101 // step() inherits the pipeline's entry-work budget; bytes are not bounded by
102 // that budget, and finish() separately finalizes compact metadata.
103 template <class P> struct query_root_builder {
104 using policy_type = P;
106 using pair_type = std::shared_ptr<blob_type const>;
107
108 explicit query_root_builder(pair_type source) : head_(std::move(source)) {
110 auto count = head_->virtual_size();
111 std::size_t levels = 0;
112 while (count > P::group_size) {
113 count = count / P::group_size + (count % P::group_size != 0);
114 ++levels;
115 }
116 if (levels) {
117 auto empty = std::make_shared<blob_type const>(blob_type::build({}));
118 pipeline_.emplace(head_, std::vector<pair_type>(levels, std::move(empty)));
119 }
120 }
125
126 bool done() const noexcept { return !pipeline_ || pipeline_->done(); }
127 bool finished() const noexcept { return finished_; }
128 std::uint64_t step(std::uint64_t quanta) {
129 if (!head_) error_detail::raise<std::logic_error>("query root preparation has no source");
130 if (finished_) error_detail::raise<std::logic_error>("query root preparation is finished");
131 return pipeline_ ? pipeline_->step(quanta) : 0;
132 }
134 if (!head_) error_detail::raise<std::logic_error>("query root preparation has no source");
135 if (!done()) error_detail::raise<std::logic_error>("query root preparation still has input");
136 if (!finished_) {
137 if (pipeline_) head_ = pipeline_->finish();
138 finished_ = true;
139 }
140 return query_root<P>(head_);
141 }
142
143 private:
145 std::optional<index_pipeline<P>> pipeline_;
146 bool finished_ = false;
147 };
148
149 // The cursor owns its query and current target pin. Each successful step
150 // visits at most catalog_budget catalogs and stops at its first native match.
151 // Take that owned match before stepping again. Equal native keys do not stop
152 // descent: the downstream route is retained for the next call. Visited pairs
153 // can be released unless a pending/returned match owns them. Copying a cursor
154 // shares its immutable query and copies its context and pending value; copies advance independently
155 // while sharing the same immutable suffix and source pins.
156 //
157 // Search costs O(K+W) entry/header work per visited catalog plus literal
158 // comparison and value copying. Root preparation adds O(log_K A) catalogs for a head
159 // of A entries. These bounds do not cover arbitrary string bytes, arrow
160 // evaluation, disk faults, or a future level scheduler.
161 template <class P, class Blob> struct query_cursor {
162 using policy_type = P;
163 using blob_type = Blob;
164 using pair_type = std::shared_ptr<blob_type const>;
166
167 explicit query_cursor(query_root<P, Blob> const & root, bit_view query)
168 : current_(root.head()), context_(query) {
169 if (!current_) error_detail::raise<std::invalid_argument>("query root has no head");
170 if (!current_->virtual_size()) current_.reset();
171 }
172 bool done() const noexcept { return !current_ && !pending_; }
173 bool has_match() const noexcept { return pending_.has_value(); }
174 bool failed() const noexcept { return failed_; }
175
176 std::uint64_t step(std::uint64_t catalog_budget = 1) {
177 if (failed_) error_detail::raise<std::logic_error>("query cursor has failed");
178 if (pending_) return 0;
179 std::uint64_t visited = 0;
180 try {
181 while (current_ && visited != catalog_budget) {
182 auto result = current_->search_window(group_, context_);
183 auto target = current_->target();
184 if (result.borrowed_predecessor) {
185 auto const & next = *result.borrowed_predecessor;
186 if (!target || next.target_ordinal % P::group_size ||
187 next.target_ordinal >= target->virtual_size())
188 error_detail::raise<std::invalid_argument>("query descent has no valid target context");
189 }
190 if (result.native)
191 pending_.emplace(match_type{current_, result.native->ordinal, std::move(result.native->value)});
192 if (result.borrowed_predecessor) {
193 auto & next = *result.borrowed_predecessor;
194 group_ = next.target_ordinal / P::group_size;
195 context_ = std::move(next.comparison);
196 current_ = std::move(target);
197 } else {
198 // The first borrowed key samples the target's minimum. No borrowed
199 // predecessor means no downstream native key can equal this query.
200 current_.reset();
201 }
202 ++visited;
203 if (pending_) break;
204 }
205 } catch (...) {
206 failed_ = true;
207 throw;
208 }
209 return visited;
210 }
212 if (failed_) error_detail::raise<std::logic_error>("query cursor has failed");
213 if (!pending_) error_detail::raise<std::logic_error>("query cursor has no pending match");
214 auto result = std::move(*pending_);
215 pending_.reset();
216 return result;
217 }
218
219 private:
222 std::uint64_t group_ = 0;
223 std::optional<match_type> pending_;
224 bool failed_ = false;
225 };
226
227 template <class P, class Blob>
229 requires std::is_same_v<Blob, profile_blob<P>> {
230 query_root_builder<P> builder(std::move(source));
231 while (!builder.done()) builder.step(4096);
232 return builder.finish();
233 }
234 template <class P, class Blob>
238}
Outlines exceptional check failures while preserving their types and messages.
Builds fractional-index chains through bounded streaming queues.
Definition active_engine.h:18
Definition profile.h:166
Definition profile.h:56
Definition profile_blob.h:187
static profile_blob build(std::span< profile_record const > native, std::span< bit_string const > borrowed={})
Definition profile_blob.h:193
Definition profile.h:328
Definition query.h:161
std::shared_ptr< blob_type const > pair_type
Definition query.h:164
P policy_type
Definition query.h:162
std::uint64_t step(std::uint64_t catalog_budget=1)
Definition query.h:176
bool failed() const noexcept
Definition query.h:174
bool done() const noexcept
Definition query.h:172
std::uint64_t group_
Definition query.h:222
pair_type current_
Definition query.h:220
query_cursor(query_root< P, Blob > const &root, bit_view query)
Definition query.h:167
match_type take_match()
Definition query.h:211
bool has_match() const noexcept
Definition query.h:173
std::optional< match_type > pending_
Definition query.h:223
bool failed_
Definition query.h:224
Blob blob_type
Definition query.h:163
profile_query_context< P > context_
Definition query.h:221
Definition query.h:37
P policy_type
Definition query.h:38
std::uint64_t ordinal
Definition query.h:42
bit_string value
Definition query.h:43
Blob blob_type
Definition query.h:39
std::shared_ptr< blob_type const > pair_type
Definition query.h:40
pair_type source
Definition query.h:41
Definition query.h:103
std::shared_ptr< blob_type const > pair_type
Definition query.h:106
query_root< P > finish()
Definition query.h:133
query_root_builder & operator=(query_root_builder const &)=delete
bool done() const noexcept
Definition query.h:126
bool finished() const noexcept
Definition query.h:127
std::uint64_t step(std::uint64_t quanta)
Definition query.h:128
std::optional< index_pipeline< P > > pipeline_
Definition query.h:145
query_root_builder(query_root_builder const &)=delete
query_root_builder & operator=(query_root_builder &&)=default
query_root_builder(pair_type source)
Definition query.h:108
bool finished_
Definition query.h:146
pair_type head_
Definition query.h:144
P policy_type
Definition query.h:104
query_root_builder(query_root_builder &&)=default
Definition query.h:51
query_root(pair_type head)
Definition query.h:71
P policy_type
Definition query.h:52
static void validate(pair_type const &source)
Definition query.h:74
query_cursor< P, Blob > cursor(bit_view query) const
Definition query.h:235
Blob blob_type
Definition query.h:53
std::shared_ptr< blob_type const > pair_type
Definition query.h:54
static query_root build(pair_type source)
Definition query.h:228
pair_type head_
Definition query.h:72
pair_type head() const noexcept
Definition query.h:66
static query_root adopt_prepared(pair_type source)
Definition query.h:60