21 std::uint16_t branching_factor) {
22 if (branching_factor < 2) {
24 "invalid branching factor " + std::to_string(branching_factor));
30 std::vector<std::uint64_t> level_num_nodes;
31 std::uint64_t n = num_items;
32 std::uint64_t num_nodes = n;
33 level_num_nodes.push_back(n);
37 level_num_nodes.push_back(n);
38 if (n < branching_factor)
42 std::vector<StreeLevelBound> bounds;
43 bounds.reserve(level_num_nodes.size());
44 std::uint64_t acc = num_nodes;
45 for (std::uint64_t size : level_num_nodes) {
62std::uint64_t read_u64_le(
bytes_view b, std::size_t at) {
64 for (std::size_t i = 0; i < 8; ++i) {
65 v |=
static_cast<std::uint64_t
>(b[at + i]) << (8 * i);
72 std::uint64_t first, std::uint64_t last) {
73 std::vector<Entry> out;
77 const std::uint64_t esz = entry_size(
kind);
83 if (block.size() < len) {
88 out.reserve(
static_cast<std::size_t
>(last - first));
89 for (std::uint64_t i = 0; i < last - first; ++i) {
90 const std::size_t base =
static_cast<std::size_t
>(i * esz);
93 e.offset = read_u64_le(
bytes_view(block), base + ksz);
94 out.push_back(std::move(e));
106BinarySearch binary_search(
const std::vector<Entry>& items,
const KeyValue&
key) {
107 std::size_t lo = 0, hi = items.size();
109 const std::size_t mid = lo + (hi - lo) / 2;
124void emit_offset(std::uint64_t off, std::uint64_t
index, RangeReader&
reader,
126 std::vector<SearchResultItem>& out) {
128 out.push_back(SearchResultItem{off,
index});
138 if (head.size() < 4) {
141 std::uint32_t count = 0;
142 for (std::size_t i = 0; i < 4; ++i)
143 count |=
static_cast<std::uint32_t
>(head[i]) << (8 * i);
145 const std::uint64_t want =
152 if (body.size() < want) {
155 for (std::uint32_t i = 0; i < count; ++i) {
156 out.push_back(SearchResultItem{read_u64_le(
bytes_view(body), 4 + i * 8),
index});
170 std::uint64_t leaf_start()
const {
return levels.front().start; }
171 std::uint64_t leaf_end()
const {
return levels.front().end; }
173 std::vector<Entry> node_at(std::uint64_t node_index, std::size_t level)
const {
174 const std::uint64_t end =
175 std::min<std::uint64_t>(node_index + node_size, levels[level].end);
176 return read_entries(reader, index_begin, kind, node_index, end);
181std::vector<SearchResultItem> find_exact(
const Tree& t,
const KeyValue&
key) {
182 std::vector<SearchResultItem> out;
183 std::deque<std::pair<std::uint64_t, std::size_t>> queue;
184 queue.emplace_back(0, t.levels.size() - 1);
186 while (!queue.empty()) {
187 const auto [node_index, level] = queue.front();
190 auto items = t.node_at(node_index, level);
194 const auto hit = binary_search(items,
key);
200 std::uint64_t child = 0;
202 child = items[hit.index].offset + t.node_size;
203 }
else if (hit.index == 0) {
204 child = items[0].offset;
205 }
else if (hit.index >= items.size()) {
206 child = items.back().offset + t.node_size;
208 child = items[hit.index].offset;
217 const std::uint64_t child_level = level - 1;
218 if (child >= t.levels[child_level].end) {
219 child = items[hit.index < items.size() ? hit.index : items.size() - 1].offset;
221 queue.emplace_back(child, child_level);
226 emit_offset(items[hit.index].offset, node_index + hit.index - t.leaf_start(), t.reader,
227 t.payload_begin, t.payload_size, out);
237std::uint64_t find_partition(
const Tree& t,
const KeyValue&
key) {
238 std::uint64_t node_index = 0;
239 for (std::size_t level = t.levels.size(); level-- > 1;) {
240 auto items = t.node_at(node_index, level);
244 const auto hit = binary_search(items,
key);
246 node_index = items[hit.index].offset;
247 }
else if (hit.index == 0) {
248 node_index = items[0].offset;
249 }
else if (hit.index >= items.size()) {
250 node_index = items.back().offset + t.node_size;
252 node_index = items[hit.index].offset;
268std::vector<SearchResultItem> scan_range(
const Tree& t,
const KeyValue& lower,
bool lower_strict,
269 const KeyValue& upper,
bool upper_strict) {
273 if (lu == 0 && (lower_strict || upper_strict))
276 const std::uint64_t lower_idx = find_partition(t, lower);
277 const std::uint64_t upper_idx = find_partition(t, upper);
279 const std::uint64_t start = std::max<std::uint64_t>(lower_idx, t.leaf_start());
289 const std::uint64_t end = std::min<std::uint64_t>(upper_idx + 2 * t.node_size, t.leaf_end());
291 std::vector<SearchResultItem> out;
292 std::uint64_t cur = start;
294 const std::uint64_t node_end = std::min<std::uint64_t>(cur + t.node_size, end);
295 auto items = read_entries(t.reader, t.index_begin, t.kind, cur, node_end);
296 for (std::size_t i = 0; i < items.size(); ++i) {
299 if (lower_strict ? cl > 0 : cl >= 0) {
300 if (upper_strict ? cu < 0 : cu <= 0) {
301 emit_offset(items[i].
offset, cur + i - t.leaf_start(), t.reader,
302 t.payload_begin, t.payload_size, out);
313std::uint64_t
stree_num_nodes(std::uint64_t num_items, std::uint16_t branching_factor) {
314 if (branching_factor < 2) {
320 std::uint64_t n = num_items;
321 std::uint64_t num_nodes = n;
325 if (n < branching_factor)
335 std::uint32_t count = 0;
336 for (std::size_t i = 0; i < 4; ++i)
337 count |=
static_cast<std::uint32_t
>(b[i]) << (8 * i);
339 if (b.
size() < 4 +
static_cast<std::size_t
>(count) * 8) {
342 std::vector<std::uint64_t> out;
344 for (std::uint32_t i = 0; i < count; ++i) {
345 out.push_back(read_u64_le(b, 4 +
static_cast<std::size_t
>(i) * 8));
351 const std::vector<std::uint64_t>& offsets) {
352 const std::uint32_t count =
static_cast<std::uint32_t
>(offsets.size());
353 for (
int i = 0; i < 4; ++i)
354 out.push_back(
static_cast<std::uint8_t
>((count >> (8 * i)) & 0xFF));
355 for (std::uint64_t off : offsets)
356 for (
int i = 0; i < 8; ++i)
357 out.push_back(
static_cast<std::uint8_t
>((off >> (8 * i)) & 0xFF));
364 if (tree_bytes >
index.length) {
366 "attribute index node region exceeds its declared length");
372 index.length - tree_bytes,
374 static_cast<std::uint64_t
>(
index.branching_factor) - 1,
384 const bool is_string =
391 return find_exact(t, value);
393 return scan_range(t, value,
false,
key_max(
kind),
false);
395 return scan_range(t,
key_min(
kind),
false, value,
false);
399 return scan_range(t, value, !is_string,
key_max(
kind),
false);
401 return scan_range(t,
key_min(
kind),
false, value, !is_string);
409 auto lo = scan_range(t,
key_min(
kind),
false, value,
true);
410 auto hi = scan_range(t, value,
true,
key_max(
kind),
false);
411 lo.insert(lo.end(), hi.begin(), hi.end());
Every failure the library reports is one of these.
Synchronous byte-range source.
virtual std::vector< std::uint8_t > read(std::uint64_t offset, std::uint64_t length)=0
Read length bytes at offset, subject to the contract above.
Minimal C++17 stand-in for std::span: a non-owning view over contiguous memory.
std::size_t size() const noexcept
std::uint64_t checked_mul(std::uint64_t a, std::uint64_t b, const char *what="mul")
std::uint64_t checked_add(std::uint64_t a, std::uint64_t b, const char *what="add")
std::uint64_t ceil_div(std::uint64_t a, std::uint64_t b)
ceil(a / b) without the (a + b - 1) overflow hazard.
span< const std::uint8_t > bytes_view
The workhorse alias: a read-only view over bytes.
bool is_payload_ref(std::uint64_t off)
std::uint64_t stree_num_nodes(std::uint64_t num_items, std::uint16_t branching_factor)
Total node count.
std::vector< std::uint64_t > decode_payload_entry(bytes_view b)
Decode a payload entry: u32 count then count x u64, all little-endian.
std::vector< StreeLevelBound > stree_level_bounds(std::uint64_t num_items, std::uint16_t branching_factor)
Mirrors Stree::generate_level_bounds (stree.rs:474-508).
std::uint64_t payload_offset(std::uint64_t off)
std::vector< SearchResultItem > stree_query(RangeReader &reader, const AttrIndexInfo &index, KeyKind kind, Operator op, const KeyValue &value)
Run one condition against one column's index blob, returning candidate feature offsets (relative to t...
void encode_payload_entry(std::vector< std::uint8_t > &out, const std::vector< std::uint64_t > &offsets)
Encode a payload entry: u32 count then count x u64, all little-endian (mirrors PayloadEntry::serializ...
KeyValue key_max(KeyKind kind)
int compare_keys(const KeyValue &a, const KeyValue &b)
Three-way comparison.
KeyKind
The concrete key types the B+tree index can hold.
std::size_t key_serialized_size(KeyKind kind)
Serialized width in bytes. DateTime is 12: i64 seconds + u32 nanos.
KeyValue decode_key(KeyKind kind, bytes_view b)
KeyValue key_min(KeyKind kind)
Sentinels used to lower open-ended range queries.
Operator
Comparison operators the attribute index supports.
std::uint64_t payload_begin
std::uint64_t index_begin
std::uint64_t payload_size
std::vector< StreeLevelBound > levels
Where one column's B+tree index lives, and how it is shaped.
Half-open [start, end) node index range for one tree level, in the flat node array shared by every le...