Everett
Loading...
Searching...
No Matches
cola_query.h
Go to the documentation of this file.
1
13#pragma once
14
15#include <everett/cola_index.h>
16
17#include <concepts>
18#include <functional>
19#include <array>
20#include <memory>
21#include <optional>
22#include <type_traits>
23#include <unordered_set>
24#include <utility>
25
26namespace everett {
27 template <class P, class Selector> struct sort_profile_view;
28 template <class P, class Blob = cola_index<P>> struct cola_query_cursor;
29
30 // Source pins the main node that yielded this match. A secondary match
31 // belongs to source->secondary_target(), not source's own native array.
32 // Traversal order does not define chronology or composition precedence.
33 template <class P, class Blob = cola_index<P>> struct cola_query_match {
34 std::shared_ptr<Blob const> source;
35 bool secondary = false;
36 std::uint64_t ordinal = 0;
38 };
39
40 template <class P, class Blob = cola_index<P>> struct cola_query_root {
41 using policy_type = P;
42 using pair_type = std::shared_ptr<Blob const>;
43 static cola_query_root build(pair_type main = {}, typename Blob::native_pointer secondary = {})
44 requires std::is_same_v<Blob, cola_index<P>> {
45 return adopt_prepared(Blob::prepare_root(std::move(main), std::move(secondary)));
46 }
48 if (!source || source->virtual_size() > P::group_size)
49 error_detail::raise<std::invalid_argument>("COLA query root needs one bounded head group");
50 std::unordered_set<Blob const *> seen;
51 for (auto current = source; current; current = current->main_target()) {
52 if (!seen.insert(current.get()).second) error_detail::raise<std::invalid_argument>("cyclic COLA main chain");
53 auto view = current->view();
54 auto main = current->main_target();
55 auto secondary = current->secondary_target();
56 auto secondary_count = secondary ? secondary->view().size() : 0;
57 if (view.borrowed(0).size() != (main ? main->group_count() : 0) ||
58 view.borrowed(1).size() != secondary_count / P::group_size + (secondary_count % P::group_size != 0))
59 error_detail::raise<std::invalid_argument>("COLA query target sample count mismatch");
60 }
61 return cola_query_root(std::move(source));
62 }
63 pair_type head() const noexcept { return head_; }
66 return cola_query_cursor<P, Blob>::from_owned(*this, std::move(query));
67 }
68 private:
69 explicit cola_query_root(pair_type head) : head_(std::move(head)) {}
71 };
72
73 namespace cola_detail {
74 // Only these concrete implementations are known not to retain comparison
75 // contexts. Custom views keep the existing owning context behavior.
76 template <class> struct scoped_native_view : std::false_type {};
77 template <class P> struct scoped_native_view<profile_view<P, stream_role::native>> : std::true_type {};
78 template <class P, class Selector> struct scoped_native_view<sort_profile_view<P, Selector>> : std::true_type {};
79 template <class> struct scoped_borrowed_view : std::false_type {};
80 template <class P> struct scoped_borrowed_view<profile_view<P, stream_role::borrowed>> : std::true_type {};
81 template <class P> struct first_window_result {
82 bool native = false;
83 std::array<std::optional<profile_blob_borrowed_predecessor<P>>, 2> predecessors;
84 };
85 struct query_access {
86 template <class P> static profile_query_context<P> borrow_query(bit_string const & query) {
88 }
89 template <class P> static profile_query_context<P> borrow_query(bit_string const &&) = delete;
90 template <class Blob> static constexpr bool scoped_views = [] {
91 using view = std::remove_cvref_t<decltype(std::declval<Blob const &>().view())>;
92 using secondary = std::remove_cvref_t<decltype(std::declval<Blob const &>().secondary_target()->view())>;
95 }();
96 struct match_probe {
97 bool operator()(std::uint64_t, bit_view) const { return true; }
98 };
99 template <class P, class Family, class Capture> static first_window_result<P> search(
100 cola_index_view<P, Family> const & view, std::uint64_t group,
101 profile_query_context<P> const & context, Capture && capture) {
102 return view.template search_window_with<first_window_result<P>>(group, context,
103 std::forward<Capture>(capture));
104 }
105 };
106
108 void operator()(auto const &, std::uint64_t) const noexcept {}
109 };
110
111 // Synchronous replacement reads decode under the current node's owner.
112 // The callback finishes before that pin is released; public matches stay owned.
113 // Inspect can capture physical record metadata from that same pinned hit.
114 template <class P, class Blob, class Query, class Decode, class Inspect = ignore_match_position>
115 requires std::same_as<std::remove_cvref_t<Query>, bit_string> &&
116 (!std::is_reference_v<std::invoke_result_t<Decode &, bit_view>>) &&
117 requires(Blob const & blob, profile_query_context<P> const & context) {
118 query_access::search(blob.view(), 0, context, query_access::match_probe{});
119 }
120 auto first_value(cola_query_root<P, Blob> const & root, Query && query, Decode && decode, Inspect inspect = {})
121 -> std::optional<std::invoke_result_t<Decode &, bit_view>> {
122 using value_type = std::invoke_result_t<Decode &, bit_view>;
123 auto current = root.head();
124 // Built-in views release all context copies before this synchronous
125 // call returns, including on decoder exceptions and nested reads. A
126 // custom view may retain a copy, so copy borrowed queries into shared
127 // ownership and move rvalue queries.
128 auto context = [&] {
129 if constexpr (query_access::scoped_views<Blob>) return query_access::borrow_query<P>(query);
130 else return profile_query_context<P>::from_owned(std::forward<Query>(query));
131 }();
132 if (!current) error_detail::raise<std::invalid_argument>("COLA query root has no head");
133 if (!current->virtual_size()) return std::nullopt;
134 std::uint64_t group = 0;
135 while (current) {
136 std::optional<value_type> value;
137 auto view = current->view();
138 auto result = query_access::search(view, group, context,
139 [&](std::uint64_t ordinal, bit_view encoded) {
140 std::invoke(inspect, view.native(), ordinal);
141 value.emplace(std::invoke(decode, encoded));
142 return true;
143 });
144 auto main = current->main_target();
145 auto secondary = current->secondary_target();
146 if (auto const & next = result.predecessors[0])
147 if (!main || next->target_ordinal % P::group_size || next->target_ordinal >= main->virtual_size())
148 error_detail::raise<std::invalid_argument>("COLA query main route has no target");
149 if (auto const & next = result.predecessors[1]) {
150 if (!secondary) error_detail::raise<std::invalid_argument>("COLA query secondary route has no target");
151 auto side = secondary->view();
152 visit_secondary<P>(side, *next, [&](std::uint64_t ordinal, bit_view encoded) {
153 if (!value) {
154 std::invoke(inspect, side, ordinal);
155 value.emplace(std::invoke(decode, encoded));
156 }
157 });
158 }
159 if (value) return value;
160 if (auto & next = result.predecessors[0]) {
161 group = next->target_ordinal / P::group_size;
162 context = std::move(next->comparison);
163 current = std::move(main);
164 } else current.reset();
165 }
166 return std::nullopt;
167 }
168 }
169
170 // Each charged main-node visit searches at most one augmented K-window and
171 // one terminal native K-window. Two equal native matches may result: neither
172 // may be discarded. Pending matches retain their source and own their values.
173 template <class P, class Blob> struct cola_query_cursor {
174 using policy_type = P;
175 using pair_type = std::shared_ptr<Blob const>;
185 : current_(std::move(other.current_)), context_(std::move(other.context_)), group_(other.group_),
186 pending_(std::move(other.pending_)), failed_(other.failed_) { other.pending_ = {}; }
188 if (this != &other) {
189 current_ = std::move(other.current_); context_ = std::move(other.context_); group_ = other.group_;
190 pending_ = std::move(other.pending_); failed_ = other.failed_; other.pending_ = {};
191 }
192 return *this;
193 }
194 bool has_match() const noexcept { return pending_[0].has_value() || pending_[1].has_value(); }
195 bool done() const noexcept { return !current_ && !has_match(); }
196 bool failed() const noexcept { return failed_; }
197 std::uint64_t step(std::uint64_t main_budget = 1) {
198 if (failed_) error_detail::raise<std::logic_error>("COLA query cursor has failed");
199 if (has_match()) return 0;
200 std::uint64_t visited = 0;
201 try {
202 while (current_ && visited < main_budget) {
203 auto result = current_->view().search_window(group_, context_);
204 auto main = current_->main_target();
205 auto secondary = current_->secondary_target();
206 if (auto const & next = result.predecessors[0])
207 if (!main || next->target_ordinal % P::group_size || next->target_ordinal >= main->virtual_size())
208 error_detail::raise<std::invalid_argument>("COLA query main route has no target");
209 std::optional<profile_blob_native_match<P>> side;
210 if (auto const & next = result.predecessors[1]) {
211 if (!secondary) error_detail::raise<std::invalid_argument>("COLA query secondary route has no target");
212 side = cola_search_secondary<P>(secondary->view(), *next);
213 }
214 if (result.native) pending_[0].emplace(match_type{current_, false,
215 result.native->ordinal, std::move(result.native->value)});
216 if (side) pending_[1].emplace(match_type{current_, true, side->ordinal, std::move(side->value)});
217 if (auto & next = result.predecessors[0]) {
218 group_ = next->target_ordinal / P::group_size;
219 context_ = std::move(next->comparison);
220 current_ = std::move(main);
221 } else current_.reset();
222 ++visited;
223 if (has_match()) break;
224 }
225 } catch (...) { failed_ = true; throw; }
226 return visited;
227 }
229 if (failed_) error_detail::raise<std::logic_error>("COLA query cursor has failed");
230 auto & slot = pending_[pending_[0] ? 0 : 1];
231 if (!slot) error_detail::raise<std::logic_error>("COLA query cursor has no match");
232 auto result = std::move(*slot);
233 slot.reset();
234 return result;
235 }
236 private:
237 struct prepared_query {};
239 : current_(root.head()), context_(std::move(context)) {
240 if (!current_) error_detail::raise<std::invalid_argument>("COLA query root has no head");
241 if (!current_->virtual_size()) current_.reset();
242 }
245 std::uint64_t group_ = 0;
246 std::array<std::optional<match_type>, 2> pending_;
247 bool failed_ = false;
248 };
249}
Declares dual-target main/secondary fractional indexes for COLA.
auto first_value(cola_query_root< P, Blob > const &root, Query &&query, Decode &&decode, Inspect inspect={}) -> std::optional< std::invoke_result_t< Decode &, bit_view > >
Definition cola_query.h:120
bit_string value(arrow_t< S > const &value)
Definition typed_world.h:99
Definition active_engine.h:18
stream_role
Definition policy.h:25
Definition profile.h:166
bit_view view() const &
Definition profile.h:178
Definition profile.h:56
bool native
Definition cola_query.h:82
std::array< std::optional< profile_blob_borrowed_predecessor< P > >, 2 > predecessors
Definition cola_query.h:83
void operator()(auto const &, std::uint64_t) const noexcept
Definition cola_query.h:108
bool operator()(std::uint64_t, bit_view) const
Definition cola_query.h:97
Definition cola_query.h:85
static constexpr bool scoped_views
Definition cola_query.h:90
static profile_query_context< P > borrow_query(bit_string const &&)=delete
static profile_query_context< P > borrow_query(bit_string const &query)
Definition cola_query.h:86
static first_window_result< P > search(cola_index_view< P, Family > const &view, std::uint64_t group, profile_query_context< P > const &context, Capture &&capture)
Definition cola_query.h:99
Definition cola_index.h:114
Definition cola_query.h:173
bool done() const noexcept
Definition cola_query.h:195
cola_query_cursor(cola_query_root< P, Blob > const &root, bit_view query)
Definition cola_query.h:177
bool has_match() const noexcept
Definition cola_query.h:194
P policy_type
Definition cola_query.h:174
cola_query_cursor(cola_query_cursor &&other) noexcept
Definition cola_query.h:184
bool failed_
Definition cola_query.h:247
cola_query_cursor(cola_query_cursor const &)=default
cola_query_cursor(cola_query_root< P, Blob > const &root, profile_query_context< P > context, prepared_query)
Definition cola_query.h:238
std::uint64_t step(std::uint64_t main_budget=1)
Definition cola_query.h:197
cola_query_cursor & operator=(cola_query_cursor const &)=default
std::array< std::optional< match_type >, 2 > pending_
Definition cola_query.h:246
profile_query_context< P > context_
Definition cola_query.h:244
std::uint64_t group_
Definition cola_query.h:245
match_type take_match()
Definition cola_query.h:228
std::shared_ptr< Blob const > pair_type
Definition cola_query.h:175
static cola_query_cursor from_owned(cola_query_root< P, Blob > const &root, bit_string query)
Definition cola_query.h:179
bool failed() const noexcept
Definition cola_query.h:196
pair_type current_
Definition cola_query.h:243
cola_query_cursor & operator=(cola_query_cursor &&other) noexcept
Definition cola_query.h:187
Definition cola_query.h:33
std::shared_ptr< Blob const > source
Definition cola_query.h:34
bit_string value
Definition cola_query.h:37
bool secondary
Definition cola_query.h:35
std::uint64_t ordinal
Definition cola_query.h:36
Definition cola_query.h:40
cola_query_root(pair_type head)
Definition cola_query.h:69
static cola_query_root adopt_prepared(pair_type source)
Definition cola_query.h:47
P policy_type
Definition cola_query.h:41
std::shared_ptr< Blob const > pair_type
Definition cola_query.h:42
static cola_query_root build(pair_type main={}, typename Blob::native_pointer secondary={})
Definition cola_query.h:43
pair_type head() const noexcept
Definition cola_query.h:63
cola_query_cursor< P, Blob > cursor(bit_view query) const
Definition cola_query.h:64
pair_type head_
Definition cola_query.h:70
cola_query_cursor< P, Blob > cursor_owned(bit_string query) const
Definition cola_query.h:65
Definition profile.h:328
static profile_query_context from_borrowed(bit_string const &query)
Definition profile.h:400
static profile_query_context from_owned(bit_string query)
Definition profile.h:336
Definition profile.h:695
Definition sort_profile.h:176