53 static_assert(std::is_integral_v<Value> &&
sizeof(Value) == 8,
54 "id auxiliary values are fixed-width 64-bit integers");
55 using MemoryKey = std::pair<std::string, std::optional<std::string>>;
56 using MemoryMap = std::map<MemoryKey, Value>;
59 std::uint64_t hash = 0;
60 std::uint64_t offset_plus_one = 0;
63 std::uint64_t hash = 0;
64 std::uint64_t first_size = 0;
65 std::uint64_t second_size = 0;
67 std::uint64_t flags = 0;
69 static_assert(
sizeof(Slot) == 16 &&
sizeof(Header) == 40,
70 "id scratch layout must have no padding");
71 static constexpr std::uint64_t kLive = 1;
72 static constexpr std::uint64_t kPair = 2;
73 static constexpr std::uint64_t kInitialSlots = 64;
79 IdPairOrderedFold ordered;
80 IdPairSlabFold bounded;
81 Header pending_header;
83 std::uint64_t pending_offset = 0, pending_at = 0;
84 bool pending =
false, slot_pending =
false;
85 FixtureFoldWork* fixture_work =
nullptr;
109 if (owner_->memory_) {
124 if (owner_ != other.owner_)
return false;
125 if (!entry_ || !other.entry_)
return !entry_ && !other.entry_;
126 return owner_->memory_ ? memory_at_ == other.memory_at_ : offset_ == other.offset_;
134 if (owner_->memory_) {
135 memory_at_ = owner_->memory_->begin();
143 if (memory_at_ != owner_->memory_->end())
144 entry_.emplace(
Entry{memory_at_->first.first, memory_at_->second,
145 memory_at_->first.second});
149 while (offset_ < owner_->data_bytes_) {
150 const Header header = owner_->read_header(offset_);
151 next_ = offset_ +
sizeof(Header) + header.first_size + header.second_size;
152 if ((header.flags & kLive) != 0) {
154 row.first.resize(
static_cast<std::size_t
>(header.first_size));
155 owner_->read_data(offset_ +
sizeof(Header), row.first.data(), row.first.size());
156 row.second = header.value;
157 if ((header.flags & kPair) != 0) {
158 row.from_entry.emplace(
static_cast<std::size_t
>(header.second_size),
'\0');
159 owner_->read_data(offset_ +
sizeof(Header) + header.first_size,
160 row.from_entry->data(), row.from_entry->size());
162 entry_.emplace(std::move(row));
169 std::uint64_t offset_ = 0;
170 std::uint64_t next_ = 0;
171 typename MemoryMap::const_iterator memory_at_{};
172 std::optional<Entry> entry_;
176 explicit IdPairStore(std::size_t cache_bytes) : read_cache_(cache_bytes) {}
181 read_cache_.set_cache_capacity(capacity);
187 if (
this != &other) {
195 if ((data_.opened() || index_.opened()) && directory != directory_)
196 throw std::logic_error(
"id table directory changed during run");
197 directory_ = directory;
200 read_cache_.release();
204 slots_ = slab_ ? fixed_slots_ : 0;
207 if constexpr (MaintainSortedFold) {
208 fold_.ordered.clear();
209 fold_.bounded.reset();
210 fold_.pending = fold_.slot_pending =
false;
212 if (memory_) memory_->clear();
214 slab_->reset(data_resource_);
215 slab_->reset(index_resource_);
216 slab_->extend(index_resource_, slots_ *
sizeof(Slot));
219 std::size_t
size() const noexcept {
return size_; }
220 bool empty() const noexcept {
return size_ == 0; }
223 const auto table = data_bytes_ + slots_ *
sizeof(Slot);
224 if constexpr (MaintainSortedFold) {
225 const auto ordered = fold_.ordered.disk_bytes();
226 const auto maximum = std::numeric_limits<std::uint64_t>::max();
227 return ordered > maximum -
table ? maximum :
table + ordered;
231 const_iterator
begin()
const {
return const_iterator(
this,
false); }
232 const_iterator
end()
const {
return const_iterator(
this,
true); }
239 if (used_ != 0 || size_ != 0 || data_.opened() || index_.opened())
240 throw std::logic_error(
"id table scratch backend changed after use");
242 throw std::logic_error(
"id table fixture oracle cannot bind production scratch");
243 std::uint64_t
limit = 0;
245 limit = slab ? slab->manifest().K_e : 0;
247 limit = slab ? slab->manifest().K_c : 0;
249 throw std::logic_error(
"id table scratch resource pair is invalid");
251 if (slab) slab->require_extend(index, count *
sizeof(Slot));
252 if constexpr (MaintainSortedFold) {
254 fold_.bounded.configure(slab->region(
data).capacity,
limit, count);
256 slab->require_extend(index, fold_.bounded.storage_bytes());
260 data_resource_ =
data;
261 index_resource_ = index;
263 fixed_slots_ = count;
276 template<
class Enumerate>
279 std::uint64_t keys = used_;
280 std::uint64_t extent = data_bytes_;
281 std::size_t ordinal = 0;
282 enumerate([&](KeyView
key) {
283 auto first =
key.first;
284 auto second =
key.second;
286 && (first.find(
'\x1f') != std::string_view::npos
287 || second->find(
'\x1f') != std::string_view::npos)) {
289 "order ids cannot contain the control character 0x1f");
295 const auto separator = first.rfind(
'\x1f', 64);
296 if (separator != std::string_view::npos) {
297 second = first.substr(separator + 1);
298 first = first.substr(0, separator);
301 require_id_components(first.size(), second ? second->size() : 0);
302 const auto own_ordinal = ordinal++;
303 if (slots_ != 0 && locate(
key.first,
key.second,
304 hash_for_key(
key.first,
key.second)).second.offset_plus_one != 0)
306 bool duplicate =
false;
307 std::size_t prior = 0;
308 enumerate([&](KeyView earlier) {
309 if (prior++ < own_ordinal && earlier.first ==
key.first
310 && earlier.second ==
key.second) duplicate =
true;
312 if (duplicate)
return;
314 preflight_new_key(keys, extent,
key.first.size(),
315 key.second ?
key.second->size() : 0);
319 std::optional<std::string_view> second = std::nullopt)
const {
320 preflight_keys([&](
const auto& emit) { emit(KeyView{first, second}); });
327 std::string_view last)
const {
330 && (first.find(
'\x1f') != std::string_view::npos
331 || last.find(
'\x1f') != std::string_view::npos)) {
333 "order ids cannot contain the control character 0x1f");
335 require_id_components(first.size(), last.size());
336 constexpr auto maximum = std::numeric_limits<std::uint64_t>::max();
337 if (first.size() >= maximum || last.size() > maximum - first.size() - 1)
340 ? slab_->manifest().B_e : slab_->manifest().B_c));
341 const auto length =
static_cast<std::uint64_t
>(first.size()) + 1 + last.size();
342 std::uint64_t hash = 1469598103934665603ULL;
343 const auto byte = [&](
unsigned char value) { hash ^=
value; hash *= 1099511628211ULL; };
345 for (
unsigned shift = 0; shift < 64; shift += 8)
346 byte(
static_cast<unsigned char>(length >> shift));
347 for (
const unsigned char value : first) byte(
value);
348 byte(
static_cast<unsigned char>(separator));
349 for (
const unsigned char value : last) byte(
value);
350 if (fixture_hash_) hash = *fixture_hash_;
352 const auto found = locate_hash(hash, [&](std::uint64_t offset,
const Header& header) {
353 return (header.flags & kPair) == 0 && header.first_size == length
354 && equal_string(offset +
sizeof(Header), first.size(), first)
355 && equal_string(offset +
sizeof(Header) + first.size(), 1,
356 std::string_view(&separator, 1))
357 && equal_string(offset +
sizeof(Header) + first.size() + 1,
360 if (found.second.offset_plus_one != 0)
return;
363 auto extent = data_bytes_;
364 preflight_new_key(keys, extent, length, 0);
370 static_assert(MaintainSortedFold,
"ordered fold must be selected for this table");
373 for (
const auto& row : *memory_) {
374 sink.s(row.first.first);
378 sink.h = fold_.bounded.apply(
sink.h);
380 sink.h = fold_.ordered.apply(
sink.h);
384 data_.fixture_count_io(io); index_.fixture_count_io(io);
385 if constexpr (MaintainSortedFold) fold_.ordered.fixture_count_io(io);
388 static_assert(MaintainSortedFold,
"fold work requires the ordered-fold table");
389 fold_.fixture_work = work;
396 if (slab_ || used_ != 0 || size_ != 0 || data_.opened() || index_.opened())
397 throw std::logic_error(
"id table fixture backend changed after use");
398 memory_ = std::make_unique<MemoryMap>();
401 if (used_ != 0 || size_ != 0 || data_.opened() || index_.opened())
402 throw std::logic_error(
"id table fixture hash changed after use");
403 fixture_hash_ = hash;
406 std::optional<Value>
lookup(std::string_view
id)
const {
return lookup_key(
id, std::nullopt); }
407 std::optional<Value>
lookup(std::string_view
id, std::string_view from_entry)
const {
408 return lookup_key(
id, from_entry);
410 Value
at(
const std::string&
id)
const {
411 const auto found =
lookup(
id);
412 if (!found)
throw std::out_of_range(
"id auxiliary key is absent");
416 return assign_key(
id, std::nullopt,
value);
419 static_assert(!MaintainSortedFold,
"consumed-cycle sorted fold accepts single keys");
420 return assign_key(
id, from_entry,
value);
423 const auto old =
lookup(
id);
427 const auto old =
lookup(
id, from_entry);
430 bool erase(std::string_view
id) {
return erase_key(
id, std::nullopt); }
431 bool erase(std::string_view
id, std::string_view from_entry) {
432 return erase_key(
id, from_entry);
436 void require_id_components(std::size_t first, std::size_t second)
const {
437 if (first > 64 || second > 64)
440 void preflight_new_key(std::uint64_t& keys, std::uint64_t& extent,
441 std::uint64_t first, std::uint64_t second)
const {
442 if (keys >= key_limit_ || keys >= slots_ / 2
443 || keys >= std::numeric_limits<std::size_t>::max())
444 throw ScratchCapacityError(index_resource_,
447 const auto append = [&](std::uint64_t bytes) {
448 const auto capacity = slab_->region(data_resource_).capacity;
449 if (extent > capacity || bytes > capacity - extent)
450 throw ScratchCapacityError(data_resource_,
453 ? slab_->manifest().B_e : slab_->manifest().B_c));
456 append(
sizeof(Header));
463 swap(read_cache_, other.read_cache_);
464 swap(data_, other.data_);
465 swap(index_, other.index_);
466 swap(directory_, other.directory_);
467 swap(data_bytes_, other.data_bytes_);
468 swap(slots_, other.slots_);
469 swap(used_, other.used_);
470 swap(size_, other.size_);
471 swap(memory_, other.memory_);
472 swap(slab_, other.slab_);
473 swap(data_resource_, other.data_resource_);
474 swap(index_resource_, other.index_resource_);
475 swap(key_limit_, other.key_limit_);
476 swap(fixed_slots_, other.fixed_slots_);
477 swap(fixture_hash_, other.fixture_hash_);
478 swap(fold_, other.fold_);
480 static MemoryKey memory_key(std::string_view first, std::optional<std::string_view> second) {
481 return {std::string(first), second ? std::optional<std::string>{std::string(*second)}
484 static std::uint64_t key_hash(std::string_view first,
485 std::optional<std::string_view> second)
noexcept {
486 std::uint64_t hash = 1469598103934665603ULL;
487 const auto byte = [&hash](
unsigned char value) {
489 hash *= 1099511628211ULL;
491 const auto string = [&byte](std::string_view
value) {
492 const auto length =
static_cast<std::uint64_t
>(
value.size());
493 for (
unsigned shift = 0; shift < 64; shift += 8)
494 byte(
static_cast<unsigned char>(length >> shift));
495 for (
const unsigned char value_byte :
value) byte(value_byte);
497 byte(second ? 1 : 0);
499 if (second)
string(*second);
502 std::uint64_t hash_for_key(std::string_view first,
503 std::optional<std::string_view> second)
const noexcept {
504 return fixture_hash_ ? *fixture_hash_ : key_hash(first, second);
506 void read_data(std::uint64_t offset,
void* destination, std::size_t length)
const {
507 if (slab_) slab_->read_at(data_resource_, offset, destination, length);
508 else read_cache_.read(data_, data_.disk_backed(), data_bytes_, 0, offset, destination, length);
510 void write_data(std::uint64_t offset,
const void* source, std::size_t length) {
511 if (slab_) slab_->write_at(data_resource_, offset, source, length);
513 if (data_.disk_backed()) read_cache_.before_write(0, data_bytes_, offset, length);
514 data_.write(offset, source, length);
517 void read_index(std::uint64_t offset,
void* destination, std::size_t length)
const {
518 if constexpr (MaintainSortedFold) {
521 slot.offset_plus_one = fold_.bounded.read_slot(*slab_, index_resource_, offset /
sizeof(Slot));
522 if (slot.offset_plus_one != 0)
523 read_data(slot.offset_plus_one - 1, &slot.hash,
sizeof(slot.hash));
524 std::memcpy(destination, &slot, length);
528 if (slab_) slab_->read_at(index_resource_, offset, destination, length);
529 else read_cache_.read(index_, index_.disk_backed(), slots_ *
sizeof(Slot),
530 1, offset, destination, length);
532 void write_index(std::uint64_t offset,
const void* source, std::size_t length) {
533 if constexpr (MaintainSortedFold) {
536 std::memcpy(&slot, source, length);
537 fold_.bounded.write_slot(*slab_, index_resource_, offset /
sizeof(Slot), slot.offset_plus_one);
541 if (slab_) slab_->write_at(index_resource_, offset, source, length);
543 if (index_.disk_backed()) read_cache_.before_write(1, slots_ *
sizeof(Slot), offset, length);
544 index_.write(offset, source, length);
547 Header read_header(std::uint64_t offset)
const {
548 if (offset > data_bytes_ || data_bytes_ - offset <
sizeof(Header))
549 throw IdPairStoreError(EIO,
"id table truncated record");
551 if constexpr (MaintainSortedFold) {
552 if (fold_.pending && offset == fold_.pending_offset) header = fold_.pending_header;
553 else read_data(offset, &header,
sizeof(header));
555 read_data(offset, &header,
sizeof(header));
557 const auto available = data_bytes_ - offset -
sizeof(Header);
558 if (header.first_size > available || header.second_size > available - header.first_size
559 || (header.flags & ~(kLive | kPair)) != 0
560 || ((header.flags & kPair) == 0 && header.second_size != 0))
561 throw IdPairStoreError(EIO,
"id table invalid record");
564 bool equal_string(std::uint64_t offset, std::uint64_t length, std::string_view
key)
const {
565 if (length !=
key.size())
return false;
566 std::array<char, 512> buffer{};
567 for (std::size_t
at = 0;
at <
key.size();) {
568 const auto count = std::min(buffer.size(),
key.size() -
at);
569 read_data(offset +
at, buffer.data(), count);
570 if (
key.compare(
at, count, buffer.data(), count) != 0)
return false;
577 std::pair<std::uint64_t, Slot> locate(std::string_view first,
578 std::optional<std::string_view> second, std::uint64_t hash)
const {
579 return locate_hash(hash, [&](std::uint64_t offset,
const Header& header) {
580 return ((header.flags & kPair) != 0) == second.has_value()
581 && equal_string(offset +
sizeof(Header), header.first_size, first)
582 && (!second || equal_string(offset +
sizeof(Header) + header.first_size,
583 header.second_size, *second));
586 template<
class Equal>
587 std::pair<std::uint64_t, Slot> locate_hash(std::uint64_t hash,
const Equal& equal)
const {
588 std::uint64_t
at = hash & (slots_ - 1);
589 for (std::uint64_t probes = 0; probes < slots_; ++probes) {
591 if constexpr (MaintainSortedFold) {
592 if (fold_.slot_pending &&
at == fold_.pending_at) slot = fold_.pending_slot;
593 else read_index(
at *
sizeof(Slot), &slot,
sizeof(slot));
595 read_index(
at *
sizeof(Slot), &slot,
sizeof(slot));
597 if (slot.offset_plus_one == 0)
return {
at, slot};
598 if (slot.hash == hash) {
599 const auto offset = slot.offset_plus_one - 1;
600 const Header header = read_header(offset);
601 if (header.hash != slot.hash)
602 throw IdPairStoreError(EIO,
"id table mismatched index");
603 if (equal(offset, header))
return {
at, slot};
605 at = (
at + 1) & (slots_ - 1);
607 throw IdPairStoreError(EIO,
"id table full index");
609 static void place_slot(IdPairScratchFile& index, std::uint64_t slots,
const Slot& slot) {
610 std::uint64_t
at = slot.hash & (
slots - 1);
611 for (std::uint64_t probes = 0; probes <
slots; ++probes) {
613 index.read(
at *
sizeof(Slot), &existing,
sizeof(existing));
614 if (existing.offset_plus_one == 0) {
615 index.write(
at *
sizeof(Slot), &slot,
sizeof(slot));
618 at = (
at + 1) & (slots - 1);
620 throw IdPairStoreError(EIO,
"id table rehash overflow");
623 constexpr auto maximum =
static_cast<std::uint64_t
>(std::numeric_limits<off_t>::max())
625 if (slots_ > maximum / 2)
throw IdPairStoreError(EFBIG,
"id table index exhausted");
626 if (data_.disk_backed() || index_.disk_backed()) read_cache_.invalidate();
627 const auto next_slots = slots_ == 0 ? kInitialSlots : slots_ * 2;
628 IdPairScratchFile next(MaintainSortedFold
630 next.fixture_count_io(index_.fixture_io());
631 next.open(directory_);
632 next.resize(next_slots *
sizeof(Slot));
633 std::array<Slot, 128> buffer{};
634 for (std::uint64_t
at = 0;
at < slots_;) {
635 const auto count = std::min<std::uint64_t>(buffer.size(), slots_ -
at);
636 index_.read(
at *
sizeof(Slot), buffer.data(),
637 static_cast<std::size_t
>(count) *
sizeof(Slot));
638 for (std::size_t i = 0; i < static_cast<std::size_t>(count); ++i)
639 if (buffer[i].offset_plus_one != 0) place_slot(next, next_slots, buffer[i]);
642 index_ = std::move(next);
643 if (data_.disk_backed() || index_.disk_backed()) read_cache_.invalidate();
646 std::optional<Value> lookup_key(std::string_view first,
647 std::optional<std::string_view> second)
const {
649 const auto found = memory_->find(memory_key(first, second));
650 return found == memory_->end() ? std::nullopt : std::optional<Value>{found->second};
652 if (slots_ == 0)
return std::nullopt;
653 const auto found = locate(first, second, hash_for_key(first, second)).second;
654 if (found.offset_plus_one == 0)
return std::nullopt;
655 const Header header = read_header(found.offset_plus_one - 1);
656 return (header.flags & kLive) != 0 ? std::optional<Value>{header.value} : std::nullopt;
658 bool assign_key(std::string_view first, std::optional<std::string_view> second, Value
value) {
660 const auto inserted = memory_->insert_or_assign(memory_key(first, second),
value);
661 if (inserted.second) ++size_;
662 return inserted.second;
666 if constexpr (MaintainSortedFold) flush_pending();
667 const auto hash = hash_for_key(first, second);
668 if (!slab_ && slots_ == 0) grow_index();
669 auto found = locate(first, second, hash);
670 if (found.second.offset_plus_one != 0) {
671 const auto offset = found.second.offset_plus_one - 1;
672 Header header = read_header(offset);
673 const bool inserted = (header.flags & kLive) == 0;
674 header.value =
value;
675 header.flags |= kLive;
676 if constexpr (MaintainSortedFold) publish_fold(first, offset, header);
677 else write_data(offset, &header,
sizeof(header));
678 if (inserted) ++size_;
681 if (used_ >= slots_ / 2) {
682 if (slab_)
throw ScratchCapacityError(index_resource_,
685 found = locate(first, second, hash);
687 if (size_ == std::numeric_limits<std::size_t>::max())
688 throw IdPairStoreError(EFBIG,
"id table size exhausted");
689 const auto second_size = second ? second->size() : 0;
690 constexpr auto maximum =
static_cast<std::uint64_t
>(std::numeric_limits<off_t>::max());
691 if (data_bytes_ > maximum -
sizeof(Header)
692 || first.size() > maximum - data_bytes_ -
sizeof(Header)
693 || second_size > maximum - data_bytes_ -
sizeof(Header) - first.size())
694 throw IdPairStoreError(EFBIG,
"id table data exhausted");
695 Header header{hash,
static_cast<std::uint64_t
>(first.size()),
696 static_cast<std::uint64_t
>(second_size),
value, kLive | (second ? kPair : 0)};
697 if (!slab_) data_.open(directory_);
698 write_data(data_bytes_, &header,
sizeof(header));
699 write_data(data_bytes_ +
sizeof(Header), first.data(), first.size());
701 write_data(data_bytes_ +
sizeof(Header) + first.size(), second->data(), second->size());
702 const Slot slot{hash, data_bytes_ + 1};
703 if constexpr (MaintainSortedFold) publish_fold(first, data_bytes_, header, found.first, slot);
704 else write_index(found.first *
sizeof(Slot), &slot,
sizeof(slot));
708 if (data_.disk_backed()) read_cache_.invalidate_from(0, data_bytes_);
709 data_bytes_ +=
sizeof(Header) + first.size() + second_size;
714 bool erase_key(std::string_view first, std::optional<std::string_view> second) {
716 const auto count = memory_->erase(memory_key(first, second));
720 if constexpr (MaintainSortedFold) flush_pending();
721 if (slots_ == 0)
return false;
722 const auto found = locate(first, second, hash_for_key(first, second)).second;
723 if (found.offset_plus_one == 0)
return false;
724 const auto offset = found.offset_plus_one - 1;
725 Header header = read_header(offset);
726 if ((header.flags & kLive) == 0)
return false;
727 header.flags &= ~kLive;
728 if constexpr (MaintainSortedFold) publish_fold(first, offset, header);
729 else write_data(offset, &header,
sizeof(header));
739 void flush_pending() {
740 if (!fold_.pending)
return;
741 write_data(fold_.pending_offset, &fold_.pending_header,
sizeof(Header));
742 if (fold_.slot_pending)
743 write_index(fold_.pending_at *
sizeof(Slot), &fold_.pending_slot,
sizeof(Slot));
744 fold_.pending = fold_.slot_pending =
false;
749 std::uint64_t changed;
750 const Header& prospective;
751 std::array<unsigned char, 512> a{}, b{};
753 Header header(std::uint64_t row)
const {
754 return row == changed ? prospective : owner.read_header(row);
756 unsigned symbol(std::uint64_t row, std::uint64_t
at)
const {
757 const auto h = header(row);
758 if (
at >= h.first_size)
return 0;
759 unsigned char byte = 0;
760 owner.read_data(row +
sizeof(Header) +
at, &
byte, 1);
761 return static_cast<unsigned>(byte) + 1;
763 int compare(std::uint64_t row, std::string_view
key) {
764 if (owner.fold_.fixture_work) ++owner.fold_.fixture_work->row_comparisons;
765 const auto h = header(row);
766 const auto common = std::min<std::uint64_t>(h.first_size,
key.size());
767 for (std::uint64_t
at = 0;
at < common;) {
768 const auto n =
static_cast<std::size_t
>(std::min<std::uint64_t>(a.size(), common -
at));
769 owner.read_data(row +
sizeof(Header) +
at, a.data(), n);
770 const int order = std::char_traits<char>::compare(
771 reinterpret_cast<const char*
>(a.data()),
key.data() +
at, n);
772 if (order != 0)
return order;
775 return h.first_size <
key.size() ? -1 : (h.first_size >
key.size() ? 1 : 0);
777 int compare_rows(std::uint64_t first, std::uint64_t second) {
778 if (owner.fold_.fixture_work) ++owner.fold_.fixture_work->row_comparisons;
779 const auto x = header(first), y = header(second);
780 const auto common = std::min(x.first_size, y.first_size);
781 for (std::uint64_t
at = 0;
at < common;) {
782 const auto n =
static_cast<std::size_t
>(std::min<std::uint64_t>(a.size(), common -
at));
783 owner.read_data(first +
sizeof(Header) +
at, a.data(), n);
784 owner.read_data(second +
sizeof(Header) +
at, b.data(), n);
785 const int order = std::char_traits<char>::compare(
786 reinterpret_cast<const char*
>(a.data()),
787 reinterpret_cast<const char*
>(b.data()), n);
788 if (order != 0)
return order;
791 return x.first_size < y.first_size ? -1 : (x.first_size > y.first_size ? 1 : 0);
793 IdPairOrderedFold::Position difference(std::uint64_t row, std::string_view
key) {
794 const auto h = header(row);
795 const auto common = std::min<std::uint64_t>(h.first_size,
key.size());
796 for (std::uint64_t
at = 0;
at < common;) {
797 const auto n =
static_cast<std::size_t
>(std::min<std::uint64_t>(a.size(), common -
at));
798 owner.read_data(row +
sizeof(Header) +
at, a.data(), n);
799 for (std::size_t i = 0; i < n; ++i) {
800 const auto byte =
static_cast<unsigned char>(
key[
at + i]);
806 if (h.first_size ==
key.size())
throw std::logic_error(
"id fold duplicate row");
810 IdPairOrderedFold::Position difference(std::uint64_t first, std::uint64_t second) {
811 const auto x = header(first), y = header(second);
812 const auto common = std::min(x.first_size, y.first_size);
813 for (std::uint64_t
at = 0;
at < common;) {
814 const auto n =
static_cast<std::size_t
>(std::min<std::uint64_t>(a.size(), common -
at));
815 owner.read_data(first +
sizeof(Header) +
at, a.data(), n);
816 owner.read_data(second +
sizeof(Header) +
at, b.data(), n);
817 for (std::size_t i = 0; i < n; ++i)
822 if (x.first_size == y.first_size)
throw std::logic_error(
"id fold duplicate row");
825 void bytes(BrokerStateByteFold& fold,
const void*
data, std::size_t
size) {
826 if (owner.fold_.fixture_work)
827 owner.fold_.fixture_work->byte_steps += 257 *
static_cast<std::uint64_t
>(
size);
830 void compose(BrokerStateByteFold& before,
const BrokerStateByteFold& after) {
831 if (owner.fold_.fixture_work) ++owner.fold_.fixture_work->compositions;
834 void append(std::uint64_t row, BrokerStateByteFold& fold) {
835 const auto h = header(row);
836 if ((h.flags & kLive) == 0)
return;
839 bytes(fold, &h.first_size,
sizeof(h.first_size));
840 for (std::uint64_t
at = 0;
at < h.first_size;) {
841 const auto n =
static_cast<std::size_t
>(std::min<std::uint64_t>(a.size(), h.first_size -
at));
842 owner.read_data(row +
sizeof(Header) +
at, a.data(), n);
843 bytes(fold, a.data(), n);
846 const auto value =
static_cast<std::int64_t
>(h.value);
850 void publish_fold(std::string_view
key, std::uint64_t offset,
const Header& header) {
851 publish_fold(
key, offset, header, 0, Slot{});
853 void publish_fold(std::string_view
key, std::uint64_t offset,
const Header& header,
854 std::uint64_t
at, Slot slot) {
855 FoldReader reader{*
this, offset, header};
857 fold_.bounded.update(*slab_, index_resource_, used_,
858 slot.offset_plus_one != 0,
key, offset, reader);
861 write_data(offset, &header,
sizeof(header));
862 if (slot.offset_plus_one != 0) write_index(
at *
sizeof(Slot), &slot,
sizeof(slot));
865 fold_.ordered.update(
key, offset, directory_, reader);
867 fold_.pending_header = header; fold_.pending_offset = offset;
868 fold_.pending_slot = slot; fold_.pending_at =
at;
869 fold_.pending =
true; fold_.slot_pending = slot.offset_plus_one != 0;
872 IdPairScratchFile data_{MaintainSortedFold
874 IdPairScratchFile index_{MaintainSortedFold
878 IdTableReadCache read_cache_;
879 std::string directory_;
880 std::uint64_t data_bytes_ = 0;
881 std::uint64_t slots_ = 0;
882 std::uint64_t used_ = 0;
883 std::size_t size_ = 0;
884 std::unique_ptr<MemoryMap> memory_;
885 std::shared_ptr<ScratchSlab> slab_;
888 std::uint64_t key_limit_ = 0;
889 std::uint64_t fixed_slots_ = 0;
890 std::optional<std::uint64_t> fixture_hash_;
891 std::conditional_t<MaintainSortedFold, FoldState, NoFold> fold_;